Skip to content

Commit a24acb9

Browse files
committed
feat: add create_from_key for accounts with user-provided keys
Allows creating ACME accounts using externally managed keys, instead of being limited to loading existing ones or registering with library-generated keys.
1 parent 8d0d156 commit a24acb9

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

src/account.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,12 +458,37 @@ impl AccountBuilder {
458458
self,
459459
key: (Key, PrivateKeyDer<'static>),
460460
directory_url: String,
461+
) -> Result<(Account, AccountCredentials), Error> {
462+
self.from_key_inner(key, directory_url, true).await
463+
}
464+
465+
/// Create a new account with the given private key
466+
///
467+
/// The returned [`AccountCredentials`] can be serialized and stored for later use.
468+
/// Use [`AccountBuilder::from_credentials()`] to restore the account from the credentials.
469+
///
470+
/// Unlike [`AccountBuilder::from_key()`] which only loads existing accounts, this method
471+
/// will create a new account if one doesn't already exist for the given key.
472+
pub async fn create_from_key(
473+
self,
474+
key: (Key, PrivateKeyDer<'static>),
475+
directory_url: String,
476+
) -> Result<(Account, AccountCredentials), Error> {
477+
self.from_key_inner(key, directory_url, false).await
478+
}
479+
480+
/// Shared implementation for `from_key` and `create_from_key`
481+
async fn from_key_inner(
482+
self,
483+
key: (Key, PrivateKeyDer<'static>),
484+
directory_url: String,
485+
only_return_existing: bool,
461486
) -> Result<(Account, AccountCredentials), Error> {
462487
Self::create_inner(
463488
&NewAccount {
464489
contact: &[],
465490
terms_of_service_agreed: true,
466-
only_return_existing: true,
491+
only_return_existing,
467492
},
468493
match key {
469494
(key, PrivateKeyDer::Pkcs8(pkcs8)) => (key, pkcs8),

tests/pebble.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,58 @@ async fn account_from_key() -> Result<(), Box<dyn StdError>> {
446446
Ok(())
447447
}
448448

449+
/// Test account creation with a provided private key
450+
#[tokio::test]
451+
#[ignore]
452+
async fn account_create_from_key() -> Result<(), Box<dyn StdError>> {
453+
try_tracing_init();
454+
455+
let env = Environment::new(EnvironmentConfig::default()).await?;
456+
let directory_url = format!("https://{}/dir", &env.config.pebble.listen_address);
457+
458+
// Generate a new key
459+
let (key, key_pkcs8) = Key::generate_pkcs8()?;
460+
461+
// Create a new account with the generated key
462+
let (account1, credentials1) = Account::builder_with_http(Box::new(env.client.clone()))
463+
.create_from_key(
464+
(
465+
key,
466+
PrivateKeyDer::try_from(key_pkcs8.secret_pkcs8_der().to_vec())?,
467+
),
468+
directory_url.clone(),
469+
)
470+
.await?;
471+
472+
// Extract the key to verify it matches what we provided
473+
#[derive(Deserialize)]
474+
struct JsonKey<'a> {
475+
key_pkcs8: &'a str,
476+
}
477+
478+
let json1 = serde_json::to_string(&credentials1)?;
479+
let json_key = serde_json::from_str::<JsonKey>(&json1)?;
480+
let key_der = BASE64_URL_SAFE_NO_PAD.decode(json_key.key_pkcs8)?;
481+
482+
// Verify the key matches
483+
assert_eq!(key_der, key_pkcs8.secret_pkcs8_der());
484+
485+
// Now try to load the account using from_key to verify it was created
486+
let key2 = Key::from_pkcs8_der(PrivatePkcs8KeyDer::from(key_der.clone()))?;
487+
let (account2, credentials2) = Account::builder_with_http(Box::new(env.client.clone()))
488+
.from_key((key2, PrivateKeyDer::try_from(key_der)?), directory_url)
489+
.await?;
490+
491+
// Both should be the same account
492+
assert_eq!(account1.id(), account2.id());
493+
assert_eq!(
494+
serde_json::to_string(&credentials1)?,
495+
serde_json::to_string(&credentials2)?,
496+
);
497+
498+
Ok(())
499+
}
500+
449501
fn try_tracing_init() {
450502
let _ = tracing_subscriber::registry()
451503
.with(fmt::layer())

0 commit comments

Comments
 (0)