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