Skip to content
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

Expose accessors for private/public key on elliptic curve keys #259

Merged
merged 24 commits into from
Dec 5, 2023

Conversation

justsmth
Copy link
Contributor

@justsmth justsmth commented Oct 24, 2023

A rebase of #234 onto the latest on main.

Description of changes:

mod signature {
    // Technically not exposed before (private type but visible via KeyPair impl on EcdsaKeyPair)
    pub struct EcdsaPublicKey { ... }
    pub struct EcdsaPrivateKey<'a> { ... }
    pub struct Ed25519Seed<'a> { ... }


    // These are buffer discriminants, not actual buffer types.
    pub struct EcPublicDer { ... }
    pub struct EcPrivateKeyBuffer { ... }
    pub struct EcPrivateKeyDer { ... }
    pub struct Ed25519SeedBuffer { ... }

    impl EcdsaKeyPair {
        fn generate(alg: &'static EcdsaSigningAlgorithm) -> Result<Self, Unspecified>;
        fn to_pkcs8v1(&self) -> Result<Document, Unspecified>;
        fn private_key(&self) -> EcdsaPrivateKey<'_>;
        fn from_private_key_der(alg: &'static EcdsaSigningAlgorithm, key: &[u8]) -> Result<Self, KeyRejected>;
    }
    impl EcdsaPrivateKey {
        fn to_buffer(&self) -> Result<Buffer<'static, EcPrivateKeyBuffer>, Unspecified>;
        fn to_der(&self) -> Result<Buffer<'static, EcPrivateKeyDer>, Unspecified>;
    }
    impl EcdsaPublicKey {
        /// Provides the public key as a DER-encoded SubjectPublicKeyInfo structure.
        fn as_der(&self) -> Buffer<'_, EcPublicDer>;
    }
    impl Ed25519Keypair {
        pub fn to_pkcs8v1(&self) -> Result<Document, Unspecified>;
        pub fn to_pkcs8v2(&self) -> Result<Document, Unspecified>;
        pub fn seed(&self) -> Result<Ed25519Seed, Unspecified>;
        impl Drop for Ed25519KeyPair { ... }
    }
    impl Ed25519Seed {
    	fn to_buffer(&self) -> Result<Buffer<'static, Ed25519SeedBuffer>, Unspecified>;
    }
}

mod buffer {
    pub struct Buffer<'a, T> { ... }
    impl<T> AsRef<[u8]> for Buffer<'_, T> { ... }
    impl<T> fmt::Debug for Buffer<'_, T> { ... }
}

Call-outs:

