Skip to main content

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