Skip to content

Commit ad21bf6

Browse files
committed
ml-dsa: implement spki
1 parent 753cfcf commit ad21bf6

File tree

12 files changed

+321
-3
lines changed

12 files changed

+321
-3
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ml-dsa/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ categories = ["cryptography"]
1515
keywords = ["crypto", "signature"]
1616

1717
[features]
18-
default = ["std", "rand_core"]
19-
std = ["sha3/std"]
18+
default = ["std", "rand_core", "alloc"]
19+
std = ["alloc", "sha3/std"]
2020
zeroize = ["dep:zeroize", "hybrid-array/zeroize"]
2121
rand_core = ["dep:rand_core", "signature/rand_core"]
22+
alloc = ["pkcs8/alloc"]
2223

2324
[dependencies]
25+
const-oid = { version = "0.10.0-rc.1", features = ["db"] }
2426
hybrid-array = { version = "0.2.3", features = ["extra-sizes"] }
2527
num-traits = "0.2.19"
28+
pkcs8 = { version = "=0.11.0-rc.1", default-features = false }
2629
rand_core = { version = "0.6.4", optional = true }
2730
sha3 = "0.10.8"
2831
signature = "2.3.0-pre.4"
@@ -32,10 +35,13 @@ zeroize = { version = "1.8.1", optional = true, default-features = false }
3235
criterion = "0.5.1"
3336
hex = { version = "0.4.3", features = ["serde"] }
3437
hex-literal = "0.4.1"
38+
pkcs8 = { version = "=0.11.0-rc.1", features = ["pem"] }
3539
rand = "0.8.5"
3640
serde = { version = "1.0.215", features = ["derive"] }
3741
serde_json = "1.0.132"
3842

3943
[[bench]]
4044
name = "ml_dsa"
4145
harness = false
46+
47+

ml-dsa/src/lib.rs

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod util;
4141
// TODO(RLB) Move module to an independent crate shared with ml_kem
4242
mod module_lattice;
4343

44+
use const_oid::db::fips204;
4445
use core::convert::{AsRef, TryFrom, TryInto};
4546
use hybrid_array::{
4647
typenum::{
@@ -49,13 +50,31 @@ use hybrid_array::{
4950
},
5051
Array,
5152
};
53+
use pkcs8::{
54+
der::AnyRef,
55+
spki::{
56+
AlgorithmIdentifier, AssociatedAlgorithmIdentifier, SignatureAlgorithmIdentifier,
57+
SubjectPublicKeyInfoRef,
58+
},
59+
AlgorithmIdentifierRef, PrivateKeyInfoRef,
60+
};
5261

5362
#[cfg(feature = "rand_core")]
5463
use rand_core::CryptoRngCore;
5564

5665
#[cfg(feature = "zeroize")]
5766
use zeroize::{Zeroize, ZeroizeOnDrop};
5867

68+
#[cfg(feature = "alloc")]
69+
use pkcs8::{
70+
der::{
71+
self,
72+
asn1::{BitString, BitStringRef},
73+
},
74+
spki::{self, SignatureBitStringEncoding, SubjectPublicKeyInfo},
75+
EncodePublicKey,
76+
};
77+
5978
use crate::algebra::{AlgebraExt, Elem, NttMatrix, NttVector, Truncate, Vector};
6079
use crate::crypto::H;
6180
use crate::hint::Hint;
@@ -124,6 +143,22 @@ impl<P: MlDsaParams> signature::SignatureEncoding for Signature<P> {
124143
type Repr = EncodedSignature<P>;
125144
}
126145

146+
#[cfg(feature = "alloc")]
147+
impl<P: MlDsaParams> SignatureBitStringEncoding for Signature<P> {
148+
fn to_bitstring(&self) -> der::Result<BitString> {
149+
BitString::new(0, self.encode().to_vec())
150+
}
151+
}
152+
153+
impl<P: MlDsaParams> AssociatedAlgorithmIdentifier for Signature<P> {
154+
type Params = AnyRef<'static>;
155+
156+
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
157+
oid: P::ALGORITHM_OID,
158+
parameters: None,
159+
};
160+
}
161+
127162
// This method takes a slice of slices so that we can accommodate the varying calculations (direct
128163
// for test vectors, 0... for sign/sign_deterministic, 1... for the pre-hashed version) without
129164
// having to allocate memory for components.
@@ -156,6 +191,23 @@ impl<P: MlDsaParams> signature::KeypairRef for KeyPair<P> {
156191
type VerifyingKey = VerifyingKey<P>;
157192
}
158193

194+
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for KeyPair<P>
195+
where
196+
P: MlDsaParams,
197+
{
198+
type Error = pkcs8::Error;
199+
200+
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
201+
private_key_info
202+
.algorithm
203+
.assert_algorithm_oid(P::ALGORITHM_OID)?;
204+
205+
let seed = Array::try_from(private_key_info.private_key.as_bytes())
206+
.map_err(|_| pkcs8::Error::KeyMalformed)?;
207+
Ok(P::key_gen_internal(&seed))
208+
}
209+
}
210+
159211
/// An ML-DSA signing key
160212
#[derive(Clone, PartialEq)]
161213
pub struct SigningKey<P: MlDsaParams> {
@@ -384,8 +436,34 @@ impl<P: MlDsaParams> signature::RandomizedSigner<Signature<P>> for SigningKey<P>
384436
}
385437
}
386438

