diff --git a/aws-lc-rs/src/kem.rs b/aws-lc-rs/src/kem.rs index 959f998fd61..fadcbb98005 100644 --- a/aws-lc-rs/src/kem.rs +++ b/aws-lc-rs/src/kem.rs @@ -11,15 +11,12 @@ //! //! ```ignore //! use aws_lc_rs::{ -//! error::Unspecified, //! kem::{Ciphertext, DecapsulationKey, EncapsulationKey}, -//! unstable::kem::{AlgorithmId, get_algorithm} +//! unstable::kem::{ML_KEM_512} //! }; //! -//! let kyber512_r3 = get_algorithm(AlgorithmId::Kyber512_R3).ok_or(Unspecified)?; -//! //! // Alice generates their (private) decapsulation key. -//! let decapsulation_key = DecapsulationKey::generate(kyber512_r3)?; +//! let decapsulation_key = DecapsulationKey::generate(&ML_KEM_512)?; //! //! // Alices computes the (public) encapsulation key. //! let encapsulation_key = decapsulation_key.encapsulation_key()?; @@ -31,12 +28,12 @@ //! let encapsulation_key_bytes = encapsulation_key_bytes.as_ref(); //! //! // Bob constructs the (public) encapsulation key from the key bytes provided by Alice. -//! let retrieved_encapsulation_key = EncapsulationKey::new(kyber512_r3, encapsulation_key_bytes)?; +//! let retrieved_encapsulation_key = EncapsulationKey::new(&ML_KEM_512, encapsulation_key_bytes)?; //! //! // Bob executes the encapsulation algorithm to to produce their copy of the secret, and associated ciphertext. //! let (ciphertext, bob_secret) = retrieved_encapsulation_key.encapsulate()?; //! -//! // Alice recieves ciphertext bytes from bob +//! // Alice receives ciphertext bytes from bob //! let ciphertext_bytes = ciphertext.as_ref(); //! //! // Bob sends Alice the ciphertext computed from the encapsulation algorithm, Alice runs decapsulation to derive their @@ -63,6 +60,67 @@ use aws_lc::{ use core::{cmp::Ordering, ptr::null_mut}; use zeroize::Zeroize; +#[cfg(not(feature = "fips"))] +pub(crate) mod semistable { + #![allow(unused)] + + use super::{Algorithm, AlgorithmId}; + + const ML_KEM_512_SHARED_SECRET_LENGTH: usize = 32; + const ML_KEM_512_PUBLIC_KEY_LENGTH: usize = 800; + const ML_KEM_512_SECRET_KEY_LENGTH: usize = 1632; + const ML_KEM_512_CIPHERTEXT_LENGTH: usize = 768; + + const ML_KEM_768_SHARED_SECRET_LENGTH: usize = 32; + const ML_KEM_768_PUBLIC_KEY_LENGTH: usize = 1184; + const ML_KEM_768_SECRET_KEY_LENGTH: usize = 2400; + const ML_KEM_768_CIPHERTEXT_LENGTH: usize = 1088; + + const ML_KEM_1024_SHARED_SECRET_LENGTH: usize = 32; + const ML_KEM_1024_PUBLIC_KEY_LENGTH: usize = 1568; + const ML_KEM_1024_SECRET_KEY_LENGTH: usize = 3168; + const ML_KEM_1024_CIPHERTEXT_LENGTH: usize = 1568; + + /// NIST FIPS 203 ML-KEM-512 algorithm. + pub const ML_KEM_512: Algorithm = Algorithm { + id: AlgorithmId::MlKem512, + decapsulate_key_size: ML_KEM_512_SECRET_KEY_LENGTH, + encapsulate_key_size: ML_KEM_512_PUBLIC_KEY_LENGTH, + ciphertext_size: ML_KEM_512_CIPHERTEXT_LENGTH, + shared_secret_size: ML_KEM_512_SHARED_SECRET_LENGTH, + }; + + /// NIST FIPS 203 ML-KEM-768 algorithm. + pub const ML_KEM_768: Algorithm = Algorithm { + id: AlgorithmId::MlKem768, + decapsulate_key_size: ML_KEM_768_SECRET_KEY_LENGTH, + encapsulate_key_size: ML_KEM_768_PUBLIC_KEY_LENGTH, + ciphertext_size: ML_KEM_768_CIPHERTEXT_LENGTH, + shared_secret_size: ML_KEM_768_SHARED_SECRET_LENGTH, + }; + + /// NIST FIPS 203 ML-KEM-1024 algorithm. + pub const ML_KEM_1024: Algorithm = Algorithm { + id: AlgorithmId::MlKem1024, + decapsulate_key_size: ML_KEM_1024_SECRET_KEY_LENGTH, + encapsulate_key_size: ML_KEM_1024_PUBLIC_KEY_LENGTH, + ciphertext_size: ML_KEM_1024_CIPHERTEXT_LENGTH, + shared_secret_size: ML_KEM_1024_SHARED_SECRET_LENGTH, + }; +} + +#[cfg(feature = "fips")] +mod missing_nid { + pub const NID_MLKEM512: i32 = 988; + pub const NID_MLKEM768: i32 = 989; + pub const NID_MLKEM1024: i32 = 990; +} + +#[cfg(feature = "fips")] +use self::missing_nid::{NID_MLKEM1024, NID_MLKEM512, NID_MLKEM768}; +#[cfg(not(feature = "fips"))] +use aws_lc::{NID_MLKEM1024, NID_MLKEM512, NID_MLKEM768}; + /// An identifier for a KEM algorithm. pub trait AlgorithmIdentifier: Copy + Clone + Debug + PartialEq + crate::sealed::Sealed + 'static @@ -136,14 +194,27 @@ where /// Identifier for a KEM algorithm. /// /// See [`crate::unstable::kem::AlgorithmId`] and [`crate::unstable::kem::get_algorithm`] for -/// access to algorithms not subject to semantic versioning gurantees. +/// access to algorithms not subject to semantic versioning guarantees. #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq)] -pub enum AlgorithmId {} +pub enum AlgorithmId { + /// NIST FIPS 203 ML-KEM-512 algorithm. + MlKem512, + + /// NIST FIPS 203 ML-KEM-768 algorithm. + MlKem768, + + /// NIST FIPS 203 ML-KEM-1024 algorithm. + MlKem1024, +} impl AlgorithmIdentifier for AlgorithmId { fn nid(self) -> i32 { - unreachable!() + match self { + AlgorithmId::MlKem512 => NID_MLKEM512, + AlgorithmId::MlKem768 => NID_MLKEM768, + AlgorithmId::MlKem1024 => NID_MLKEM1024, + } } } @@ -459,6 +530,15 @@ fn kem_key_generate(nid: i32) -> Result, Unspecified> { mod tests { use super::{Ciphertext, SharedSecret}; + #[cfg(not(feature = "fips"))] + use crate::error::KeyRejected; + + #[cfg(not(feature = "fips"))] + use super::{DecapsulationKey, EncapsulationKey}; + + #[cfg(not(feature = "fips"))] + use crate::kem::semistable::{ML_KEM_1024, ML_KEM_512, ML_KEM_768}; + #[test] fn ciphertext() { let ciphertext_bytes = vec![42u8; 4]; @@ -477,4 +557,110 @@ mod tests { let shared_secret = SharedSecret::new(secret_bytes.into_boxed_slice()); assert_eq!(shared_secret.as_ref(), &[42, 42, 42, 42]); } + + #[test] + #[cfg(not(feature = "fips"))] + fn test_kem_serialize() { + for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] { + let priv_key = DecapsulationKey::generate(algorithm).unwrap(); + assert_eq!(priv_key.algorithm(), algorithm); + + let pub_key = priv_key.encapsulation_key().unwrap(); + let pubkey_raw_bytes = pub_key.key_bytes().unwrap(); + let pub_key_from_bytes = + EncapsulationKey::new(algorithm, pubkey_raw_bytes.as_ref()).unwrap(); + + assert_eq!( + pub_key.key_bytes().unwrap().as_ref(), + pub_key_from_bytes.key_bytes().unwrap().as_ref() + ); + assert_eq!(pub_key.algorithm(), pub_key_from_bytes.algorithm()); + } + } + + #[test] + #[cfg(not(feature = "fips"))] + fn test_kem_wrong_sizes() { + for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] { + let too_long_bytes = vec![0u8; algorithm.encapsulate_key_size() + 1]; + let long_pub_key_from_bytes = EncapsulationKey::new(algorithm, &too_long_bytes); + assert_eq!( + long_pub_key_from_bytes.err(), + Some(KeyRejected::too_large()) + ); + + let too_short_bytes = vec![0u8; algorithm.encapsulate_key_size() - 1]; + let short_pub_key_from_bytes = EncapsulationKey::new(algorithm, &too_short_bytes); + assert_eq!( + short_pub_key_from_bytes.err(), + Some(KeyRejected::too_small()) + ); + } + } + + #[test] + #[cfg(not(feature = "fips"))] + fn test_kem_e2e() { + for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] { + let priv_key = DecapsulationKey::generate(algorithm).unwrap(); + assert_eq!(priv_key.algorithm(), algorithm); + + let pub_key = priv_key.encapsulation_key().unwrap(); + + let (alice_ciphertext, alice_secret) = + pub_key.encapsulate().expect("encapsulate successful"); + + let bob_secret = priv_key + .decapsulate(alice_ciphertext) + .expect("decapsulate successful"); + + assert_eq!(alice_secret.as_ref(), bob_secret.as_ref()); + } + } + + #[test] + #[cfg(not(feature = "fips"))] + fn test_serialized_kem_e2e() { + for algorithm in [&ML_KEM_512, &ML_KEM_768, &ML_KEM_1024] { + let priv_key = DecapsulationKey::generate(algorithm).unwrap(); + assert_eq!(priv_key.algorithm(), algorithm); + + let pub_key = priv_key.encapsulation_key().unwrap(); + + // Generate public key bytes to send to bob + let pub_key_bytes = pub_key.key_bytes().unwrap(); + + // Test that priv_key's EVP_PKEY isn't entirely freed since we remove this pub_key's reference. + drop(pub_key); + + let retrieved_pub_key = + EncapsulationKey::new(algorithm, pub_key_bytes.as_ref()).unwrap(); + let (ciphertext, bob_secret) = retrieved_pub_key + .encapsulate() + .expect("encapsulate successful"); + + let alice_secret = priv_key + .decapsulate(ciphertext) + .expect("decapsulate successful"); + + assert_eq!(alice_secret.as_ref(), bob_secret.as_ref()); + } + } + + #[test] + #[cfg(not(feature = "fips"))] + fn test_debug_fmt() { + let private = DecapsulationKey::generate(&ML_KEM_512).expect("successful generation"); + assert_eq!( + format!("{private:?}"), + "DecapsulationKey { algorithm: MlKem512, .. }" + ); + assert_eq!( + format!( + "{:?}", + private.encapsulation_key().expect("public key retrievable") + ), + "EncapsulationKey { algorithm: MlKem512, .. }" + ); + } } diff --git a/aws-lc-rs/src/unstable/kdf.rs b/aws-lc-rs/src/unstable/kdf.rs index 01657d9cc17..658ca950ecc 100644 --- a/aws-lc-rs/src/unstable/kdf.rs +++ b/aws-lc-rs/src/unstable/kdf.rs @@ -116,7 +116,7 @@ //! ``` //! # Single-step Key Derivation Function (SSKDF) //! -//! [`sskdf_digest`] and [`sskd_hmac`] provided implementations of a one-step key derivation function defined in +//! [`sskdf_digest`] and [`sskdf_hmac`] provided implementations of a one-step key derivation function defined in //! section 4 of [NIST SP 800-56Cr2](https://doi.org/10.6028/NIST.SP.800-56Cr2). //! //! These functions are used to derive keying material from a shared secret during a key establishment scheme. diff --git a/aws-lc-rs/src/unstable/kem.rs b/aws-lc-rs/src/unstable/kem.rs index f4306f186f0..60ae941154a 100644 --- a/aws-lc-rs/src/unstable/kem.rs +++ b/aws-lc-rs/src/unstable/kem.rs @@ -9,17 +9,14 @@ //! //! # Example //! -//! ``` +//! ```ignore //! use aws_lc_rs::{ -//! error::Unspecified, //! kem::{Ciphertext, DecapsulationKey, EncapsulationKey}, -//! unstable::kem::{AlgorithmId, get_algorithm} +//! unstable::kem::{ML_KEM_512} //! }; //! -//! let kyber512_r3 = get_algorithm(AlgorithmId::Kyber512_R3).ok_or(Unspecified)?; -//! //! // Alice generates their (private) decapsulation key. -//! let decapsulation_key = DecapsulationKey::generate(kyber512_r3)?; +//! let decapsulation_key = DecapsulationKey::generate(&ML_KEM_512)?; //! //! // Alices computes the (public) encapsulation key. //! let encapsulation_key = decapsulation_key.encapsulation_key()?; @@ -31,12 +28,12 @@ //! let encapsulation_key_bytes = encapsulation_key_bytes.as_ref(); //! //! // Bob constructs the (public) encapsulation key from the key bytes provided by Alice. -//! let retrieved_encapsulation_key = EncapsulationKey::new(kyber512_r3, encapsulation_key_bytes)?; +//! let retrieved_encapsulation_key = EncapsulationKey::new(&ML_KEM_512, encapsulation_key_bytes)?; //! //! // Bob executes the encapsulation algorithm to to produce their copy of the secret, and associated ciphertext. //! let (ciphertext, bob_secret) = retrieved_encapsulation_key.encapsulate()?; //! -//! // Alice recieves ciphertext bytes from bob +//! // Alice receives ciphertext bytes from bob //! let ciphertext_bytes = ciphertext.as_ref(); //! //! // Bob sends Alice the ciphertext computed from the encapsulation algorithm, Alice runs decapsulation to derive their @@ -54,6 +51,9 @@ use core::fmt::Debug; use crate::kem::Algorithm; use aws_lc::{NID_KYBER1024_R3, NID_KYBER512_R3, NID_KYBER768_R3}; +#[cfg(not(feature = "fips"))] +pub use crate::kem::semistable::{ML_KEM_1024, ML_KEM_512, ML_KEM_768}; + // Key lengths defined as stated on the CRYSTALS website: // https://pq-crystals.org/kyber/ @@ -73,6 +73,7 @@ const KYBER1024_R3_PUBLIC_KEY_LENGTH: usize = 1568; const KYBER1024_R3_SHARED_SECRET_LENGTH: usize = 32; /// NIST Round 3 submission of the Kyber-512 algorithm. +#[allow(deprecated)] const KYBER512_R3: Algorithm = Algorithm { id: AlgorithmId::Kyber512_R3, decapsulate_key_size: KYBER512_R3_SECRET_KEY_LENGTH, @@ -82,6 +83,7 @@ const KYBER512_R3: Algorithm = Algorithm { }; /// NIST Round 3 submission of the Kyber-768 algorithm. +#[allow(deprecated)] const KYBER768_R3: Algorithm = Algorithm { id: AlgorithmId::Kyber768_R3, decapsulate_key_size: KYBER768_R3_SECRET_KEY_LENGTH, @@ -91,6 +93,7 @@ const KYBER768_R3: Algorithm = Algorithm { }; /// NIST Round 3 submission of the Kyber-1024 algorithm. +#[allow(deprecated)] const KYBER1024_R3: Algorithm = Algorithm { id: AlgorithmId::Kyber1024_R3, decapsulate_key_size: KYBER1024_R3_SECRET_KEY_LENGTH, @@ -105,18 +108,22 @@ const KYBER1024_R3: Algorithm = Algorithm { #[derive(Clone, Copy, Debug, PartialEq)] pub enum AlgorithmId { /// NIST Round 3 submission of the Kyber-512 algorithm. + #[deprecated] Kyber512_R3, /// NIST Round 3 submission of the Kyber-768 algorithm. + #[deprecated] Kyber768_R3, /// NIST Round 3 submission of the Kyber-1024 algorithm. + #[deprecated] Kyber1024_R3, } impl crate::kem::AlgorithmIdentifier for AlgorithmId { #[inline] fn nid(self) -> i32 { + #[allow(deprecated)] match self { AlgorithmId::Kyber512_R3 => NID_KYBER512_R3, AlgorithmId::Kyber768_R3 => NID_KYBER768_R3, @@ -131,6 +138,7 @@ impl crate::sealed::Sealed for AlgorithmId {} /// May return [`None`] if support for the algorithm has been removed from the unstable module. #[must_use] pub const fn get_algorithm(id: AlgorithmId) -> Option<&'static Algorithm> { + #[allow(deprecated)] match id { AlgorithmId::Kyber512_R3 => Some(&KYBER512_R3), AlgorithmId::Kyber768_R3 => Some(&KYBER768_R3), @@ -140,6 +148,8 @@ pub const fn get_algorithm(id: AlgorithmId) -> Option<&'static Algorithm