  • There’s inconsistency currently around whether to have a function names like to_pkcs8v1 versus to_pkcs8. Perhaps we need both?

(From #234)

We should discuss & agree to the general shape of the EcdsaKeyPair / EcdsaPrivateKey / EcdsaPublicKey types, as well as the new PrivateBuffer, to decide whether that shape makes sense. It's likely that replicating it for the other keys (e.g., RSA) will make sense. It's worth noting that the openssl crate takes a different approach with a general PKey type with functions to do a variety of operations -- that approach definitely leads to a simpler API, but it's less ring-like.

The current API surface is already a bit awkward -- for example, the only way to generate a key is via EcdsaKeyPair::generate_pkcs8 which gives you a serialized form rather than the Rust type. This PR adds a dedicated generate function, for latter use via to_pkcs8v1() if desired, but doesn't deprecate or remove the old API. We're also asking for a full signature algorithm rather than just the key type.

Testing:

(From #234)

A few unit tests are added. I'd like to agree on the general API shape before extending these further and it would help to gain some understanding of how much we want to test "trivial" wrappers around underlying AWS-LC functions for serialization/deserialization.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.

@justsmth justsmth requested a review from a team as a code owner October 24, 2023 14:32
Copy link

@Mark-Simulacrum Mark-Simulacrum left a comment

Choose a reason for hiding this comment

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

Just a couple tiny nits.

aws-lc-rs/src/ed25519.rs Show resolved Hide resolved
aws-lc-rs/src/ed25519.rs Show resolved Hide resolved
@codecov-commenter
Copy link

codecov-commenter commented Oct 24, 2023

Codecov Report

Attention: 5 lines in your changes are missing coverage. Please review.

Comparison is base (95228a8) 95.42% compared to head (712f0f2) 95.47%.

Files Patch % Lines
aws-lc-rs/src/ec.rs 94.28% 4 Missing ⚠️
aws-lc-rs/src/ec/key_pair.rs 98.21% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #259      +/-   ##
==========================================
+ Coverage   95.42%   95.47%   +0.05%     
==========================================
  Files          51       52       +1     
  Lines        6733     6921     +188     
==========================================
+ Hits         6425     6608     +183     
- Misses        308      313       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

aws-lc-rs/src/buffer.rs Show resolved Hide resolved
aws-lc-rs/src/ec.rs Outdated Show resolved Hide resolved
PublicKey(pubkey_box)
/// Provides the public key as a DER-encoded `SubjectPublicKeyInfo` structure.
#[must_use]
pub fn as_der(&self) -> Buffer<'_, EcPublicKeyDer> {
Copy link
Member

@skmcgrail skmcgrail Oct 26, 2023

Choose a reason for hiding this comment

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

I'm really not to fond of how we are using the names as_der and EcPublicKeyDer to mean "write the output as DER following the X.509 ASN.1 SubjectPublicKeyInfo type definition". DER is an ASN.1 encoding scheme, SubjectPublicKeyInfo is the structure to describe a public key that could be encoded in various formats, PEM, DER, BER, etc. I feel like we should be more explicit for own benefit in case we need to serialize to different structure types in the future.

Case in point, you could have a function that just writes the raw key bytes as a DER encode OCTET STRING. That's valid DER, but not a SubjectPublicKeyInfo.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

Would as_x509 and X509PublicKey any better?

As a reference, in the BC-FIPS Java world, the import of the der bytes (after base64 decoding if any) is something like:

new X509EncodedKeySpec(bytes)

///
/// # Errors
/// `error::Unspecified` if serialization failed.
pub fn to_der(&self) -> Result<Buffer<'static, EcPrivateKeyDer>, Unspecified> {
Copy link
Member

@skmcgrail skmcgrail Oct 26, 2023

Choose a reason for hiding this comment

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

Just wanted to highlight this as another example of ambiguity around meaning of to to_der. EcsdaPrivateKey encode to DER but follow PKCS#8 (v1 or v2) and is explicit with the functions. In this case though to_der is serializing only to the specific PrivateKey format, specifically from RFC 5915. This is the same structure that is wrapped by PKCS#8 structure! Now you could argue that KeyPair type makes sense to use PKCS#8 cause that supports both being present (V2). But it also supports serializing just the private key, so why does to_der here not use that? Basically I think the intent needs to be more clear for the serialization functions. I'm not saying we shouldn't support these types, but don't want to overload the term DER. ASN.1 is already confusing enough :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with your concerns. I'm still thinking about what changes need to be made regarding them.

Here are a few thoughts I have about what's important regarding the serialization/deserialization methods we provide for our structs:

  • Documentation should be unambiguous about the encoding(s) provided or required.
  • Method names should be clear about their purpose, but don't need to indicate details of the encoding.
  • Method names should be consistent/symmetric within a given struct. For example, the encoding serialized by a to_der function should be appropriate for a from_der to deserialize the same struct (if such a method exists).
  • Structs/Types (e.g., EcPrivateKeyDer) should be unambiguous and guide users toward proper usage of serialized data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My latest commit adds/improves the documentation so that the encoding provided by serialization methods is more clear. I also moved the structs that differentiate buffer types to a signature::encoding module. We might also rename the (e.g.) to_der methods to more directly indicate the type of encoding provided, but I'm not yet sure that's needed.

This is a one-way-door API decision, so it should be carefully considered.

Copy link
Member

@skmcgrail skmcgrail left a comment

Choose a reason for hiding this comment

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

Just some minor feedback and one question, otherwise this looks good.

aws-lc-rs/src/ed25519.rs Show resolved Hide resolved
aws-lc-rs/src/ec/key_pair.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@hansonchar hansonchar left a comment

Choose a reason for hiding this comment

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

Instead of always marshaling a public key eagerly into both octet string and x509 formats, would it make more sense to have an API that allows either format to be lazily retrieved instead?

I can imagine cases where only one format is needed but not the other.

skmcgrail
skmcgrail previously approved these changes Dec 4, 2023
Copy link
Member

@skmcgrail skmcgrail left a comment

Choose a reason for hiding this comment

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

Hopefully my final comment :)

aws-lc-rs/src/ec.rs Outdated Show resolved Hide resolved
@skmcgrail skmcgrail self-requested a review December 4, 2023 23:43
pub struct PublicKey(Box<[u8]>);
pub struct PublicKey {
algorithm: &'static EcdsaSigningAlgorithm,
octets: Box<[u8]>,
Copy link
Contributor

@samuel40791765 samuel40791765 Dec 5, 2023

Choose a reason for hiding this comment

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

Just a general question: We were doing this previously as well, so its not a blocker. We allocate the public key with a maximum length on the stack in marshal_public_key, does octets have to be a Box here?

@@ -103,6 +103,7 @@ extern crate core;

pub mod aead;
pub mod agreement;
mod buffer;
Copy link
Contributor

Choose a reason for hiding this comment

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

minor nit: Noticed this when checking if Buffer was a new public interface, should we move buffer down to list of other non-public modules below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have another PR ready after this. I'll update the order there.

@justsmth justsmth merged commit e6f69b1 into aws:main Dec 5, 2023
101 of 106 checks passed
@justsmth justsmth deleted the ec-raw-api branch December 5, 2023 19:32
@hansonchar
Copy link
Contributor

👏

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.

6 participants