ssh_key/private/
keypair.rs

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