Skip to content

add feature to load an account by private key #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

axos88
Copy link

@axos88 axos88 commented Mar 17, 2025

Addresses #93

src/lib.rs Outdated
pub async fn load(
private_key_pkcs8_der: &[u8],
server_url: &str,
http: Option<Box<dyn HttpClient>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the Option type really makes sense for http? If it is Some you don't want to pull in the DefaultClient dependendencies; if it is None you need to. Which one do you want to use?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I did not see that was behind a feature gate.

src/lib.rs Outdated
None => Client::new(server_url, Box::new(DefaultClient::try_new()?)).await?,
};

let key = Key::from_pkcs8_der(private_key_pkcs8_der)?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably externalize this, instead make Key::from_pkcs8_der() public, and have this function take a Key.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first did it that way, but wasn't sure you were open to extend the area of public API.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But to be honest, if we are making Key part of the public API, it would make more sense to add the Key as the param of the Account::create as well, so that the user has the ability to create an account with a chosen key instead of forcing to generate one for them.

And in that case for_existing_account would probably just be a convenience wrapper around that.

src/lib.rs Outdated
Comment on lines 512 to 514
&ignored_account, // This field is ignored as per rfc8555 7.3.1
(key, pkcs8),
None, // This field is ignored as per rfc8555 7.3.1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments don't quite make sense in their current form.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first comment is there to explain why a dummy ignored_account is created and why sending that as part of the request is okay. The second one might need to be removed - agreed.

src/lib.rs Outdated
///
/// 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 load(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call this for_existing_account() and move it up above from_parts()?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

@cpu
Copy link
Collaborator

cpu commented Mar 17, 2025

It would be nice to see test coverage for this as well. Maybe a Pebble integration test (https://github.com/djc/instant-acme/blob/main/tests/pebble.rs) that:

  1. Spins up a Pebble env, and registers a new account with it
  2. Re-creates the account from just the private key, and verifies that works with the new API
  3. Drops the old env
  4. Spins up a second Pebble env
  5. Tries to use the account private key from the first env to recreate the account: this should error

@axos88
Copy link
Author

axos88 commented Mar 18, 2025

How can I run the pebble test locally?

Copy link
Owner

@djc djc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please squash all your commits.

/// the `DefaultClient::try_new()?` can be used as the http client by default.
/// 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")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still don't really want to have both the _http() and non-_http() variants. Which one of these do you want to use?

@@ -907,22 +916,22 @@ struct Key {
}

impl Key {
fn generate() -> Result<(Self, crypto::pkcs8::Document), Error> {
pub fn generate() -> Result<(Self, crypto::pkcs8::Document), Error> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with making generate() and from_pkcs8_der() public. I don't think new() and to_pkcs8_der() need to become public.

@cpu thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpu thoughts?

Yeah, in the absence of some additional motivation I agree 👍

Comment on lines +510 to +518
/// This is a special value for `NewAccount` that is supplied
/// when loading an account from a private key.
/// According to rfc8555 7.3.1 this field needs to be supplied,
/// but MUST be ignored by the ACME server.
pub(crate) static LOAD_EXISTING_ACCOUNT: NewAccount = NewAccount {
only_return_existing: true,
contact: &[],
terms_of_service_agreed: true,
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this to:

impl NewAccount<'_> {
     pub(crate) const EXISTING: Self = Self {
          ..
     };
}

@cpu
Copy link
Collaborator

cpu commented Mar 18, 2025

How can I run the pebble test locally?

It's a little more tricky than it should be at the moment because we're waiting on an upstream Pebble release. The basic process is:

  1. Get pebble and pebble-challtestsrv binaries
  2. Put them in the root of your instant-acme checkout
  3. Run cargo test --features=x509-parser --features=time -- --ignored

For step 1, since we're presently waiting on a fresh release of Pebble you'll have to build it from source (which requires Go) for all the tests to pass. You can see how CI does it here: https://github.com/djc/instant-acme/blob/main/.github/workflows/pebble.yml

Alternatively, you could download pre-built releases here: https://github.com/letsencrypt/pebble/releases/tag/v2.7.0 but you'll see that one of the Pebble tests fails (account_deactivate) because it's missing an unreleased Pebble fix.

Copy link
Collaborator

@cpu cpu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I think this is coming along 👍

Comment on lines 31 to +32
mod types;
use crate::types::LOAD_EXISTING_ACCOUNT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this import should be moved into the existing use types::{ ... } group on L40..43

@@ -389,6 +390,8 @@ impl Deref for ChallengeHandle<'_> {
/// Create an [`Account`] with [`Account::create()`] or restore it from serialized data
/// by passing deserialized [`AccountCredentials`] to [`Account::from_credentials()`].
///
/// Alternatively, you can load an account using the private key using [`Account::loadgit ()`].
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some details shifted here and Account::loadgit() isn't a fn?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lol, i guess my cli command got there by mistake

@@ -423,6 +426,48 @@ impl Account {
})
}

/// Load a new account by private key, with a default or custom HTTP client
///
/// https://www.rfc-editor.org/rfc/rfc8555#section-7.3.1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// https://www.rfc-editor.org/rfc/rfc8555#section-7.3.1
/// <https://www.rfc-editor.org/rfc/rfc8555#section-7.3.1>

@@ -423,6 +426,48 @@ impl Account {
})
}

