diff --git a/crypto/factory/Cargo.toml b/crypto/factory/Cargo.toml index 0f31c933..5b409e3e 100644 --- a/crypto/factory/Cargo.toml +++ b/crypto/factory/Cargo.toml @@ -7,6 +7,8 @@ edition.workspace = true bouncycastle-core.workspace = true bouncycastle-hkdf.workspace = true bouncycastle-hmac.workspace = true +bouncycastle-mldsa.workspace = true +bouncycastle-mlkem.workspace = true bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true diff --git a/crypto/factory/src/kem_factory.rs b/crypto/factory/src/kem_factory.rs new file mode 100644 index 00000000..ceaffe23 --- /dev/null +++ b/crypto/factory/src/kem_factory.rs @@ -0,0 +1,328 @@ +//! KEM factory for creating instances of algorithms that implement KEM traits. +//! +//! As with all Factory objects, this constructs algorithms from strings and defaults. +//! Supported objects are encapsulated in enums that pass operations through to the underlying types. +//! +//! # Design note on traits +//! +//! The core [`KEMEncapsulator`] and [`KEMDecapsulator`] traits are parameterized by const-generic +//! key and ciphertext sizes. A single enum that wraps ML-KEM-512/768/1024 cannot implement those +//! traits with one fixed set of const parameters. This module therefore wraps keys in enums and +//! exposes inherent methods with the same shape as the core traits. +//! +//! Example usage: +//! ``` +//! use bouncycastle_factory::AlgorithmFactory; +//! use bouncycastle_factory::kem_factory::KEMFactory; +//! use bouncycastle_mlkem::ML_KEM_768_NAME; +//! +//! let factory = KEMFactory::new(ML_KEM_768_NAME).unwrap(); +//! assert_eq!(factory.algorithm_name(), ML_KEM_768_NAME); +//! // keygen/encaps/decaps pass through to the underlying ML-KEM types; +//! // see the crate tests for full round-trip examples. +//! ``` + +use crate::{AlgorithmFactory, DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; +use bouncycastle_core::errors::KEMError; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::{ + KEMDecapsulator as _, KEMEncapsulator as _, KEMPrivateKey as KEMPrivateKeyTrait, + KEMPublicKey as KEMPublicKeyTrait, RNG, +}; +use bouncycastle_mlkem as mlkem; +use bouncycastle_mlkem::{ + MLKEM512, MLKEM768, MLKEM1024, MLKEMTrait, MLKEM_SS_LEN, ML_KEM_512_NAME, ML_KEM_768_NAME, + ML_KEM_1024_NAME, +}; + +/*** Defaults ***/ +/// Default KEM algorithm name (192-bit class / ML-KEM-768). +pub const DEFAULT_KEM_NAME: &str = ML_KEM_768_NAME; +/// Default KEM algorithm at the 128-bit security level. +pub const DEFAULT_128BIT_KEM_NAME: &str = ML_KEM_512_NAME; +/// Default KEM algorithm at the 256-bit security level. +pub const DEFAULT_256BIT_KEM_NAME: &str = ML_KEM_1024_NAME; + +/// Wrapper for all supported KEM public (encapsulation) keys. +pub enum KEMPublicKey { + /// ML-KEM-512 public key. + MLKEM512(mlkem::MLKEM512PublicKey), + /// ML-KEM-768 public key. + MLKEM768(mlkem::MLKEM768PublicKey), + /// ML-KEM-1024 public key. + MLKEM1024(mlkem::MLKEM1024PublicKey), +} + +impl KEMPublicKey { + /// Encode the public key to its standard byte encoding. + pub fn encode(&self) -> Vec { + match self { + Self::MLKEM512(pk) => pk.encode().to_vec(), + Self::MLKEM768(pk) => pk.encode().to_vec(), + Self::MLKEM1024(pk) => pk.encode().to_vec(), + } + } + + /// Decode a public key from bytes for the named algorithm. + pub fn from_bytes(alg_name: &str, bytes: &[u8]) -> Result { + match alg_name { + ML_KEM_512_NAME => Ok(Self::MLKEM512( + mlkem::MLKEM512PublicKey::from_bytes(bytes).map_err(kem_err)?, + )), + ML_KEM_768_NAME => Ok(Self::MLKEM768( + mlkem::MLKEM768PublicKey::from_bytes(bytes).map_err(kem_err)?, + )), + ML_KEM_1024_NAME => Ok(Self::MLKEM1024( + mlkem::MLKEM1024PublicKey::from_bytes(bytes).map_err(kem_err)?, + )), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known KEM" + ))), + } + } + + /// Algorithm name for this key. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLKEM512(_) => ML_KEM_512_NAME, + Self::MLKEM768(_) => ML_KEM_768_NAME, + Self::MLKEM1024(_) => ML_KEM_1024_NAME, + } + } +} + +/// Wrapper for all supported KEM private (decapsulation) keys. +pub enum KEMPrivateKey { + /// ML-KEM-512 private key. + MLKEM512(mlkem::MLKEM512PrivateKey), + /// ML-KEM-768 private key. + MLKEM768(mlkem::MLKEM768PrivateKey), + /// ML-KEM-1024 private key. + MLKEM1024(mlkem::MLKEM1024PrivateKey), +} + +impl KEMPrivateKey { + /// Encode the private key to its standard byte encoding. + pub fn encode(&self) -> Vec { + match self { + Self::MLKEM512(sk) => sk.encode().to_vec(), + Self::MLKEM768(sk) => sk.encode().to_vec(), + Self::MLKEM1024(sk) => sk.encode().to_vec(), + } + } + + /// Decode a private key from bytes for the named algorithm. + pub fn from_bytes(alg_name: &str, bytes: &[u8]) -> Result { + match alg_name { + ML_KEM_512_NAME => Ok(Self::MLKEM512( + mlkem::MLKEM512PrivateKey::from_bytes(bytes).map_err(kem_err)?, + )), + ML_KEM_768_NAME => Ok(Self::MLKEM768( + mlkem::MLKEM768PrivateKey::from_bytes(bytes).map_err(kem_err)?, + )), + ML_KEM_1024_NAME => Ok(Self::MLKEM1024( + mlkem::MLKEM1024PrivateKey::from_bytes(bytes).map_err(kem_err)?, + )), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known KEM" + ))), + } + } + + /// Algorithm name for this key. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLKEM512(_) => ML_KEM_512_NAME, + Self::MLKEM768(_) => ML_KEM_768_NAME, + Self::MLKEM1024(_) => ML_KEM_1024_NAME, + } + } +} + +/// Factory / algorithm selector for all supported KEM algorithms. +/// +/// Constructed by name via [`AlgorithmFactory::new`] or the default helpers. +/// Operations pass through to the underlying ML-KEM parameter sets. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum KEMFactory { + /// ML-KEM-512 (NIST security category 1 / ~128-bit class). + MLKEM512, + /// ML-KEM-768 (NIST security category 3 / ~192-bit class). + MLKEM768, + /// ML-KEM-1024 (NIST security category 5 / ~256-bit class). + MLKEM1024, +} + +impl Default for KEMFactory { + fn default() -> Self { + Self::MLKEM768 + } +} + +impl AlgorithmFactory for KEMFactory { + fn default_128_bit() -> Self { + Self::MLKEM512 + } + + fn default_256_bit() -> Self { + Self::MLKEM1024 + } + + fn new(alg_name: &str) -> Result { + match alg_name { + DEFAULT => Ok(Self::default()), + DEFAULT_128_BIT => Ok(Self::default_128_bit()), + DEFAULT_256_BIT => Ok(Self::default_256_bit()), + ML_KEM_512_NAME => Ok(Self::MLKEM512), + ML_KEM_768_NAME => Ok(Self::MLKEM768), + ML_KEM_1024_NAME => Ok(Self::MLKEM1024), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known KEM" + ))), + } + } +} + +impl KEMFactory { + /// Algorithm name string for this factory selection. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLKEM512 => ML_KEM_512_NAME, + Self::MLKEM768 => ML_KEM_768_NAME, + Self::MLKEM1024 => ML_KEM_1024_NAME, + } + } + + /// Generate a fresh key pair using the library default OS-backed RNG. + pub fn keygen(&self) -> Result<(KEMPublicKey, KEMPrivateKey), KEMError> { + match self { + Self::MLKEM512 => { + let (pk, sk) = MLKEM512::keygen()?; + Ok((KEMPublicKey::MLKEM512(pk), KEMPrivateKey::MLKEM512(sk))) + } + Self::MLKEM768 => { + let (pk, sk) = MLKEM768::keygen()?; + Ok((KEMPublicKey::MLKEM768(pk), KEMPrivateKey::MLKEM768(sk))) + } + Self::MLKEM1024 => { + let (pk, sk) = MLKEM1024::keygen()?; + Ok((KEMPublicKey::MLKEM1024(pk), KEMPrivateKey::MLKEM1024(sk))) + } + } + } + + /// Generate a key pair using the provided RNG. + pub fn keygen_from_rng( + &self, + rng: &mut dyn RNG, + ) -> Result<(KEMPublicKey, KEMPrivateKey), KEMError> { + match self { + Self::MLKEM512 => { + let (pk, sk) = MLKEM512::keygen_from_rng(rng)?; + Ok((KEMPublicKey::MLKEM512(pk), KEMPrivateKey::MLKEM512(sk))) + } + Self::MLKEM768 => { + let (pk, sk) = MLKEM768::keygen_from_rng(rng)?; + Ok((KEMPublicKey::MLKEM768(pk), KEMPrivateKey::MLKEM768(sk))) + } + Self::MLKEM1024 => { + let (pk, sk) = MLKEM1024::keygen_from_rng(rng)?; + Ok((KEMPublicKey::MLKEM1024(pk), KEMPrivateKey::MLKEM1024(sk))) + } + } + } + + /// Generate a key pair from a 64-byte seed. + pub fn keygen_from_seed( + &self, + seed: &KeyMaterial<64>, + ) -> Result<(KEMPublicKey, KEMPrivateKey), KEMError> { + match self { + Self::MLKEM512 => { + let (pk, sk) = MLKEM512::keygen_from_seed(seed)?; + Ok((KEMPublicKey::MLKEM512(pk), KEMPrivateKey::MLKEM512(sk))) + } + Self::MLKEM768 => { + let (pk, sk) = MLKEM768::keygen_from_seed(seed)?; + Ok((KEMPublicKey::MLKEM768(pk), KEMPrivateKey::MLKEM768(sk))) + } + Self::MLKEM1024 => { + let (pk, sk) = MLKEM1024::keygen_from_seed(seed)?; + Ok((KEMPublicKey::MLKEM1024(pk), KEMPrivateKey::MLKEM1024(sk))) + } + } + } + + /// Encapsulate to the given public key (pass-through to [`KEMEncapsulator::encaps`]). + /// + /// Returns `(shared_secret, ciphertext)`. + pub fn encaps( + &self, + pk: &KEMPublicKey, + ) -> Result<(KeyMaterial, Vec), KEMError> { + match (self, pk) { + (Self::MLKEM512, KEMPublicKey::MLKEM512(pk)) => { + let (ss, ct) = MLKEM512::encaps(pk)?; + Ok((ss, ct.to_vec())) + } + (Self::MLKEM768, KEMPublicKey::MLKEM768(pk)) => { + let (ss, ct) = MLKEM768::encaps(pk)?; + Ok((ss, ct.to_vec())) + } + (Self::MLKEM1024, KEMPublicKey::MLKEM1024(pk)) => { + let (ss, ct) = MLKEM1024::encaps(pk)?; + Ok((ss, ct.to_vec())) + } + _ => Err(KEMError::GenericError( + "KEM public key does not match the selected KEMFactory algorithm", + )), + } + } + + /// Encapsulate using a caller-provided RNG (pass-through to [`KEMEncapsulator::encaps_rng`]). + pub fn encaps_rng( + &self, + pk: &KEMPublicKey, + rng: &mut dyn RNG, + ) -> Result<(KeyMaterial, Vec), KEMError> { + match (self, pk) { + (Self::MLKEM512, KEMPublicKey::MLKEM512(pk)) => { + let (ss, ct) = MLKEM512::encaps_rng(pk, rng)?; + Ok((ss, ct.to_vec())) + } + (Self::MLKEM768, KEMPublicKey::MLKEM768(pk)) => { + let (ss, ct) = MLKEM768::encaps_rng(pk, rng)?; + Ok((ss, ct.to_vec())) + } + (Self::MLKEM1024, KEMPublicKey::MLKEM1024(pk)) => { + let (ss, ct) = MLKEM1024::encaps_rng(pk, rng)?; + Ok((ss, ct.to_vec())) + } + _ => Err(KEMError::GenericError( + "KEM public key does not match the selected KEMFactory algorithm", + )), + } + } + + /// Decapsulate a ciphertext (pass-through to [`KEMDecapsulator::decaps`]). + pub fn decaps( + &self, + sk: &KEMPrivateKey, + ct: &[u8], + ) -> Result, KEMError> { + match (self, sk) { + (Self::MLKEM512, KEMPrivateKey::MLKEM512(sk)) => MLKEM512::decaps(sk, ct), + (Self::MLKEM768, KEMPrivateKey::MLKEM768(sk)) => MLKEM768::decaps(sk, ct), + (Self::MLKEM1024, KEMPrivateKey::MLKEM1024(sk)) => MLKEM1024::decaps(sk, ct), + _ => Err(KEMError::GenericError( + "KEM private key does not match the selected KEMFactory algorithm", + )), + } + } +} + +fn kem_err(e: KEMError) -> FactoryError { + FactoryError::UnsupportedAlgorithm(format!("KEM key decode failed: {e:?}")) +} + + diff --git a/crypto/factory/src/lib.rs b/crypto/factory/src/lib.rs index 8d1c1634..df880135 100644 --- a/crypto/factory/src/lib.rs +++ b/crypto/factory/src/lib.rs @@ -35,8 +35,10 @@ use bouncycastle_core::errors::MACError; pub mod hash_factory; pub mod kdf_factory; +pub mod kem_factory; pub mod mac_factory; pub mod rng_factory; +pub mod signature_factory; pub mod xof_factory; /*** String constants ***/ diff --git a/crypto/factory/src/signature_factory.rs b/crypto/factory/src/signature_factory.rs new file mode 100644 index 00000000..b35a9ebc --- /dev/null +++ b/crypto/factory/src/signature_factory.rs @@ -0,0 +1,413 @@ +//! Signature factory for creating instances of algorithms that implement signature traits. +//! +//! As with all Factory objects, this constructs algorithms from strings and defaults. +//! Supported objects are encapsulated in enums that pass operations through to the underlying types. +//! +//! # Design note on traits +//! +//! The core [`Signer`] and [`SignatureVerifier`] traits are parameterized by const-generic key and +//! signature sizes (`SK_LEN`, `SIG_LEN`, `PK_LEN`). A single enum that wraps ML-DSA-44/65/87 cannot +//! implement those traits with one fixed set of const parameters. This module therefore: +//! +//! * Wraps public/private keys in [`SignaturePublicKey`] / [`SignaturePrivateKey`] enums +//! * Wraps streaming sign/verify engines in [`SignatureSigner`] / [`SignatureVerifierEngine`] enums +//! * Exposes inherent methods on those enums with the same shape as the core traits, dispatching +//! to the underlying ML-DSA types +//! +//! Example usage: +//! ``` +//! use bouncycastle_factory::AlgorithmFactory; +//! use bouncycastle_factory::signature_factory::SignatureFactory; +//! use bouncycastle_mldsa::ML_DSA_65_NAME; +//! +//! let factory = SignatureFactory::new(ML_DSA_65_NAME).unwrap(); +//! assert_eq!(factory.algorithm_name(), ML_DSA_65_NAME); +//! // keygen/sign/verify pass through to the underlying ML-DSA types; +//! // see the crate tests for full round-trip examples (they need a larger stack). +//! ``` + +use crate::{AlgorithmFactory, DEFAULT, DEFAULT_128_BIT, DEFAULT_256_BIT, FactoryError}; +use bouncycastle_core::errors::SignatureError; +use bouncycastle_core::key_material::KeyMaterial; +use bouncycastle_core::traits::{ + SignaturePrivateKey as SignaturePrivateKeyTrait, SignaturePublicKey as SignaturePublicKeyTrait, + SignatureVerifier as _, Signer as _, RNG, +}; +use bouncycastle_mldsa as mldsa; +use bouncycastle_mldsa::{ + MLDSA44, MLDSA65, MLDSA87, MLDSATrait, ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME, +}; + +/*** Defaults ***/ +/// Default signature algorithm name (192-bit class / ML-DSA-65). +pub const DEFAULT_SIGNATURE_NAME: &str = ML_DSA_65_NAME; +/// Default signature algorithm at the 128-bit security level. +pub const DEFAULT_128BIT_SIGNATURE_NAME: &str = ML_DSA_44_NAME; +/// Default signature algorithm at the 256-bit security level. +pub const DEFAULT_256BIT_SIGNATURE_NAME: &str = ML_DSA_87_NAME; + +/// Wrapper for all supported signature public keys. +pub enum SignaturePublicKey { + /// ML-DSA-44 public key. + MLDSA44(mldsa::MLDSA44PublicKey), + /// ML-DSA-65 public key. + MLDSA65(mldsa::MLDSA65PublicKey), + /// ML-DSA-87 public key. + MLDSA87(mldsa::MLDSA87PublicKey), +} + +impl SignaturePublicKey { + /// Encode the public key to its standard byte encoding. + pub fn encode(&self) -> Vec { + match self { + Self::MLDSA44(pk) => pk.encode().to_vec(), + Self::MLDSA65(pk) => pk.encode().to_vec(), + Self::MLDSA87(pk) => pk.encode().to_vec(), + } + } + + /// Decode a public key from bytes for the named algorithm. + pub fn from_bytes(alg_name: &str, bytes: &[u8]) -> Result { + match alg_name { + ML_DSA_44_NAME => Ok(Self::MLDSA44( + mldsa::MLDSA44PublicKey::from_bytes(bytes).map_err(signature_err)?, + )), + ML_DSA_65_NAME => Ok(Self::MLDSA65( + mldsa::MLDSA65PublicKey::from_bytes(bytes).map_err(signature_err)?, + )), + ML_DSA_87_NAME => Ok(Self::MLDSA87( + mldsa::MLDSA87PublicKey::from_bytes(bytes).map_err(signature_err)?, + )), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known Signature" + ))), + } + } + + /// Algorithm name for this key. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLDSA44(_) => ML_DSA_44_NAME, + Self::MLDSA65(_) => ML_DSA_65_NAME, + Self::MLDSA87(_) => ML_DSA_87_NAME, + } + } +} + +/// Wrapper for all supported signature private keys. +pub enum SignaturePrivateKey { + /// ML-DSA-44 private key. + MLDSA44(mldsa::MLDSA44PrivateKey), + /// ML-DSA-65 private key. + MLDSA65(mldsa::MLDSA65PrivateKey), + /// ML-DSA-87 private key. + MLDSA87(mldsa::MLDSA87PrivateKey), +} + +impl SignaturePrivateKey { + /// Encode the private key to its standard byte encoding. + pub fn encode(&self) -> Vec { + match self { + Self::MLDSA44(sk) => sk.encode().to_vec(), + Self::MLDSA65(sk) => sk.encode().to_vec(), + Self::MLDSA87(sk) => sk.encode().to_vec(), + } + } + + /// Decode a private key from bytes for the named algorithm. + pub fn from_bytes(alg_name: &str, bytes: &[u8]) -> Result { + match alg_name { + ML_DSA_44_NAME => Ok(Self::MLDSA44( + mldsa::MLDSA44PrivateKey::from_bytes(bytes).map_err(signature_err)?, + )), + ML_DSA_65_NAME => Ok(Self::MLDSA65( + mldsa::MLDSA65PrivateKey::from_bytes(bytes).map_err(signature_err)?, + )), + ML_DSA_87_NAME => Ok(Self::MLDSA87( + mldsa::MLDSA87PrivateKey::from_bytes(bytes).map_err(signature_err)?, + )), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known Signature" + ))), + } + } + + /// Algorithm name for this key. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLDSA44(_) => ML_DSA_44_NAME, + Self::MLDSA65(_) => ML_DSA_65_NAME, + Self::MLDSA87(_) => ML_DSA_87_NAME, + } + } +} + +/// Streaming signer engine encapsulating supported signature algorithm state machines. +/// +/// Obtained from [`SignatureFactory::sign_init`]. Passes [`Signer::sign_update`] / +/// [`Signer::sign_final`] through to the underlying type. +pub enum SignatureSigner { + /// ML-DSA-44 streaming signer. + MLDSA44(MLDSA44), + /// ML-DSA-65 streaming signer. + MLDSA65(MLDSA65), + /// ML-DSA-87 streaming signer. + MLDSA87(MLDSA87), +} + +impl SignatureSigner { + /// Absorb the next message chunk (pass-through to underlying [`Signer::sign_update`]). + pub fn sign_update(&mut self, msg_chunk: &[u8]) { + match self { + Self::MLDSA44(s) => s.sign_update(msg_chunk), + Self::MLDSA65(s) => s.sign_update(msg_chunk), + Self::MLDSA87(s) => s.sign_update(msg_chunk), + } + } + + /// Finish signing and return the signature bytes. + pub fn sign_final(self) -> Result, SignatureError> { + match self { + Self::MLDSA44(s) => Ok(s.sign_final()?.to_vec()), + Self::MLDSA65(s) => Ok(s.sign_final()?.to_vec()), + Self::MLDSA87(s) => Ok(s.sign_final()?.to_vec()), + } + } +} + +/// Streaming verifier engine encapsulating supported signature algorithm state machines. +/// +/// Obtained from [`SignatureFactory::verify_init`]. +pub enum SignatureVerifierEngine { + /// ML-DSA-44 streaming verifier. + MLDSA44(MLDSA44), + /// ML-DSA-65 streaming verifier. + MLDSA65(MLDSA65), + /// ML-DSA-87 streaming verifier. + MLDSA87(MLDSA87), +} + +impl SignatureVerifierEngine { + /// Absorb the next message chunk (pass-through to underlying [`SignatureVerifier::verify_update`]). + pub fn verify_update(&mut self, msg_chunk: &[u8]) { + match self { + Self::MLDSA44(s) => s.verify_update(msg_chunk), + Self::MLDSA65(s) => s.verify_update(msg_chunk), + Self::MLDSA87(s) => s.verify_update(msg_chunk), + } + } + + /// Finish verification against the provided signature. + pub fn verify_final(self, sig: &[u8]) -> Result<(), SignatureError> { + match self { + Self::MLDSA44(s) => s.verify_final(sig), + Self::MLDSA65(s) => s.verify_final(sig), + Self::MLDSA87(s) => s.verify_final(sig), + } + } +} + +/// Factory / algorithm selector for all supported signature algorithms. +/// +/// Constructed by name via [`AlgorithmFactory::new`] or the default helpers. +/// Operations pass through to the underlying ML-DSA parameter sets. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SignatureFactory { + /// ML-DSA-44 (NIST security category 2 / ~128-bit class). + MLDSA44, + /// ML-DSA-65 (NIST security category 3 / ~192-bit class). + MLDSA65, + /// ML-DSA-87 (NIST security category 5 / ~256-bit class). + MLDSA87, +} + +impl Default for SignatureFactory { + fn default() -> Self { + Self::MLDSA65 + } +} + +impl AlgorithmFactory for SignatureFactory { + fn default_128_bit() -> Self { + Self::MLDSA44 + } + + fn default_256_bit() -> Self { + Self::MLDSA87 + } + + fn new(alg_name: &str) -> Result { + match alg_name { + DEFAULT => Ok(Self::default()), + DEFAULT_128_BIT => Ok(Self::default_128_bit()), + DEFAULT_256_BIT => Ok(Self::default_256_bit()), + ML_DSA_44_NAME => Ok(Self::MLDSA44), + ML_DSA_65_NAME => Ok(Self::MLDSA65), + ML_DSA_87_NAME => Ok(Self::MLDSA87), + _ => Err(FactoryError::UnsupportedAlgorithm(format!( + "The algorithm: \"{alg_name}\" is not a known Signature" + ))), + } + } +} + +impl SignatureFactory { + /// Algorithm name string for this factory selection. + pub fn algorithm_name(&self) -> &'static str { + match self { + Self::MLDSA44 => ML_DSA_44_NAME, + Self::MLDSA65 => ML_DSA_65_NAME, + Self::MLDSA87 => ML_DSA_87_NAME, + } + } + + /// Generate a fresh key pair using the library default OS-backed RNG. + pub fn keygen(&self) -> Result<(SignaturePublicKey, SignaturePrivateKey), SignatureError> { + match self { + Self::MLDSA44 => { + let (pk, sk) = MLDSA44::keygen()?; + Ok((SignaturePublicKey::MLDSA44(pk), SignaturePrivateKey::MLDSA44(sk))) + } + Self::MLDSA65 => { + let (pk, sk) = MLDSA65::keygen()?; + Ok((SignaturePublicKey::MLDSA65(pk), SignaturePrivateKey::MLDSA65(sk))) + } + Self::MLDSA87 => { + let (pk, sk) = MLDSA87::keygen()?; + Ok((SignaturePublicKey::MLDSA87(pk), SignaturePrivateKey::MLDSA87(sk))) + } + } + } + + /// Generate a key pair using the provided RNG. + pub fn keygen_from_rng( + &self, + rng: &mut dyn RNG, + ) -> Result<(SignaturePublicKey, SignaturePrivateKey), SignatureError> { + match self { + Self::MLDSA44 => { + let (pk, sk) = MLDSA44::keygen_from_rng(rng)?; + Ok((SignaturePublicKey::MLDSA44(pk), SignaturePrivateKey::MLDSA44(sk))) + } + Self::MLDSA65 => { + let (pk, sk) = MLDSA65::keygen_from_rng(rng)?; + Ok((SignaturePublicKey::MLDSA65(pk), SignaturePrivateKey::MLDSA65(sk))) + } + Self::MLDSA87 => { + let (pk, sk) = MLDSA87::keygen_from_rng(rng)?; + Ok((SignaturePublicKey::MLDSA87(pk), SignaturePrivateKey::MLDSA87(sk))) + } + } + } + + /// Generate a key pair from a 32-byte seed. + pub fn keygen_from_seed( + &self, + seed: &KeyMaterial<32>, + ) -> Result<(SignaturePublicKey, SignaturePrivateKey), SignatureError> { + match self { + Self::MLDSA44 => { + let (pk, sk) = MLDSA44::keygen_from_seed(seed)?; + Ok((SignaturePublicKey::MLDSA44(pk), SignaturePrivateKey::MLDSA44(sk))) + } + Self::MLDSA65 => { + let (pk, sk) = MLDSA65::keygen_from_seed(seed)?; + Ok((SignaturePublicKey::MLDSA65(pk), SignaturePrivateKey::MLDSA65(sk))) + } + Self::MLDSA87 => { + let (pk, sk) = MLDSA87::keygen_from_seed(seed)?; + Ok((SignaturePublicKey::MLDSA87(pk), SignaturePrivateKey::MLDSA87(sk))) + } + } + } + + /// One-shot sign (pass-through to underlying [`Signer::sign`]). + pub fn sign( + &self, + sk: &SignaturePrivateKey, + msg: &[u8], + ctx: Option<&[u8]>, + ) -> Result, SignatureError> { + match (self, sk) { + (Self::MLDSA44, SignaturePrivateKey::MLDSA44(sk)) => { + Ok(MLDSA44::sign(sk, msg, ctx)?.to_vec()) + } + (Self::MLDSA65, SignaturePrivateKey::MLDSA65(sk)) => { + Ok(MLDSA65::sign(sk, msg, ctx)?.to_vec()) + } + (Self::MLDSA87, SignaturePrivateKey::MLDSA87(sk)) => { + Ok(MLDSA87::sign(sk, msg, ctx)?.to_vec()) + } + _ => Err(SignatureError::GenericError( + "Signature private key does not match the selected SignatureFactory algorithm", + )), + } + } + + /// One-shot verify (pass-through to underlying [`SignatureVerifier::verify`]). + pub fn verify( + &self, + pk: &SignaturePublicKey, + msg: &[u8], + ctx: Option<&[u8]>, + sig: &[u8], + ) -> Result<(), SignatureError> { + match (self, pk) { + (Self::MLDSA44, SignaturePublicKey::MLDSA44(pk)) => MLDSA44::verify(pk, msg, ctx, sig), + (Self::MLDSA65, SignaturePublicKey::MLDSA65(pk)) => MLDSA65::verify(pk, msg, ctx, sig), + (Self::MLDSA87, SignaturePublicKey::MLDSA87(pk)) => MLDSA87::verify(pk, msg, ctx, sig), + _ => Err(SignatureError::GenericError( + "Signature public key does not match the selected SignatureFactory algorithm", + )), + } + } + + /// Begin a streaming sign operation (pass-through to [`Signer::sign_init`]). + pub fn sign_init( + &self, + sk: &SignaturePrivateKey, + ctx: Option<&[u8]>, + ) -> Result { + match (self, sk) { + (Self::MLDSA44, SignaturePrivateKey::MLDSA44(sk)) => { + Ok(SignatureSigner::MLDSA44(MLDSA44::sign_init(sk, ctx)?)) + } + (Self::MLDSA65, SignaturePrivateKey::MLDSA65(sk)) => { + Ok(SignatureSigner::MLDSA65(MLDSA65::sign_init(sk, ctx)?)) + } + (Self::MLDSA87, SignaturePrivateKey::MLDSA87(sk)) => { + Ok(SignatureSigner::MLDSA87(MLDSA87::sign_init(sk, ctx)?)) + } + _ => Err(SignatureError::GenericError( + "Signature private key does not match the selected SignatureFactory algorithm", + )), + } + } + + /// Begin a streaming verify operation (pass-through to [`SignatureVerifier::verify_init`]). + pub fn verify_init( + &self, + pk: &SignaturePublicKey, + ctx: Option<&[u8]>, + ) -> Result { + match (self, pk) { + (Self::MLDSA44, SignaturePublicKey::MLDSA44(pk)) => { + Ok(SignatureVerifierEngine::MLDSA44(MLDSA44::verify_init(pk, ctx)?)) + } + (Self::MLDSA65, SignaturePublicKey::MLDSA65(pk)) => { + Ok(SignatureVerifierEngine::MLDSA65(MLDSA65::verify_init(pk, ctx)?)) + } + (Self::MLDSA87, SignaturePublicKey::MLDSA87(pk)) => { + Ok(SignatureVerifierEngine::MLDSA87(MLDSA87::verify_init(pk, ctx)?)) + } + _ => Err(SignatureError::GenericError( + "Signature public key does not match the selected SignatureFactory algorithm", + )), + } + } +} + +fn signature_err(e: SignatureError) -> FactoryError { + FactoryError::UnsupportedAlgorithm(format!("Signature key decode failed: {e:?}")) +} + diff --git a/crypto/factory/tests/signature_kem_factory_tests.rs b/crypto/factory/tests/signature_kem_factory_tests.rs new file mode 100644 index 00000000..4733c266 --- /dev/null +++ b/crypto/factory/tests/signature_kem_factory_tests.rs @@ -0,0 +1,123 @@ +//! Tests for SignatureFactory and KEMFactory. + +use bouncycastle_factory::AlgorithmFactory; +use bouncycastle_factory::kem_factory::{KEMFactory, KEMPrivateKey, KEMPublicKey}; +use bouncycastle_factory::signature_factory::{ + SignatureFactory, SignaturePrivateKey, SignaturePublicKey, +}; +use bouncycastle_mldsa::{ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME}; +use bouncycastle_mlkem::{ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME}; + +#[test] +fn signature_defaults_and_names() { + assert_eq!( + SignatureFactory::default().algorithm_name(), + ML_DSA_65_NAME + ); + assert_eq!( + SignatureFactory::default_128_bit().algorithm_name(), + ML_DSA_44_NAME + ); + assert_eq!( + SignatureFactory::default_256_bit().algorithm_name(), + ML_DSA_87_NAME + ); + assert_eq!( + SignatureFactory::new("Default").unwrap().algorithm_name(), + ML_DSA_65_NAME + ); + assert_eq!( + SignatureFactory::new(ML_DSA_44_NAME) + .unwrap() + .algorithm_name(), + ML_DSA_44_NAME + ); + assert!(SignatureFactory::new("not-a-sig").is_err()); +} + +#[test] +fn signature_sign_verify_roundtrip_all_variants() { + let msg = b"factory signature test message"; + for name in [ML_DSA_44_NAME, ML_DSA_65_NAME, ML_DSA_87_NAME] { + let factory = SignatureFactory::new(name).unwrap(); + let (pk, sk) = factory.keygen().unwrap(); + assert_eq!(pk.algorithm_name(), name); + assert_eq!(sk.algorithm_name(), name); + + let sig = factory.sign(&sk, msg, None).unwrap(); + factory.verify(&pk, msg, None, &sig).unwrap(); + assert!(factory.verify(&pk, b"tampered", None, &sig).is_err()); + + // encode/decode keys + let pk2 = SignaturePublicKey::from_bytes(name, &pk.encode()).unwrap(); + let sk2 = SignaturePrivateKey::from_bytes(name, &sk.encode()).unwrap(); + let sig2 = factory.sign(&sk2, msg, Some(b"ctx")).unwrap(); + factory.verify(&pk2, msg, Some(b"ctx"), &sig2).unwrap(); + } +} + +#[test] +fn signature_streaming_roundtrip() { + let factory = SignatureFactory::default(); + let (pk, sk) = factory.keygen().unwrap(); + let mut signer = factory.sign_init(&sk, None).unwrap(); + signer.sign_update(b"hello "); + signer.sign_update(b"world"); + let sig = signer.sign_final().unwrap(); + + let mut verifier = factory.verify_init(&pk, None).unwrap(); + verifier.verify_update(b"hello "); + verifier.verify_update(b"world"); + verifier.verify_final(&sig).unwrap(); +} + +#[test] +fn kem_defaults_and_names() { + assert_eq!(KEMFactory::default().algorithm_name(), ML_KEM_768_NAME); + assert_eq!( + KEMFactory::default_128_bit().algorithm_name(), + ML_KEM_512_NAME + ); + assert_eq!( + KEMFactory::default_256_bit().algorithm_name(), + ML_KEM_1024_NAME + ); + assert_eq!( + KEMFactory::new("Default").unwrap().algorithm_name(), + ML_KEM_768_NAME + ); + assert!(KEMFactory::new("not-a-kem").is_err()); +} + +#[test] +fn kem_encaps_decaps_roundtrip_all_variants() { + for name in [ML_KEM_512_NAME, ML_KEM_768_NAME, ML_KEM_1024_NAME] { + let factory = KEMFactory::new(name).unwrap(); + let (pk, sk) = factory.keygen().unwrap(); + assert_eq!(pk.algorithm_name(), name); + assert_eq!(sk.algorithm_name(), name); + + let (ss, ct) = factory.encaps(&pk).unwrap(); + let ss2 = factory.decaps(&sk, &ct).unwrap(); + assert_eq!(ss, ss2); + + let pk2 = KEMPublicKey::from_bytes(name, &pk.encode()).unwrap(); + let sk2 = KEMPrivateKey::from_bytes(name, &sk.encode()).unwrap(); + let (ss3, ct3) = factory.encaps(&pk2).unwrap(); + let ss4 = factory.decaps(&sk2, &ct3).unwrap(); + assert_eq!(ss3, ss4); + } +} + +#[test] +fn key_algorithm_mismatch_errors() { + let sig = SignatureFactory::new(ML_DSA_65_NAME).unwrap(); + let (_pk65, sk65) = sig.keygen().unwrap(); + let sig44 = SignatureFactory::new(ML_DSA_44_NAME).unwrap(); + assert!(sig44.sign(&sk65, b"msg", None).is_err()); + + let kem = KEMFactory::new(ML_KEM_768_NAME).unwrap(); + let (pk768, _sk768) = kem.keygen().unwrap(); + let kem512 = KEMFactory::new(ML_KEM_512_NAME).unwrap(); + assert!(kem512.encaps(&pk768).is_err()); +}