Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,12 +458,38 @@ impl AccountBuilder {
self,
key: (Key, PrivateKeyDer<'static>),
directory_url: String,
) -> Result<(Account, AccountCredentials), Error> {
self.from_key_inner(key, directory_url, true).await
}

/// Create a new account with the given private key
///
/// The returned [`AccountCredentials`] can be serialized and stored for later use.
/// Use [`AccountBuilder::from_credentials()`] to restore the account from the credentials.
///
/// Unlike [`AccountBuilder::from_key()`] which only loads existing accounts, this method
/// will create a new account if one doesn't already exist for the given key.
pub async fn create_from_key(
self,
key: (Key, PrivateKeyDer<'static>),
directory_url: String,
) -> Result<(Account, AccountCredentials), Error> {
self.from_key_inner(key, directory_url, false).await
}

/// Shared implementation for `from_key` and `create_from_key`
#[allow(clippy::wrong_self_convention)]
async fn from_key_inner(
self,
key: (Key, PrivateKeyDer<'static>),
directory_url: String,
only_return_existing: bool,
) -> Result<(Account, AccountCredentials), Error> {
Self::create_inner(
&NewAccount {
contact: &[],
terms_of_service_agreed: true,
only_return_existing: true,
only_return_existing,
},
match key {
(key, PrivateKeyDer::Pkcs8(pkcs8)) => (key, pkcs8),
Expand Down
52 changes: 52 additions & 0 deletions tests/pebble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,58 @@ async fn account_from_key() -> Result<(), Box<dyn StdError>> {
Ok(())
}

/// Test account creation with a provided private key
#[tokio::test]
#[ignore]
async fn account_create_from_key() -> Result<(), Box<dyn StdError>> {
try_tracing_init();

let env = Environment::new(EnvironmentConfig::default()).await?;
let directory_url = format!("https://{}/dir", &env.config.pebble.listen_address);

// Generate a new key
let (key, key_pkcs8) = Key::generate_pkcs8()?;

// Create a new account with the generated key
let (account1, credentials1) = Account::builder_with_http(Box::new(env.client.clone()))
.create_from_key(
(
key,
PrivateKeyDer::try_from(key_pkcs8.secret_pkcs8_der().to_vec())?,
),
directory_url.clone(),
)
.await?;

// Extract the key to verify it matches what we provided
#[derive(Deserialize)]
struct JsonKey<'a> {
key_pkcs8: &'a str,
}

let json1 = serde_json::to_string(&credentials1)?;
let json_key = serde_json::from_str::<JsonKey>(&json1)?;
let key_der = BASE64_URL_SAFE_NO_PAD.decode(json_key.key_pkcs8)?;

// Verify the key matches
assert_eq!(key_der, key_pkcs8.secret_pkcs8_der());

// Now try to load the account using from_key to verify it was created
let key2 = Key::from_pkcs8_der(PrivatePkcs8KeyDer::from(key_der.clone()))?;
let (account2, credentials2) = Account::builder_with_http(Box::new(env.client.clone()))
.from_key((key2, PrivateKeyDer::try_from(key_der)?), directory_url)
.await?;

// Both should be the same account
assert_eq!(account1.id(), account2.id());
assert_eq!(
serde_json::to_string(&credentials1)?,
serde_json::to_string(&credentials2)?,
);

Ok(())
}

fn try_tracing_init() {
let _ = tracing_subscriber::registry()
.with(fmt::layer())
Expand Down