Skip to content

Commit f1d81af

Browse files
authored
ml-dsa: update PKCS#8 support (#1093)
The latest versions of `draft-ietf-lamps-dilithium-certificates` place the seed in an implicit Context-Specific field with a tag number of 0. This updates the test vectors from the following commit: https://github.com/lamps-wg/dilithium-certificates/tree/1fcc048b8a6e3404b0e0bb80c71286551ac9604d We only support seeds, at least until the standard is out-of-draft. Closes #1092
1 parent 67e9a34 commit f1d81af

File tree

10 files changed

+242
-221
lines changed

10 files changed

+242
-221
lines changed

ml-dsa/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ signature = { version = "3.0.0-rc.4", default-features = false, features = ["dig
3939

4040
# optional dependencies
4141
const-oid = { version = "0.10", features = ["db"], optional = true }
42-
pkcs8 = { version = "0.11.0-rc.6", default-features = false, optional = true }
42+
pkcs8 = { version = "0.11.0-rc.7", default-features = false, optional = true }
4343
rand_core = { version = "0.9", optional = true }
4444
zeroize = { version = "1.8.1", optional = true, default-features = false }
4545

ml-dsa/src/lib.rs

Lines changed: 13 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mod encode;
3838
mod hint;
3939
mod ntt;
4040
mod param;
41+
mod pkcs8;
4142
mod sampling;
4243
mod util;
4344

@@ -52,37 +53,17 @@ use hybrid_array::{
5253
U75, U80, U88, Unsigned,
5354
},
5455
};
56+
use sha3::Shake256;
5557
use signature::{DigestSigner, DigestVerifier, MultipartSigner, MultipartVerifier, Signer};
5658

5759
#[cfg(feature = "rand_core")]
58-
use signature::{RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner};
59-
60-
#[cfg(feature = "rand_core")]
61-
use rand_core::{CryptoRng, TryCryptoRng};
62-
63-
use sha3::Shake256;
64-
#[cfg(feature = "zeroize")]
65-
use zeroize::{Zeroize, ZeroizeOnDrop};
66-
67-
#[cfg(feature = "pkcs8")]
6860
use {
69-
const_oid::db::fips204,
70-
pkcs8::{
71-
AlgorithmIdentifierRef, PrivateKeyInfoRef,
72-
der::{self, AnyRef},
73-
spki::{
74-
self, AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier,
75-
SubjectPublicKeyInfoRef,
76-
},
77-
},
61+
rand_core::{CryptoRng, TryCryptoRng},
62+
signature::{RandomizedDigestSigner, RandomizedMultipartSigner, RandomizedSigner},
7863
};
7964

80-
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
81-
use pkcs8::{
82-
EncodePrivateKey, EncodePublicKey,
83-
der::asn1::{BitString, BitStringRef, OctetStringRef},
84-
spki::{SignatureBitStringEncoding, SubjectPublicKeyInfo},
85-
};
65+
#[cfg(feature = "zeroize")]
66+
use zeroize::{Zeroize, ZeroizeOnDrop};
8667

8768
use crate::algebra::{AlgebraExt, Elem, NttMatrix, NttVector, Truncate, Vector};
8869
use crate::crypto::H;
@@ -97,6 +78,10 @@ pub use crate::param::{EncodedSignature, EncodedSigningKey, EncodedVerifyingKey,
9778
pub use crate::util::B32;
9879
pub use signature::{self, Error};
9980

81+
/// ML-DSA seeds are signing (private) keys, which are consistently 32-bytes across all security
82+
/// levels, and are the preferred serialization for representing such keys.
83+
pub type Seed = B32;
84+
10085
/// An ML-DSA signature
10186
#[derive(Clone, PartialEq, Debug)]
10287
pub struct Signature<P: MlDsaParams> {
@@ -153,24 +138,6 @@ impl<P: MlDsaParams> signature::SignatureEncoding for Signature<P> {
153138
type Repr = EncodedSignature<P>;
154139
}
155140

156-
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
157-
impl<P: MlDsaParams> SignatureBitStringEncoding for Signature<P> {
158-
fn to_bitstring(&self) -> der::Result<BitString> {
159-
BitString::new(0, self.encode().to_vec())
160-
}
161-
}
162-
163-
#[cfg(feature = "pkcs8")]
164-
impl<P> AssociatedAlgorithmIdentifier for Signature<P>
165-
where
166-
P: MlDsaParams,
167-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
168-
{
169-
type Params = AnyRef<'static>;
170-
171-
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = P::ALGORITHM_IDENTIFIER;
172-
}
173-
174141
struct MuBuilder(H);
175142

176143
impl MuBuilder {
@@ -256,26 +223,6 @@ impl<P: MlDsaParams> signature::KeypairRef for KeyPair<P> {
256223
type VerifyingKey = VerifyingKey<P>;
257224
}
258225

259-
#[cfg(feature = "pkcs8")]
260-
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for KeyPair<P>
261-
where
262-
P: MlDsaParams,
263-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
264-
{
265-
type Error = pkcs8::Error;
266-
267-
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
268-
match private_key_info.algorithm {
269-
alg if alg == P::ALGORITHM_IDENTIFIER => {}
270-
other => return Err(spki::Error::OidUnknown { oid: other.oid }.into()),
271-
}
272-
273-
let seed = Array::try_from(private_key_info.private_key.as_bytes())
274-
.map_err(|_| pkcs8::Error::KeyMalformed)?;
275-
Ok(P::from_seed(&seed))
276-
}
277-
}
278-
279226
/// The `Signer` implementation for `KeyPair` uses the optional deterministic variant of ML-DSA, and
280227
/// only supports signing with an empty context string.
281228
impl<P: MlDsaParams> Signer<Signature<P>> for KeyPair<P> {
@@ -303,33 +250,6 @@ impl<P: MlDsaParams> DigestSigner<Shake256, Signature<P>> for KeyPair<P> {
303250
}
304251
}
305252

306-
#[cfg(feature = "pkcs8")]
307-
impl<P> SignatureAlgorithmIdentifier for KeyPair<P>
308-
where
309-
P: MlDsaParams,
310-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
311-
{
312-
type Params = AnyRef<'static>;
313-
314-
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
315-
Signature::<P>::ALGORITHM_IDENTIFIER;
316-
}
317-
318-
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
319-
impl<P> EncodePrivateKey for KeyPair<P>
320-
where
321-
P: MlDsaParams,
322-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
323-
{
324-
fn to_pkcs8_der(&self) -> pkcs8::Result<der::SecretDocument> {
325-
let pkcs8_key = pkcs8::PrivateKeyInfoRef::new(
326-
P::ALGORITHM_IDENTIFIER,
327-
OctetStringRef::new(&self.seed)?,
328-
);
329-
Ok(der::SecretDocument::encode_msg(&pkcs8_key)?)
330-
}
331-
}
332-
333253
/// An ML-DSA signing key
334254
#[derive(Clone, PartialEq)]
335255
pub struct SigningKey<P: MlDsaParams> {
@@ -403,7 +323,7 @@ impl<P: MlDsaParams> SigningKey<P> {
403323
/// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204, but only returns a
404324
/// signing key.
405325
#[must_use]
406-
pub fn from_seed(seed: &B32) -> Self {
326+
pub fn from_seed(seed: &Seed) -> Self {
407327
let kp = P::from_seed(seed);
408328
kp.signing_key
409329
}
@@ -712,33 +632,6 @@ impl<P: MlDsaParams> RandomizedDigestSigner<Shake256, Signature<P>> for SigningK
712632
}
713633
}
714634

715-
#[cfg(feature = "pkcs8")]
716-
impl<P> SignatureAlgorithmIdentifier for SigningKey<P>
717-
where
718-
P: MlDsaParams,
719-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
720-
{
721-
type Params = AnyRef<'static>;
722-
723-
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
724-
Signature::<P>::ALGORITHM_IDENTIFIER;
725-
}
726-
727-
#[cfg(feature = "pkcs8")]
728-
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for SigningKey<P>
729-
where
730-
P: MlDsaParams,
731-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
732-
{
733-
type Error = pkcs8::Error;
734-
735-
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
736-
let keypair = KeyPair::try_from(private_key_info)?;
737-
738-
Ok(keypair.signing_key)
739-
}
740-
}
741-
742635
/// An ML-DSA verification key
743636
#[derive(Clone, Debug, PartialEq)]
744637
pub struct VerifyingKey<P: ParameterSet> {
@@ -895,61 +788,6 @@ impl<P: MlDsaParams> DigestVerifier<Shake256, Signature<P>> for VerifyingKey<P>
895788
}
896789
}
897790

898-
#[cfg(feature = "pkcs8")]
899-
impl<P> SignatureAlgorithmIdentifier for VerifyingKey<P>
900-
where
901-
P: MlDsaParams,
902-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
903-
{
904-
type Params = AnyRef<'static>;
905-
906-
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
907-
Signature::<P>::ALGORITHM_IDENTIFIER;
908-
}
909-
910-
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
911-
impl<P> EncodePublicKey for VerifyingKey<P>
912-
where
913-
P: MlDsaParams,
914-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
915-
{
916-
fn to_public_key_der(&self) -> spki::Result<der::Document> {
917-
let public_key = self.encode();
918-
let subject_public_key = BitStringRef::new(0, &public_key)?;
919-
920-
SubjectPublicKeyInfo {
921-
algorithm: P::ALGORITHM_IDENTIFIER,
922-
subject_public_key,
923-
}
924-
.try_into()
925-
}
926-
}
927-
928-
#[cfg(feature = "pkcs8")]
929-
impl<P> TryFrom<SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P>
930-
where
931-
P: MlDsaParams,
932-
P: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
933-
{
934-
type Error = spki::Error;
935-
936-
fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
937-
match spki.algorithm {
938-
alg if alg == P::ALGORITHM_IDENTIFIER => {}
939-
other => return Err(spki::Error::OidUnknown { oid: other.oid }),
940-
}
941-
942-
Ok(Self::decode(
943-
&EncodedVerifyingKey::<P>::try_from(
944-
spki.subject_public_key
945-
.as_bytes()
946-
.ok_or_else(|| der::Tag::BitString.value_error().to_error())?,
947-
)
948-
.map_err(|_| pkcs8::Error::KeyMalformed)?,
949-
))
950-
}
951-
}
952-
953791
/// `MlDsa44` is the parameter set for security category 2.
954792
#[derive(Default, Clone, Debug, PartialEq)]
955793
pub struct MlDsa44;
@@ -967,16 +805,6 @@ impl ParameterSet for MlDsa44 {
967805
const TAU: usize = 39;
968806
}
969807

970-
#[cfg(feature = "pkcs8")]
971-
impl AssociatedAlgorithmIdentifier for MlDsa44 {
972-
type Params = AnyRef<'static>;
973-
974-
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
975-
oid: fips204::ID_ML_DSA_44,
976-
parameters: None,
977-
};
978-
}
979-
980808
/// `MlDsa65` is the parameter set for security category 3.
981809
#[derive(Default, Clone, Debug, PartialEq)]
982810
pub struct MlDsa65;
@@ -994,16 +822,6 @@ impl ParameterSet for MlDsa65 {
994822
const TAU: usize = 49;
995823
}
996824

