ssh_key/
algorithm.rs

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