Skip to main content

ssh_key/private/
keypair.rs

1//! Private key pairs.
2
3use super::ed25519::Ed25519Keypair;
4use crate::{Algorithm, Error, Result, public};
5use ctutils::{Choice, CtEq};
6use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
7
8#[cfg(feature = "alloc")]
9use {
10    super::{DsaKeypair, OpaqueKeypair, RsaKeypair, SkEd25519},
11    alloc::vec::Vec,
12};
13
14#[cfg(feature = "ecdsa")]
15use super::EcdsaKeypair;
16
17#[cfg(all(feature = "alloc", feature = "ecdsa"))]
18use super::SkEcdsaSha2NistP256;
19
20/// Private key data: digital signature key pairs.
21///
22/// SSH private keys contain pairs of public and private keys for various
23/// supported digital signature algorithms.
24// TODO(tarcieri): pseudo-private keys for FIDO/U2F security keys
25#[derive(Clone, Debug)]
26#[non_exhaustive]
27pub enum KeypairData {
28    /// Digital Signature Algorithm (DSA) keypair.
29    #[cfg(feature = "alloc")]
30    Dsa(DsaKeypair),
31
32    /// ECDSA keypair.
33    #[cfg(feature = "ecdsa")]
34    Ecdsa(EcdsaKeypair),
35
36    /// Ed25519 keypair.
37    Ed25519(Ed25519Keypair),
38
39    /// Encrypted private key (ciphertext).
40    #[cfg(feature = "alloc")]
41    Encrypted(Vec<u8>),
42
43    /// RSA keypair.
44    #[cfg(feature = "alloc")]
45    Rsa(RsaKeypair),
46
47    /// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
48    ///
49    /// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
50    #[cfg(all(feature = "alloc", feature = "ecdsa"))]
51    SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
52
53    /// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
54    ///
55    /// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
56    #[cfg(feature = "alloc")]
57    SkEd25519(SkEd25519),
58
59    /// Opaque keypair.
60    #[cfg(feature = "alloc")]
61    Other(OpaqueKeypair),
62}
63
64impl KeypairData {
65    /// Get the [`Algorithm`] for this private key.
66    ///
67    /// # Errors
68    /// Returns [`Error::Encrypted`] if the private key is encrypted.
69    pub fn algorithm(&self) -> Result<Algorithm> {
70        Ok(match self {
71            #[cfg(feature = "alloc")]
72            Self::Dsa(_) => Algorithm::Dsa,
73            #[cfg(feature = "ecdsa")]
74            Self::Ecdsa(key) => key.algorithm(),
75            Self::Ed25519(_) => Algorithm::Ed25519,
76            #[cfg(feature = "alloc")]
77            Self::Encrypted(_) => return Err(Error::Encrypted),
78            #[cfg(feature = "alloc")]
79            Self::Rsa(_) => Algorithm::Rsa { hash: None },
80            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
81            Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
82            #[cfg(feature = "alloc")]
83            Self::SkEd25519(_) => Algorithm::SkEd25519,
84            #[cfg(feature = "alloc")]
85            Self::Other(key) => key.algorithm(),
86        })
87    }
88
89    /// Get DSA keypair if this key is the correct type.
90    #[cfg(feature = "alloc")]
91    #[must_use]
92    pub fn dsa(&self) -> Option<&DsaKeypair> {
93        match self {
94            Self::Dsa(key) => Some(key),
95            _ => None,
96        }
97    }
98
99    /// Get ECDSA private key if this key is the correct type.
100    #[cfg(feature = "ecdsa")]
101    #[must_use]
102    pub fn ecdsa(&self) -> Option<&EcdsaKeypair> {
103        match self {
104            Self::Ecdsa(keypair) => Some(keypair),
105            _ => None,
106        }
107    }
108
109    /// Get Ed25519 private key if this key is the correct type.
110    #[must_use]
111    pub fn ed25519(&self) -> Option<&Ed25519Keypair> {
112        match self {
113            Self::Ed25519(key) => Some(key),
114            #[allow(unreachable_patterns)]
115            _ => None,
116        }
117    }
118
119    /// Get the encrypted ciphertext if this key is encrypted.
120    #[cfg(feature = "alloc")]
121    #[must_use]
122    pub fn encrypted(&self) -> Option<&[u8]> {
123        match self {
124            Self::Encrypted(ciphertext) => Some(ciphertext),
125            _ => None,
126        }
127    }
128
129    /// Get RSA keypair if this key is the correct type.
130    #[cfg(feature = "alloc")]
131    #[must_use]
132    pub fn rsa(&self) -> Option<&RsaKeypair> {
133        match self {
134            Self::Rsa(key) => Some(key),
135            _ => None,
136        }
137    }
138
139    /// Get FIDO/U2F ECDSA/NIST P-256 private key if this key is the correct type.
140    #[cfg(all(feature = "alloc", feature = "ecdsa"))]
141    #[must_use]
142    pub fn sk_ecdsa_p256(&self) -> Option<&SkEcdsaSha2NistP256> {
143        match self {
144            Self::SkEcdsaSha2NistP256(sk) => Some(sk),
145            _ => None,
146        }
147    }
148
149    /// Get FIDO/U2F Ed25519 private key if this key is the correct type.
150    #[cfg(feature = "alloc")]
151    #[must_use]
152    pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
153        match self {
154            Self::SkEd25519(sk) => Some(sk),
155            _ => None,
156        }
157    }
158
159    /// Get the custom, opaque private key if this key is the correct type.
160    #[cfg(feature = "alloc")]
161    #[must_use]
162    pub fn other(&self) -> Option<&OpaqueKeypair> {
163        match self {
164            Self::Other(key) => Some(key),
165            _ => None,
166        }
167    }
168
169    /// Is this key a DSA key?
170    #[cfg(feature = "alloc")]
171    #[must_use]
172    pub fn is_dsa(&self) -> bool {
173        matches!(self, Self::Dsa(_))
174    }
175
176    /// Is this key an ECDSA key?
177    #[cfg(feature = "ecdsa")]
178    #[must_use]
179    pub fn is_ecdsa(&self) -> bool {
180        matches!(self, Self::Ecdsa(_))
181    }
182
183    /// Is this key an Ed25519 key?
184    #[must_use]
185    pub fn is_ed25519(&self) -> bool {
186        matches!(self, Self::Ed25519(_))
187    }
188
189    /// Is this key encrypted?
190    #[cfg(not(feature = "alloc"))]
191    #[must_use]
192    pub fn is_encrypted(&self) -> bool {
193        false
194    }
195
196    /// Is this key encrypted?
197    #[cfg(feature = "alloc")]
198    #[must_use]
199    pub fn is_encrypted(&self) -> bool {
200        matches!(self, Self::Encrypted(_))
201    }
202
203    /// Is this key an RSA key?
204    #[cfg(feature = "alloc")]
205    #[must_use]
206    pub fn is_rsa(&self) -> bool {
207        matches!(self, Self::Rsa(_))
208    }
209
210    /// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
211    #[cfg(all(feature = "alloc", feature = "ecdsa"))]
212    #[must_use]
213    pub fn is_sk_ecdsa_p256(&self) -> bool {
214        matches!(self, Self::SkEcdsaSha2NistP256(_))
215    }
216
217    /// Is this key a FIDO/U2F Ed25519 key?
218    #[cfg(feature = "alloc")]
219    #[must_use]
220    pub fn is_sk_ed25519(&self) -> bool {
221        matches!(self, Self::SkEd25519(_))
222    }
223
224    /// Is this a key with a custom algorithm?
225    #[cfg(feature = "alloc")]
226    #[must_use]
227    pub fn is_other(&self) -> bool {
228        matches!(self, Self::Other(_))
229    }
230
231    /// Compute a deterministic "checkint" for this private key.
232    ///
233    /// This is a sort of primitive pseudo-MAC used by the OpenSSH key format.
234    // TODO(tarcieri): true randomness or a better algorithm?
235    pub(super) fn checkint(&self) -> u32 {
236        let bytes = match self {
237            #[cfg(feature = "alloc")]
238            Self::Dsa(dsa) => dsa.private().as_bytes(),
239            #[cfg(feature = "ecdsa")]
240            Self::Ecdsa(ecdsa) => ecdsa.private_key_bytes(),
241            Self::Ed25519(ed25519) => ed25519.private.as_ref(),
242            #[cfg(feature = "alloc")]
243            Self::Encrypted(ciphertext) => ciphertext.as_ref(),
244            #[cfg(feature = "alloc")]
245            Self::Rsa(rsa) => rsa.private().d().as_bytes(),
246            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
247            Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
248            #[cfg(feature = "alloc")]
249            Self::SkEd25519(sk) => sk.key_handle(),
250            #[cfg(feature = "alloc")]
251            Self::Other(key) => key.private.as_ref(),
252        };
253
254        let mut n = 0u32;
255
256        for chunk in bytes.chunks_exact(4) {
257            n ^= u32::from_be_bytes(chunk.try_into().expect("not 4 bytes"));
258        }
259
260        n
261    }
262
263    /// Decode [`KeypairData`] for the specified algorithm.
264    ///
265    /// # Errors
266    /// - Returns [`Error::AlgorithmUnknown`] if the provided `algorithm` is unknown or unsupported
267    ///   by this library.
268    /// - Returns [`Error::Encoding`] in the event of an encoding error.
269    pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result<Self> {
270        match algorithm {
271            #[cfg(feature = "alloc")]
272            Algorithm::Dsa => DsaKeypair::decode(reader).map(Self::Dsa),
273            #[cfg(feature = "ecdsa")]
274            Algorithm::Ecdsa { curve } => match EcdsaKeypair::decode(reader)? {
275                keypair if keypair.curve() == curve => Ok(Self::Ecdsa(keypair)),
276                _ => Err(Error::AlgorithmUnknown),
277            },
278            Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
279            #[cfg(feature = "alloc")]
280            Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
281            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
282            Algorithm::SkEcdsaSha2NistP256 => {
283                SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
284            }
285            #[cfg(feature = "alloc")]
286            Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
287            #[cfg(feature = "alloc")]
288            algorithm @ Algorithm::Other(_) => {
289                OpaqueKeypair::decode_as(reader, algorithm).map(Self::Other)
290            }
291            #[allow(unreachable_patterns)]
292            _ => Err(Error::AlgorithmUnknown),
293        }
294    }
295}
296
297impl CtEq for KeypairData {
298    fn ct_eq(&self, other: &Self) -> Choice {
299        // Note: constant-time with respect to key *data* comparisons, not algorithms
300        match (self, other) {
301            #[cfg(feature = "alloc")]
302            (Self::Dsa(a), Self::Dsa(b)) => a.ct_eq(b),
303            #[cfg(feature = "ecdsa")]
304            (Self::Ecdsa(a), Self::Ecdsa(b)) => a.ct_eq(b),
305            (Self::Ed25519(a), Self::Ed25519(b)) => a.ct_eq(b),
306            #[cfg(feature = "alloc")]
307            (Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
308            #[cfg(feature = "alloc")]
309            (Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
310            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
311            (Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
312                // Security Keys store the actual private key in hardware.
313                // The key structs contain all public data.
314                Choice::from(u8::from(a == b))
315            }
316            #[cfg(feature = "alloc")]
317            (Self::SkEd25519(a), Self::SkEd25519(b)) => {
318                // Security Keys store the actual private key in hardware.
319                // The key structs contain all public data.
320                Choice::from(u8::from(a == b))
321            }
322            #[cfg(feature = "alloc")]
323            (Self::Other(a), Self::Other(b)) => a.ct_eq(b),
324            #[allow(unreachable_patterns)]
325            _ => Choice::from(0),
326        }
327    }
328}
329
330impl Eq for KeypairData {}
331
332impl PartialEq for KeypairData {
333    fn eq(&self, other: &Self) -> bool {
334        self.ct_eq(other).into()
335    }
336}
337
338impl Decode for KeypairData {
339    type Error = Error;
340
341    fn decode(reader: &mut impl Reader) -> Result<Self> {
342        let algorithm = Algorithm::decode(reader)?;
343        Self::decode_as(reader, algorithm)
344    }
345}
346
347impl Encode for KeypairData {
348    fn encoded_len(&self) -> encoding::Result<usize> {
349        let alg_len = self
350            .algorithm()
351            .ok()
352            .map(|alg| alg.encoded_len())
353            .transpose()?
354            .unwrap_or(0);
355
356        let key_len = match self {
357            #[cfg(feature = "alloc")]
358            Self::Dsa(key) => key.encoded_len()?,
359            #[cfg(feature = "ecdsa")]
360            Self::Ecdsa(key) => key.encoded_len()?,
361            Self::Ed25519(key) => key.encoded_len()?,
362            #[cfg(feature = "alloc")]
363            Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
364            #[cfg(feature = "alloc")]
365            Self::Rsa(key) => key.encoded_len()?,
366            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
367            Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
368            #[cfg(feature = "alloc")]
369            Self::SkEd25519(sk) => sk.encoded_len()?,
370            #[cfg(feature = "alloc")]
371            Self::Other(key) => key.encoded_len()?,
372        };
373
374        [alg_len, key_len].checked_sum()
375    }
376
377    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
378        if let Ok(alg) = self.algorithm() {
379            alg.encode(writer)?;
380        }
381
382        match self {
383            #[cfg(feature = "alloc")]
384            Self::Dsa(key) => key.encode(writer)?,
385            #[cfg(feature = "ecdsa")]
386            Self::Ecdsa(key) => key.encode(writer)?,
387            Self::Ed25519(key) => key.encode(writer)?,
388            #[cfg(feature = "alloc")]
389            Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
390            #[cfg(feature = "alloc")]
391            Self::Rsa(key) => key.encode(writer)?,
392            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
393            Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
394            #[cfg(feature = "alloc")]
395            Self::SkEd25519(sk) => sk.encode(writer)?,
396            #[cfg(feature = "alloc")]
397            Self::Other(key) => key.encode(writer)?,
398        }
399
400        Ok(())
401    }
402}
403
404impl TryFrom<&KeypairData> for public::KeyData {
405    type Error = Error;
406
407    fn try_from(keypair_data: &KeypairData) -> Result<public::KeyData> {
408        Ok(match keypair_data {
409            #[cfg(feature = "alloc")]
410            KeypairData::Dsa(dsa) => public::KeyData::Dsa(dsa.into()),
411            #[cfg(feature = "ecdsa")]
412            KeypairData::Ecdsa(ecdsa) => public::KeyData::Ecdsa(ecdsa.into()),
413            KeypairData::Ed25519(ed25519) => public::KeyData::Ed25519(ed25519.into()),
414            #[cfg(feature = "alloc")]
415            KeypairData::Encrypted(_) => return Err(Error::Encrypted),
416            #[cfg(feature = "alloc")]
417            KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
418            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
419            KeypairData::SkEcdsaSha2NistP256(sk) => {
420                public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
421            }
422            #[cfg(feature = "alloc")]
423            KeypairData::SkEd25519(sk) => public::KeyData::SkEd25519(sk.public().clone()),
424            #[cfg(feature = "alloc")]
425            KeypairData::Other(key) => public::KeyData::Other(key.into()),
426        })
427    }
428}
429
430#[cfg(feature = "alloc")]
431impl From<DsaKeypair> for KeypairData {
432    fn from(keypair: DsaKeypair) -> KeypairData {
433        Self::Dsa(keypair)
434    }
435}
436
437#[cfg(feature = "ecdsa")]
438impl From<EcdsaKeypair> for KeypairData {
439    fn from(keypair: EcdsaKeypair) -> KeypairData {
440        Self::Ecdsa(keypair)
441    }
442}
443
444impl From<Ed25519Keypair> for KeypairData {
445    fn from(keypair: Ed25519Keypair) -> KeypairData {
446        Self::Ed25519(keypair)
447    }
448}
449
450#[cfg(feature = "alloc")]
451impl From<RsaKeypair> for KeypairData {
452    fn from(keypair: RsaKeypair) -> KeypairData {
453        Self::Rsa(keypair)
454    }
455}
456
457#[cfg(all(feature = "alloc", feature = "ecdsa"))]
458impl From<SkEcdsaSha2NistP256> for KeypairData {
459    fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {
460        Self::SkEcdsaSha2NistP256(keypair)
461    }
462}
463
464#[cfg(feature = "alloc")]
465impl From<SkEd25519> for KeypairData {
466    fn from(keypair: SkEd25519) -> KeypairData {
467        Self::SkEd25519(keypair)
468    }
469}