From f7e7312d9442ce00a47f6fc8fdb374e9431c869a Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Sat, 25 Jan 2025 09:30:30 -0800 Subject: [PATCH] ml-dsa: implement initial `pkcs8`/`spki` traits (#891) --- Cargo.lock | 2 + ml-dsa/Cargo.toml | 8 +- ml-dsa/src/lib.rs | 192 ++++++++++++++++++++++++++- ml-dsa/tests/examples/ML-DSA-44.priv | 4 + ml-dsa/tests/examples/ML-DSA-44.pub | 30 +++++ ml-dsa/tests/examples/ML-DSA-65.priv | 4 + ml-dsa/tests/examples/ML-DSA-65.pub | 44 ++++++ ml-dsa/tests/examples/ML-DSA-87.priv | 4 + ml-dsa/tests/examples/ML-DSA-87.pub | 57 ++++++++ ml-dsa/tests/examples/README.md | 1 + ml-dsa/tests/pkcs8.rs | 46 +++++++ 11 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 ml-dsa/tests/examples/ML-DSA-44.priv create mode 100644 ml-dsa/tests/examples/ML-DSA-44.pub create mode 100644 ml-dsa/tests/examples/ML-DSA-65.priv create mode 100644 ml-dsa/tests/examples/ML-DSA-65.pub create mode 100644 ml-dsa/tests/examples/ML-DSA-87.priv create mode 100644 ml-dsa/tests/examples/ML-DSA-87.pub create mode 100644 ml-dsa/tests/examples/README.md create mode 100644 ml-dsa/tests/pkcs8.rs diff --git a/Cargo.lock b/Cargo.lock index f3ab18c1..c4f2d006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,11 +683,13 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" name = "ml-dsa" version = "0.1.0-pre.0" dependencies = [ + "const-oid", "criterion", "hex", "hex-literal", "hybrid-array", "num-traits", + "pkcs8", "rand", "rand_core", "serde", diff --git a/ml-dsa/Cargo.toml b/ml-dsa/Cargo.toml index 04aad121..ebecf487 100644 --- a/ml-dsa/Cargo.toml +++ b/ml-dsa/Cargo.toml @@ -15,9 +15,11 @@ categories = ["cryptography"] keywords = ["crypto", "signature"] [features] -default = ["rand_core"] +default = ["rand_core", "alloc", "pkcs8"] zeroize = ["dep:zeroize", "hybrid-array/zeroize"] rand_core = ["dep:rand_core", "signature/rand_core"] +alloc = ["pkcs8?/alloc"] +pkcs8 = ["dep:const-oid", "dep:pkcs8"] [dependencies] hybrid-array = { version = "0.2.3", features = ["extra-sizes"] } @@ -27,10 +29,14 @@ sha3 = "0.10.8" signature = "2.3.0-pre.4" zeroize = { version = "1.8.1", optional = true, default-features = false } +const-oid = { version = "0.10.0-rc.1", features = ["db"], optional = true } +pkcs8 = { version = "=0.11.0-rc.1", default-features = false, optional = true } + [dev-dependencies] criterion = "0.5.1" hex = { version = "0.4.3", features = ["serde"] } hex-literal = "0.4.1" +pkcs8 = { version = "=0.11.0-rc.1", features = ["pem"] } rand = "0.8.5" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.132" diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 7e44ab15..d5567db5 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -56,6 +56,26 @@ use rand_core::CryptoRngCore; #[cfg(feature = "zeroize")] use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "pkcs8")] +use { + const_oid::db::fips204, + pkcs8::{ + der::{self, AnyRef}, + spki::{ + self, AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier, + SubjectPublicKeyInfoRef, + }, + AlgorithmIdentifierRef, PrivateKeyInfoRef, + }, +}; + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +use pkcs8::{ + der::asn1::{BitString, BitStringRef}, + spki::{SignatureBitStringEncoding, SubjectPublicKeyInfo}, + EncodePublicKey, +}; + use crate::algebra::{AlgebraExt, Elem, NttMatrix, NttVector, Truncate, Vector}; use crate::crypto::H; use crate::hint::Hint; @@ -124,6 +144,24 @@ impl signature::SignatureEncoding for Signature

{ type Repr = EncodedSignature

; } +#[cfg(feature = "alloc")] +impl SignatureBitStringEncoding for Signature

{ + fn to_bitstring(&self) -> der::Result { + BitString::new(0, self.encode().to_vec()) + } +} + +#[cfg(feature = "pkcs8")] +impl

AssociatedAlgorithmIdentifier for Signature

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = P::ALGORITHM_IDENTIFIER; +} + // This method takes a slice of slices so that we can accommodate the varying calculations (direct // for test vectors, 0... for sign/sign_deterministic, 1... for the pre-hashed version) without // having to allocate memory for components. @@ -156,6 +194,46 @@ impl signature::KeypairRef for KeyPair

