1#![cfg_attr(all(feature = "encryption", feature = "std"), doc = " ```")]
14#![cfg_attr(not(all(feature = "encryption", feature = "std")), doc = " ```ignore")]
15#![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#![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#[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
167const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
169
170#[cfg(feature = "rsa")]
172const DEFAULT_RSA_KEY_SIZE: usize = 4096;
173
174const MAX_BLOCK_SIZE: usize = 16;
178
179const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
181
182const PEM_LINE_WIDTH: usize = 70;
184
185#[cfg(all(unix, feature = "std"))]
187const UNIX_FILE_PERMISSIONS: u32 = 0o600;
188
189#[derive(Clone, Debug)]
191pub struct PrivateKey {
192 cipher: Cipher,
194
195 kdf: Kdf,
197
198 checkint: Option<u32>,
200
201 public_key: PublicKey,
203
204 key_data: KeypairData,
206}
207
208impl PrivateKey {
209 const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
211
212 #[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 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 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 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 #[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 #[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 #[cfg(feature = "std")]
291 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
292 pub fn read_openssh_file(path: &Path) -> Result<Self> {
293 let pem = Zeroizing::new(fs::read_to_string(path)?);
295 Self::from_openssh(&*pem)
296 }
297
298 #[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 #[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 #[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 #[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 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 pub fn algorithm(&self) -> Algorithm {
399 self.public_key.algorithm()
400 }
401
402 pub fn comment(&self) -> &str {
404 self.public_key.comment()
405 }
406
407 pub fn cipher(&self) -> Cipher {
409 self.cipher
410 }
411
412 #[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 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 pub fn kdf(&self) -> &Kdf {
432 &self.kdf
433 }
434
435 pub fn key_data(&self) -> &KeypairData {
437 &self.key_data
438 }
439
440 pub fn public_key(&self) -> &PublicKey {
442 &self.public_key
443 }
444
445 #[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 #[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 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 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 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 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 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 fn unpadded_privatekey_comment_pair_len(&self) -> Result<usize> {
599 if self.is_encrypted() {
601 return Err(Error::Encrypted);
602 }
603
604 [
605 8, 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 if nkeys != 1 {
628 return Err(Error::Length);
629 }
630
631 let public_key = reader.read_nested(public::KeyData::decode)?;
632
633 #[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 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 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, 4, self.public_key.key_data().encoded_len()?,
686 4, 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 1usize.encode(writer)?;
699
700 self.public_key.key_data().encode_nested(writer)?;
702
703 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 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 {}