diff --git a/examples/provision.rs b/examples/provision.rs index 57d8a44..a413d69 100644 --- a/examples/provision.rs +++ b/examples/provision.rs @@ -19,7 +19,7 @@ async fn main() -> anyhow::Result<()> { // Alternatively, restore an account from serialized credentials by // using `Account::from_credentials()`. - let account = Account::create( + let (account, credentials) = Account::create( &NewAccount { contact: &[], terms_of_service_agreed: true, @@ -29,6 +29,10 @@ async fn main() -> anyhow::Result<()> { None, ) .await?; + info!( + "account credentials:\n\n{}", + serde_json::to_string_pretty(&credentials).unwrap() + ); // Create the ACME order based on the given domain names. // Note that this only needs an `&Account`, so the library will let you @@ -141,11 +145,6 @@ async fn main() -> anyhow::Result<()> { info!("certficate chain:\n\n{}", cert_chain_pem); info!("private key:\n\n{}", cert.serialize_private_key_pem()); - info!( - "account credentials:\n\n{}", - serde_json::to_string_pretty(&account.credentials()).unwrap() - ); - Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 433fe2f..9db0e61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ #![warn(unreachable_pub)] #![warn(missing_docs)] -use std::borrow::Cow; use std::fmt; use std::future::Future; use std::pin::Pin; @@ -203,53 +202,61 @@ impl Account { /// /// The [`AccountCredentials`] type is opaque, but supports deserialization. #[cfg(feature = "hyper-rustls")] - pub fn from_credentials(credentials: AccountCredentials<'_>) -> Result { + pub async fn from_credentials(credentials: AccountCredentials) -> Result { Ok(Self { - inner: Arc::new(AccountInner::from_credentials( - credentials, - Box::::default(), - )?), + inner: Arc::new( + AccountInner::from_credentials(credentials, Box::::default()) + .await?, + ), }) } /// Restore an existing account from the given credentials and HTTP client /// /// The [`AccountCredentials`] type is opaque, but supports deserialization. - pub fn from_credentials_and_http( - credentials: AccountCredentials<'_>, + pub async fn from_credentials_and_http( + credentials: AccountCredentials, http: Box, ) -> Result { Ok(Self { - inner: Arc::new(AccountInner::from_credentials(credentials, http)?), + inner: Arc::new(AccountInner::from_credentials(credentials, http).await?), }) } /// Create a new account on the `server_url` with the information in [`NewAccount`] + /// + /// The returned [`AccountCredentials`] can be serialized and stored for later use. + /// Use [`Account::from_credentials()`] to restore the account from the credentials. #[cfg(feature = "hyper-rustls")] pub async fn create( account: &NewAccount<'_>, server_url: &str, external_account: Option<&ExternalAccountKey>, - ) -> Result { + ) -> Result<(Account, AccountCredentials), Error> { Self::create_inner( account, external_account, Client::new(server_url, Box::::default()).await?, + server_url, ) .await } /// Create a new account with a custom HTTP client + /// + /// The returned [`AccountCredentials`] can be serialized and stored for later use. + /// Use [`Account::from_credentials()`] to restore the account from the credentials. pub async fn create_with_http( account: &NewAccount<'_>, server_url: &str, external_account: Option<&ExternalAccountKey>, http: Box, - ) -> Result { + ) -> Result<(Account, AccountCredentials), Error> { Self::create_inner( account, external_account, Client::new(server_url, http).await?, + server_url, ) .await } @@ -258,7 +265,8 @@ impl Account { account: &NewAccount<'_>, external_account: Option<&ExternalAccountKey>, client: Client, - ) -> Result { + server_url: &str, + ) -> Result<(Account, AccountCredentials), Error> { let key = Key::generate()?; let payload = NewAccountPayload { new_account: account, @@ -285,13 +293,28 @@ impl Account { // The response redirects, we don't need the body let _ = Problem::from_response(rsp).await?; - Ok(Self { - inner: Arc::new(AccountInner { - client, - key, - id: account_url.ok_or("failed to get account URL")?, - }), - }) + let id = account_url.ok_or("failed to get account URL")?; + let credentials = AccountCredentials { + id: id.clone(), + key_pkcs8: BASE64_URL_SAFE_NO_PAD.encode(&key.pkcs8_der), + directory: Some(server_url.to_owned()), + // We support deserializing URLs for compatibility with versions pre 0.4, + // but we prefer to get fresh URLs from the `server_url` for newer credentials. + urls: None, + }; + + let account = AccountInner { + client, + key, + id: id.clone(), + }; + + Ok(( + Self { + inner: Arc::new(account), + }, + credentials, + )) } /// Create a new order based on the given [`NewOrder`] @@ -320,13 +343,6 @@ impl Account { url: order_url.ok_or("no order URL found")?, }) } - - /// Get the account's credentials, which can be serialized - /// - /// Pass the credentials to [`Account::from_credentials`] to regain access to the `Account`. - pub fn credentials(&self) -> AccountCredentials<'_> { - self.inner.credentials() - } } struct AccountInner { @@ -336,17 +352,18 @@ struct AccountInner { } impl AccountInner { - fn from_credentials( - credentials: AccountCredentials<'_>, + async fn from_credentials( + credentials: AccountCredentials, http: Box, ) -> Result { Ok(Self { + id: credentials.id, key: Key::from_pkcs8_der(BASE64_URL_SAFE_NO_PAD.decode(&credentials.key_pkcs8)?)?, - client: Client { - http, - urls: credentials.urls.into_owned(), + client: match (credentials.directory, credentials.urls) { + (Some(server_url), _) => Client::new(&server_url, http).await?, + (None, Some(urls)) => Client { http, urls }, + (None, None) => return Err("no server URLs found".into()), }, - id: credentials.id.into_owned(), }) } @@ -368,14 +385,6 @@ impl AccountInner { ) -> Result, Error> { self.client.post(payload, nonce, self, url).await } - - fn credentials(&self) -> AccountCredentials<'_> { - AccountCredentials { - id: Cow::Borrowed(&self.id), - key_pkcs8: BASE64_URL_SAFE_NO_PAD.encode(&self.key.pkcs8_der), - urls: Cow::Borrowed(&self.client.urls), - } - } } impl Signer for AccountInner { diff --git a/src/types.rs b/src/types.rs index 0295d9a..3ab588c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::fmt; use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; @@ -53,10 +52,11 @@ impl From<&'static str> for Error { /// the account credentials to a file or secret manager and restore the /// account from persistent storage. #[derive(Deserialize, Serialize)] -pub struct AccountCredentials<'a> { - pub(crate) id: Cow<'a, str>, +pub struct AccountCredentials { + pub(crate) id: String, pub(crate) key_pkcs8: String, - pub(crate) urls: Cow<'a, DirectoryUrls>, + pub(crate) directory: Option, + pub(crate) urls: Option, } /// An RFC 7807 problem document as returned by the ACME server