ssh_key/private/
keypair.rs

1//! Private key pairs.
2
3use super::ed25519::Ed25519Keypair;
4use crate::{
5    checked::CheckedSum, decode::Decode, encode::Encode, public, reader::Reader, writer::Writer,
6    Algorithm, Error, Result,
7};
8
9#[cfg(feature = "alloc")]
10use {
11    super::{DsaKeypair, RsaKeypair, SkEd25519},
12    alloc::vec::Vec,
13};
14
15#[cfg(feature = "ecdsa")]
16use super::EcdsaKeypair;
17
18#[cfg(all(feature = "alloc", feature = "ecdsa"))]
19use super::SkEcdsaSha2NistP256;
20
21#[cfg(feature = "subtle")]
22use subtle::{Choice, ConstantTimeEq};
23
24/// Private key data: digital signature key pairs.
25///
26/// SSH private keys contain pairs of public and private keys for various
27/// supported digital signature algorithms.
28// TODO(tarcieri): pseudo-private keys for FIDO/U2F security keys
29#[derive(Clone, Debug)]
30#[non_exhaustive]
31pub enum KeypairData {
32    /// Digital Signature Algorithm (DSA) keypair.
33    #[cfg(feature = "alloc")]
34    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
35    Dsa(DsaKeypair),
36
37    /// ECDSA keypair.
38    #[cfg(feature = "ecdsa")]
39    #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
40    Ecdsa(EcdsaKeypair),
41
42    /// Ed25519 keypair.
43    Ed25519(Ed25519Keypair),
44
45    /// Encrypted private key (ciphertext).
46    #[cfg(feature = "alloc")]
47    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
48    Encrypted(Vec<u8>),
49
50    /// RSA keypair.
51    #[cfg(feature = "alloc")]
52    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
53    Rsa(RsaKeypair),
54
55    /// Security Key (FIDO/U2F) using ECDSA/NIST P-256 as specified in [PROTOCOL.u2f].
56    ///
57    /// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
58    #[cfg(all(feature = "alloc", feature = "ecdsa"))]
59    #[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "ecdsa"))))]
60    SkEcdsaSha2NistP256(SkEcdsaSha2NistP256),
61
62    /// Security Key (FIDO/U2F) using Ed25519 as specified in [PROTOCOL.u2f].
63    ///
64    /// [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
65    #[cfg(feature = "alloc")]
66    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
67    SkEd25519(SkEd25519),
68}
69
70impl KeypairData {
71    /// Get the [`Algorithm`] for this private key.
72    pub fn algorithm(&self) -> Result<Algorithm> {
73        Ok(match self {
74            #[cfg(feature = "alloc")]
75            Self::Dsa(_) => Algorithm::Dsa,
76            #[cfg(feature = "ecdsa")]
77            Self::Ecdsa(key) => key.algorithm(),
78            Self::Ed25519(_) => Algorithm::Ed25519,
79            #[cfg(feature = "alloc")]
80            Self::Encrypted(_) => return Err(Error::Encrypted),
81            #[cfg(feature = "alloc")]
82            Self::Rsa(_) => Algorithm::Rsa { hash: None },
83            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
84            Self::SkEcdsaSha2NistP256(_) => Algorithm::SkEcdsaSha2NistP256,
85            #[cfg(feature = "alloc")]
86            Self::SkEd25519(_) => Algorithm::SkEd25519,
87        })
88    }
89
90    /// Get DSA keypair if this key is the correct type.
91    #[cfg(feature = "alloc")]
92    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
93    pub fn dsa(&self) -> Option<&DsaKeypair> {
94        match self {
95            Self::Dsa(key) => Some(key),
96            _ => None,
97        }
98    }
99
100    /// Get ECDSA private key if this key is the correct type.
101    #[cfg(feature = "ecdsa")]
102    #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
103    pub fn ecdsa(&self) -> Option<&EcdsaKeypair> {
104        match self {
105            Self::Ecdsa(keypair) => Some(keypair),
106            _ => None,
107        }
108    }
109
110    /// Get Ed25519 private key if this key is the correct type.
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    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
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    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
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    #[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "ecdsa"))))]
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    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
152    pub fn sk_ed25519(&self) -> Option<&SkEd25519> {
153        match self {
154            Self::SkEd25519(sk) => Some(sk),
155            _ => None,
156        }
157    }
158
159    /// Is this key a DSA key?
160    #[cfg(feature = "alloc")]
161    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
162    pub fn is_dsa(&self) -> bool {
163        matches!(self, Self::Dsa(_))
164    }
165
166    /// Is this key an ECDSA key?
167    #[cfg(feature = "ecdsa")]
168    #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
169    pub fn is_ecdsa(&self) -> bool {
170        matches!(self, Self::Ecdsa(_))
171    }
172
173    /// Is this key an Ed25519 key?
174    pub fn is_ed25519(&self) -> bool {
175        matches!(self, Self::Ed25519(_))
176    }
177
178    /// Is this key encrypted?
179    #[cfg(not(feature = "alloc"))]
180    pub fn is_encrypted(&self) -> bool {
181        false
182    }
183
184    /// Is this key encrypted?
185    #[cfg(feature = "alloc")]
186    pub fn is_encrypted(&self) -> bool {
187        matches!(self, Self::Encrypted(_))
188    }
189
190    /// Is this key an RSA key?
191    #[cfg(feature = "alloc")]
192    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
193    pub fn is_rsa(&self) -> bool {
194        matches!(self, Self::Rsa(_))
195    }
196
197    /// Is this key a FIDO/U2F ECDSA/NIST P-256 key?
198    #[cfg(all(feature = "alloc", feature = "ecdsa"))]
199    #[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "ecdsa"))))]
200    pub fn is_sk_ecdsa_p256(&self) -> bool {
201        matches!(self, Self::SkEcdsaSha2NistP256(_))
202    }
203
204    /// Is this key a FIDO/U2F Ed25519 key?
205    #[cfg(feature = "alloc")]
206    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
207    pub fn is_sk_ed25519(&self) -> bool {
208        matches!(self, Self::SkEd25519(_))
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        };
231
232        let mut n = 0u32;
233
234        for chunk in bytes.chunks_exact(4) {
235            n ^= u32::from_be_bytes(chunk.try_into().expect("not 4 bytes"));
236        }
237
238        n
239    }
240}
241
242impl Decode for KeypairData {
243    fn decode(reader: &mut impl Reader) -> Result<Self> {
244        match Algorithm::decode(reader)? {
245            #[cfg(feature = "alloc")]
246            Algorithm::Dsa => DsaKeypair::decode(reader).map(Self::Dsa),
247            #[cfg(feature = "ecdsa")]
248            Algorithm::Ecdsa { curve } => match EcdsaKeypair::decode(reader)? {
249                keypair if keypair.curve() == curve => Ok(Self::Ecdsa(keypair)),
250                _ => Err(Error::Algorithm),
251            },
252            Algorithm::Ed25519 => Ed25519Keypair::decode(reader).map(Self::Ed25519),
253            #[cfg(feature = "alloc")]
254            Algorithm::Rsa { .. } => RsaKeypair::decode(reader).map(Self::Rsa),
255            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
256            Algorithm::SkEcdsaSha2NistP256 => {
257                SkEcdsaSha2NistP256::decode(reader).map(Self::SkEcdsaSha2NistP256)
258            }
259            #[cfg(feature = "alloc")]
260            Algorithm::SkEd25519 => SkEd25519::decode(reader).map(Self::SkEd25519),
261            #[allow(unreachable_patterns)]
262            _ => Err(Error::Algorithm),
263        }
264    }
265}
266
267impl Encode for KeypairData {
268    fn encoded_len(&self) -> Result<usize> {
269        let key_len = match self {
270            #[cfg(feature = "alloc")]
271            Self::Dsa(key) => key.encoded_len()?,
272            #[cfg(feature = "ecdsa")]
273            Self::Ecdsa(key) => key.encoded_len()?,
274            Self::Ed25519(key) => key.encoded_len()?,
275            #[cfg(feature = "alloc")]
276            Self::Encrypted(ciphertext) => return Ok(ciphertext.len()),
277            #[cfg(feature = "alloc")]
278            Self::Rsa(key) => key.encoded_len()?,
279            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
280            Self::SkEcdsaSha2NistP256(sk) => sk.encoded_len()?,
281            #[cfg(feature = "alloc")]
282            Self::SkEd25519(sk) => sk.encoded_len()?,
283        };
284
285        [self.algorithm()?.encoded_len()?, key_len].checked_sum()
286    }
287
288    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
289        if !self.is_encrypted() {
290            self.algorithm()?.encode(writer)?;
291        }
292
293        match self {
294            #[cfg(feature = "alloc")]
295            Self::Dsa(key) => key.encode(writer),
296            #[cfg(feature = "ecdsa")]
297            Self::Ecdsa(key) => key.encode(writer),
298            Self::Ed25519(key) => key.encode(writer),
299            #[cfg(feature = "alloc")]
300            Self::Encrypted(ciphertext) => writer.write(ciphertext),
301            #[cfg(feature = "alloc")]
302            Self::Rsa(key) => key.encode(writer),
303            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
304            Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer),
305            #[cfg(feature = "alloc")]
306            Self::SkEd25519(sk) => sk.encode(writer),
307        }
308    }
309}
310
311impl TryFrom<&KeypairData> for public::KeyData {
312    type Error = Error;
313
314    fn try_from(keypair_data: &KeypairData) -> Result<public::KeyData> {
315        Ok(match keypair_data {
316            #[cfg(feature = "alloc")]
317            KeypairData::Dsa(dsa) => public::KeyData::Dsa(dsa.into()),
318            #[cfg(feature = "ecdsa")]
319            KeypairData::Ecdsa(ecdsa) => public::KeyData::Ecdsa(ecdsa.into()),
320            KeypairData::Ed25519(ed25519) => public::KeyData::Ed25519(ed25519.into()),
321            #[cfg(feature = "alloc")]
322            KeypairData::Encrypted(_) => return Err(Error::Encrypted),
323            #[cfg(feature = "alloc")]
324            KeypairData::Rsa(rsa) => public::KeyData::Rsa(rsa.into()),
325            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
326            KeypairData::SkEcdsaSha2NistP256(sk) => {
327                public::KeyData::SkEcdsaSha2NistP256(sk.public().clone())
328            }
329            #[cfg(feature = "alloc")]
330            KeypairData::SkEd25519(sk) => public::KeyData::SkEd25519(sk.public().clone()),
331        })
332    }
333}
334
335#[cfg(feature = "alloc")]
336#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
337impl From<DsaKeypair> for KeypairData {
338    fn from(keypair: DsaKeypair) -> KeypairData {
339        Self::Dsa(keypair)
340    }
341}
342
343#[cfg(feature = "ecdsa")]
344#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
345impl From<EcdsaKeypair> for KeypairData {
346    fn from(keypair: EcdsaKeypair) -> KeypairData {
347        Self::Ecdsa(keypair)
348    }
349}
350
351impl From<Ed25519Keypair> for KeypairData {
352    fn from(keypair: Ed25519Keypair) -> KeypairData {
353        Self::Ed25519(keypair)
354    }
355}
356
357#[cfg(feature = "alloc")]
358#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
359impl From<RsaKeypair> for KeypairData {
360    fn from(keypair: RsaKeypair) -> KeypairData {
361        Self::Rsa(keypair)
362    }
363}
364
365#[cfg(all(feature = "alloc", feature = "ecdsa"))]
366#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "ecdsa"))))]
367impl From<SkEcdsaSha2NistP256> for KeypairData {
368    fn from(keypair: SkEcdsaSha2NistP256) -> KeypairData {
369        Self::SkEcdsaSha2NistP256(keypair)
370    }
371}
372
373#[cfg(feature = "alloc")]
374#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
375impl From<SkEd25519> for KeypairData {
376    fn from(keypair: SkEd25519) -> KeypairData {
377        Self::SkEd25519(keypair)
378    }
379}
380
381#[cfg(feature = "subtle")]
382#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
383impl ConstantTimeEq for KeypairData {
384    fn ct_eq(&self, other: &Self) -> Choice {
385        // Note: constant-time with respect to key *data* comparisons, not algorithms
386        match (self, other) {
387            #[cfg(feature = "alloc")]
388            (Self::Dsa(a), Self::Dsa(b)) => a.ct_eq(b),
389            #[cfg(feature = "ecdsa")]
390            (Self::Ecdsa(a), Self::Ecdsa(b)) => a.ct_eq(b),
391            (Self::Ed25519(a), Self::Ed25519(b)) => a.ct_eq(b),
392            #[cfg(feature = "alloc")]
393            (Self::Encrypted(a), Self::Encrypted(b)) => a.ct_eq(b),
394            #[cfg(feature = "alloc")]
395            (Self::Rsa(a), Self::Rsa(b)) => a.ct_eq(b),
396            #[cfg(all(feature = "alloc", feature = "ecdsa"))]
397            (Self::SkEcdsaSha2NistP256(a), Self::SkEcdsaSha2NistP256(b)) => {
398                // Security Keys store the actual private key in hardware.
399                // The key structs contain all public data.
400                Choice::from((a == b) as u8)
401            }
402            #[cfg(feature = "alloc")]
403            (Self::SkEd25519(a), Self::SkEd25519(b)) => {
404                // Security Keys store the actual private key in hardware.
405                // The key structs contain all public data.
406                Choice::from((a == b) as u8)
407            }
408            _ => Choice::from(0),
409        }
410    }
411}
412
413#[cfg(feature = "subtle")]
414#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
415impl PartialEq for KeypairData {
416    fn eq(&self, other: &Self) -> bool {
417        self.ct_eq(other).into()
418    }
419}
420
421#[cfg(feature = "subtle")]
422#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
423impl Eq for KeypairData {}