diff --git a/aws-lc-rs/src/buffer.rs b/aws-lc-rs/src/buffer.rs new file mode 100644 index 00000000000..67c8e9bb1fd --- /dev/null +++ b/aws-lc-rs/src/buffer.rs @@ -0,0 +1,85 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! This module exposes a buffer type used in crate APIs returning private keys and other "private" +//! contents. + +#![allow(clippy::module_name_repetitions)] + +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; + +use zeroize::Zeroize; + +/// This is a buffer type for some data exposed by various APIs in this crate. +/// +/// `T` acts as a discriminant between different kinds of data. +/// +/// The buffer will be zeroed on drop if it is owned. +pub struct Buffer<'a, T>(Cow<'a, [u8]>, PhantomData); + +impl<'a, T> Drop for Buffer<'a, T> { + fn drop(&mut self) { + if let Cow::Owned(b) = &mut self.0 { + b.zeroize(); + } + } +} + +impl<'a, T> Buffer<'a, T> { + pub(crate) fn new(owned: Vec) -> Buffer<'a, T> { + Buffer(Cow::Owned(owned), PhantomData) + } + + pub(crate) fn take_from_slice(slice: &mut [u8]) -> Buffer<'a, T> { + let owned = slice.to_vec(); + slice.zeroize(); + Buffer(Cow::Owned(owned), PhantomData) + } + + // TODO: remove this "allow" once this is used. + #[allow(dead_code)] + pub(crate) fn public_from_slice(slice: &[u8]) -> Buffer<'_, T> { + Buffer(Cow::Borrowed(slice), PhantomData) + } +} + +impl fmt::Debug for Buffer<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Buffer(...)") + } +} + +impl AsRef<[u8]> for Buffer<'_, T> { + #[inline] + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let buffer: Buffer = Buffer::new(vec![1, 2, 3]); + assert_eq!(buffer.as_ref(), &[1, 2, 3]); + } + + #[test] + fn test_take_from_slice() { + let mut slice = [1, 2, 3]; + let buffer: Buffer = Buffer::take_from_slice(&mut slice); + assert_eq!(buffer.as_ref(), &[1, 2, 3]); + assert_eq!(slice, [0, 0, 0]); + } + + #[test] + fn test_public_from_slice() { + let slice = [1, 2, 3]; + let buffer: Buffer = Buffer::public_from_slice(&slice); + assert_eq!(buffer.as_ref(), &[1, 2, 3]); + } +} diff --git a/aws-lc-rs/src/ec.rs b/aws-lc-rs/src/ec.rs index 97f8ea9b2ca..adb1996c1b6 100644 --- a/aws-lc-rs/src/ec.rs +++ b/aws-lc-rs/src/ec.rs @@ -3,34 +3,7 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use crate::digest::digest_ctx::DigestContext; -use crate::error::{KeyRejected, Unspecified}; use core::fmt; - -use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; - -use crate::fips::indicator_check; -use crate::signature::{Signature, VerificationAlgorithm}; -use crate::{digest, sealed, test}; -#[cfg(feature = "fips")] -use aws_lc::EC_KEY_check_fips; -#[cfg(not(feature = "fips"))] -use aws_lc::EC_KEY_check_key; -use aws_lc::{ - point_conversion_form_t, ECDSA_SIG_from_bytes, ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, - ECDSA_SIG_new, ECDSA_SIG_set0, ECDSA_SIG_to_bytes, EC_GROUP_get_curve_name, - EC_GROUP_new_by_curve_name, EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_new, - EC_KEY_set_group, EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, - EC_POINT_oct2point, EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, - EVP_PKEY_CTX_new_id, EVP_PKEY_CTX_set_ec_paramgen_curve_nid, EVP_PKEY_assign_EC_KEY, - EVP_PKEY_get0_EC_KEY, EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new, - NID_X9_62_prime256v1, NID_secp256k1, NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, - EC_POINT, EVP_PKEY, EVP_PKEY_EC, -}; - -#[cfg(test)] -use aws_lc::EC_POINT_mul; - use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; use std::ops::Deref; @@ -42,12 +15,39 @@ use std::ptr::null_mut; #[cfg(feature = "ring-sig-verify")] use untrusted::Input; +#[cfg(feature = "fips")] +use aws_lc::EC_KEY_check_fips; +#[cfg(not(feature = "fips"))] +use aws_lc::EC_KEY_check_key; +#[cfg(test)] +use aws_lc::EC_POINT_mul; +use aws_lc::{ + point_conversion_form_t, BN_bn2bin_padded, BN_num_bytes, ECDSA_SIG_from_bytes, + ECDSA_SIG_get0_r, ECDSA_SIG_get0_s, ECDSA_SIG_new, ECDSA_SIG_set0, ECDSA_SIG_to_bytes, + EC_GROUP_get_curve_name, EC_GROUP_new_by_curve_name, EC_KEY_get0_group, + EC_KEY_get0_private_key, EC_KEY_get0_public_key, EC_KEY_new, EC_KEY_set_group, + EC_KEY_set_private_key, EC_KEY_set_public_key, EC_POINT_new, EC_POINT_oct2point, + EC_POINT_point2oct, EVP_DigestVerify, EVP_DigestVerifyInit, EVP_PKEY_CTX_new_id, + EVP_PKEY_CTX_set_ec_paramgen_curve_nid, EVP_PKEY_assign_EC_KEY, EVP_PKEY_get0_EC_KEY, + EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new, NID_X9_62_prime256v1, NID_secp256k1, + NID_secp384r1, NID_secp521r1, BIGNUM, ECDSA_SIG, EC_GROUP, EC_POINT, EVP_PKEY, EVP_PKEY_EC, +}; + +use crate::buffer::Buffer; +use crate::digest::digest_ctx::DigestContext; +use crate::encoding::AsDer; +use crate::error::{KeyRejected, Unspecified}; +use crate::fips::indicator_check; +use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr, Pointer}; +use crate::signature::{Signature, VerificationAlgorithm}; +use crate::{digest, sealed, test}; + pub(crate) mod key_pair; const ELEM_MAX_BITS: usize = 521; -pub const ELEM_MAX_BYTES: usize = (ELEM_MAX_BITS + 7) / 8; +pub(crate) const ELEM_MAX_BYTES: usize = (ELEM_MAX_BITS + 7) / 8; -pub const SCALAR_MAX_BYTES: usize = ELEM_MAX_BYTES; +pub(crate) const SCALAR_MAX_BYTES: usize = ELEM_MAX_BYTES; /// The maximum length, in bytes, of an encoded public key. pub(crate) const PUBLIC_KEY_MAX_LEN: usize = 1 + (2 * ELEM_MAX_BYTES); @@ -114,25 +114,63 @@ impl AlgorithmID { } } +/// Elliptic curve public key. #[derive(Clone)] -pub struct PublicKey(Box<[u8]>); +pub struct PublicKey { + algorithm: &'static EcdsaSigningAlgorithm, + octets: Box<[u8]>, +} -impl Debug for PublicKey { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(&format!("PublicKey(\"{}\")", test::to_hex(self.0.as_ref()))) +#[allow(clippy::module_name_repetitions)] +pub struct EcPublicKeyX509DerType { + _priv: (), +} +/// An elliptic curve public key as a DER-encoded (X509) `SubjectPublicKeyInfo` structure +#[allow(clippy::module_name_repetitions)] +pub type EcPublicKeyX509Der = Buffer<'static, EcPublicKeyX509DerType>; + +impl AsDer for PublicKey { + /// Provides the public key as a DER-encoded (X.509) `SubjectPublicKeyInfo` structure. + /// # Errors + /// Returns an error if the underlying implementation is unable to marshal the point. + fn as_der(&self) -> Result { + let ec_group = unsafe { LcPtr::new(EC_GROUP_new_by_curve_name(self.algorithm.id.nid()))? }; + let ec_point = unsafe { ec_point_from_bytes(&ec_group, self.as_ref())? }; + let ec_key = unsafe { LcPtr::new(EC_KEY_new())? }; + if 1 != unsafe { EC_KEY_set_group(*ec_key, *ec_group) } { + return Err(Unspecified); + } + if 1 != unsafe { EC_KEY_set_public_key(*ec_key, *ec_point) } { + return Err(Unspecified); + } + let mut buffer = null_mut::(); + let len = unsafe { aws_lc::i2d_EC_PUBKEY(*ec_key, &mut buffer) }; + if len < 0 || buffer.is_null() { + return Err(Unspecified); + } + let buffer = LcPtr::new(buffer)?; + let der = unsafe { std::slice::from_raw_parts(*buffer, len.try_into()?) }.to_owned(); + + Ok(Buffer::new(der)) } } -impl PublicKey { - fn new(pubkey_box: Box<[u8]>) -> Self { - PublicKey(pubkey_box) +impl Debug for PublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(&format!( + "EcdsaPublicKey(\"{}\")", + test::to_hex(self.octets.as_ref()) + )) } } impl AsRef<[u8]> for PublicKey { #[inline] + /// Serializes the public key in an uncompressed form (X9.62) using the + /// Octet-String-to-Elliptic-Curve-Point algorithm in + /// [SEC 1: Elliptic Curve Cryptography, Version 2.0]. fn as_ref(&self) -> &[u8] { - self.0.as_ref() + self.octets.as_ref() } } @@ -265,6 +303,26 @@ unsafe fn validate_evp_key( Ok(()) } +pub(crate) unsafe fn marshal_private_key_to_buffer( + alg_id: &'static AlgorithmID, + evp_pkey: &ConstPointer, +) -> Result, Unspecified> { + let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(**evp_pkey))?; + let private_bn = ConstPointer::new(EC_KEY_get0_private_key(*ec_key))?; + let private_size: usize = ecdsa_fixed_number_byte_size(alg_id); + { + let size: usize = BN_num_bytes(*private_bn).try_into()?; + debug_assert!(size <= private_size); + } + + let mut buffer = vec![0u8; private_size]; + if 1 != BN_bn2bin_padded(buffer.as_mut_ptr(), private_size, *private_bn) { + return Err(Unspecified); + } + + Ok(buffer) +} + pub(crate) unsafe fn marshal_public_key_to_buffer( buffer: &mut [u8; PUBLIC_KEY_MAX_LEN], evp_pkey: &ConstPointer, @@ -284,12 +342,16 @@ pub(crate) unsafe fn marshal_public_key_to_buffer( pub(crate) fn marshal_public_key( evp_pkey: &ConstPointer, + algorithm: &'static EcdsaSigningAlgorithm, ) -> Result { + let mut pub_key_bytes = [0u8; PUBLIC_KEY_MAX_LEN]; unsafe { - let mut pub_key_bytes = [0u8; PUBLIC_KEY_MAX_LEN]; let key_len = marshal_public_key_to_buffer(&mut pub_key_bytes, evp_pkey)?; - let pub_key = Vec::from(&pub_key_bytes[0..key_len]); - Ok(PublicKey::new(pub_key.into_boxed_slice())) + + Ok(PublicKey { + algorithm, + octets: pub_key_bytes[0..key_len].into(), + }) } } @@ -524,8 +586,10 @@ unsafe fn ecdsa_sig_from_fixed( #[cfg(test)] mod tests { - use crate::ec::key_pair::EcdsaKeyPair; - use crate::signature::ECDSA_P256_SHA256_FIXED_SIGNING; + use crate::encoding::AsDer; + use crate::signature::EcPublicKeyX509Der; + use crate::signature::EcdsaKeyPair; + use crate::signature::{KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; use crate::test::from_dirty_hex; use crate::{signature, test}; @@ -539,7 +603,26 @@ mod tests { ); let result = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &input); - result.unwrap(); + assert!(result.is_ok()); + let key_pair = result.unwrap(); + assert_eq!("EcdsaKeyPair { public_key: EcdsaPublicKey(\"04cf0d13a3a7577231ea1b66cf4021cd54f21f4ac4f5f2fdd28e05bc7d2bd099d1374cd08d2ef654d6f04498db462f73e0282058dd661a4c9b0437af3f7af6e724\") }", + format!("{key_pair:?}")); + assert_eq!( + "EcdsaPrivateKey(ECDSA_P256)", + format!("{:?}", key_pair.private_key()) + ); + let pub_key = key_pair.public_key(); + let der_pub_key: EcPublicKeyX509Der = pub_key.as_der().unwrap(); + + assert_eq!( + from_dirty_hex( + r"3059301306072a8648ce3d020106082a8648ce3d03010703420004cf0d13a3a7577231ea1b66cf402 + 1cd54f21f4ac4f5f2fdd28e05bc7d2bd099d1374cd08d2ef654d6f04498db462f73e0282058dd661a4c9 + b0437af3f7af6e724", + ) + .as_slice(), + der_pub_key.as_ref() + ); } #[test] @@ -563,8 +646,9 @@ mod tests { r"30440220341f6779b75e98bb42e01095dd48356cbf9002dc704ac8bd2a8240b8 8d3796c60220555843b1b4e264fe6ffe6e2b705a376c05c09404303ffe5d2711f3e3b3a010a1", ); - let actual_result = - signature::UnparsedPublicKey::new(alg, &public_key).verify(msg.as_bytes(), &sig); + let unparsed_pub_key = signature::UnparsedPublicKey::new(alg, &public_key); + + let actual_result = unparsed_pub_key.verify(msg.as_bytes(), &sig); assert!(actual_result.is_ok(), "Key: {}", test::to_hex(public_key)); } } diff --git a/aws-lc-rs/src/ec/key_pair.rs b/aws-lc-rs/src/ec/key_pair.rs index afb60f6e16c..a9cbfb6a166 100644 --- a/aws-lc-rs/src/ec/key_pair.rs +++ b/aws-lc-rs/src/ec/key_pair.rs @@ -3,23 +3,26 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +use std::fmt; +use std::fmt::{Debug, Formatter}; +use std::mem::MaybeUninit; +use std::ptr::{null, null_mut}; + +use aws_lc::{EVP_DigestSign, EVP_DigestSignInit, EVP_PKEY_get0_EC_KEY, EVP_PKEY, EVP_PKEY_EC}; + +use crate::buffer::Buffer; use crate::digest::digest_ctx::DigestContext; use crate::ec::{ evp_key_generate, validate_evp_key, EcdsaSignatureFormat, EcdsaSigningAlgorithm, PublicKey, }; +use crate::encoding::{AsBigEndian, AsDer}; use crate::error::{KeyRejected, Unspecified}; use crate::fips::indicator_check; use crate::pkcs8::{Document, Version}; -use crate::ptr::{DetachableLcPtr, LcPtr}; +use crate::ptr::{ConstPointer, DetachableLcPtr, LcPtr}; use crate::rand::SecureRandom; -use crate::signature::{KeyPair, Signature}; +use crate::signature::{EcdsaPublicKey, KeyPair, Signature}; use crate::{digest, ec}; -use aws_lc::{EVP_DigestSign, EVP_DigestSignInit, EVP_PKEY}; -use std::fmt; -use std::mem::MaybeUninit; -use std::ptr::{null, null_mut}; - -use std::fmt::{Debug, Formatter}; /// An ECDSA key pair, used for signing. #[allow(clippy::module_name_repetitions)] @@ -43,7 +46,8 @@ impl KeyPair for EcdsaKeyPair { type PublicKey = PublicKey; #[inline] - fn public_key(&self) -> &Self::PublicKey { + /// Provides the public key. + fn public_key(&self) -> &EcdsaPublicKey { &self.pubkey } } @@ -54,7 +58,7 @@ impl EcdsaKeyPair { algorithm: &'static EcdsaSigningAlgorithm, evp_pkey: LcPtr, ) -> Result { - let pubkey = ec::marshal_public_key(&evp_pkey.as_const())?; + let pubkey = ec::marshal_public_key(&evp_pkey.as_const(), algorithm)?; Ok(Self { algorithm, @@ -63,6 +67,19 @@ impl EcdsaKeyPair { }) } + /// Generates a new key pair. + /// + /// # Errors + /// `error::Unspecified` on internal error. + /// + pub fn generate(alg: &'static EcdsaSigningAlgorithm) -> Result { + unsafe { + let evp_pkey = evp_key_generate(alg.0.id.nid())?; + + Ok(Self::new(alg, evp_pkey)?) + } + } + /// Constructs an ECDSA key pair by parsing an unencrypted PKCS#8 v1 /// id-ecPublicKey `ECPrivateKey` key. /// @@ -96,9 +113,18 @@ impl EcdsaKeyPair { alg: &'static EcdsaSigningAlgorithm, _rng: &dyn SecureRandom, ) -> Result { - let evp_pkey = evp_key_generate(alg.0.id.nid())?; + let key_pair = Self::generate(alg)?; + + key_pair.to_pkcs8v1() + } - evp_pkey.marshall_private_key(Version::V1) + /// Serializes this `EcdsaKeyPair` into a PKCS#8 v1 document. + /// + /// # Errors + /// `error::Unspecified` on internal error. + /// + pub fn to_pkcs8v1(&self) -> Result { + self.evp_pkey.marshall_private_key(Version::V1) } /// Constructs an ECDSA key pair from the private key and public key bytes @@ -138,6 +164,51 @@ impl EcdsaKeyPair { } } + /// Deserializes a DER-encoded private key structure to produce a `EcdsaKeyPair`. + /// + /// This function is typically used to deserialize RFC 5915 encoded private keys, but it will + /// attempt to automatically detect other key formats. This function supports unencrypted + /// PKCS#8 `PrivateKeyInfo` structures as well as key type specific formats. + /// + /// See `EcdsaPrivateKey::as_der`. + /// + /// # Errors + /// `error::KeyRejected` if parsing failed or key otherwise unacceptable. + /// + /// # Panics + pub fn from_private_key_der( + alg: &'static EcdsaSigningAlgorithm, + private_key: &[u8], + ) -> Result { + unsafe { + let mut out = std::ptr::null_mut(); + if aws_lc::d2i_PrivateKey( + EVP_PKEY_EC, + &mut out, + &mut private_key.as_ptr(), + private_key + .len() + .try_into() + .map_err(|_| KeyRejected::too_large())?, + ) + .is_null() + { + // FIXME: unclear which error or if we can get more detail + return Err(KeyRejected::unexpected_error()); + } + let evp_pkey = LcPtr::new(out)?; + validate_evp_key(&evp_pkey.as_const(), alg.id.nid())?; + + Ok(Self::new(alg, evp_pkey)?) + } + } + + /// Access functions related to the private key. + #[must_use] + pub fn private_key(&self) -> PrivateKey<'_> { + PrivateKey(self) + } + /// Returns the signature of the message using a random nonce. /// /// # *ring* Compatibility @@ -224,3 +295,61 @@ fn compute_ecdsa_signature<'a>( Ok(&mut signature[0..out_sig_len]) } + +/// Elliptic curve private key. +pub struct PrivateKey<'a>(&'a EcdsaKeyPair); + +impl Debug for PrivateKey<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&format!("EcdsaPrivateKey({:?})", self.0.algorithm.id)) + } +} + +pub struct EcPrivateKeyBinType { + _priv: (), +} +/// Elliptic curve private key data encoded as a big-endian fixed-length integer. +pub type EcPrivateKeyBin = Buffer<'static, EcPrivateKeyBinType>; + +pub struct EcPrivateKeyRfc5915DerType { + _priv: (), +} +/// Elliptic curve private key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. +pub type EcPrivateKeyRfc5915Der = Buffer<'static, EcPrivateKeyRfc5915DerType>; + +impl AsBigEndian for PrivateKey<'_> { + /// Exposes the private key encoded as a big-endian fixed-length integer. + /// + /// For most use-cases, `EcdsaKeyPair::to_pkcs8()` should be preferred. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_be_bytes(&self) -> Result { + unsafe { + let buffer = ec::marshal_private_key_to_buffer( + self.0.algorithm.id, + &self.0.evp_pkey.as_const(), + )?; + Ok(EcPrivateKeyBin::new(buffer)) + } + } +} + +impl AsDer for PrivateKey<'_> { + /// Serializes the key as a DER-encoded `ECPrivateKey` (RFC 5915) structure. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_der(&self) -> Result { + unsafe { + let mut outp = null_mut::(); + let ec_key = ConstPointer::new(EVP_PKEY_get0_EC_KEY(*self.0.evp_pkey))?; + let length = usize::try_from(aws_lc::i2d_ECPrivateKey(*ec_key, &mut outp)) + .map_err(|_| Unspecified)?; + let outp = LcPtr::new(outp)?; + Ok(Buffer::take_from_slice(std::slice::from_raw_parts_mut( + *outp, length, + ))) + } + } +} diff --git a/aws-lc-rs/src/ed25519.rs b/aws-lc-rs/src/ed25519.rs index e58049ac381..d4bb089d45a 100644 --- a/aws-lc-rs/src/ed25519.rs +++ b/aws-lc-rs/src/ed25519.rs @@ -3,18 +3,6 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -use crate::error::{KeyRejected, Unspecified}; -use crate::fips::indicator_check; -use crate::pkcs8::{Document, Version}; -use crate::ptr::LcPtr; -use crate::rand::SecureRandom; -use crate::signature::{KeyPair, Signature, VerificationAlgorithm}; -use crate::{constant_time, sealed, test}; -use aws_lc::{ - ED25519_keypair_from_seed, ED25519_sign, ED25519_verify, EVP_PKEY_CTX_new_id, - EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, - EVP_PKEY_keygen_init, EVP_PKEY, EVP_PKEY_ED25519, -}; use std::fmt; use std::fmt::{Debug, Formatter}; use std::mem::MaybeUninit; @@ -22,6 +10,23 @@ use std::ptr::null_mut; #[cfg(feature = "ring-sig-verify")] use untrusted::Input; +use zeroize::Zeroize; + +use aws_lc::{ + ED25519_keypair_from_seed, ED25519_sign, ED25519_verify, EVP_PKEY_CTX_new_id, + EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, + EVP_PKEY_keygen_init, EVP_PKEY_new_raw_private_key, EVP_PKEY, EVP_PKEY_ED25519, +}; + +use crate::buffer::Buffer; +use crate::encoding::AsBigEndian; +use crate::error::{KeyRejected, Unspecified}; +use crate::fips::indicator_check; +use crate::pkcs8::{Document, Version}; +use crate::ptr::LcPtr; +use crate::rand::SecureRandom; +use crate::signature::{KeyPair, Signature, VerificationAlgorithm}; +use crate::{constant_time, sealed, test}; /// The length of an Ed25519 public key. pub const ED25519_PUBLIC_KEY_LEN: usize = aws_lc::ED25519_PUBLIC_KEY_LEN as usize; @@ -77,7 +82,7 @@ impl VerificationAlgorithm for EdDSAParameters { /// An Ed25519 key pair, for signing. #[allow(clippy::module_name_repetitions)] pub struct Ed25519KeyPair { - private_key: [u8; ED25519_PRIVATE_KEY_LEN], + private_key: Box<[u8; ED25519_PRIVATE_KEY_LEN]>, public_key: PublicKey, } @@ -90,22 +95,58 @@ impl Debug for Ed25519KeyPair { } } +impl Drop for Ed25519KeyPair { + fn drop(&mut self) { + self.private_key.zeroize(); + } +} + #[derive(Clone)] #[allow(clippy::module_name_repetitions)] -pub struct PublicKey { - public_key: [u8; ED25519_PUBLIC_KEY_LEN], +/// The seed value for the `EdDSA` signature scheme using Curve25519 +pub struct Seed<'a>(&'a Ed25519KeyPair); + +#[allow(clippy::module_name_repetitions)] +pub struct Ed25519SeedBufferType { + _priv: (), } +/// Elliptic curve private key data encoded as a big-endian fixed-length integer. +#[allow(clippy::module_name_repetitions)] +pub type Ed25519SeedBin = Buffer<'static, Ed25519SeedBufferType>; + +impl AsBigEndian for Seed<'_> { + /// Exposes the seed encoded as a big-endian fixed-length integer. + /// + /// For most use-cases, `EcdsaKeyPair::to_pkcs8()` should be preferred. + /// + /// # Errors + /// `error::Unspecified` if serialization failed. + fn as_be_bytes(&self) -> Result { + let buffer = Vec::from(&self.0.private_key[..ED25519_PRIVATE_KEY_SEED_LEN]); + Ok(Ed25519SeedBin::new(buffer)) + } +} + +impl Debug for Seed<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("Ed25519Seed()") + } +} + +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct PublicKey([u8; ED25519_PUBLIC_KEY_LEN]); impl AsRef<[u8]> for PublicKey { #[inline] fn as_ref(&self) -> &[u8] { - &self.public_key + &self.0 } } impl Debug for PublicKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&format!("PublicKey(\"{}\")", test::to_hex(self.public_key))) + f.write_str(&format!("PublicKey(\"{}\")", test::to_hex(self.0))) } } @@ -163,6 +204,24 @@ impl Ed25519KeyPair { evp_pkey.marshall_private_key(Version::V2) } + /// Serializes this `Ed25519KeyPair` into a PKCS#8 v2 document. + /// + /// # Errors + /// `error::Unspecified` on internal error. + /// + pub fn to_pkcs8(&self) -> Result { + unsafe { + let evp_pkey: LcPtr = LcPtr::new(EVP_PKEY_new_raw_private_key( + EVP_PKEY_ED25519, + null_mut(), + self.private_key.as_ref().as_ptr(), + ED25519_PRIVATE_KEY_SEED_LEN, + ))?; + + evp_pkey.marshall_private_key(Version::V2) + } + } + /// Generates a `Ed25519KeyPair` using the `rng` provided, then serializes that key as a /// PKCS#8 document. /// @@ -182,6 +241,24 @@ impl Ed25519KeyPair { evp_pkey.marshall_private_key(Version::V1) } + /// Serializes this `Ed25519KeyPair` into a PKCS#8 v1 document. + /// + /// # Errors + /// `error::Unspecified` on internal error. + /// + pub fn to_pkcs8v1(&self) -> Result { + unsafe { + let evp_pkey: LcPtr = LcPtr::new(EVP_PKEY_new_raw_private_key( + EVP_PKEY_ED25519, + null_mut(), + self.private_key.as_ref().as_ptr(), + ED25519_PRIVATE_KEY_SEED_LEN, + ))?; + + evp_pkey.marshall_private_key(Version::V1) + } + } + /// Constructs an Ed25519 key pair from the private key seed `seed` and its /// public key `public_key`. /// @@ -209,17 +286,17 @@ impl Ed25519KeyPair { seed.as_ptr(), ); let derived_public_key = derived_public_key.assume_init(); - let private_key = private_key.assume_init(); + let mut private_key = private_key.assume_init(); constant_time::verify_slices_are_equal(public_key, &derived_public_key) .map_err(|_| KeyRejected::inconsistent_components())?; - Ok(Self { - private_key, - public_key: PublicKey { - public_key: derived_public_key, - }, - }) + let key_pair = Self { + private_key: Box::new(private_key), + public_key: PublicKey(derived_public_key), + }; + private_key.zeroize(); + Ok(key_pair) } } @@ -282,9 +359,10 @@ impl Ed25519KeyPair { private_key[ED25519_PRIVATE_KEY_SEED_LEN..].copy_from_slice(&public_key); let key_pair = Self { - private_key, - public_key: PublicKey { public_key }, + private_key: Box::new(private_key), + public_key: PublicKey(public_key), }; + private_key.zeroize(); Ok(key_pair) } @@ -326,25 +404,43 @@ impl Ed25519KeyPair { ED25519_SIGNATURE_LEN })) } + + /// Provides the private key "seed" for this `Ed25519` key pair. + /// + /// For serialization of the key pair, `Ed25519KeyPair::to_pkcs8()` is preferred. + /// + /// # Errors + /// Currently the function cannot fail, but it might in future implementations. + pub fn seed(&self) -> Result { + Ok(Seed(self)) + } } #[cfg(test)] mod tests { + use crate::ed25519::Ed25519KeyPair; + use crate::rand::SystemRandom; use crate::test; #[test] fn test_generate_pkcs8() { - let rng = crate::rand::SystemRandom::new(); + let rng = SystemRandom::new(); let document = Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); - let _: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); - let _: Ed25519KeyPair = + let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); + let kp2: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap(); + assert_eq!(kp1.private_key.as_slice(), kp2.private_key.as_slice()); + assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref()); let document = Ed25519KeyPair::generate_pkcs8v1(&rng).unwrap(); - let _: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); - let _: Ed25519KeyPair = + let kp1: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap(); + let kp2: Ed25519KeyPair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(document.as_ref()).unwrap(); + assert_eq!(kp1.private_key.as_slice(), kp2.private_key.as_slice()); + assert_eq!(kp1.public_key.as_ref(), kp2.public_key.as_ref()); + let seed = kp1.seed().unwrap(); + assert_eq!("Ed25519Seed()", format!("{seed:?}")); } #[test] diff --git a/aws-lc-rs/src/lib.rs b/aws-lc-rs/src/lib.rs index bf4609aeb76..c3b55963187 100644 --- a/aws-lc-rs/src/lib.rs +++ b/aws-lc-rs/src/lib.rs @@ -103,6 +103,7 @@ extern crate core; pub mod aead; pub mod agreement; +mod buffer; pub mod constant_time; pub mod digest; pub mod error; @@ -200,6 +201,26 @@ mod sealed { // ``` pub trait Sealed {} } +/// Serialization formats +pub mod encoding { + /// Trait for structs that can be serialized into a DER format. + pub trait AsDer { + /// Serializes into a DER format. + /// + /// # Errors + /// Returns Unspecified if serialization fails. + fn as_der(&self) -> Result; + } + + /// Trait for values that can be serialized into a big-endian format + pub trait AsBigEndian { + /// Serializes into a big-endian format. + /// + /// # Errors + /// Returns Unspecified if serialization fails. + fn as_be_bytes(&self) -> Result; + } +} #[cfg(test)] mod tests { diff --git a/aws-lc-rs/src/signature.rs b/aws-lc-rs/src/signature.rs index 0276789ed3b..6e9950cfb5c 100644 --- a/aws-lc-rs/src/signature.rs +++ b/aws-lc-rs/src/signature.rs @@ -239,21 +239,27 @@ //! sign_and_verify_rsa(&private_key_path, &public_key_path).unwrap() //! } //! ``` -use crate::rsa; +use std::fmt::{Debug, Formatter}; + +#[cfg(feature = "ring-sig-verify")] +use untrusted::Input; + use rsa::{RSASigningAlgorithmId, RSAVerificationAlgorithmId, RsaSignatureEncoding}; pub use rsa::{ RsaEncoding, RsaKeyPair, RsaParameters, RsaPublicKeyComponents, RsaSubjectPublicKey, }; -use crate::{digest, ec, error, sealed, test}; -use std::fmt::{Debug, Formatter}; -#[cfg(feature = "ring-sig-verify")] -use untrusted::Input; - -pub use crate::ec::key_pair::EcdsaKeyPair; +pub use crate::ec::key_pair::{EcdsaKeyPair, PrivateKey as EcdsaPrivateKey}; use crate::ec::EcdsaSignatureFormat; -pub use crate::ec::{EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm}; -pub use crate::ed25519::{Ed25519KeyPair, EdDSAParameters, ED25519_PUBLIC_KEY_LEN}; +pub use crate::ec::{ + key_pair::EcPrivateKeyBin, key_pair::EcPrivateKeyRfc5915Der, EcPublicKeyX509Der, + EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm, PublicKey as EcdsaPublicKey, +}; +pub use crate::ed25519::{ + Ed25519KeyPair, Ed25519SeedBin, EdDSAParameters, Seed as Ed25519Seed, ED25519_PUBLIC_KEY_LEN, +}; +use crate::rsa; +use crate::{digest, ec, error, sealed, test}; /// The longest signature is an ASN.1 P-384 signature where *r* and *s* are of /// maximum length with the leading high bit set on each. Then each component @@ -708,9 +714,10 @@ pub static ED25519: EdDSAParameters = EdDSAParameters {}; #[cfg(test)] mod tests { + use regex::Regex; + use crate::rand::{generate, SystemRandom}; use crate::signature::{UnparsedPublicKey, ED25519}; - use regex::Regex; #[cfg(feature = "fips")] mod fips; diff --git a/aws-lc-rs/src/test.rs b/aws-lc-rs/src/test.rs index e724abd1050..1e4708d891c 100644 --- a/aws-lc-rs/src/test.rs +++ b/aws-lc-rs/src/test.rs @@ -422,7 +422,7 @@ fn parse_test_case( } // A blank line ends a test case if the test case isn't empty. - Some(line) if line.is_empty() => { + Some("") => { if !is_first_line { return Some(TestCase { attributes }); } diff --git a/aws-lc-rs/tests/data/ecdsa_test_public_key_p256_debug.txt b/aws-lc-rs/tests/data/ecdsa_test_public_key_p256_debug.txt index b590fc84904..0be46ece4cf 100644 --- a/aws-lc-rs/tests/data/ecdsa_test_public_key_p256_debug.txt +++ b/aws-lc-rs/tests/data/ecdsa_test_public_key_p256_debug.txt @@ -1 +1 @@ -PublicKey("04fc116698a3e3236550c4c9efa9bd4d0619602a65d2930e9150ab33e84dbc83f8a6a6b9933f35ab59245e5b5a7af5dca76b33cbe7aeee5981b3ca350bebf52ecd") \ No newline at end of file +EcdsaPublicKey("04fc116698a3e3236550c4c9efa9bd4d0619602a65d2930e9150ab33e84dbc83f8a6a6b9933f35ab59245e5b5a7af5dca76b33cbe7aeee5981b3ca350bebf52ecd") \ No newline at end of file diff --git a/aws-lc-rs/tests/ecdsa_tests.rs b/aws-lc-rs/tests/ecdsa_tests.rs index 50d6aa0cdba..48939d39f63 100644 --- a/aws-lc-rs/tests/ecdsa_tests.rs +++ b/aws-lc-rs/tests/ecdsa_tests.rs @@ -3,23 +3,26 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +use aws_lc_rs::encoding::AsBigEndian; +use aws_lc_rs::signature::EcPrivateKeyRfc5915Der; use aws_lc_rs::{ - rand, - signature::{self, KeyPair}, + encoding::AsDer, + rand::SystemRandom, + signature::{self, EcdsaKeyPair, KeyPair, Signature, UnparsedPublicKey}, test, test_file, }; use mirai_annotations::unrecoverable; #[test] fn ecdsa_traits() { - test::compile_time_assert_send::(); - test::compile_time_assert_sync::(); - test::compile_time_assert_send::(); - test::compile_time_assert_sync::(); - test::compile_time_assert_send::>(); - test::compile_time_assert_sync::>(); - test::compile_time_assert_send::>>(); - test::compile_time_assert_sync::>>(); + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); + test::compile_time_assert_send::(); + test::compile_time_assert_sync::(); + test::compile_time_assert_send::>(); + test::compile_time_assert_sync::>(); + test::compile_time_assert_send::>>(); + test::compile_time_assert_sync::>>(); } #[test] @@ -68,10 +71,7 @@ fn ecdsa_from_pkcs8_test() { let error = test_case.consume_optional_string("Error"); - match ( - signature::EcdsaKeyPair::from_pkcs8(this_fixed, &input), - error.clone(), - ) { + match (EcdsaKeyPair::from_pkcs8(this_fixed, &input), error.clone()) { (Ok(_), None) => (), (Err(e), None) => panic!( "Failed with error \"{}\", but expected to succeed. Input: {}", @@ -91,10 +91,7 @@ fn ecdsa_from_pkcs8_test() { ), }; - match ( - signature::EcdsaKeyPair::from_pkcs8(this_asn1, &input), - error, - ) { + match (EcdsaKeyPair::from_pkcs8(this_asn1, &input), error) { (Ok(_), None) => (), (Err(e), None) => { unrecoverable!("Failed with error \"{}\", but expected to succeed", e); @@ -104,12 +101,12 @@ fn ecdsa_from_pkcs8_test() { }; assert!( - signature::EcdsaKeyPair::from_pkcs8(other_fixed, &input).is_err(), + EcdsaKeyPair::from_pkcs8(other_fixed, &input).is_err(), "Input: {}", test::to_hex(&input) ); assert!( - signature::EcdsaKeyPair::from_pkcs8(other_asn1, &input).is_err(), + EcdsaKeyPair::from_pkcs8(other_asn1, &input).is_err(), "Input: {}", test::to_hex(&input) ); @@ -122,7 +119,7 @@ fn ecdsa_from_pkcs8_test() { // Verify that, at least, we generate PKCS#8 documents that we can read. #[test] fn ecdsa_generate_pkcs8_test() { - let rng = rand::SystemRandom::new(); + let rng = SystemRandom::new(); for alg in &[ &signature::ECDSA_P256_SHA256_ASN1_SIGNING, @@ -140,7 +137,7 @@ fn ecdsa_generate_pkcs8_test() { &signature::ECDSA_P256K1_SHA3_256_ASN1_SIGNING, &signature::ECDSA_P256K1_SHA3_256_FIXED_SIGNING, ] { - let pkcs8 = signature::EcdsaKeyPair::generate_pkcs8(alg, &rng).unwrap(); + let pkcs8 = EcdsaKeyPair::generate_pkcs8(alg, &rng).unwrap(); println!(); for b in pkcs8.as_ref() { print!("{:02x}", *b); @@ -148,7 +145,7 @@ fn ecdsa_generate_pkcs8_test() { println!(); println!(); - signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap(); + EcdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap(); } } @@ -188,7 +185,7 @@ fn test_signature_ecdsa_verify_asn1(data_file: test::File) { } }; - let actual_result = signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig); + let actual_result = UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig); assert_eq!(actual_result.is_ok(), is_valid); Ok(()) @@ -232,7 +229,7 @@ fn test_signature_ecdsa_verify_fixed(data_file: test::File) { let is_valid = expected_result == "P (0 )"; - let actual_result = signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig); + let actual_result = UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig); assert_eq!(actual_result.is_ok(), is_valid); Ok(()) @@ -245,11 +242,8 @@ fn ecdsa_test_public_key_coverage() { const PUBLIC_KEY: &[u8] = include_bytes!("data/ecdsa_test_public_key_p256.der"); const PUBLIC_KEY_DEBUG: &str = include_str!("data/ecdsa_test_public_key_p256_debug.txt"); - let key_pair = signature::EcdsaKeyPair::from_pkcs8( - &signature::ECDSA_P256_SHA256_FIXED_SIGNING, - PRIVATE_KEY, - ) - .unwrap(); + let key_pair = + EcdsaKeyPair::from_pkcs8(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, PRIVATE_KEY).unwrap(); // Test `AsRef<[u8]>` assert_eq!(key_pair.public_key().as_ref(), PUBLIC_KEY); @@ -260,7 +254,7 @@ fn ecdsa_test_public_key_coverage() { // Test `Copy`. #[allow(let_underscore_drop)] - let _: ::PublicKey = *key_pair.public_key(); + let _: ::PublicKey = *key_pair.public_key(); // Test `Debug`. assert_eq!(PUBLIC_KEY_DEBUG, format!("{:?}", key_pair.public_key())); @@ -287,7 +281,7 @@ fn signature_ecdsa_sign_fixed_sign_and_verify_sha3_test() { // different each time. Because of that, here we simply verify that the // signature verifies correctly. fn test_signature_ecdsa_sign_fixed_sign_and_verify(data_file: test::File) { - let rng = rand::SystemRandom::new(); + let rng = SystemRandom::new(); test::run(data_file, |section, test_case| { assert_eq!(section, ""); @@ -338,11 +332,11 @@ fn test_signature_ecdsa_sign_fixed_sign_and_verify(data_file: test::File) { }; let private_key = - signature::EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap(); + EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap(); let signature = private_key.sign(&rng, &msg).unwrap(); - let public_key = signature::UnparsedPublicKey::new(verification_alg, q); + let public_key = UnparsedPublicKey::new(verification_alg, q); let vfy_result = public_key.verify(&msg, signature.as_ref()); assert!(vfy_result.is_ok()); @@ -365,7 +359,7 @@ fn signature_ecdsa_sign_asn1_sha3_test() { // different each time. Because of that, here we simply verify that the // signature verifies correctly. fn test_signature_ecdsa_sign_asn1(data_file: test::File) { - let rng = rand::SystemRandom::new(); + let rng = SystemRandom::new(); test::run(data_file, |section, test_case| { assert_eq!(section, ""); @@ -416,13 +410,80 @@ fn test_signature_ecdsa_sign_asn1(data_file: test::File) { }; let private_key = - signature::EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap(); + EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap(); let signature = private_key.sign(&rng, &msg).unwrap(); - let public_key = signature::UnparsedPublicKey::new(verification_alg, q); + let public_key = UnparsedPublicKey::new(verification_alg, q); assert_eq!(public_key.verify(&msg, signature.as_ref()), Ok(())); Ok(()) }); } + +#[test] +fn test_to_pkcs8() { + for signing_alg in [ + &signature::ECDSA_P521_SHA3_512_ASN1_SIGNING, + &signature::ECDSA_P521_SHA3_512_FIXED_SIGNING, + &signature::ECDSA_P521_SHA512_ASN1_SIGNING, + &signature::ECDSA_P521_SHA512_FIXED_SIGNING, + &signature::ECDSA_P384_SHA3_384_ASN1_SIGNING, + &signature::ECDSA_P384_SHA3_384_FIXED_SIGNING, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + &signature::ECDSA_P384_SHA384_FIXED_SIGNING, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING, + &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + ] { + let rnd = SystemRandom::new(); + let key_pair_doc = EcdsaKeyPair::generate_pkcs8(signing_alg, &rnd).unwrap(); + let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_pair_doc.as_ref()).unwrap(); + + let key_pair_export_doc = key_pair.to_pkcs8v1().unwrap(); + // Verify that the exported bytes match the original generated bytes + assert_eq!(key_pair_doc.as_ref(), key_pair_export_doc.as_ref()); + } +} + +#[test] +fn test_private_key() { + for signing_alg in [ + &signature::ECDSA_P521_SHA3_512_ASN1_SIGNING, + &signature::ECDSA_P521_SHA3_512_FIXED_SIGNING, + &signature::ECDSA_P521_SHA512_ASN1_SIGNING, + &signature::ECDSA_P521_SHA512_FIXED_SIGNING, + &signature::ECDSA_P384_SHA3_384_ASN1_SIGNING, + &signature::ECDSA_P384_SHA3_384_FIXED_SIGNING, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + &signature::ECDSA_P384_SHA384_FIXED_SIGNING, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING, + &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + ] { + let rnd = SystemRandom::new(); + let key_pair_doc = EcdsaKeyPair::generate_pkcs8(signing_alg, &rnd).unwrap(); + let key_pair = EcdsaKeyPair::from_pkcs8(signing_alg, key_pair_doc.as_ref()).unwrap(); + + { + let private_key = key_pair.private_key().as_be_bytes().unwrap(); + let public_key = key_pair.public_key(); + let key_pair_copy = EcdsaKeyPair::from_private_key_and_public_key( + signing_alg, + private_key.as_ref(), + public_key.as_ref(), + ) + .unwrap(); + let key_pair_copy_doc = key_pair_copy.to_pkcs8v1().unwrap(); + assert_eq!(key_pair_doc.as_ref(), key_pair_copy_doc.as_ref()); + } + { + let private_key_der: EcPrivateKeyRfc5915Der = key_pair.private_key().as_der().unwrap(); + assert_eq!("Buffer(...)", format!("{private_key_der:?}")); + assert!(EcdsaKeyPair::from_pkcs8(signing_alg, private_key_der.as_ref()).is_err()); + + let key_pair_copy = + EcdsaKeyPair::from_private_key_der(signing_alg, private_key_der.as_ref()).unwrap(); + let key_pair_copy_doc = key_pair_copy.to_pkcs8v1().unwrap(); + assert_eq!(key_pair_doc.as_ref(), key_pair_copy_doc.as_ref()); + } + } +} diff --git a/aws-lc-rs/tests/ed25519_tests.rs b/aws-lc-rs/tests/ed25519_tests.rs index 482d90ab8dd..50c1c820851 100644 --- a/aws-lc-rs/tests/ed25519_tests.rs +++ b/aws-lc-rs/tests/ed25519_tests.rs @@ -3,6 +3,9 @@ // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC +use aws_lc_rs::encoding::AsBigEndian; +use aws_lc_rs::rand::SystemRandom; +use aws_lc_rs::signature::Ed25519SeedBin; use aws_lc_rs::{ error, signature::{self, Ed25519KeyPair, KeyPair}, @@ -191,3 +194,43 @@ fn ed25519_test_public_key_coverage() { format!("{key_pair:?}") ); } + +#[test] +fn test_to_pkcs8() { + let rnd = SystemRandom::new(); + let key_pair_doc = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap(); + let key_pair = Ed25519KeyPair::from_pkcs8(key_pair_doc.as_ref()).unwrap(); + + let key_pair_export_doc = key_pair.to_pkcs8().unwrap(); + // Verify that the exported bytes match the original generated bytes + assert_eq!(key_pair_doc.as_ref(), key_pair_export_doc.as_ref()); +} + +#[test] +fn test_to_pkcs8v1() { + let rnd = SystemRandom::new(); + let key_pair_doc = Ed25519KeyPair::generate_pkcs8v1(&rnd).unwrap(); + let key_pair = Ed25519KeyPair::from_pkcs8(key_pair_doc.as_ref()).unwrap(); + + let key_pair_export_doc = key_pair.to_pkcs8v1().unwrap(); + // Verify that the exported bytes match the original generated bytes + assert_eq!(key_pair_doc.as_ref(), key_pair_export_doc.as_ref()); +} + +#[test] +fn test_seed() { + let rnd = SystemRandom::new(); + let key_pair_doc = Ed25519KeyPair::generate_pkcs8(&rnd).unwrap(); + + let key_pair = Ed25519KeyPair::from_pkcs8(key_pair_doc.as_ref()).unwrap(); + let seed = key_pair.seed().unwrap(); + let seed_buffer: Ed25519SeedBin = seed.as_be_bytes().unwrap(); + + let pub_key = key_pair.public_key(); + + let key_pair_copy = + Ed25519KeyPair::from_seed_and_public_key(seed_buffer.as_ref(), pub_key.as_ref()).unwrap(); + let key_pair_copy_doc = key_pair_copy.to_pkcs8().unwrap(); + + assert_eq!(key_pair_doc.as_ref(), key_pair_copy_doc.as_ref()); +}