Skip to content

Commit 19a0d80

Browse files
committed
feat(ssh-key): add ML-DSA private key and keypair types
1 parent 91a867f commit 19a0d80

3 files changed

Lines changed: 304 additions & 1 deletion

File tree

ssh-key/src/private.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ mod ecdsa;
100100
mod ed25519;
101101
mod keypair;
102102
#[cfg(feature = "alloc")]
103+
mod mldsa;
104+
#[cfg(feature = "alloc")]
103105
mod opaque;
104106
#[cfg(feature = "alloc")]
105107
mod rsa;
@@ -116,6 +118,7 @@ pub use crate::{
116118
Comment, SshSig,
117119
private::{
118120
dsa::{DsaKeypair, DsaPrivateKey},
121+
mldsa::{MlDsaKeypair, MlDsaPrivateKey},
119122
opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
120123
rsa::{RsaKeypair, RsaPrivateKey},
121124
sk::SkEd25519,
@@ -622,6 +625,8 @@ impl PrivateKey {
622625
Algorithm::Rsa { .. } => {
623626
KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
624627
}
628+
#[cfg(feature = "mldsa")]
629+
Algorithm::MlDsa { params } => KeypairData::from(MlDsaKeypair::random(rng, params)?),
625630
_ => return Err(Error::AlgorithmUnknown),
626631
};
627632
let public_key = public::KeyData::try_from(&key_data)?;
@@ -1002,6 +1007,15 @@ impl From<RsaKeypair> for PrivateKey {
10021007
}
10031008
}
10041009

1010+
#[cfg(feature = "alloc")]
1011+
impl From<MlDsaKeypair> for PrivateKey {
1012+
fn from(keypair: MlDsaKeypair) -> PrivateKey {
1013+
KeypairData::from(keypair)
1014+
.try_into()
1015+
.expect(CONVERSION_ERROR_MSG)
1016+
}
1017+
}
1018+
10051019
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
10061020
impl From<SkEcdsaSha2NistP256> for PrivateKey {
10071021
fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {

ssh-key/src/private/keypair.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
77

88
#[cfg(feature = "alloc")]
99
use {
10-
super::{DsaKeypair, OpaqueKeypair, RsaKeypair, SkEd25519},
10+
super::{DsaKeypair, MlDsaKeypair, OpaqueKeypair, RsaKeypair, SkEd25519},
1111
alloc::vec::Vec,
1212
};
1313

@@ -44,6 +44,10 @@ pub enum KeypairData {
4444
#[cfg(feature = "alloc")]
4545
Rsa(RsaKeypair),
4646

47+
/// ML-DSA keypair
48+
#[cfg(feature = "alloc")]
49+
MlDsa(MlDsaKeypair),
50+
4751
/// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
4852
///
4953
/// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
@@ -77,6 +81,8 @@ impl KeypairData {
7781
Self::Encrypted(_) => return Err(Error::Encrypted),
7882
#[cfg(feature = "alloc")]
7983
Self::Rsa(_) => Algorithm::Rsa { hash: None },
84+
#[cfg(feature = "alloc")]
85+
Self::MlDsa(key) => key.algorithm(),
8086
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
8187
Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
8288
#[cfg(feature = "alloc")]
@@ -136,6 +142,16 @@ impl KeypairData {
136142
}
137143
}
138144

145+
/// Get ML-DSA keypair if this key is the correct type.
146+
#[cfg(feature = "alloc")]
147+
#[must_use]
148+
pub fn mldsa(&self) -> Option<&MlDsaKeypair> {
149+
match self {
150+
Self::MlDsa(key) => Some(key),
151+
_ => None,
152+
}
153+
}
154+
139155
/// Get FIDO/U2F ECDSA/NIST P-256 private key if this key is the correct type.
140156
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
141157
#[must_use]
@@ -207,6 +223,13 @@ impl KeypairData {
207223
matches!(self, Self::Rsa(_))
208224
}
209225

226+
/// Is this key an ML-DSA key?
227+
#[cfg(feature = "alloc")]
228+
#[must_use]
229+
pub fn is_mldsa(&self) -> bool {
230+
matches!(self, Self::MlDsa(_))
231+
}
232+
210233
/// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
211234
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
212235
#[must_use]
@@ -243,6 +266,8 @@ impl KeypairData {
243266
Self::Encrypted(ciphertext) => ciphertext.as_ref(),
244267
#[cfg(feature = "alloc")]
245268
Self::Rsa(rsa) => rsa.private().d().as_bytes(),
269+
#[cfg(feature = "alloc")]
270+
Self::MlDsa(mldsa) => mldsa.private.as_ref(),
246271
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
247272
Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
248273
#[cfg(feature = "alloc")]
@@ -278,6 +303,8 @@ impl KeypairData {
278303
Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
279304
#[cfg(feature = "alloc")]
280305
Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
306+
#[cfg(feature = "alloc")]
307+
Algorithm::MlDsa { params } => MlDsaKeypair::decode_as(reader, params).map(Self::MlDsa),
281308
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
282309
Algorithm::SkEcdsaSha2NistP256 => {
283310
SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
@@ -307,6 +334,8 @@ impl CtEq for KeypairData {
307334
(Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
308335
#[cfg(feature = "alloc")]
309336
(Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
337+
#[cfg(feature = "alloc")]
338+
(Self::MlDsa(a), Self::MlDsa(b)) => a.ct_eq(b),
310339
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
311340
(Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
312341
// Security Keys store the actual private key in hardware.
@@ -363,6 +392,8 @@ impl Encode for KeypairData {
363392
Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
364393
#[cfg(feature = "alloc")]
365394
Self::Rsa(key) => key.encoded_len()?,
395+
#[cfg(feature = "alloc")]
396+
Self::MlDsa(key) => key.encoded_len()?,
366397
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
367398
Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
368399
#[cfg(feature = "alloc")]
@@ -389,6 +420,8 @@ impl Encode for KeypairData {
389420
Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
390421
#[cfg(feature = "alloc")]
391422
Self::Rsa(key) => key.encode(writer)?,
423+
#[cfg(feature = "alloc")]
424+
Self::MlDsa(key) => key.encode(writer)?,
392425
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
393426
Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
394427
#[cfg(feature = "alloc")]
@@ -415,6 +448,8 @@ impl TryFrom<&KeypairData> for public::KeyData {
415448
KeypairData::Encrypted(_) => return Err(Error::Encrypted),
416449
#[cfg(feature = "alloc")]
417450
KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
451+
#[cfg(feature = "alloc")]
452+
KeypairData::MlDsa(mldsa) => public::KeyData::MlDsa(mldsa.into()),
418453
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
419454
KeypairData::SkEcdsaSha2NistP256(sk) => {
420455
public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
@@ -454,6 +489,13 @@ impl From<RsaKeypair> for KeypairData {
454489
}
455490
}
456491

492+
#[cfg(feature = "alloc")]
493+
impl From<MlDsaKeypair> for KeypairData {
494+
fn from(keypair: MlDsaKeypair) -> KeypairData {
495+
Self::MlDsa(keypair)
496+
}
497+
}
498+
457499
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
458500
impl From<SkEcdsaSha2NistP256> for KeypairData {
459501
fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {

0 commit comments

Comments
 (0)