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    pub fn new(id: &str) -> Result<Self> {
144        Ok(id.parse()?)
145    }
146
147    /// Decode algorithm from the given string identifier as used by
148    /// the OpenSSH certificate format.
149    ///
150    /// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
151    /// See [PROTOCOL.certkeys] for more information.
152    ///
153    /// # Supported algorithms
154    /// - `ssh-rsa-cert-v01@openssh.com`
155    /// - `ssh-dss-cert-v01@openssh.com`
156    /// - `ecdsa-sha2-nistp256-cert-v01@openssh.com`
157    /// - `ecdsa-sha2-nistp384-cert-v01@openssh.com`
158    /// - `ecdsa-sha2-nistp521-cert-v01@openssh.com`
159    /// - `ssh-ed25519-cert-v01@openssh.com`
160    /// - `sk-ecdsa-sha2-nistp256-cert-v01@openssh.com` (FIDO/U2F key)
161    /// - `sk-ssh-ed25519-cert-v01@openssh.com` (FIDO/U2F key)
162    ///
163    /// Any other algorithms are mapped to the [`Algorithm::Other`] variant.
164    ///
165    /// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
166    pub fn new_certificate(id: &str) -> Result<Self> {
167        match id {
168            CERT_DSA => Ok(Algorithm::Dsa),
169            CERT_ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
170                curve: EcdsaCurve::NistP256,
171            }),
172            CERT_ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
173                curve: EcdsaCurve::NistP384,
174            }),
175            CERT_ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
176                curve: EcdsaCurve::NistP521,
177            }),
178            CERT_ED25519 => Ok(Algorithm::Ed25519),
179            CERT_RSA => Ok(Algorithm::Rsa { hash: None }),
180            CERT_RSA_SHA2_256 => Ok(Algorithm::Rsa {
181                hash: Some(HashAlg::Sha256),
182            }),
183            CERT_RSA_SHA2_512 => Ok(Algorithm::Rsa {
184                hash: Some(HashAlg::Sha512),
185            }),
186            CERT_SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
187            CERT_SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
188            #[cfg(feature = "alloc")]
189            _ => Ok(Algorithm::Other(AlgorithmName::from_certificate_type(id)?)),
190            #[cfg(not(feature = "alloc"))]
191            _ => Err(Error::AlgorithmUnknown),
192        }
193    }
194
195    /// Get the string identifier which corresponds to this algorithm.
196    pub fn as_str(&self) -> &str {
197        match self {
198            Algorithm::Dsa => SSH_DSA,
199            Algorithm::Ecdsa { curve } => match curve {
200                EcdsaCurve::NistP256 => ECDSA_SHA2_P256,
201                EcdsaCurve::NistP384 => ECDSA_SHA2_P384,
202                EcdsaCurve::NistP521 => ECDSA_SHA2_P521,
203            },
204            Algorithm::Ed25519 => SSH_ED25519,
205            Algorithm::Rsa { hash } => match hash {
206                None => SSH_RSA,
207                Some(HashAlg::Sha256) => RSA_SHA2_256,
208                Some(HashAlg::Sha512) => RSA_SHA2_512,
209            },
210            Algorithm::SkEcdsaSha2NistP256 => SK_ECDSA_SHA2_P256,
211            Algorithm::SkEd25519 => SK_SSH_ED25519,
212            #[cfg(feature = "alloc")]
213            Algorithm::Other(algorithm) => algorithm.as_str(),
214        }
215    }
216
217    /// Get the string identifier which corresponds to the OpenSSH certificate
218    /// format.
219    ///
220    /// OpenSSH certificate algorithms end in `*-cert-v01@openssh.com`.
221    /// See [PROTOCOL.certkeys] for more information.
222    ///
223    /// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
224    #[cfg(feature = "alloc")]
225    pub fn to_certificate_type(&self) -> String {
226        match self {
227            Algorithm::Dsa => CERT_DSA,
228            Algorithm::Ecdsa { curve } => match curve {
229                EcdsaCurve::NistP256 => CERT_ECDSA_SHA2_P256,
230                EcdsaCurve::NistP384 => CERT_ECDSA_SHA2_P384,
231                EcdsaCurve::NistP521 => CERT_ECDSA_SHA2_P521,
232            },
233            Algorithm::Ed25519 => CERT_ED25519,
234            Algorithm::Rsa { hash: None } => CERT_RSA,
235            Algorithm::Rsa {
236                hash: Some(HashAlg::Sha256),
237            } => CERT_RSA_SHA2_256,
238            Algorithm::Rsa {
239                hash: Some(HashAlg::Sha512),
240            } => CERT_RSA_SHA2_512,
241            Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256,
242            Algorithm::SkEd25519 => CERT_SK_SSH_ED25519,
243            Algorithm::Other(algorithm) => return algorithm.certificate_type(),
244        }
245        .to_owned()
246    }
247
248    /// Is the algorithm DSA?
249    pub fn is_dsa(self) -> bool {
250        self == Algorithm::Dsa
251    }
252
253    /// Is the algorithm ECDSA?
254    pub fn is_ecdsa(self) -> bool {
255        matches!(self, Algorithm::Ecdsa { .. })
256    }
257
258    /// Is the algorithm Ed25519?
259    pub fn is_ed25519(self) -> bool {
260        self == Algorithm::Ed25519
261    }
262
263    /// Is the algorithm RSA?
264    pub fn is_rsa(self) -> bool {
265        matches!(self, Algorithm::Rsa { .. })
266    }
267
268    /// Return an error indicating this algorithm is unsupported.
269    #[allow(dead_code)]
270    pub(crate) fn unsupported_error(self) -> Error {
271        Error::AlgorithmUnsupported { algorithm: self }
272    }
273}
274
275impl AsRef<str> for Algorithm {
276    fn as_ref(&self) -> &str {
277        self.as_str()
278    }
279}
280
281impl Label for Algorithm {}
282
283impl fmt::Display for Algorithm {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        f.write_str(self.as_str())
286    }
287}
288
289impl str::FromStr for Algorithm {
290    type Err = LabelError;
291
292    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
293        match id {
294            SSH_DSA => Ok(Algorithm::Dsa),
295            ECDSA_SHA2_P256 => Ok(Algorithm::Ecdsa {
296                curve: EcdsaCurve::NistP256,
297            }),
298            ECDSA_SHA2_P384 => Ok(Algorithm::Ecdsa {
299                curve: EcdsaCurve::NistP384,
300            }),
301            ECDSA_SHA2_P521 => Ok(Algorithm::Ecdsa {
302                curve: EcdsaCurve::NistP521,
303            }),
304            RSA_SHA2_256 => Ok(Algorithm::Rsa {
305                hash: Some(HashAlg::Sha256),
306            }),
307            RSA_SHA2_512 => Ok(Algorithm::Rsa {
308                hash: Some(HashAlg::Sha512),
309            }),
310            SSH_ED25519 => Ok(Algorithm::Ed25519),
311            SSH_RSA => Ok(Algorithm::Rsa { hash: None }),
312            SK_ECDSA_SHA2_P256 => Ok(Algorithm::SkEcdsaSha2NistP256),
313            SK_SSH_ED25519 => Ok(Algorithm::SkEd25519),
314            #[cfg(feature = "alloc")]
315            _ => Ok(Algorithm::Other(AlgorithmName::from_str(id)?)),
316            #[cfg(not(feature = "alloc"))]
317            _ => Err(LabelError::new(id)),
318        }
319    }
320}
321
322/// Elliptic curves supported for use with ECDSA.
323#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
324pub enum EcdsaCurve {
325    /// NIST P-256 (a.k.a. prime256v1, secp256r1)
326    NistP256,
327
328    /// NIST P-384 (a.k.a. secp384r1)
329    NistP384,
330
331    /// NIST P-521 (a.k.a. secp521r1)
332    NistP521,
333}
334
335impl EcdsaCurve {
336    /// Decode elliptic curve from the given string identifier.
337    ///
338    /// # Supported curves
339    ///
340    /// - `nistp256`
341    /// - `nistp384`
342    /// - `nistp521`
343    pub fn new(id: &str) -> Result<Self> {
344        Ok(id.parse()?)
345    }
346
347    /// Get the string identifier which corresponds to this ECDSA elliptic curve.
348    pub fn as_str(self) -> &'static str {
349        match self {
350            EcdsaCurve::NistP256 => "nistp256",
351            EcdsaCurve::NistP384 => "nistp384",
352            EcdsaCurve::NistP521 => "nistp521",
353        }
354    }
355
356    /// Get the number of bytes needed to encode a field element for this curve.
357    #[cfg(feature = "alloc")]
358    pub(crate) const fn field_size(self) -> usize {
359        match self {
360            EcdsaCurve::NistP256 => 32,
361            EcdsaCurve::NistP384 => 48,
362            EcdsaCurve::NistP521 => 66,
363        }
364    }
365}
366
367impl AsRef<str> for EcdsaCurve {
368    fn as_ref(&self) -> &str {
369        self.as_str()
370    }
371}
372
373impl Label for EcdsaCurve {}
374
375impl fmt::Display for EcdsaCurve {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        f.write_str(self.as_str())
378    }
379}
380
381impl str::FromStr for EcdsaCurve {
382    type Err = LabelError;
383
384    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
385        match id {
386            "nistp256" => Ok(EcdsaCurve::NistP256),
387            "nistp384" => Ok(EcdsaCurve::NistP384),
388            "nistp521" => Ok(EcdsaCurve::NistP521),
389            _ => Err(LabelError::new(id)),
390        }
391    }
392}
393
394/// Hashing algorithms a.k.a. digest functions.
395#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
396#[non_exhaustive]
397pub enum HashAlg {
398    /// SHA-256
399    #[default]
400    Sha256,
401
402    /// SHA-512
403    Sha512,
404}
405
406impl HashAlg {
407    /// Decode elliptic curve from the given string identifier.
408    ///
409    /// # Supported hash algorithms
410    ///
411    /// - `sha256`
412    /// - `sha512`
413    pub fn new(id: &str) -> Result<Self> {
414        Ok(id.parse()?)
415    }
416
417    /// Get the string identifier for this hash algorithm.
418    pub fn as_str(self) -> &'static str {
419        match self {
420            HashAlg::Sha256 => SHA256,
421            HashAlg::Sha512 => SHA512,
422        }
423    }
424
425    /// Get the size of a digest produced by this hash function.
426    pub const fn digest_size(self) -> usize {
427        match self {
428            HashAlg::Sha256 => 32,
429            HashAlg::Sha512 => 64,
430        }
431    }
432
433    /// Compute a digest of the given message using this hash function.
434    #[cfg(feature = "alloc")]
435    pub fn digest(self, msg: &[u8]) -> Vec<u8> {
436        match self {
437            HashAlg::Sha256 => Sha256::digest(msg).to_vec(),
438            HashAlg::Sha512 => Sha512::digest(msg).to_vec(),
439        }
440    }
441}
442
443impl Label for HashAlg {}
444
445impl AsRef<str> for HashAlg {
446    fn as_ref(&self) -> &str {
447        self.as_str()
448    }
449}
450
451impl fmt::Display for HashAlg {
452    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453        f.write_str(self.as_str())
454    }
455}
456
457impl str::FromStr for HashAlg {
458    type Err = LabelError;
459
460    fn from_str(id: &str) -> core::result::Result<Self, LabelError> {
461        match id {
462            SHA256 => Ok(HashAlg::Sha256),
463            SHA512 => Ok(HashAlg::Sha512),
464            _ => Err(LabelError::new(id)),
465        }
466    }
467}
468
469/// Associate an SSH [`HashAlg`] with the given type.
470pub trait AssociatedHashAlg: Digest {
471    /// Algorithm identifier for this hash.
472    const HASH_ALG: HashAlg;
473}
474
475impl AssociatedHashAlg for Sha256 {
476    const HASH_ALG: HashAlg = HashAlg::Sha256;
477}
478
479impl AssociatedHashAlg for Sha512 {
480    const HASH_ALG: HashAlg = HashAlg::Sha512;
481}
482
483/// Key Derivation Function (KDF) algorithms.
484#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
485#[non_exhaustive]
486pub enum KdfAlg {
487    /// None.
488    None,
489
490    /// bcrypt-pbkdf.
491    #[default]
492    Bcrypt,
493}
494
495impl KdfAlg {
496    /// Decode KDF algorithm from the given `kdfname`.
497    ///
498    /// # Supported KDF names
499    /// - `none`
500    pub fn new(kdfname: &str) -> Result<Self> {
501        Ok(kdfname.parse()?)
502    }
503
504    /// Get the string identifier which corresponds to this algorithm.
505    pub fn as_str(self) -> &'static str {
506        match self {
507            Self::None => NONE,
508            Self::Bcrypt => BCRYPT,
509        }
510    }
511
512    /// Is the KDF algorithm "none"?
513    pub fn is_none(self) -> bool {
514        self == Self::None
515    }
516}
517
518impl Label for KdfAlg {}
519
520impl AsRef<str> for KdfAlg {
521    fn as_ref(&self) -> &str {
522        self.as_str()
523    }
524}
525
526impl fmt::Display for KdfAlg {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        f.write_str(self.as_str())
529    }
530}
531
532impl str::FromStr for KdfAlg {
533    type Err = LabelError;
534
535    fn from_str(kdfname: &str) -> core::result::Result<Self, LabelError> {
536        match kdfname {
537            NONE => Ok(Self::None),
538            BCRYPT => Ok(Self::Bcrypt),
539            _ => Err(LabelError::new(kdfname)),
540        }
541    }
542}