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 rsa;
115#[cfg(feature = "alloc")]
116mod sk;
117
118pub use self::ed25519::{Ed25519Keypair, Ed25519PrivateKey};
119pub use self::keypair::KeypairData;
120
121#[cfg(feature = "alloc")]
122pub use self::{
123    dsa::{DsaKeypair, DsaPrivateKey},
124    rsa::{RsaKeypair, RsaPrivateKey},
125    sk::SkEd25519,
126};
127
128#[cfg(feature = "ecdsa")]
129pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
130
131#[cfg(all(feature = "alloc", feature = "ecdsa"))]
132pub use self::sk::SkEcdsaSha2NistP256;
133
134use crate::{
135    checked::CheckedSum,
136    decode::Decode,
137    encode::Encode,
138    pem::{self, LineEnding, PemLabel},
139    public,
140    reader::Reader,
141    writer::Writer,
142    Algorithm, Cipher, Error, Kdf, PublicKey, Result,
143};
144use core::str;
145
146#[cfg(feature = "alloc")]
147use {
148    alloc::{string::String, vec::Vec},
149    zeroize::Zeroizing,
150};
151
152#[cfg(feature = "fingerprint")]
153use crate::{Fingerprint, HashAlg};
154
155#[cfg(feature = "rand_core")]
156use rand_core::{CryptoRng, RngCore};
157
158#[cfg(feature = "std")]
159use std::{fs, path::Path};
160
161#[cfg(all(unix, feature = "std"))]
162use std::{io::Write, os::unix::fs::OpenOptionsExt};
163
164#[cfg(feature = "subtle")]
165use subtle::{Choice, ConstantTimeEq};
166
167/// Error message for infallible conversions (used by `expect`)
168const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
169
170/// Default key size to use for RSA keys in bits.
171#[cfg(feature = "rsa")]
172const DEFAULT_RSA_KEY_SIZE: usize = 4096;
173
174/// Maximum supported block size.
175///
176/// This is the block size used by e.g. AES.
177const MAX_BLOCK_SIZE: usize = 16;
178
179/// Padding bytes to use.
180const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
181
182/// Line width used by the PEM encoding of OpenSSH private keys.
183const PEM_LINE_WIDTH: usize = 70;
184
185/// Unix file permissions for SSH private keys.
186#[cfg(all(unix, feature = "std"))]
187const UNIX_FILE_PERMISSIONS: u32 = 0o600;
188
189/// SSH private key.
190#[derive(Clone, Debug)]
191pub struct PrivateKey {
192    /// Cipher algorithm.
193    cipher: Cipher,
194
195    /// KDF options.
196    kdf: Kdf,
197
198    /// "Checkint" value used to verify successful decryption.
199    checkint: Option<u32>,
200
201    /// Public key.
202    public_key: PublicKey,
203
204    /// Private keypair data.
205    key_data: KeypairData,
206}
207
208impl PrivateKey {
209    /// Magic string used to identify keys in this format.
210    const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
211
212    /// Create a new unencrypted private key with the given keypair data and comment.
213    ///
214    /// On `no_std` platforms, use `PrivateKey::from(key_data)` instead.
215    #[cfg(feature = "alloc")]
216    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
217    pub fn new(key_data: KeypairData, comment: impl Into<String>) -> Result<Self> {
218        if key_data.is_encrypted() {
219            return Err(Error::Encrypted);
220        }
221
222        let mut private_key = Self::try_from(key_data)?;
223        private_key.public_key.comment = comment.into();
224        Ok(private_key)
225    }
226
227    /// Parse an OpenSSH-formatted PEM private key.
228    ///
229    /// OpenSSH-formatted private keys begin with the following:
230    ///
231    /// ```text
232    /// -----BEGIN OPENSSH PRIVATE KEY-----
233    /// ```
234    pub fn from_openssh(input: impl AsRef<[u8]>) -> Result<Self> {
235        let mut reader = pem::Decoder::new_wrapped(input.as_ref(), PEM_LINE_WIDTH)?;
236        Self::validate_pem_label(reader.type_label())?;
237        let private_key = Self::decode(&mut reader)?;
238        reader.finish(private_key)
239    }
240
241    /// Parse a raw binary SSH private key.
242    pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
243        let reader = &mut bytes;
244        let private_key = Self::decode(reader)?;
245        reader.finish(private_key)
246    }
247
248    /// Encode OpenSSH-formatted (PEM) private key.
249    pub fn encode_openssh<'o>(
250        &self,
251        line_ending: LineEnding,
252        out: &'o mut [u8],
253    ) -> Result<&'o str> {
254        let mut writer =
255            pem::Encoder::new_wrapped(Self::PEM_LABEL, PEM_LINE_WIDTH, line_ending, out)?;
256
257        self.encode(&mut writer)?;
258        let encoded_len = writer.finish()?;
259        Ok(str::from_utf8(&out[..encoded_len])?)
260    }
261
262    /// Encode an OpenSSH-formatted PEM private key, allocating a
263    /// self-zeroizing [`String`] for the result.
264    #[cfg(feature = "alloc")]
265    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
266    pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
267        let encoded_len = pem::encapsulated_len_wrapped(
268            Self::PEM_LABEL,
269            PEM_LINE_WIDTH,
270            line_ending,
271            self.encoded_len()?,
272        )?;
273
274        let mut buf = vec![0u8; encoded_len];
275        let actual_len = self.encode_openssh(line_ending, &mut buf)?.len();
276        buf.truncate(actual_len);
277        Ok(Zeroizing::new(String::from_utf8(buf)?))
278    }
279
280    /// Serialize SSH private key as raw bytes.
281    #[cfg(feature = "alloc")]
282    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
283    pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
284        let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
285        self.encode(&mut private_key_bytes)?;
286        Ok(Zeroizing::new(private_key_bytes))
287    }
288
289    /// Read private key from an OpenSSH-formatted PEM file.
290    #[cfg(feature = "std")]
291    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
292    pub fn read_openssh_file(path: &Path) -> Result<Self> {
293        // TODO(tarcieri): verify file permissions match `UNIX_FILE_PERMISSIONS`
294        let pem = Zeroizing::new(fs::read_to_string(path)?);
295        Self::from_openssh(&*pem)
296    }
297
298    /// Write private key as an OpenSSH-formatted PEM file.
299    #[cfg(feature = "std")]
300    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
301    pub fn write_openssh_file(&self, path: &Path, line_ending: LineEnding) -> Result<()> {
302        let pem = self.to_openssh(line_ending)?;
303
304        #[cfg(not(unix))]
305        fs::write(path, pem.as_bytes())?;
306        #[cfg(unix)]
307        fs::OpenOptions::new()
308            .create(true)
309            .write(true)
310            .truncate(true)
311            .mode(UNIX_FILE_PERMISSIONS)
312            .open(path)
313            .and_then(|mut file| file.write_all(pem.as_bytes()))?;
314
315        Ok(())
316    }
317
318    /// Attempt to decrypt an encrypted private key using the provided
319    /// password to derive an encryption key.
320    ///
321    /// Returns [`Error::Decrypted`] if the private key is already decrypted.
322    #[cfg(feature = "encryption")]
323    #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
324    pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
325        let (key_bytes, iv_bytes) = self.kdf.derive_key_and_iv(self.cipher, password)?;
326
327        let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
328        let mut buffer = Zeroizing::new(ciphertext.to_vec());
329        self.cipher.decrypt(&key_bytes, &iv_bytes, &mut buffer)?;
330
331        Self::decode_privatekey_comment_pair(
332            &mut &**buffer,
333            self.public_key.key_data.clone(),
334            self.cipher.block_size(),
335        )
336    }
337
338    /// Encrypt an unencrypted private key using the provided password to
339    /// derive an encryption key.
340    ///
341    /// Uses the following algorithms:
342    /// - Cipher: [`Cipher::Aes256Ctr`]
343    /// - KDF: [`Kdf::Bcrypt`] (i.e. `bcrypt-pbkdf`)
344    ///
345    /// Returns [`Error::Encrypted`] if the private key is already encrypted.
346    #[cfg(feature = "encryption")]
347    #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
348    pub fn encrypt(
349        &self,
350        mut rng: impl CryptoRng + RngCore,
351        password: impl AsRef<[u8]>,
352    ) -> Result<Self> {
353        let checkint = rng.next_u32();
354
355        self.encrypt_with(
356            Cipher::default(),
357            Kdf::new(Default::default(), rng)?,
358            checkint,
359            password,
360        )
361    }
362
363    /// Encrypt an unencrypted private key using the provided cipher and KDF
364    /// configuration.
365    ///
366    /// Returns [`Error::Encrypted`] if the private key is already encrypted.
367    #[cfg(feature = "encryption")]
368    #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
369    pub fn encrypt_with(
370        &self,
371        cipher: Cipher,
372        kdf: Kdf,
373        checkint: u32,
374        password: impl AsRef<[u8]>,
375    ) -> Result<Self> {
376        if self.is_encrypted() {
377            return Err(Error::Encrypted);
378        }
379
380        let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
381        let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
382        let mut out = Vec::with_capacity(msg_len);
383
384        // Encode and encrypt private key
385        self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
386        cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
387
388        Ok(Self {
389            cipher,
390            kdf,
391            checkint: None,
392            public_key: self.public_key.key_data.clone().into(),
393            key_data: KeypairData::Encrypted(out),
394        })
395    }
396
397    /// Get the digital signature [`Algorithm`] used by this key.
398    pub fn algorithm(&self) -> Algorithm {
399        self.public_key.algorithm()
400    }
401
402    /// Comment on the key (e.g. email address).
403    pub fn comment(&self) -> &str {
404        self.public_key.comment()
405    }
406
407    /// Cipher algorithm (a.k.a. `ciphername`).
408    pub fn cipher(&self) -> Cipher {
409        self.cipher
410    }
411
412    /// Compute key fingerprint.
413    ///
414    /// Use [`Default::default()`] to use the default hash function (SHA-256).
415    #[cfg(feature = "fingerprint")]
416    #[cfg_attr(docsrs, doc(cfg(feature = "fingerprint")))]
417    pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
418        self.public_key.fingerprint(hash_alg)
419    }
420
421    /// Is this key encrypted?
422    pub fn is_encrypted(&self) -> bool {
423        let ret = self.key_data.is_encrypted();
424        debug_assert_eq!(ret, self.cipher.is_some());
425        ret
426    }
427
428    /// Key Derivation Function (KDF) used to encrypt this key.
429    ///
430    /// Returns [`Kdf::None`] if this key is not encrypted.
431    pub fn kdf(&self) -> &Kdf {
432        &self.kdf
433    }
434
435    /// Keypair data.
436    pub fn key_data(&self) -> &KeypairData {
437        &self.key_data
438    }
439
440    /// Get the [`PublicKey`] which corresponds to this private key.
441    pub fn public_key(&self) -> &PublicKey {
442        &self.public_key
443    }
444
445    /// Generate a random key which uses the given algorithm.
446    ///
447    /// # Returns
448    /// - `Error::Algorithm` if the algorithm is unsupported.
449    #[cfg(feature = "rand_core")]
450    #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
451    #[allow(unreachable_code, unused_variables)]
452    pub fn random(mut rng: impl CryptoRng + RngCore, algorithm: Algorithm) -> Result<Self> {
453        let checkint = rng.next_u32();
454        let key_data = match algorithm {
455            #[cfg(feature = "p256")]
456            Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
457            #[cfg(feature = "ed25519")]
458            Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
459            #[cfg(feature = "rsa")]
460            Algorithm::Rsa { .. } => {
461                KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
462            }
463            _ => return Err(Error::Algorithm),
464        };
465        let public_key = public::KeyData::try_from(&key_data)?;
466
467        Ok(Self {
468            cipher: Cipher::None,
469            kdf: Kdf::None,
470            checkint: Some(checkint),
471            public_key: public_key.into(),
472            key_data,
473        })
474    }
475
476    /// Set the comment on the key.
477    #[cfg(feature = "alloc")]
478    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
479    pub fn set_comment(&mut self, comment: impl Into<String>) {
480        self.public_key.set_comment(comment);
481    }
482
483    /// Decode [`KeypairData`] along with its associated checkints and comment,
484    /// storing the comment in the provided public key on success.
485    ///
486    /// This method also checks padding for validity and ensures that the
487    /// decoded private key matches the provided public key.
488    ///
489    /// For private key format specification, see OpenSSH [PROTOCOL.key] ยง 3:
490    ///
491    /// ```text
492    /// uint32  checkint
493    /// uint32  checkint
494    /// byte[]  privatekey1
495    /// string  comment1
496    /// byte[]  privatekey2
497    /// string  comment2
498    /// ...
499    /// string  privatekeyN
500    /// string  commentN
501    /// char    1
502    /// char    2
503    /// char    3
504    /// ...
505    /// char    padlen % 255
506    /// ```
507    ///
508    /// [PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
509    fn decode_privatekey_comment_pair(
510        reader: &mut impl Reader,
511        public_key: public::KeyData,
512        block_size: usize,
513    ) -> Result<Self> {
514        debug_assert!(block_size <= MAX_BLOCK_SIZE);
515
516        // Ensure input data is padding-aligned
517        if reader.remaining_len().checked_rem(block_size) != Some(0) {
518            return Err(Error::Length);
519        }
520
521        let checkint1 = u32::decode(reader)?;
522        let checkint2 = u32::decode(reader)?;
523
524        if checkint1 != checkint2 {
525            return Err(Error::Crypto);
526        }
527
528        let key_data = KeypairData::decode(reader)?;
529
530        // Ensure public key matches private key
531        if public_key != public::KeyData::try_from(&key_data)? {
532            return Err(Error::PublicKey);
533        }
534
535        let mut public_key = PublicKey::from(public_key);
536        public_key.decode_comment(reader)?;
537
538        let padding_len = reader.remaining_len();
539
540        if padding_len >= block_size {
541            return Err(Error::Length);
542        }
543
544        if padding_len != 0 {
545            let mut padding = [0u8; MAX_BLOCK_SIZE];
546            reader.read(&mut padding[..padding_len])?;
547
548            if PADDING_BYTES[..padding_len] != padding[..padding_len] {
549                return Err(Error::FormatEncoding);
550            }
551        }
552
553        if !reader.is_finished() {
554            return Err(Error::TrailingData {
555                remaining: reader.remaining_len(),
556            });
557        }
558
559        Ok(Self {
560            cipher: Cipher::None,
561            kdf: Kdf::None,
562            checkint: Some(checkint1),
563            public_key,
564            key_data,
565        })
566    }
567
568    /// Encode [`KeypairData`] along with its associated checkints, comment,
569    /// and padding.
570    fn encode_privatekey_comment_pair(
571        &self,
572        writer: &mut impl Writer,
573        cipher: Cipher,
574        checkint: u32,
575    ) -> Result<()> {
576        let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
577        let padding_len = cipher.padding_len(unpadded_len);
578
579        checkint.encode(writer)?;
580        checkint.encode(writer)?;
581        self.key_data.encode(writer)?;
582        self.comment().encode(writer)?;
583        writer.write(&PADDING_BYTES[..padding_len])?;
584        Ok(())
585    }
586
587    /// Get the length of this private key when encoded with the given comment
588    /// and padded using the padding size for the given cipher.
589    fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> Result<usize> {
590        let len = self.unpadded_privatekey_comment_pair_len()?;
591        [len, cipher.padding_len(len)].checked_sum()
592    }
593
594    /// Get the length of this private key when encoded with the given comment.
595    ///
596    /// This length is just the checkints, private key data, and comment sans
597    /// any padding.
598    fn unpadded_privatekey_comment_pair_len(&self) -> Result<usize> {
599        // This method is intended for use with unencrypted keys only
600        if self.is_encrypted() {
601            return Err(Error::Encrypted);
602        }
603
604        [
605            8, // 2 x uint32 checkints,
606            self.key_data.encoded_len()?,
607            self.comment().encoded_len()?,
608        ]
609        .checked_sum()
610    }
611}
612
613impl Decode for PrivateKey {
614    fn decode(reader: &mut impl Reader) -> Result<Self> {
615        let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
616        reader.read(&mut auth_magic)?;
617
618        if auth_magic != Self::AUTH_MAGIC {
619            return Err(Error::FormatEncoding);
620        }
621
622        let cipher = Cipher::decode(reader)?;
623        let kdf = Kdf::decode(reader)?;
624        let nkeys = usize::decode(reader)?;
625
626        // TODO(tarcieri): support more than one key?
627        if nkeys != 1 {
628            return Err(Error::Length);
629        }
630
631        let public_key = reader.read_nested(public::KeyData::decode)?;
632
633        // Handle encrypted private key
634        #[cfg(not(feature = "alloc"))]
635        if cipher.is_some() {
636            return Err(Error::Encrypted);
637        }
638        #[cfg(feature = "alloc")]
639        if cipher.is_some() {
640            let ciphertext = Vec::decode(reader)?;
641
642            // Ensure ciphertext is padded to the expected length
643            if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
644                return Err(Error::Crypto);
645            }
646
647            if !reader.is_finished() {
648                return Err(Error::Length);
649            }
650
651            return Ok(Self {
652                cipher,
653                kdf,
654                checkint: None,
655                public_key: public_key.into(),
656                key_data: KeypairData::Encrypted(ciphertext),
657            });
658        }
659
660        // Processing unencrypted key. No KDF should be set.
661        if kdf.is_some() {
662            return Err(Error::Crypto);
663        }
664
665        reader.read_nested(|reader| {
666            Self::decode_privatekey_comment_pair(reader, public_key, cipher.block_size())
667        })
668    }
669}
670
671impl Encode for PrivateKey {
672    fn encoded_len(&self) -> Result<usize> {
673        let private_key_len = if self.is_encrypted() {
674            self.key_data.encoded_len()?
675        } else {
676            self.encoded_privatekey_comment_pair_len(Cipher::None)?
677        };
678
679        [
680            Self::AUTH_MAGIC.len(),
681            self.cipher.encoded_len()?,
682            self.kdf.encoded_len()?,
683            4, // number of keys (uint32)
684            4, // public key length prefix (uint32)
685            self.public_key.key_data().encoded_len()?,
686            4, // private key length prefix (uint32)
687            private_key_len,
688        ]
689        .checked_sum()
690    }
691
692    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
693        writer.write(Self::AUTH_MAGIC)?;
694        self.cipher.encode(writer)?;
695        self.kdf.encode(writer)?;
696
697        // TODO(tarcieri): support for encoding more than one private key
698        1usize.encode(writer)?;
699
700        // Encode public key
701        self.public_key.key_data().encode_nested(writer)?;
702
703        // Encode private key
704        if self.is_encrypted() {
705            self.key_data.encode_nested(writer)?;
706        } else {
707            self.encoded_privatekey_comment_pair_len(Cipher::None)?
708                .encode(writer)?;
709
710            let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
711            self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
712        }
713
714        Ok(())
715    }
716}
717
718impl From<PrivateKey> for PublicKey {
719    fn from(private_key: PrivateKey) -> PublicKey {
720        private_key.public_key
721    }
722}
723
724impl From<&PrivateKey> for PublicKey {
725    fn from(private_key: &PrivateKey) -> PublicKey {
726        private_key.public_key.clone()
727    }
728}
729
730impl From<PrivateKey> for public::KeyData {
731    fn from(private_key: PrivateKey) -> public::KeyData {
732        private_key.public_key.key_data
733    }
734}
735
736impl From<&PrivateKey> for public::KeyData {
737    fn from(private_key: &PrivateKey) -> public::KeyData {
738        private_key.public_key.key_data.clone()
739    }
740}
741
742#[cfg(feature = "alloc")]
743#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
744impl From<DsaKeypair> for PrivateKey {
745    fn from(keypair: DsaKeypair) -> PrivateKey {
746        KeypairData::from(keypair)
747            .try_into()
748            .expect(CONVERSION_ERROR_MSG)
749    }
750}
751
752#[cfg(feature = "ecdsa")]
753#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
754impl From<EcdsaKeypair> for PrivateKey {
755    fn from(keypair: EcdsaKeypair) -> PrivateKey {
756        KeypairData::from(keypair)
757            .try_into()
758            .expect(CONVERSION_ERROR_MSG)
759    }
760}
761
762impl From<Ed25519Keypair> for PrivateKey {
763    fn from(keypair: Ed25519Keypair) -> PrivateKey {
764        KeypairData::from(keypair)
765            .try_into()
766            .expect(CONVERSION_ERROR_MSG)
767    }
768}
769
770#[cfg(feature = "alloc")]
771#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
772impl From<RsaKeypair> for PrivateKey {
773    fn from(keypair: RsaKeypair) -> PrivateKey {
774        KeypairData::from(keypair)
775            .try_into()
776            .expect(CONVERSION_ERROR_MSG)
777    }
778}
779
780#[cfg(all(feature = "alloc", feature = "ecdsa"))]
781#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "ecdsa"))))]
782impl From<SkEcdsaSha2NistP256> for PrivateKey {
783    fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
784        KeypairData::from(keypair)
785            .try_into()
786            .expect(CONVERSION_ERROR_MSG)
787    }
788}
789
790#[cfg(feature = "alloc")]
791#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
792impl From<SkEd25519> for PrivateKey {
793    fn from(keypair: SkEd25519) -> PrivateKey {
794        KeypairData::from(keypair)
795            .try_into()
796            .expect(CONVERSION_ERROR_MSG)
797    }
798}
799
800impl TryFrom<KeypairData> for PrivateKey {
801    type Error = Error;
802
803    fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
804        let public_key = public::KeyData::try_from(&key_data)?;
805
806        Ok(Self {
807            cipher: Cipher::None,
808            kdf: Kdf::None,
809            checkint: None,
810            public_key: public_key.into(),
811            key_data,
812        })
813    }
814}
815
816impl PemLabel for PrivateKey {
817    const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
818}
819
820impl str::FromStr for PrivateKey {
821    type Err = Error;
822
823    fn from_str(s: &str) -> Result<Self> {
824        Self::from_openssh(s)
825    }
826}
827
828#[cfg(feature = "subtle")]
829#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
830impl ConstantTimeEq for PrivateKey {
831    fn ct_eq(&self, other: &Self) -> Choice {
832        // Constant-time with respect to private key data
833        self.key_data.ct_eq(&other.key_data)
834            & Choice::from(
835                (self.cipher == other.cipher
836                    && self.kdf == other.kdf
837                    && self.public_key == other.public_key) as u8,
838            )
839    }
840}
841
842#[cfg(feature = "subtle")]
843#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
844impl PartialEq for PrivateKey {
845    fn eq(&self, other: &Self) -> bool {
846        self.ct_eq(other).into()
847    }
848}
849
850#[cfg(feature = "subtle")]
851#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
852impl Eq for PrivateKey {}