ssh_key/
algorithm.rs

1//! Algorithm support.
2
3#[cfg(feature = "alloc")]
4mod name;
5
6use crate::{Error, Result};
7use core::{fmt, str};
8use encoding::{Label, LabelError};
9
10#[cfg(feature = "alloc")]
11use {
12    alloc::{borrow::ToOwned, string::String, vec::Vec},
13    sha2::{Digest, Sha256, Sha512},
14};
15
16#[cfg(feature = "alloc")]
17pub use name::AlgorithmName;
18
19/// bcrypt-pbkdf
20const BCRYPT: &str = "bcrypt";
21
22/// OpenSSH certificate for DSA public key
23const CERT_DSA: &str = "ssh-dss-cert-v01@openssh.com";
24
25/// OpenSSH certificate for ECDSA (NIST P-256) public key
26const CERT_ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256-cert-v01@openssh.com";
27
28/// OpenSSH certificate for ECDSA (NIST P-384) public key
29const CERT_ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384-cert-v01@openssh.com";
30
31/// OpenSSH certificate for ECDSA (NIST P-521) public key
32const CERT_ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521-cert-v01@openssh.com";
33
34/// OpenSSH certificate for Ed25519 public key
35const CERT_ED25519: &str = "ssh-ed25519-cert-v01@openssh.com";
36
37/// OpenSSH certificate with RSA public key
38const CERT_RSA: &str = "ssh-rsa-cert-v01@openssh.com";
39
40/// OpenSSH certificate for ECDSA (NIST P-256) U2F/FIDO security key
41const CERT_SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com";
42
43/// OpenSSH certificate for Ed25519 U2F/FIDO security key
44const CERT_SK_SSH_ED25519: &str = "sk-ssh-ed25519-cert-v01@openssh.com";
45
46/// ECDSA with SHA-256 + NIST P-256
47const ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256";
48
49/// ECDSA with SHA-256 + NIST P-256
50const ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384";
51
52/// ECDSA with SHA-256 + NIST P-256
53const ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521";
54
55/// None
56const NONE: &str = "none";
57
58/// RSA with SHA-256 as described in RFC8332 § 3
59const RSA_SHA2_256: &str = "rsa-sha2-256";
60
61/// RSA with SHA-512 as described in RFC8332 § 3
62const RSA_SHA2_512: &str = "rsa-sha2-512";
63
64/// SHA-256 hash function
65const SHA256: &str = "sha256";
66
67/// SHA-512 hash function
68const SHA512: &str = "sha512";
69
70/// Digital Signature Algorithm
71const SSH_DSA: &str = "ssh-dss";
72
73/// Ed25519
74const SSH_ED25519: &str = "ssh-ed25519";
75
76/// RSA
77const SSH_RSA: &str = "ssh-rsa";
78
79/// U2F/FIDO security key with ECDSA/NIST P-256
80const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
81
82/// U2F/FIDO security key with Ed25519
83const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com";
84
85/// SSH key algorithms.
86///
87/// This type provides a registry of supported digital signature algorithms
88/// used for SSH keys.
89#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
90#[non_exhaustive]
91pub enum Algorithm {
92    /// Digital Signature Algorithm
93    Dsa,
94
95    /// Elliptic Curve Digital Signature Algorithm
96    Ecdsa {
97        /// Elliptic curve with which to instantiate ECDSA.
98        curve: EcdsaCurve,
99    },
100
101    /// Ed25519
102    #[default]
103    Ed25519,
104
105    /// RSA
106    Rsa {
107        /// Hash function to use with RSASSA-PKCS#1v15 signatures as specified
108        /// using [RFC8332] algorithm identifiers.
109        ///
110        /// If `hash` is set to `None`, then `ssh-rsa` is used as the algorithm
111        /// name.
112        ///
113        /// [RFC8332]: https://datatracker.ietf.org/doc/html/rfc8332
114        hash: Option<HashAlg>,
115    },
116
117    /// FIDO/U2F key with ECDSA/NIST-P256 + SHA-256
118    SkEcdsaSha2NistP256,
119
120    /// FIDO/U2F key with Ed25519
121    SkEd25519,
122
123    /// Other
124    #[cfg(feature = "alloc")]
125    Other(AlgorithmName),
126}
127
128impl Algorithm {
129    /// Decode algorithm from the given string identifier.
130    ///
131    /// # Supported algorithms
132    /// - `ecdsa-sha2-nistp256`
133    /// - `ecdsa-sha2-nistp384`
134    /// - `ecdsa-sha2-nistp521`
135    /// - `ssh-dss`
136    /// - `ssh-ed25519`
137    /// - `ssh-rsa`
138    /// - `sk-ecdsa-sha2-nistp256@openssh.com` (FIDO/U2F key)
139    /// - `sk-ssh-ed25519@openssh.com` (FIDO/U2F key)
140    ///
141    /// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
142    pub fn new(id: &str) -> Result<Self> {
143        Ok(id.parse()?)
144    }
145
146    /// Decode algorithm from the given string identifier as used by
147    /// the OpenSSH certificate format.
148    ///
149    /// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
150    /// See [PROTOCOL.certkeys] for more information.
151    ///
152    /// # Supported algorithms
153    /// - `ssh-rsa-cert-v01@openssh.com`
154    /// - `ssh-dss-cert-v01@openssh.com`
155    /// - `ecdsa-sha2-nistp256-cert-v01@openssh.com`
156    /// - `ecdsa-sha2-nistp384-cert-v01@openssh.com`
157    /// - `ecdsa-sha2-nistp521-cert-v01@openssh.com`
158    /// - `ssh-ed25519-cert-v01@openssh.com`
159    /// - `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com` (FIDO/U2F key)
160    /// - `sk-ssh-ed25519-cert-v01@openssh.com` (FIDO/U2F key)
161    ///
162    /// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
163    ///
164    /// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
165    pub fn new_certificate(id: &str) -> Result<Self> {
166        match id {
167            CERT_DSA => Ok(Algorithm::Dsa),
168            CERT_ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
169                curve: EcdsaCurve::NistP256,
170            }),
171            CERT_ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
172                curve: EcdsaCurve::NistP384,
173            }),
174            CERT_ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
175                curve: EcdsaCurve::NistP521,
176            }),
177            CERT_ED25519 => Ok(Algorithm::Ed25519),
178            CERT_RSA => Ok(Algorithm::Rsa { hash: None }),
179            CERT_SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
180            CERT_SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
181            #[cfg(feature = "alloc")]
182            _ => Ok(Algorithm::Other(AlgorithmName::from_certificate_type(id)?)),
183            #[cfg(not(feature = "alloc"))]
184            _ => Err(Error::AlgorithmUnknown),
185        }
186    }
187
188    /// Get the string identifier which corresponds to this algorithm.
189    pub fn as_str(&self) -> &str {
190        match self {
191            Algorithm::Dsa => SSH_DSA,
192            Algorithm::Ecdsa { curve } => match curve {
193                EcdsaCurve::NistP256 => ECDSA_SHA2_P256,
194                EcdsaCurve::NistP384 => ECDSA_SHA2_P384,
195                EcdsaCurve::NistP521 => ECDSA_SHA2_P521,
196            },
197            Algorithm::Ed25519 => SSH_ED25519,
198            Algorithm::Rsa { hash } => match hash {
199                None => SSH_RSA,
200                Some(HashAlg::Sha256) => RSA_SHA2_256,
201                Some(HashAlg::Sha512) => RSA_SHA2_512,
202            },
203            Algorithm::SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256,
204            Algorithm::SkEd25519 => SK_SSH_ED25519,
205            #[cfg(feature = "alloc")]
206            Algorithm::Other(algorithm) => algorithm.as_str(),
207        }
208    }
209
210    /// Get the string identifier which corresponds to the OpenSSH certificate
211    /// format.
212    ///
213    /// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
214    /// See [PROTOCOL.certkeys] for more information.
215    ///
216    /// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
217    #[cfg(feature = "alloc")]
218    pub fn to_certificate_type(&self) -> String {
219        match self {
220            Algorithm::Dsa => CERT_DSA,
221            Algorithm::Ecdsa { curve } => match curve {
222                EcdsaCurve::NistP256 => CERT_ECDSA_SHA2_P256,
223                EcdsaCurve::NistP384 => CERT_ECDSA_SHA2_P384,
224                EcdsaCurve::NistP521 => CERT_ECDSA_SHA2_P521,
225            },
226            Algorithm::Ed25519 => CERT_ED25519,
227            Algorithm::Rsa { .. } => CERT_RSA,
228            Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256,
229            Algorithm::SkEd25519 => CERT_SK_SSH_ED25519,
230            Algorithm::Other(algorithm) => return algorithm.certificate_type(),
231        }
232        .to_owned()
233    }
234
235    /// Is the algorithm DSA?
236    pub fn is_dsa(self) -> bool {
237        self == Algorithm::Dsa
238    }
239
240    /// Is the algorithm ECDSA?
241    pub fn is_ecdsa(self) -> bool {
242        matches!(self, Algorithm::Ecdsa { .. })
243    }
244
245    /// Is the algorithm Ed25519?
246    pub fn is_ed25519(self) -> bool {
247        self == Algorithm::Ed25519
248    }
249
250    /// Is the algorithm RSA?
251    pub fn is_rsa(self) -> bool {
252        matches!(self, Algorithm::Rsa { .. })
253    }
254
255    /// Return an error indicating this algorithm is unsupported.
256    #[allow(dead_code)]
257    pub(crate) fn unsupported_error(self) -> Error {
258        Error::AlgorithmUnsupported { algorithm: self }
259    }
260}
261
262impl AsRef<str> for Algorithm {
263    fn as_ref(&self) -> &str {
264        self.as_str()
265    }
266}
267
268impl Label for Algorithm {}
269
270impl fmt::Display for Algorithm {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.write_str(self.as_str())
273    }
274}
275
276impl str::FromStr for Algorithm {
277    type Err = LabelError;
278
279    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
280        match id {
281            SSH_DSA => Ok(Algorithm::Dsa),
282            ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
283                curve: EcdsaCurve::NistP256,
284            }),
285            ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
286                curve: EcdsaCurve::NistP384,
287            }),
288            ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
289                curve: EcdsaCurve::NistP521,
290            }),
291            RSA_SHA2_256 => Ok(Algorithm::Rsa {
292                hash: Some(HashAlg::Sha256),
293            }),
294            RSA_SHA2_512 => Ok(Algorithm::Rsa {
295                hash: Some(HashAlg::Sha512),
296            }),
297            SSH_ED25519 => Ok(Algorithm::Ed25519),
298            SSH_RSA => Ok(Algorithm::Rsa { hash: None }),
299            SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
300            SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
301            #[cfg(feature = "alloc")]
302            _ => Ok(Algorithm::Other(AlgorithmName::from_str(id)?)),
303            #[cfg(not(feature = "alloc"))]
304            _ => Err(LabelError::new(id)),
305        }
306    }
307}
308
309/// Elliptic curves supported for use with ECDSA.
310#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
311pub enum EcdsaCurve {
312    /// NIST P-256 (a.k.a. prime256v1, secp256r1)
313    NistP256,
314
315    /// NIST P-384 (a.k.a. secp384r1)
316    NistP384,
317
318    /// NIST P-521 (a.k.a. secp521r1)
319    NistP521,
320}
321
322impl EcdsaCurve {
323    /// Decode elliptic curve from the given string identifier.
324    ///
325    /// # Supported curves
326    ///
327    /// - `nistp256`
328    /// - `nistp384`
329    /// - `nistp521`
330    pub fn new(id: &str) -> Result<Self> {
331        Ok(id.parse()?)
332    }
333
334    /// Get the string identifier which corresponds to this ECDSA elliptic curve.
335    pub fn as_str(self) -> &'static str {
336        match self {
337            EcdsaCurve::NistP256 => "nistp256",
338            EcdsaCurve::NistP384 => "nistp384",
339            EcdsaCurve::NistP521 => "nistp521",
340        }
341    }
342
343    /// Get the number of bytes needed to encode a field element for this curve.
344    #[cfg(feature = "alloc")]
345    pub(crate) const fn field_size(self) -> usize {
346        match self {
347            EcdsaCurve::NistP256 => 32,
348            EcdsaCurve::NistP384 => 48,
349            EcdsaCurve::NistP521 => 66,
350        }
351    }
352}
353
354impl AsRef<str> for EcdsaCurve {
355    fn as_ref(&self) -> &str {
356        self.as_str()
357    }
358}
359
360impl Label for EcdsaCurve {}
361
362impl fmt::Display for EcdsaCurve {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        f.write_str(self.as_str())
365    }
366}
367
368impl str::FromStr for EcdsaCurve {
369    type Err = LabelError;
370
371    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
372        match id {
373            "nistp256" => Ok(EcdsaCurve::NistP256),
374            "nistp384" => Ok(EcdsaCurve::NistP384),
375            "nistp521" => Ok(EcdsaCurve::NistP521),
376            _ => Err(LabelError::new(id)),
377        }
378    }
379}
380
381/// Hashing algorithms a.k.a. digest functions.
382#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
383#[non_exhaustive]
384pub enum HashAlg {
385    /// SHA-256
386    #[default]
387    Sha256,
388
389    /// SHA-512
390    Sha512,
391}
392
393impl HashAlg {
394    /// Decode elliptic curve from the given string identifier.
395    ///
396    /// # Supported hash algorithms
397    ///
398    /// - `sha256`
399    /// - `sha512`
400    pub fn new(id: &str) -> Result<Self> {
401        Ok(id.parse()?)
402    }
403
404    /// Get the string identifier for this hash algorithm.
405    pub fn as_str(self) -> &'static str {
406        match self {
407            HashAlg::Sha256 => SHA256,
408            HashAlg::Sha512 => SHA512,
409        }
410    }
411
412    /// Get the size of a digest produced by this hash function.
413    pub const fn digest_size(self) -> usize {
414        match self {
415            HashAlg::Sha256 => 32,
416            HashAlg::Sha512 => 64,
417        }
418    }
419
420    /// Compute a digest of the given message using this hash function.
421    #[cfg(feature = "alloc")]
422    pub fn digest(self, msg: &[u8]) -> Vec<u8> {
423        match self {
424            HashAlg::Sha256 => Sha256::digest(msg).to_vec(),
425            HashAlg::Sha512 => Sha512::digest(msg).to_vec(),
426        }
427    }
428}
429
430impl Label for HashAlg {}
431
432impl AsRef<str> for HashAlg {
433    fn as_ref(&self) -> &str {
434        self.as_str()
435    }
436}
437
438impl fmt::Display for HashAlg {
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        f.write_str(self.as_str())
441    }
442}
443
444impl str::FromStr for HashAlg {
445    type Err = LabelError;
446
447    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
448        match id {
449            SHA256 => Ok(HashAlg::Sha256),
450            SHA512 => Ok(HashAlg::Sha512),
451            _ => Err(LabelError::new(id)),
452        }
453    }
454}
455
456/// Key Derivation Function (KDF) algorithms.
457#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
458#[non_exhaustive]
459pub enum KdfAlg {
460    /// None.
461    None,
462
463    /// bcrypt-pbkdf.
464    #[default]
465    Bcrypt,
466}
467
468impl KdfAlg {
469    /// Decode KDF algorithm from the given `kdfname`.
470    ///
471    /// # Supported KDF names
472    /// - `none`
473    pub fn new(kdfname: &str) -> Result<Self> {
474        Ok(kdfname.parse()?)
475    }
476
477    /// Get the string identifier which corresponds to this algorithm.
478    pub fn as_str(self) -> &'static str {
479        match self {
480            Self::None => NONE,
481            Self::Bcrypt => BCRYPT,
482        }
483    }
484
485    /// Is the KDF algorithm "none"?
486    pub fn is_none(self) -> bool {
487        self == Self::None
488    }
489}
490
491impl Label for KdfAlg {}
492
493impl AsRef<str> for KdfAlg {
494    fn as_ref(&self) -> &str {
495        self.as_str()
496    }
497}
498
499impl fmt::Display for KdfAlg {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        f.write_str(self.as_str())
502    }
503}
504
505impl str::FromStr for KdfAlg {
506    type Err = LabelError;
507
508    fn from_str(kdfname: &str) -> core::result::Result<Self, LabelError> {
509        match kdfname {
510            NONE => Ok(Self::None),
511            BCRYPT => Ok(Self::Bcrypt),
512            _ => Err(LabelError::new(kdfname)),
513        }
514    }
515}