/// Load a new account by private key, with a default or custom HTTP client
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this headline is accurate for this fn: it always uses the default HTTP client.

///
/// https://www.rfc-editor.org/rfc/rfc8555#section-7.3.1
///
/// the `DefaultClient::try_new()?` can be used as the http client by default.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to my prev comment, for this fn (if both are kept):

Suggested change
/// the `DefaultClient::try_new()?` can be used as the http client by default.
/// the `DefaultClient::try_new()?` will be used as the http client.

let rng = crypto::SystemRandom::new();
let pkcs8 =
crypto::EcdsaKeyPair::generate_pkcs8(&crypto::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)?;
Self::new(pkcs8.as_ref(), rng).map(|key| (key, pkcs8))
}

fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self, Error> {
/// Create a key from a der encoded ES256
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can be more specific here:

Suggested change
/// Create a key from a der encoded ES256
/// Create a `Key` from a DER encoded PKCS8 format ECDSA P-256 private key

Self::new(pkcs8_der, crypto::SystemRandom::new())
}

fn new(pkcs8_der: &[u8], rng: crypto::SystemRandom) -> Result<Self, Error> {
/// Export a key to a der encoded ES256 key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fn stays I think similar feedback as the from_pkcs8_der() specificity applies.

Ok(self.inner.to_pkcs8v1()?)
}

/// Create a key from a der encoded ES256 key and a crypto::SystemRandom
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fn stays I think similar feedback as the from_pkcs8_der() specificity applies.

Comment on lines +510 to +513
/// This is a special value for `NewAccount` that is supplied
/// when loading an account from a private key.
/// According to rfc8555 7.3.1 this field needs to be supplied,
/// but MUST be ignored by the ACME server.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can describe this a bit clearer:

Suggested change
/// This is a special value for `NewAccount` that is supplied
/// when loading an account from a private key.
/// According to rfc8555 7.3.1 this field needs to be supplied,
/// but MUST be ignored by the ACME server.
/// A `NewAccount` endpoint request payload for fetching an existing account.
///
/// `only_return_existing` is set to true to avoid creating a new account if the
/// existing account is not known to the ACME server for some reason.

Comment on lines +293 to +294
use serde_json::Value;
use instant_acme::Key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's import these top-level

@axos88
Copy link
Author

axos88 commented Mar 21, 2025

Thanks for the feedback I'll reiterate in a few days when i get some free time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants