Skip to content

Commit

Permalink
ml-dsa: implement initial pkcs8/spki traits (#891)
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo authored Jan 25, 2025
1 parent ba7f5e4 commit f7e7312
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion ml-dsa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand All @@ -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"
Expand Down
192 changes: 191 additions & 1 deletion ml-dsa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,6 +144,24 @@ impl<P: MlDsaParams> signature::SignatureEncoding for Signature<P> {
type Repr = EncodedSignature<P>;
}

#[cfg(feature = "alloc")]
impl<P: MlDsaParams> SignatureBitStringEncoding for Signature<P> {
fn to_bitstring(&self) -> der::Result<BitString> {
BitString::new(0, self.encode().to_vec())
}
}

#[cfg(feature = "pkcs8")]
impl<P> AssociatedAlgorithmIdentifier for Signature<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
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.
Expand Down Expand Up @@ -156,6 +194,46 @@ impl<P: MlDsaParams> signature::KeypairRef for KeyPair<P> {
type VerifyingKey = VerifyingKey<P>;
}

#[cfg(feature = "pkcs8")]
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for KeyPair<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Error = pkcs8::Error;

fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
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<P: MlDsaParams> signature::Signer<Signature<P>> for KeyPair<P> {
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, Error> {
self.signing_key.sign_deterministic(msg, &[])
}
}

#[cfg(feature = "pkcs8")]
impl<P> SignatureAlgorithmIdentifier for KeyPair<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Params = AnyRef<'static>;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
Signature::<P>::ALGORITHM_IDENTIFIER;
}

/// An ML-DSA signing key
#[derive(Clone, PartialEq)]
pub struct SigningKey<P: MlDsaParams> {
Expand Down Expand Up @@ -384,8 +462,35 @@ impl<P: MlDsaParams> signature::RandomizedSigner<Signature<P>> for SigningKey<P>
}
}

#[cfg(feature = "pkcs8")]
impl<P> SignatureAlgorithmIdentifier for SigningKey<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Params = AnyRef<'static>;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
Signature::<P>::ALGORITHM_IDENTIFIER;
}

#[cfg(feature = "pkcs8")]
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for SigningKey<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Error = pkcs8::Error;

fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
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<P: ParameterSet> {
rho: B32,
t1: Vector<P::K>,
Expand Down Expand Up @@ -488,6 +593,61 @@ impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
}
}

#[cfg(feature = "pkcs8")]
impl<P> SignatureAlgorithmIdentifier for VerifyingKey<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Params = AnyRef<'static>;

const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
Signature::<P>::ALGORITHM_IDENTIFIER;
}

#[cfg(feature = "alloc")]
impl<P> EncodePublicKey for VerifyingKey<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
fn to_public_key_der(&self) -> spki::Result<der::Document> {
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<P> TryFrom<SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P>
where
P: MlDsaParams,
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
{
type Error = spki::Error;

fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
match spki.algorithm {
alg if alg == P::ALGORITHM_IDENTIFIER => {}
other => return Err(spki::Error::OidUnknown { oid: other.oid }),
};

Ok(Self::decode(
&EncodedVerifyingKey::<P>::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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions ml-dsa/tests/examples/ML-DSA-44.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
HB0eHw==
-----END PRIVATE KEY-----
30 changes: 30 additions & 0 deletions ml-dsa/tests/examples/ML-DSA-44.pub
Original file line number Diff line number Diff line change
@@ -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-----
4 changes: 4 additions & 0 deletions ml-dsa/tests/examples/ML-DSA-65.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
HB0eHw==
-----END PRIVATE KEY-----
44 changes: 44 additions & 0 deletions ml-dsa/tests/examples/ML-DSA-65.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
-----BEGIN PUBLIC KEY-----
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
HgLy6ERP
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions ml-dsa/tests/examples/ML-DSA-87.priv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
HB0eHw==
-----END PRIVATE KEY-----
Loading

0 comments on commit f7e7312

Please sign in to comment.