ssh_key/
private.rs

1//! SSH private key support.
2//!
3//! Support for decoding SSH private keys (i.e. digital signature keys)
4//! from the OpenSSH file format:
5//!
6//! <https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD>
7//!
8//! ## Decrypting encrypted private keys
9//!
10//! When the `encryption` feature of this crate is enabled, it's possible to
11//! decrypt keys which have been encrypted under a password:
12//!
13#![cfg_attr(all(feature = "encryption", feature = "std"), doc = " ```")]
14#![cfg_attr(not(all(feature = "encryption", feature = "std")), doc = " ```ignore")]
15//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
16//! use ssh_key::PrivateKey;
17//!
18//! // WARNING: don't actually hardcode private keys in source code!!!
19//! let encoded_key = r#"
20//! -----BEGIN OPENSSH PRIVATE KEY-----
21//! b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBKH96ujW
22//! umB6/WnTNPjTeaAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN
23//! 796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoFzvbvyFMhAiwBOXF0mhUUacPUCMZXivG2up2c
24//! hEnAw1b6BLRPyWbY5cC2n9ggD4ivJ1zSts6sBgjyiXQAReyrP35myYvT/OIB/NpwZM/xIJ
25//! N7MHSUzlkX4adBrga3f7GS4uv4ChOoxC4XsE5HsxtGsq1X8jzqLlZTmOcxkcEneYQexrUc
26//! bQP0o+gL5aKK8cQgiIlXeDbRjqhc4+h4EF6lY=
27//! -----END OPENSSH PRIVATE KEY-----
28//! "#;
29//!
30//! let encrypted_key = PrivateKey::from_openssh(encoded_key)?;
31//! assert!(encrypted_key.is_encrypted());
32//!
33//! // WARNING: don't hardcode passwords, and this one's bad anyway
34//! let password = "hunter42";
35//!
36//! let decrypted_key = encrypted_key.decrypt(password)?;
37//! assert!(!decrypted_key.is_encrypted());
38//! # Ok(())
39//! # }
40//! ```
41//!
42//! ## Encrypting plaintext private keys
43//!
44//! When the `encryption` feature of this crate is enabled, it's possible to
45//! encrypt plaintext private keys under a provided password.
46//!
47//! The example below also requires enabling this crate's `getrandom` feature.
48//!
49#![cfg_attr(
50    all(
51        feature = "ed25519",
52        feature = "encryption",
53        feature = "getrandom",
54        feature = "std"
55    ),
56    doc = " ```"
57)]
58#![cfg_attr(
59    not(all(
60        feature = "ed25519",
61        feature = "encryption",
62        feature = "getrandom",
63        feature = "std"
64    )),
65    doc = " ```ignore"
66)]
67//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
68//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
69//!
70//! // Generate a random key
71//! let unencrypted_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
72//!
73//! // WARNING: don't hardcode passwords, and this one's bad anyway
74//! let password = "hunter42";
75//!
76//! let encrypted_key = unencrypted_key.encrypt(&mut OsRng, password)?;
77//! assert!(encrypted_key.is_encrypted());
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! ## Generating random keys
83//!
84//! This crate supports generation of random keys using algorithm-specific
85//! backends gated on cargo features.
86//!
87//! The examples below require enabling this crate's `getrandom` feature as
88//! well as the crate feature identified in backticks in the title of each
89//! example.
90//!
91#![cfg_attr(
92    all(feature = "ed25519", feature = "getrandom", feature = "std"),
93    doc = " ```"
94)]
95#![cfg_attr(
96    not(all(feature = "ed25519", feature = "getrandom", feature = "std")),
97    doc = " ```ignore"
98)]
99//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
100//! use ssh_key::{Algorithm, PrivateKey, rand_core::OsRng};
101//!
102//! let private_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
103//! # Ok(())
104//! # }
105//! ```
106
107#[cfg(feature = "alloc")]
108mod dsa;
109#[cfg(feature = "ecdsa")]
110mod ecdsa;
111mod ed25519;
112mod keypair;
113#[cfg(feature = "alloc")]
114mod opaque;
115#[cfg(feature = "alloc")]
116mod rsa;
117#[cfg(feature = "alloc")]
118mod sk;
119
120pub use self::{
121    ed25519::{Ed25519Keypair, Ed25519PrivateKey},
122    keypair::KeypairData,
123};
124
125#[cfg(feature = "alloc")]
126pub use crate::{
127    private::{
128        dsa::{DsaKeypair, DsaPrivateKey},
129        opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
130        rsa::{RsaKeypair, RsaPrivateKey},
131        sk::SkEd25519,
132    },
133    SshSig,
134};
135
136#[cfg(feature = "ecdsa")]
137pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
138
139#[cfg(all(feature = "alloc", feature = "ecdsa"))]
140pub use self::sk::SkEcdsaSha2NistP256;
141
142use crate::{public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result};
143use cipher::Tag;
144use core::str;
145use encoding::{
146    pem::{LineEnding, PemLabel},
147    CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
148};
149use subtle::{Choice, ConstantTimeEq};
150
151#[cfg(feature = "alloc")]
152use {
153    alloc::{string::String, vec::Vec},
154    zeroize::Zeroizing,
155};
156
157#[cfg(feature = "rand_core")]
158use rand_core::CryptoRngCore;
159
160#[cfg(feature = "std")]
161use std::{fs, path::Path};
162
163#[cfg(all(unix, feature = "std"))]
164use std::{io::Write, os::unix::fs::OpenOptionsExt};
165
166/// Error message for infallible conversions (used by `expect`)
167const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
168
169/// Default key size to use for RSA keys in bits.
170#[cfg(all(feature = "rand_core", feature = "rsa"))]
171const DEFAULT_RSA_KEY_SIZE: usize = 4096;
172
173/// Maximum supported block size.
174///
175/// This is the block size used by e.g. AES.
176const MAX_BLOCK_SIZE: usize = 16;
177
178/// Padding bytes to use.
179const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
180
181/// Unix file permissions for SSH private keys.
182#[cfg(all(unix, feature = "std"))]
183const UNIX_FILE_PERMISSIONS: u32 = 0o600;
184
185/// SSH private key.
186#[derive(Clone, Debug)]
187pub struct PrivateKey {
188    /// Cipher algorithm.
189    cipher: Cipher,
190
191    /// KDF options.
192    kdf: Kdf,
193
194    /// "Checkint" value used to verify successful decryption.
195    checkint: Option<u32>,
196
197    /// Public key.
198    public_key: PublicKey,
199
200    /// Private keypair data.
201    key_data: KeypairData,
202
203    /// Authentication tag for authenticated encryption modes.
204    auth_tag: Option<Tag>,
205}
206
207impl PrivateKey {
208    /// Magic string used to identify keys in this format.
209    const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
210
211    /// Create a new unencrypted private key with the given keypair data and comment.
212    ///
213    /// On `no_std` platforms, use `PrivateKey::from(key_data)` instead.
214    #[cfg(feature = "alloc")]
215    pub fn new(key_data: KeypairData, comment: impl Into<String>) -> Result<Self> {
216        if key_data.is_encrypted() {
217            return Err(Error::Encrypted);
218        }
219
220        let mut private_key = Self::try_from(key_data)?;
221        private_key.public_key.comment = comment.into();
222        Ok(private_key)
223    }
224
225    /// Parse an OpenSSH-formatted PEM private key.
226    ///
227    /// OpenSSH-formatted private keys begin with the following:
228    ///
229    /// ```text
230    /// -----BEGIN OPENSSH PRIVATE KEY-----
231    /// ```
232    pub fn from_openssh(pem: impl AsRef<[u8]>) -> Result<Self> {
233        Self::decode_pem(pem)
234    }
235
236    /// Parse a raw binary SSH private key.
237    pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
238        let reader = &mut bytes;
239        let private_key = Self::decode(reader)?;
240        Ok(reader.finish(private_key)?)
241    }
242
243    /// Encode OpenSSH-formatted (PEM) private key.
244    pub fn encode_openssh<'o>(
245        &self,
246        line_ending: LineEnding,
247        out: &'o mut [u8],
248    ) -> Result<&'o str> {
249        Ok(self.encode_pem(line_ending, out)?)
250    }
251
252    /// Encode an OpenSSH-formatted PEM private key, allocating a
253    /// self-zeroizing [`String`] for the result.
254    #[cfg(feature = "alloc")]
255    pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
256        Ok(self.encode_pem_string(line_ending).map(Zeroizing::new)?)
257    }
258
259    /// Serialize SSH private key as raw bytes.
260    #[cfg(feature = "alloc")]
261    pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
262        let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
263        self.encode(&mut private_key_bytes)?;
264        Ok(Zeroizing::new(private_key_bytes))
265    }
266
267    /// Sign the given message using this private key, returning an [`SshSig`].
268    ///
269    /// These signatures can be produced using `ssh-keygen -Y sign`. They're
270    /// encoded as PEM and begin with the following:
271    ///
272    /// ```text
273    /// -----BEGIN SSH SIGNATURE-----
274    /// ```
275    ///
276    /// See [PROTOCOL.sshsig] for more information.
277    ///
278    /// # Usage
279    ///
280    /// See also: [`PublicKey::verify`].
281    ///
282    #[cfg_attr(feature = "ed25519", doc = "```")]
283    #[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
284    /// # fn main() -> Result<(), ssh_key::Error> {
285    /// use ssh_key::{PrivateKey, HashAlg, SshSig};
286    ///
287    /// // Message to be signed.
288    /// let message = b"testing";
289    ///
290    /// // Example domain/namespace used for the message.
291    /// let namespace = "example";
292    ///
293    /// // Private key to use when computing the signature.
294    /// // WARNING: don't actually hardcode private keys in source code!!!
295    /// let encoded_private_key = r#"
296    /// -----BEGIN OPENSSH PRIVATE KEY-----
297    /// b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
298    /// QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM
299    /// XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg
300    /// AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf
301    /// ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ==
302    /// -----END OPENSSH PRIVATE KEY-----
303    /// "#;
304    ///
305    /// let private_key = encoded_private_key.parse::<PrivateKey>()?;
306    /// let signature = private_key.sign(namespace, HashAlg::default(), message)?;
307    /// // assert!(private_key.public_key().verify(namespace, message, &signature).is_ok());
308    /// # Ok(())
309    /// # }
310    /// ```
311    ///
312    /// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
313    #[cfg(feature = "alloc")]
314    pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
315        SshSig::sign(self, namespace, hash_alg, msg)
316    }
317
318    /// Read private key from an OpenSSH-formatted PEM file.
319    #[cfg(feature = "std")]
320    pub fn read_openssh_file(path: &Path) -> Result<Self> {
321        // TODO(tarcieri): verify file permissions match `UNIX_FILE_PERMISSIONS`
322        let pem = Zeroizing::new(fs::read_to_string(path)?);
323        Self::from_openssh(&*pem)
324    }
325
326    /// Write private key as an OpenSSH-formatted PEM file.
327    #[cfg(feature = "std")]
328    pub fn write_openssh_file(&self, path: &Path, line_ending: LineEnding) -> Result<()> {
329        let pem = self.to_openssh(line_ending)?;
330
331        #[cfg(not(unix))]
332        fs::write(path, pem.as_bytes())?;
333        #[cfg(unix)]
334        fs::OpenOptions::new()
335            .create(true)
336            .write(true)
337            .truncate(true)
338            .mode(UNIX_FILE_PERMISSIONS)
339            .open(path)
340            .and_then(|mut file| file.write_all(pem.as_bytes()))?;
341
342        Ok(())
343    }
344
345    /// Attempt to decrypt an encrypted private key using the provided
346    /// password to derive an encryption key.
347    ///
348    /// Returns [`Error::Decrypted`] if the private key is already decrypted.
349    #[cfg(feature = "encryption")]
350    pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
351        let (key, iv) = self.kdf.derive_key_and_iv(self.cipher, password)?;
352
353        let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
354        let mut buffer = Zeroizing::new(ciphertext.to_vec());
355        self.cipher.decrypt(&key, &iv, &mut buffer, self.auth_tag)?;
356
357        Self::decode_privatekey_comment_pair(
358            &mut &**buffer,
359            self.public_key.key_data.clone(),
360            self.cipher.block_size(),
361        )
362    }
363
364    /// Encrypt an unencrypted private key using the provided password to
365    /// derive an encryption key.
366    ///
367    /// Uses the following algorithms:
368    /// - Cipher: [`Cipher::Aes256Ctr`]
369    /// - KDF: [`Kdf::Bcrypt`] (i.e. `bcrypt-pbkdf`)
370    ///
371    /// Returns [`Error::Encrypted`] if the private key is already encrypted.
372    #[cfg(feature = "encryption")]
373    pub fn encrypt(
374        &self,
375        rng: &mut impl CryptoRngCore,
376        password: impl AsRef<[u8]>,
377    ) -> Result<Self> {
378        self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password)
379    }
380
381    /// Encrypt an unencrypted private key using the provided password to
382    /// derive an encryption key for the provided [`Cipher`].
383    ///
384    /// Returns [`Error::Encrypted`] if the private key is already encrypted.
385    #[cfg(feature = "encryption")]
386    pub fn encrypt_with_cipher(
387        &self,
388        rng: &mut impl CryptoRngCore,
389        cipher: Cipher,
390        password: impl AsRef<[u8]>,
391    ) -> Result<Self> {
392        let checkint = rng.next_u32();
393
394        self.encrypt_with(
395            cipher,
396            Kdf::new(Default::default(), rng)?,
397            checkint,
398            password,
399        )
400    }
401
402    /// Encrypt an unencrypted private key using the provided cipher and KDF
403    /// configuration.
404    ///
405    /// Returns [`Error::Encrypted`] if the private key is already encrypted.
406    #[cfg(feature = "encryption")]
407    pub fn encrypt_with(
408        &self,
409        cipher: Cipher,
410        kdf: Kdf,
411        checkint: u32,
412        password: impl AsRef<[u8]>,
413    ) -> Result<Self> {
414        if self.is_encrypted() {
415            return Err(Error::Encrypted);
416        }
417
418        let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
419        let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
420        let mut out = Vec::with_capacity(msg_len);
421
422        // Encode and encrypt private key
423        self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
424        let auth_tag = cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
425
426        Ok(Self {
427            cipher,
428            kdf,
429            checkint: None,
430            public_key: self.public_key.key_data.clone().into(),
431            key_data: KeypairData::Encrypted(out),
432            auth_tag,
433        })
434    }
435
436    /// Get the digital signature [`Algorithm`] used by this key.
437    pub fn algorithm(&self) -> Algorithm {
438        self.public_key.algorithm()
439    }
440
441    /// Comment on the key (e.g. email address).
442    pub fn comment(&self) -> &str {
443        self.public_key.comment()
444    }
445
446    /// Cipher algorithm (a.k.a. `ciphername`).
447    pub fn cipher(&self) -> Cipher {
448        self.cipher
449    }
450
451    /// Compute key fingerprint.
452    ///
453    /// Use [`Default::default()`] to use the default hash function (SHA-256).
454    pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
455        self.public_key.fingerprint(hash_alg)
456    }
457
458    /// Is this key encrypted?
459    pub fn is_encrypted(&self) -> bool {
460        let ret = self.key_data.is_encrypted();
461        debug_assert_eq!(ret, self.cipher.is_some());
462        ret
463    }
464
465    /// Key Derivation Function (KDF) used to encrypt this key.
466    ///
467    /// Returns [`Kdf::None`] if this key is not encrypted.
468    pub fn kdf(&self) -> &Kdf {
469        &self.kdf
470    }
471
472    /// Keypair data.
473    pub fn key_data(&self) -> &KeypairData {
474        &self.key_data
475    }
476
477    /// Get the [`PublicKey`] which corresponds to this private key.
478    pub fn public_key(&self) -> &PublicKey {
479        &self.public_key
480    }
481
482    /// Generate a random key which uses the given algorithm.
483    ///
484    /// # Returns
485    /// - `Error::AlgorithmUnknown` if the algorithm is unsupported.
486    #[cfg(feature = "rand_core")]
487    #[allow(unreachable_code, unused_variables)]
488    pub fn random(rng: &mut impl CryptoRngCore, algorithm: Algorithm) -> Result<Self> {
489        let checkint = rng.next_u32();
490        let key_data = match algorithm {
491            #[cfg(feature = "dsa")]
492            Algorithm::Dsa => KeypairData::from(DsaKeypair::random(rng)?),
493            #[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
494            Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
495            #[cfg(feature = "ed25519")]
496            Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
497            #[cfg(feature = "rsa")]
498            Algorithm::Rsa { .. } => {
499                KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
500            }
501            _ => return Err(Error::AlgorithmUnknown),
502        };
503        let public_key = public::KeyData::try_from(&key_data)?;
504
505        Ok(Self {
506            cipher: Cipher::None,
507            kdf: Kdf::None,
508            checkint: Some(checkint),
509            public_key: public_key.into(),
510            key_data,
511            auth_tag: None,
512        })
513    }
514
515    /// Set the comment on the key.
516    #[cfg(feature = "alloc")]
517    pub fn set_comment(&mut self, comment: impl Into<String>) {
518        self.public_key.set_comment(comment);
519    }
520
521    /// Decode [`KeypairData`] along with its associated checkints and comment,
522    /// storing the comment in the provided public key on success.
523    ///
524    /// This method also checks padding for validity and ensures that the
525    /// decoded private key matches the provided public key.
526    ///
527    /// For private key format specification, see OpenSSH [PROTOCOL.key] ยง 3:
528    ///
529    /// ```text
530    /// uint32  checkint
531    /// uint32  checkint
532    /// byte[]  privatekey1
533    /// string  comment1
534    /// byte[]  privatekey2
535    /// string  comment2
536    /// ...
537    /// string  privatekeyN
538    /// string  commentN
539    /// char    1
540    /// char    2
541    /// char    3
542    /// ...
543    /// char    padlen % 255
544    /// ```
545    ///
546    /// [PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
547    fn decode_privatekey_comment_pair(
548        reader: &mut impl Reader,
549        public_key: public::KeyData,
550        block_size: usize,
551    ) -> Result<Self> {
552        debug_assert!(block_size <= MAX_BLOCK_SIZE);
553
554        // Ensure input data is padding-aligned
555        if reader.remaining_len().checked_rem(block_size) != Some(0) {
556            return Err(encoding::Error::Length.into());
557        }
558
559        let checkint1 = u32::decode(reader)?;
560        let checkint2 = u32::decode(reader)?;
561
562        if checkint1 != checkint2 {
563            return Err(Error::Crypto);
564        }
565
566        let key_data = KeypairData::decode(reader)?;
567
568        // Ensure public key matches private key
569        if public_key != public::KeyData::try_from(&key_data)? {
570            return Err(Error::PublicKey);
571        }
572
573        let mut public_key = PublicKey::from(public_key);
574        public_key.decode_comment(reader)?;
575
576        let padding_len = reader.remaining_len();
577
578        if padding_len >= block_size {
579            return Err(encoding::Error::Length.into());
580        }
581
582        if padding_len != 0 {
583            let mut padding = [0u8; MAX_BLOCK_SIZE];
584            reader.read(&mut padding[..padding_len])?;
585
586            if PADDING_BYTES[..padding_len] != padding[..padding_len] {
587                return Err(Error::FormatEncoding);
588            }
589        }
590
591        if !reader.is_finished() {
592            return Err(Error::TrailingData {
593                remaining: reader.remaining_len(),
594            });
595        }
596
597        Ok(Self {
598            cipher: Cipher::None,
599            kdf: Kdf::None,
600            checkint: Some(checkint1),
601            public_key,
602            key_data,
603            auth_tag: None,
604        })
605    }
606
607    /// Encode [`KeypairData`] along with its associated checkints, comment,
608    /// and padding.
609    fn encode_privatekey_comment_pair(
610        &self,
611        writer: &mut impl Writer,
612        cipher: Cipher,
613        checkint: u32,
614    ) -> encoding::Result<()> {
615        let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
616        let padding_len = cipher.padding_len(unpadded_len);
617
618        checkint.encode(writer)?;
619        checkint.encode(writer)?;
620        self.key_data.encode(writer)?;
621        self.comment().encode(writer)?;
622        writer.write(&PADDING_BYTES[..padding_len])?;
623        Ok(())
624    }
625
626    /// Get the length of this private key when encoded with the given comment
627    /// and padded using the padding size for the given cipher.
628    fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> encoding::Result<usize> {
629        let len = self.unpadded_privatekey_comment_pair_len()?;
630        [len, cipher.padding_len(len)].checked_sum()
631    }
632
633    /// Get the length of this private key when encoded with the given comment.
634    ///
635    /// This length is just the checkints, private key data, and comment sans
636    /// any padding.
637    fn unpadded_privatekey_comment_pair_len(&self) -> encoding::Result<usize> {
638        // This method is intended for use with unencrypted keys only
639        debug_assert!(!self.is_encrypted(), "called on encrypted key");
640
641        [
642            8, // 2 x uint32 checkints,
643            self.key_data.encoded_len()?,
644            self.comment().encoded_len()?,
645        ]
646        .checked_sum()
647    }
648}
649
650impl ConstantTimeEq for PrivateKey {
651    fn ct_eq(&self, other: &Self) -> Choice {
652        // Constant-time with respect to private key data
653        self.key_data.ct_eq(&other.key_data)
654            & Choice::from(
655                (self.cipher == other.cipher
656                    && self.kdf == other.kdf
657                    && self.public_key == other.public_key) as u8,
658            )
659    }
660}
661
662impl Eq for PrivateKey {}
663
664impl PartialEq for PrivateKey {
665    fn eq(&self, other: &Self) -> bool {
666        self.ct_eq(other).into()
667    }
668}
669
670impl Decode for PrivateKey {
671    type Error = Error;
672
673    fn decode(reader: &mut impl Reader) -> Result<Self> {
674        let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
675        reader.read(&mut auth_magic)?;
676
677        if auth_magic != Self::AUTH_MAGIC {
678            return Err(Error::FormatEncoding);
679        }
680
681        let cipher = Cipher::decode(reader)?;
682        let kdf = Kdf::decode(reader)?;
683        let nkeys = usize::decode(reader)?;
684
685        // TODO(tarcieri): support more than one key?
686        if nkeys != 1 {
687            return Err(encoding::Error::Length.into());
688        }
689
690        let public_key = reader.read_prefixed(public::KeyData::decode)?;
691
692        // Handle encrypted private key
693        #[cfg(not(feature = "alloc"))]
694        if cipher.is_some() {
695            return Err(Error::Encrypted);
696        }
697        #[cfg(feature = "alloc")]
698        if cipher.is_some() {
699            let ciphertext = Vec::decode(reader)?;
700
701            // Ensure ciphertext is padded to the expected length
702            if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
703                return Err(Error::Crypto);
704            }
705
706            let auth_tag = if cipher.has_tag() {
707                let mut tag = Tag::default();
708                reader.read(&mut tag)?;
709                Some(tag)
710            } else {
711                None
712            };
713
714            if !reader.is_finished() {
715                return Err(Error::TrailingData {
716                    remaining: reader.remaining_len(),
717                });
718            }
719
720            return Ok(Self {
721                cipher,
722                kdf,
723                checkint: None,
724                public_key: public_key.into(),
725                key_data: KeypairData::Encrypted(ciphertext),
726                auth_tag,
727            });
728        }
729
730        // Processing unencrypted key. No KDF should be set.
731        if kdf.is_some() {
732            return Err(Error::Crypto);
733        }
734
735        reader.read_prefixed(|reader| {
736            Self::decode_privatekey_comment_pair(reader, public_key, cipher.block_size())
737        })
738    }
739}
740
741impl Encode for PrivateKey {
742    fn encoded_len(&self) -> encoding::Result<usize> {
743        let private_key_len = if self.is_encrypted() {
744            self.key_data.encoded_len_prefixed()?
745        } else {
746            [4, self.encoded_privatekey_comment_pair_len(Cipher::None)?].checked_sum()?
747        };
748
749        [
750            Self::AUTH_MAGIC.len(),
751            self.cipher.encoded_len()?,
752            self.kdf.encoded_len()?,
753            4, // number of keys (uint32)
754            self.public_key.key_data().encoded_len_prefixed()?,
755            private_key_len,
756            self.auth_tag.map(|tag| tag.len()).unwrap_or(0),
757        ]
758        .checked_sum()
759    }
760
761    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
762        writer.write(Self::AUTH_MAGIC)?;
763        self.cipher.encode(writer)?;
764        self.kdf.encode(writer)?;
765
766        // TODO(tarcieri): support for encoding more than one private key
767        1usize.encode(writer)?;
768
769        // Encode public key
770        self.public_key.key_data().encode_prefixed(writer)?;
771
772        // Encode private key
773        if self.is_encrypted() {
774            self.key_data.encode_prefixed(writer)?;
775
776            if let Some(tag) = &self.auth_tag {
777                writer.write(tag)?;
778            }
779        } else {
780            self.encoded_privatekey_comment_pair_len(Cipher::None)?
781                .encode(writer)?;
782
783            let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
784            self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
785        }
786
787        Ok(())
788    }
789}
790
791impl From<PrivateKey> for PublicKey {
792    fn from(private_key: PrivateKey) -> PublicKey {
793        private_key.public_key
794    }
795}
796
797impl From<&PrivateKey> for PublicKey {
798    fn from(private_key: &PrivateKey) -> PublicKey {
799        private_key.public_key.clone()
800    }
801}
802
803impl From<PrivateKey> for public::KeyData {
804    fn from(private_key: PrivateKey) -> public::KeyData {
805        private_key.public_key.key_data
806    }
807}
808
809impl From<&PrivateKey> for public::KeyData {
810    fn from(private_key: &PrivateKey) -> public::KeyData {
811        private_key.public_key.key_data.clone()
812    }
813}
814
815#[cfg(feature = "alloc")]
816impl From<DsaKeypair> for PrivateKey {
817    fn from(keypair: DsaKeypair) -> PrivateKey {
818        KeypairData::from(keypair)
819            .try_into()
820            .expect(CONVERSION_ERROR_MSG)
821    }
822}
823
824#[cfg(feature = "ecdsa")]
825impl From<EcdsaKeypair> for PrivateKey {
826    fn from(keypair: EcdsaKeypair) -> PrivateKey {
827        KeypairData::from(keypair)
828            .try_into()
829            .expect(CONVERSION_ERROR_MSG)
830    }
831}
832
833impl From<Ed25519Keypair> for PrivateKey {
834    fn from(keypair: Ed25519Keypair) -> PrivateKey {
835        KeypairData::from(keypair)
836            .try_into()
837            .expect(CONVERSION_ERROR_MSG)
838    }
839}
840
841#[cfg(feature = "alloc")]
842impl From<RsaKeypair> for PrivateKey {
843    fn from(keypair: RsaKeypair) -> PrivateKey {
844        KeypairData::from(keypair)
845            .try_into()
846            .expect(CONVERSION_ERROR_MSG)
847    }
848}
849
850#[cfg(all(feature = "alloc", feature = "ecdsa"))]
851impl From<SkEcdsaSha2NistP256> for PrivateKey {
852    fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
853        KeypairData::from(keypair)
854            .try_into()
855            .expect(CONVERSION_ERROR_MSG)
856    }
857}
858
859#[cfg(feature = "alloc")]
860impl From<SkEd25519> for PrivateKey {
861    fn from(keypair: SkEd25519) -> PrivateKey {
862        KeypairData::from(keypair)
863            .try_into()
864            .expect(CONVERSION_ERROR_MSG)
865    }
866}
867
868impl TryFrom<KeypairData> for PrivateKey {
869    type Error = Error;
870
871    fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
872        let public_key = public::KeyData::try_from(&key_data)?;
873
874        Ok(Self {
875            cipher: Cipher::None,
876            kdf: Kdf::None,
877            checkint: None,
878            public_key: public_key.into(),
879            key_data,
880            auth_tag: None,
881        })
882    }
883}
884
885impl PemLabel for PrivateKey {
886    const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
887}
888
889impl str::FromStr for PrivateKey {
890    type Err = Error;
891
892    fn from_str(s: &str) -> Result<Self> {
893        Self::from_openssh(s)
894    }
895}