Only provide AccountCredentials at account creation time

This commit is contained in:
Dirkjan Ochtman 2023-08-01 10:16:53 +02:00
parent 266ccc2415
commit 11c231bace
3 changed files with 58 additions and 50 deletions

View File

@ -19,7 +19,7 @@ async fn main() -> anyhow::Result<()> {
// Alternatively, restore an account from serialized credentials by // Alternatively, restore an account from serialized credentials by
// using `Account::from_credentials()`. // using `Account::from_credentials()`.
let account = Account::create( let (account, credentials) = Account::create(
&NewAccount { &NewAccount {
contact: &[], contact: &[],
terms_of_service_agreed: true, terms_of_service_agreed: true,
@ -29,6 +29,10 @@ async fn main() -> anyhow::Result<()> {
None, None,
) )
.await?; .await?;
info!(
"account credentials:\n\n{}",
serde_json::to_string_pretty(&credentials).unwrap()
);
// Create the ACME order based on the given domain names. // Create the ACME order based on the given domain names.
// Note that this only needs an `&Account`, so the library will let you // 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!("certficate chain:\n\n{}", cert_chain_pem);
info!("private key:\n\n{}", cert.serialize_private_key_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(()) Ok(())
} }

View File

@ -3,7 +3,6 @@
#![warn(unreachable_pub)] #![warn(unreachable_pub)]
#![warn(missing_docs)] #![warn(missing_docs)]
use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@ -203,53 +202,61 @@ impl Account {
/// ///
/// The [`AccountCredentials`] type is opaque, but supports deserialization. /// The [`AccountCredentials`] type is opaque, but supports deserialization.
#[cfg(feature = "hyper-rustls")] #[cfg(feature = "hyper-rustls")]
pub fn from_credentials(credentials: AccountCredentials<'_>) -> Result<Self, Error> { pub async fn from_credentials(credentials: AccountCredentials) -> Result<Self, Error> {
Ok(Self { Ok(Self {
inner: Arc::new(AccountInner::from_credentials( inner: Arc::new(
credentials, AccountInner::from_credentials(credentials, Box::<DefaultClient>::default())
Box::<DefaultClient>::default(), .await?,
)?), ),
}) })
} }
/// Restore an existing account from the given credentials and HTTP client /// Restore an existing account from the given credentials and HTTP client
/// ///
/// The [`AccountCredentials`] type is opaque, but supports deserialization. /// The [`AccountCredentials`] type is opaque, but supports deserialization.
pub fn from_credentials_and_http( pub async fn from_credentials_and_http(
credentials: AccountCredentials<'_>, credentials: AccountCredentials,
http: Box<dyn HttpClient>, http: Box<dyn HttpClient>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self { 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`] /// 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")] #[cfg(feature = "hyper-rustls")]
pub async fn create( pub async fn create(
account: &NewAccount<'_>, account: &NewAccount<'_>,
server_url: &str, server_url: &str,
external_account: Option<&ExternalAccountKey>, external_account: Option<&ExternalAccountKey>,
) -> Result<Account, Error> { ) -> Result<(Account, AccountCredentials), Error> {
Self::create_inner( Self::create_inner(
account, account,
external_account, external_account,
Client::new(server_url, Box::<DefaultClient>::default()).await?, Client::new(server_url, Box::<DefaultClient>::default()).await?,
server_url,
) )
.await .await
} }
/// Create a new account with a custom HTTP client /// 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( pub async fn create_with_http(
account: &NewAccount<'_>, account: &NewAccount<'_>,
server_url: &str, server_url: &str,
external_account: Option<&ExternalAccountKey>, external_account: Option<&ExternalAccountKey>,
http: Box<dyn HttpClient>, http: Box<dyn HttpClient>,
) -> Result<Account, Error> { ) -> Result<(Account, AccountCredentials), Error> {
Self::create_inner( Self::create_inner(
account, account,
external_account, external_account,
Client::new(server_url, http).await?, Client::new(server_url, http).await?,
server_url,
) )
.await .await
} }
@ -258,7 +265,8 @@ impl Account {
account: &NewAccount<'_>, account: &NewAccount<'_>,
external_account: Option<&ExternalAccountKey>, external_account: Option<&ExternalAccountKey>,
client: Client, client: Client,
) -> Result<Account, Error> { server_url: &str,
) -> Result<(Account, AccountCredentials), Error> {
let key = Key::generate()?; let key = Key::generate()?;
let payload = NewAccountPayload { let payload = NewAccountPayload {
new_account: account, new_account: account,
@ -285,13 +293,28 @@ impl Account {
// The response redirects, we don't need the body // The response redirects, we don't need the body
let _ = Problem::from_response(rsp).await?; let _ = Problem::from_response(rsp).await?;
Ok(Self { let id = account_url.ok_or("failed to get account URL")?;
inner: Arc::new(AccountInner { let credentials = AccountCredentials {
client, id: id.clone(),
key, key_pkcs8: BASE64_URL_SAFE_NO_PAD.encode(&key.pkcs8_der),
id: account_url.ok_or("failed to get account URL")?, 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`] /// Create a new order based on the given [`NewOrder`]
@ -320,13 +343,6 @@ impl Account {
url: order_url.ok_or("no order URL found")?, 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 { struct AccountInner {
@ -336,17 +352,18 @@ struct AccountInner {
} }
impl AccountInner { impl AccountInner {
fn from_credentials( async fn from_credentials(
credentials: AccountCredentials<'_>, credentials: AccountCredentials,
http: Box<dyn HttpClient>, http: Box<dyn HttpClient>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self { Ok(Self {
id: credentials.id,
key: Key::from_pkcs8_der(BASE64_URL_SAFE_NO_PAD.decode(&credentials.key_pkcs8)?)?, key: Key::from_pkcs8_der(BASE64_URL_SAFE_NO_PAD.decode(&credentials.key_pkcs8)?)?,
client: Client { client: match (credentials.directory, credentials.urls) {
http, (Some(server_url), _) => Client::new(&server_url, http).await?,
urls: credentials.urls.into_owned(), (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<Response<Body>, Error> { ) -> Result<Response<Body>, Error> {
self.client.post(payload, nonce, self, url).await 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 { impl Signer for AccountInner {

View File

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::fmt; use std::fmt;
use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; 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 /// the account credentials to a file or secret manager and restore the
/// account from persistent storage. /// account from persistent storage.
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct AccountCredentials<'a> { pub struct AccountCredentials {
pub(crate) id: Cow<'a, str>, pub(crate) id: String,
pub(crate) key_pkcs8: String, pub(crate) key_pkcs8: String,
pub(crate) urls: Cow<'a, DirectoryUrls>, pub(crate) directory: Option<String>,
pub(crate) urls: Option<DirectoryUrls>,
} }
/// An RFC 7807 problem document as returned by the ACME server /// An RFC 7807 problem document as returned by the ACME server