{ type VerifyingKey = VerifyingKey

; } +#[cfg(feature = "pkcs8")] +impl

TryFrom> for KeyPair

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + match private_key_info.algorithm { + alg if alg == P::ALGORITHM_IDENTIFIER => {} + other => return Err(spki::Error::OidUnknown { oid: other.oid }.into()), + }; + + let seed = Array::try_from(private_key_info.private_key.as_bytes()) + .map_err(|_| pkcs8::Error::KeyMalformed)?; + Ok(P::key_gen_internal(&seed)) + } +} + +/// The `Signer` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA, and +/// only supports signing with an empty context string. +impl signature::Signer> for KeyPair

{ + fn try_sign(&self, msg: &[u8]) -> Result, Error> { + self.signing_key.sign_deterministic(msg, &[]) + } +} + +#[cfg(feature = "pkcs8")] +impl

SignatureAlgorithmIdentifier for KeyPair

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::

::ALGORITHM_IDENTIFIER; +} + /// An ML-DSA signing key #[derive(Clone, PartialEq)] pub struct SigningKey { @@ -384,8 +462,35 @@ impl signature::RandomizedSigner> for SigningKey

} } +#[cfg(feature = "pkcs8")] +impl

SignatureAlgorithmIdentifier for SigningKey

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::

::ALGORITHM_IDENTIFIER; +} + +#[cfg(feature = "pkcs8")] +impl

TryFrom> for SigningKey

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Error = pkcs8::Error; + + fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result { + let keypair = KeyPair::try_from(private_key_info)?; + + Ok(keypair.signing_key) + } +} + /// An ML-DSA verification key -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct VerifyingKey { rho: B32, t1: Vector, @@ -488,6 +593,61 @@ impl signature::Verifier> for VerifyingKey

{ } } +#[cfg(feature = "pkcs8")] +impl

SignatureAlgorithmIdentifier for VerifyingKey

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + Signature::

::ALGORITHM_IDENTIFIER; +} + +#[cfg(feature = "alloc")] +impl

EncodePublicKey for VerifyingKey

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + fn to_public_key_der(&self) -> spki::Result { + let public_key = self.encode(); + let subject_public_key = BitStringRef::new(0, &public_key)?; + + SubjectPublicKeyInfo { + algorithm: P::ALGORITHM_IDENTIFIER, + subject_public_key, + } + .try_into() + } +} + +#[cfg(feature = "pkcs8")] +impl

TryFrom> for VerifyingKey

