Skip to content

Commit 6cb88f0

Browse files
committed
feat(ssh-key): add ML-DSA private key and keypair types
1 parent e860090 commit 6cb88f0

3 files changed

Lines changed: 302 additions & 0 deletions

File tree

ssh-key/src/private.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ mod dsa;
9999
mod ecdsa;
100100
mod ed25519;
101101
mod keypair;
102+
#[cfg(feature = "mldsa")]
103+
mod mldsa;
102104
#[cfg(feature = "alloc")]
103105
mod opaque;
104106
#[cfg(feature = "alloc")]
@@ -123,6 +125,9 @@ pub use crate::{
123125
sha2::Digest,
124126
};
125127

128+
#[cfg(feature = "mldsa")]
129+
pub use crate::private::mldsa::{MlDsaKeypair, MlDsaPrivateKey};
130+
126131
#[cfg(feature = "ecdsa")]
127132
pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
128133

@@ -622,6 +627,8 @@ impl PrivateKey {
622627
Algorithm::Rsa { .. } => {
623628
KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
624629
}
630+
#[cfg(feature = "mldsa")]
631+
Algorithm::MlDsa { params } => KeypairData::from(MlDsaKeypair::random(rng, params)?),
625632
_ => return Err(Error::AlgorithmUnknown),
626633
};
627634
let public_key = public::KeyData::try_from(&key_data)?;
@@ -1002,6 +1009,15 @@ impl From<RsaKeypair> for PrivateKey {
10021009
}
10031010
}
10041011

1012+
#[cfg(feature = "mldsa")]
1013+
impl From<MlDsaKeypair> for PrivateKey {
1014+
fn from(keypair: MlDsaKeypair) -> PrivateKey {
1015+
KeypairData::from(keypair)
1016+
.try_into()
1017+
.expect(CONVERSION_ERROR_MSG)
1018+
}
1019+
}
1020+
10051021
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
10061022
impl From<SkEcdsaSha2NistP256> for PrivateKey {
10071023
fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {

ssh-key/src/private/keypair.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use {
1111
alloc::vec::Vec,
1212
};
1313

14+
#[cfg(feature = "mldsa")]
15+
use super::MlDsaKeypair;
16+
1417
#[cfg(feature = "ecdsa")]
1518
use super::EcdsaKeypair;
1619

@@ -44,6 +47,10 @@ pub enum KeypairData {
4447
#[cfg(feature = "alloc")]
4548
Rsa(RsaKeypair),
4649

50+
/// ML-DSA keypair
51+
#[cfg(feature = "mldsa")]
52+
MlDsa(MlDsaKeypair),
53+
4754
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
4855
///
4956
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
@@ -77,6 +84,8 @@ impl KeypairData {
7784
Self::Encrypted(_) => return Err(Error::Encrypted),
7885
#[cfg(feature = "alloc")]
7986
Self::Rsa(_) => Algorithm::Rsa { hash: None },
87+
#[cfg(feature = "mldsa")]
88+
Self::MlDsa(key) => key.algorithm(),
8089
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
8190
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
8291
#[cfg(feature = "alloc")]
@@ -136,6 +145,16 @@ impl KeypairData {
136145
}
137146
}
138147

148+
/// Get ML-DSA keypair if this key is the correct type.
149+
#[cfg(feature = "mldsa")]
150+
#[must_use]
151+
pub fn mldsa(&self) -> Option<&MlDsaKeypair> {
152+
match self {
153+
Self::MlDsa(key) => Some(key),
154+
_ => None,
155+
}
156+
}
157+
139158
/// Get FIDO/U2F ECDSA/NIST P-256 private key if this key is the correct type.
140159
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
141160
#[must_use]
@@ -207,6 +226,13 @@ impl KeypairData {
207226
matches!(self, Self::Rsa(_))
208227
}
209228

229+
/// Is this key an ML-DSA key?
230+
#[cfg(feature = "mldsa")]
231+
#[must_use]
232+
pub fn is_mldsa(&self) -> bool {
233+
matches!(self, Self::MlDsa(_))
234+
}
235+
210236
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
211237
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
212238
#[must_use]
@@ -243,6 +269,8 @@ impl KeypairData {
243269
Self::Encrypted(ciphertext) => ciphertext.as_ref(),
244270
#[cfg(feature = "alloc")]
245271
Self::Rsa(rsa) => rsa.private().d().as_bytes(),
272+
#[cfg(feature = "mldsa")]
273+
Self::MlDsa(mldsa) => mldsa.private.as_ref(),
246274
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
247275
Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
248276
#[cfg(feature = "alloc")]
@@ -278,6 +306,8 @@ impl KeypairData {
278306
Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
279307
#[cfg(feature = "alloc")]
280308
Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
309+
#[cfg(feature = "mldsa")]
310+
Algorithm::MlDsa { params } => MlDsaKeypair::decode_as(reader, params).map(Self::MlDsa),
281311
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
282312
Algorithm::SkEcdsaSha2NistP256 => {
283313
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
@@ -307,6 +337,8 @@ impl CtEq for KeypairData {
307337
(Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
308338
#[cfg(feature = "alloc")]
309339
(Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
340+
#[cfg(feature = "mldsa")]
341+
(Self::MlDsa(a), Self::MlDsa(b)) => a.ct_eq(b),
310342
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
311343
(Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
312344
// Security Keys store the actual private key in hardware.
@@ -363,6 +395,8 @@ impl Encode for KeypairData {
363395
Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
364396
#[cfg(feature = "alloc")]
365397
Self::Rsa(key) => key.encoded_len()?,
398+
#[cfg(feature = "mldsa")]
399+
Self::MlDsa(key) => key.encoded_len()?,
366400
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
367401
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
368402
#[cfg(feature = "alloc")]
@@ -389,6 +423,8 @@ impl Encode for KeypairData {
389423
Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
390424
#[cfg(feature = "alloc")]
391425
Self::Rsa(key) => key.encode(writer)?,
426+
#[cfg(feature = "mldsa")]
427+
Self::MlDsa(key) => key.encode(writer)?,
392428
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
393429
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
394430
#[cfg(feature = "alloc")]
@@ -415,6 +451,8 @@ impl TryFrom<&KeypairData> for public::KeyData {
415451
KeypairData::Encrypted(_) => return Err(Error::Encrypted),
416452
#[cfg(feature = "alloc")]
417453
KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
454+
#[cfg(feature = "mldsa")]
455+
KeypairData::MlDsa(mldsa) => public::KeyData::MlDsa(mldsa.into()),
418456
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
419457
KeypairData::SkEcdsaSha2NistP256(sk) => {
420458
public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
@@ -454,6 +492,13 @@ impl From<RsaKeypair> for KeypairData {
454492
}
455493
}
456494

495+
#[cfg(feature = "mldsa")]
496+
impl From<MlDsaKeypair> for KeypairData {
497+
fn from(keypair: MlDsaKeypair) -> KeypairData {
498+
Self::MlDsa(keypair)
499+
}
500+
}
501+
457502
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
458503
impl From<SkEcdsaSha2NistP256> for KeypairData {
459504
fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {

0 commit comments

Comments
 (0)