@@ -47,6 +47,15 @@ const CERT_SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.c
4747/// OpenSSH certificate for Ed25519 U2F/FIDO security key
4848const CERT_SK_SSH_ED25519 : & str = "sk-ssh-ed25519-cert-v01@openssh.com" ;
4949
50+ /// OpenSSH certificate for ML-DSA-44 public key
51+ const CERT_MLDSA_44 : & str = "ssh-mldsa-44-cert-v01@openssh.com" ;
52+
53+ /// OpenSSH certificate for ML-DSA-65 public key
54+ const CERT_MLDSA_65 : & str = "ssh-mldsa-65-cert-v01@openssh.com" ;
55+
56+ /// OpenSSH certificate for ML-DSA-87 public key
57+ const CERT_MLDSA_87 : & str = "ssh-mldsa-87-cert-v01@openssh.com" ;
58+
5059/// ECDSA with SHA-256 + NIST P-256
5160const ECDSA_SHA2_P256 : & str = "ecdsa-sha2-nistp256" ;
5261
@@ -86,6 +95,15 @@ const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
8695/// U2F/FIDO security key with Ed25519
8796const SK_SSH_ED25519 : & str = "sk-ssh-ed25519@openssh.com" ;
8897
98+ /// ML-DSA-44 (FIPS 204, security category 2)
99+ const SSH_MLDSA_44 : & str = "ssh-mldsa-44" ;
100+
101+ /// ML-DSA-65 (FIPS 204, security category 3)
102+ const SSH_MLDSA_65 : & str = "ssh-mldsa-65" ;
103+
104+ /// ML-DSA-87 (FIPS 204, security category 5)
105+ const SSH_MLDSA_87 : & str = "ssh-mldsa-87" ;
106+
89107/// SSH key algorithms, i.e. digital signature algorithms used with SSH private/public keys.
90108#[ derive( Clone , Debug , Default , Eq , Hash , PartialEq , PartialOrd , Ord ) ]
91109#[ non_exhaustive]
@@ -121,6 +139,12 @@ pub enum Algorithm {
121139 /// FIDO/U2F key with Ed25519
122140 SkEd25519 ,
123141
142+ /// ML-DSA
143+ MlDsa {
144+ /// ML-DSA parameter set to use.
145+ params : MlDsaParams ,
146+ } ,
147+
124148 /// Other
125149 #[ cfg( feature = "alloc" ) ]
126150 Other ( AlgorithmName ) ,
@@ -138,6 +162,9 @@ impl Algorithm {
138162 /// - `ssh-rsa`
139163 /// - `sk-ecdsa-sha2-nistp256@openssh.com` (FIDO/U2F key)
140164 /// - `sk-ssh-ed25519@openssh.com` (FIDO/U2F key)
165+ /// - `ssh-mldsa-44`
166+ /// - `ssh-mldsa-65`
167+ /// - `ssh-mldsa-87`
141168 ///
142169 /// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
143170 ///
@@ -161,6 +188,9 @@ impl Algorithm {
161188 /// - `ssh-ed25519-cert-v01@openssh.com`
162189 /// - `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com` (FIDO/U2F key)
163190 /// - `sk-ssh-ed25519-cert-v01@openssh.com` (FIDO/U2F key)
191+ /// - `ssh-mldsa-44-cert-v01@openssh.com`
192+ /// - `ssh-mldsa-65-cert-v01@openssh.com`
193+ /// - `ssh-mldsa-87-cert-v01@openssh.com`
164194 ///
165195 /// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
166196 ///
@@ -190,6 +220,15 @@ impl Algorithm {
190220 } ) ,
191221 CERT_SK_ECDSA_SHA2_P256 => Ok ( Algorithm :: SkEcdsaSha2NistP256 ) ,
192222 CERT_SK_SSH_ED25519 => Ok ( Algorithm :: SkEd25519 ) ,
223+ CERT_MLDSA_44 => Ok ( Algorithm :: MlDsa {
224+ params : MlDsaParams :: MlDsa44 ,
225+ } ) ,
226+ CERT_MLDSA_65 => Ok ( Algorithm :: MlDsa {
227+ params : MlDsaParams :: MlDsa65 ,
228+ } ) ,
229+ CERT_MLDSA_87 => Ok ( Algorithm :: MlDsa {
230+ params : MlDsaParams :: MlDsa87 ,
231+ } ) ,
193232 #[ cfg( feature = "alloc" ) ]
194233 _ => Ok ( Algorithm :: Other ( AlgorithmName :: from_certificate_type ( id) ?) ) ,
195234 #[ cfg( not( feature = "alloc" ) ) ]
@@ -215,6 +254,11 @@ impl Algorithm {
215254 } ,
216255 Algorithm :: SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256 ,
217256 Algorithm :: SkEd25519 => SK_SSH_ED25519 ,
257+ Algorithm :: MlDsa { params } => match params {
258+ MlDsaParams :: MlDsa44 => SSH_MLDSA_44 ,
259+ MlDsaParams :: MlDsa65 => SSH_MLDSA_65 ,
260+ MlDsaParams :: MlDsa87 => SSH_MLDSA_87 ,
261+ } ,
218262 #[ cfg( feature = "alloc" ) ]
219263 Algorithm :: Other ( algorithm) => algorithm. as_str ( ) ,
220264 }
@@ -247,6 +291,11 @@ impl Algorithm {
247291 } => CERT_RSA_SHA2_512 ,
248292 Algorithm :: SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256 ,
249293 Algorithm :: SkEd25519 => CERT_SK_SSH_ED25519 ,
294+ Algorithm :: MlDsa { params } => match params {
295+ MlDsaParams :: MlDsa44 => CERT_MLDSA_44 ,
296+ MlDsaParams :: MlDsa65 => CERT_MLDSA_65 ,
297+ MlDsaParams :: MlDsa87 => CERT_MLDSA_87 ,
298+ } ,
250299 Algorithm :: Other ( algorithm) => return algorithm. certificate_type ( ) ,
251300 }
252301 . to_owned ( )
@@ -276,6 +325,12 @@ impl Algorithm {
276325 matches ! ( self , Algorithm :: Rsa { .. } )
277326 }
278327
328+ /// Is the algorithm ML-DSA?
329+ #[ must_use]
330+ pub fn is_mldsa ( self ) -> bool {
331+ matches ! ( self , Algorithm :: MlDsa { .. } )
332+ }
333+
279334 /// Return an error indicating this algorithm is unsupported.
280335 #[ allow( dead_code) ]
281336 pub ( crate ) fn unsupported_error ( self ) -> Error {
@@ -322,6 +377,15 @@ impl str::FromStr for Algorithm {
322377 SSH_RSA => Ok ( Algorithm :: Rsa { hash : None } ) ,
323378 SK_ECDSA_SHA2_P256 => Ok ( Algorithm :: SkEcdsaSha2NistP256 ) ,
324379 SK_SSH_ED25519 => Ok ( Algorithm :: SkEd25519 ) ,
380+ SSH_MLDSA_44 => Ok ( Algorithm :: MlDsa {
381+ params : MlDsaParams :: MlDsa44 ,
382+ } ) ,
383+ SSH_MLDSA_65 => Ok ( Algorithm :: MlDsa {
384+ params : MlDsaParams :: MlDsa65 ,
385+ } ) ,
386+ SSH_MLDSA_87 => Ok ( Algorithm :: MlDsa {
387+ params : MlDsaParams :: MlDsa87 ,
388+ } ) ,
325389 #[ cfg( feature = "alloc" ) ]
326390 _ => Ok ( Algorithm :: Other ( AlgorithmName :: from_str ( id) ?) ) ,
327391 #[ cfg( not( feature = "alloc" ) ) ]
@@ -412,6 +476,108 @@ impl str::FromStr for EcdsaCurve {
412476 }
413477}
414478
479+ /// ML-DSA parameter sets supported for use with SSH as specified in [FIPS204].
480+ ///
481+ /// Each parameter set corresponds to a NIST security category.
482+ ///
483+ /// [FIPS204]: https://csrc.nist.gov/pubs/fips/204/final
484+ #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq , PartialOrd , Ord ) ]
485+ pub enum MlDsaParams {
486+ /// ML-DSA-44 (security category 2).
487+ MlDsa44 ,
488+
489+ /// ML-DSA-65 (security category 3), the recommended parameter set.
490+ MlDsa65 ,
491+
492+ /// ML-DSA-87 (security category 5).
493+ MlDsa87 ,
494+ }
495+
496+ impl MlDsaParams {
497+ /// Decode an ML-DSA parameter set from the given SSH algorithm identifier.
498+ ///
499+ /// # Supported identifiers
500+ ///
501+ /// - `ssh-mldsa-44`
502+ /// - `ssh-mldsa-65`
503+ /// - `ssh-mldsa-87`
504+ ///
505+ /// # Errors
506+ /// Returns [`Error::Encoding`] in the event the identifier is not known.
507+ pub fn new ( id : & str ) -> Result < Self > {
508+ Ok ( id. parse ( ) ?)
509+ }
510+
511+ /// Get the SSH algorithm identifier which corresponds to this parameter set.
512+ #[ must_use]
513+ pub fn as_str ( self ) -> & ' static str {
514+ match self {
515+ MlDsaParams :: MlDsa44 => SSH_MLDSA_44 ,
516+ MlDsaParams :: MlDsa65 => SSH_MLDSA_65 ,
517+ MlDsaParams :: MlDsa87 => SSH_MLDSA_87 ,
518+ }
519+ }
520+
521+ /// Size in bytes of a FIPS 204 public key for this parameter set.
522+ #[ must_use]
523+ pub const fn public_key_size ( self ) -> usize {
524+ match self {
525+ MlDsaParams :: MlDsa44 => 1312 ,
526+ MlDsaParams :: MlDsa65 => 1952 ,
527+ MlDsaParams :: MlDsa87 => 2592 ,
528+ }
529+ }
530+
531+ /// Size in bytes of a FIPS 204 signature for this parameter set.
532+ #[ must_use]
533+ pub const fn signature_size ( self ) -> usize {
534+ match self {
535+ MlDsaParams :: MlDsa44 => 2420 ,
536+ MlDsaParams :: MlDsa65 => 3309 ,
537+ MlDsaParams :: MlDsa87 => 4627 ,
538+ }
539+ }
540+
541+ /// Size in bytes of the seed (ξ) used to derive an ML-DSA key.
542+ ///
543+ /// This is 32 bytes for all parameter sets.
544+ #[ must_use]
545+ pub const fn seed_size ( self ) -> usize {
546+ 32
547+ }
548+ }
549+
550+ impl AsRef < str > for MlDsaParams {
551+ fn as_ref ( & self ) -> & str {
552+ self . as_str ( )
553+ }
554+ }
555+
556+ impl From < MlDsaParams > for Algorithm {
557+ fn from ( params : MlDsaParams ) -> Algorithm {
558+ Algorithm :: MlDsa { params }
559+ }
560+ }
561+
562+ impl fmt:: Display for MlDsaParams {
563+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
564+ f. write_str ( self . as_str ( ) )
565+ }
566+ }
567+
568+ impl str:: FromStr for MlDsaParams {
569+ type Err = LabelError ;
570+
571+ fn from_str ( id : & str ) -> core:: result:: Result < Self , LabelError > {
572+ match id {
573+ SSH_MLDSA_44 => Ok ( MlDsaParams :: MlDsa44 ) ,
574+ SSH_MLDSA_65 => Ok ( MlDsaParams :: MlDsa65 ) ,
575+ SSH_MLDSA_87 => Ok ( MlDsaParams :: MlDsa87 ) ,
576+ _ => Err ( LabelError :: new ( id) ) ,
577+ }
578+ }
579+ }
580+
415581/// Hashing algorithms a.k.a. digest functions.
416582#[ derive( Copy , Clone , Debug , Default , Eq , Hash , PartialEq , PartialOrd , Ord ) ]
417583#[ non_exhaustive]
0 commit comments