+where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, +{ + type Error = spki::Error; + + fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> spki::Result { + match spki.algorithm { + alg if alg == P::ALGORITHM_IDENTIFIER => {} + other => return Err(spki::Error::OidUnknown { oid: other.oid }), + }; + + Ok(Self::decode( + &EncodedVerifyingKey::

::try_from( + spki.subject_public_key + .as_bytes() + .ok_or_else(|| der::Tag::BitString.value_error())?, + ) + .map_err(|_| pkcs8::Error::KeyMalformed)?, + )) + } +} + /// `MlDsa44` is the parameter set for security category 2. #[derive(Default, Clone, Debug, PartialEq)] pub struct MlDsa44; @@ -505,6 +665,16 @@ impl ParameterSet for MlDsa44 { const TAU: usize = 39; } +#[cfg(feature = "pkcs8")] +impl AssociatedAlgorithmIdentifier for MlDsa44 { + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef { + oid: fips204::ID_ML_DSA_44, + parameters: None, + }; +} + /// `MlDsa65` is the parameter set for security category 3. #[derive(Default, Clone, Debug, PartialEq)] pub struct MlDsa65; @@ -522,6 +692,16 @@ impl ParameterSet for MlDsa65 { const TAU: usize = 49; } +#[cfg(feature = "pkcs8")] +impl AssociatedAlgorithmIdentifier for MlDsa65 { + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef { + oid: fips204::ID_ML_DSA_65, + parameters: None, + }; +} + /// `MlKem87` is the parameter set for security category 5. #[derive(Default, Clone, Debug, PartialEq)] pub struct MlDsa87; @@ -539,6 +719,16 @@ impl ParameterSet for MlDsa87 { const TAU: usize = 60; } +#[cfg(feature = "pkcs8")] +impl AssociatedAlgorithmIdentifier for MlDsa87 { + type Params = AnyRef<'static>; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef { + oid: fips204::ID_ML_DSA_87, + parameters: None, + }; +} + /// A parameter set that knows how to generate key pairs pub trait KeyGen: MlDsaParams { /// The type that is returned by key generation diff --git a/ml-dsa/tests/examples/ML-DSA-44.priv b/ml-dsa/tests/examples/ML-DSA-44.priv new file mode 100644 index 00000000..1d1e8786 --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-44.priv @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob +HB0eHw== +-----END PRIVATE KEY----- diff --git a/ml-dsa/tests/examples/ML-DSA-44.pub b/ml-dsa/tests/examples/ML-DSA-44.pub new file mode 100644 index 00000000..106f158a --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-44.pub @@ -0,0 +1,30 @@ +-----BEGIN PUBLIC KEY----- +MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw +JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD +l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD +atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+ +r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN +kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB +ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn +/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B +rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D +GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i +svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8 +YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2 +plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij +DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee +SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa +DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas +QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH +wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU +7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV +oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn +j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl +Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI +bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/ +XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br +IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP +cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO +t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo +7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI= +-----END PUBLIC KEY----- diff --git a/ml-dsa/tests/examples/ML-DSA-65.priv b/ml-dsa/tests/examples/ML-DSA-65.priv new file mode 100644 index 00000000..a9f7c419 --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-65.priv @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob +HB0eHw== +-----END PRIVATE KEY----- diff --git a/ml-dsa/tests/examples/ML-DSA-65.pub b/ml-dsa/tests/examples/ML-DSA-65.pub new file mode 100644 index 00000000..3f2512be --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-65.pub @@ -0,0 +1,44 @@ +-----BEGIN PUBLIC KEY----- +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 +HgLy6ERP +-----END PUBLIC KEY----- diff --git a/ml-dsa/tests/examples/ML-DSA-87.priv b/ml-dsa/tests/examples/ML-DSA-87.priv new file mode 100644 index 00000000..6a547337 --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-87.priv @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob +HB0eHw== +-----END PRIVATE KEY----- diff --git a/ml-dsa/tests/examples/ML-DSA-87.pub b/ml-dsa/tests/examples/ML-DSA-87.pub new file mode 100644 index 00000000..b627e0de --- /dev/null +++ b/ml-dsa/tests/examples/ML-DSA-87.pub @@ -0,0 +1,57 @@ +-----BEGIN PUBLIC KEY----- +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 +5VvgYiIvi6kKl0BzMmsxISrs4KNKYA== +-----END PUBLIC KEY----- diff --git a/ml-dsa/tests/examples/README.md b/ml-dsa/tests/examples/README.md new file mode 100644 index 00000000..9e886c9e --- /dev/null +++ b/ml-dsa/tests/examples/README.md @@ -0,0 +1 @@ +Examples found in this directory can be found in https://github.com/lamps-wg/dilithium-certificates/tree/main/examples diff --git a/ml-dsa/tests/pkcs8.rs b/ml-dsa/tests/pkcs8.rs new file mode 100644 index 00000000..648b9bf4 --- /dev/null +++ b/ml-dsa/tests/pkcs8.rs @@ -0,0 +1,46 @@ +#![cfg(all(feature = "pkcs8", feature = "alloc"))] + +use core::ops::Deref; +use ml_dsa::{KeyPair, MlDsa44, MlDsa65, MlDsa87, MlDsaParams, SigningKey, VerifyingKey}; +use pkcs8::{ + der::{pem::LineEnding, AnyRef}, + spki::AssociatedAlgorithmIdentifier, + DecodePrivateKey, DecodePublicKey, EncodePublicKey, +}; +use signature::Keypair; + +#[test] +fn private_key_serialization() { + fn test_roundtrip

(private_bytes: &str, public_bytes: &str) + where + P: MlDsaParams, + P: AssociatedAlgorithmIdentifier>, + { + let sk = SigningKey::

::from_pkcs8_pem(private_bytes).expect("parse private key"); + let kp = KeyPair::

::from_pkcs8_pem(private_bytes).expect("parse private key"); + assert!(sk == kp.signing_key); + + let pk = VerifyingKey::

::from_public_key_pem(public_bytes).expect("parse public key"); + assert_eq!( + pk.to_public_key_pem(LineEnding::LF) + .expect("serialize public key") + .deref(), + public_bytes + ); + + assert_eq!(kp.verifying_key(), pk); + } + + test_roundtrip::( + include_str!("examples/ML-DSA-44.priv"), + include_str!("examples/ML-DSA-44.pub"), + ); + test_roundtrip::( + include_str!("examples/ML-DSA-65.priv"), + include_str!("examples/ML-DSA-65.pub"), + ); + test_roundtrip::( + include_str!("examples/ML-DSA-87.priv"), + include_str!("examples/ML-DSA-87.pub"), + ); +}