997-
#[cfg(feature = "pkcs8")]
998-
impl AssociatedAlgorithmIdentifier for MlDsa65 {
999-
type Params = AnyRef<'static>;
1000-
1001-
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
1002-
oid: fips204::ID_ML_DSA_65,
1003-
parameters: None,
1004-
};
1005-
}
1006-
1007825
/// `MlKem87` is the parameter set for security category 5.
1008826
#[derive(Default, Clone, Debug, PartialEq)]
1009827
pub struct MlDsa87;
@@ -1021,16 +839,6 @@ impl ParameterSet for MlDsa87 {
1021839
const TAU: usize = 60;
1022840
}
1023841

1024-
#[cfg(feature = "pkcs8")]
1025-
impl AssociatedAlgorithmIdentifier for MlDsa87 {
1026-
type Params = AnyRef<'static>;
1027-
1028-
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
1029-
oid: fips204::ID_ML_DSA_87,
1030-
parameters: None,
1031-
};
1032-
}
1033-
1034842
/// A parameter set that knows how to generate key pairs
1035843
pub trait KeyGen: MlDsaParams {
1036844
/// The type that is returned by key generation
@@ -1065,7 +873,7 @@ where
1065873
///
1066874
/// This method reflects the ML-DSA.KeyGen_internal algorithm from FIPS 204.
1067875
// Algorithm 6 ML-DSA.KeyGen_internal
1068-
fn from_seed(xi: &B32) -> KeyPair<P>
876+
fn from_seed(xi: &Seed) -> KeyPair<P>
1069877
where
1070878
P: MlDsaParams,
1071879
{
@@ -1367,7 +1175,7 @@ mod test {
13671175
where
13681176
P: MlDsaParams,
13691177
{
1370-
let seed = Array([0u8; 32]);
1178+
let seed = Seed::default();
13711179
let kp1 = P::from_seed(&seed);
13721180
let sk1 = SigningKey::<P>::from_seed(&seed);
13731181
let vk1 = sk1.verifying_key();

0 commit comments

Comments
 (0)