439+
impl<P: MlDsaParams> SignatureAlgorithmIdentifier for SigningKey<P> {
440+
type Params = AnyRef<'static>;
441+
442+
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
443+
Signature::<P>::ALGORITHM_IDENTIFIER;
444+
}
445+
446+
impl<P> TryFrom<PrivateKeyInfoRef<'_>> for SigningKey<P>
447+
where
448+
P: MlDsaParams,
449+
{
450+
type Error = pkcs8::Error;
451+
452+
fn try_from(private_key_info: pkcs8::PrivateKeyInfoRef<'_>) -> pkcs8::Result<Self> {
453+
private_key_info
454+
.algorithm
455+
.assert_algorithm_oid(P::ALGORITHM_OID)?;
456+
457+
let seed = Array::try_from(private_key_info.private_key.as_bytes())
458+
.map_err(|_| pkcs8::Error::KeyMalformed)?;
459+
let keypair = P::key_gen_internal(&seed);
460+
461+
Ok(keypair.signing_key)
462+
}
463+
}
464+
387465
/// An ML-DSA verification key
388-
#[derive(Clone, PartialEq)]
466+
#[derive(Clone, Debug, PartialEq)]
389467
pub struct VerifyingKey<P: ParameterSet> {
390468
rho: B32,
391469
t1: Vector<P::K>,
@@ -488,6 +566,49 @@ impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
488566
}
489567
}
490568

569+
impl<P: MlDsaParams> SignatureAlgorithmIdentifier for VerifyingKey<P> {
570+
type Params = AnyRef<'static>;
571+
572+
const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
573+
Signature::<P>::ALGORITHM_IDENTIFIER;
574+
}
575+
576+
#[cfg(feature = "alloc")]
577+
impl<P: MlDsaParams> EncodePublicKey for VerifyingKey<P> {
578+
fn to_public_key_der(&self) -> spki::Result<der::Document> {
579+
let algorithm_identifier = AlgorithmIdentifierRef {
580+
oid: P::ALGORITHM_OID,
581+
parameters: None,
582+
};
583+
584+
let public_key = self.encode();
585+
let subject_public_key = BitStringRef::new(0, &public_key)?;
586+
587+
SubjectPublicKeyInfo {
588+
algorithm: algorithm_identifier,
589+
subject_public_key,
590+
}
591+
.try_into()
592+
}
593+
}
594+
595+
impl<P: MlDsaParams> TryFrom<SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P> {
596+
type Error = spki::Error;
597+
598+
fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
599+
spki.algorithm.assert_algorithm_oid(P::ALGORITHM_OID)?;
600+
601+
Ok(Self::decode(
602+
&EncodedVerifyingKey::<P>::try_from(
603+
spki.subject_public_key
604+
.as_bytes()
605+
.ok_or_else(|| der::Tag::BitString.value_error())?,
606+
)
607+
.map_err(|_| pkcs8::Error::KeyMalformed)?,
608+
))
609+
}
610+
}
611+
491612
/// `MlDsa44` is the parameter set for security category 2.
492613
#[derive(Default, Clone, Debug, PartialEq)]
493614
pub struct MlDsa44;
@@ -503,6 +624,7 @@ impl ParameterSet for MlDsa44 {
503624
type Lambda = U32;
504625
type Omega = U80;
505626
const TAU: usize = 39;
627+
const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips204::ID_ML_DSA_44;
506628
}
507629

508630
/// `MlDsa65` is the parameter set for security category 3.
@@ -520,6 +642,7 @@ impl ParameterSet for MlDsa65 {
520642
type Lambda = U48;
521643
type Omega = U55;
522644
const TAU: usize = 49;
645+
const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips204::ID_ML_DSA_65;
523646
}
524647

525648
/// `MlKem87` is the parameter set for security category 5.
@@ -537,6 +660,7 @@ impl ParameterSet for MlDsa87 {
537660
type Lambda = U64;
538661
type Omega = U75;
539662
const TAU: usize = 60;
663+
const ALGORITHM_OID: pkcs8::ObjectIdentifier = fips204::ID_ML_DSA_87;
540664
}
541665

542666
/// A parameter set that knows how to generate key pairs

ml-dsa/src/param.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub trait ParameterSet {
113113
#[allow(clippy::as_conversions)]
114114
#[allow(clippy::cast_possible_truncation)]
115115
const BETA: u32 = (Self::TAU as u32) * Self::Eta::U32;
116+
117+
/// Associated OID with the Parameter
118+
const ALGORITHM_OID: pkcs8::ObjectIdentifier;
116119
}
117120

118121
pub trait SigningKeyParams: ParameterSet {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
3+
HB0eHw==
4+
-----END PRIVATE KEY-----
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw
3+
JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD
4+
l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD
5+
atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+
6+
r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN
7+
kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB
8+
ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn
9+
/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B
10+
rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D
11+
GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i
12+
svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8
13+
YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2
14+
plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij
15+
DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee
16+
SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa
17+
DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas
18+
QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH
19+
wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU
20+
7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV
21+
oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn
22+
j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl
23+
Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI
24+
bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/
25+
XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br
26+
IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP
27+
cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO
28+
t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo
29+
7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI=
30+
-----END PUBLIC KEY-----
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
3+
HB0eHw==
4+
-----END PRIVATE KEY-----
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
-----BEGIN PUBLIC KEY-----
2+
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
43+
HgLy6ERP
44+
-----END PUBLIC KEY-----
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
3+
HB0eHw==
4+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)