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 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 Comment, SshSig,
128 private::{
129 dsa::{DsaKeypair, DsaPrivateKey},
130 opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
131 rsa::{RsaKeypair, RsaPrivateKey},
132 sk::SkEd25519,
133 },
134 sha2::Digest,
135};
136
137#[cfg(feature = "ecdsa")]
138pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
139
140#[cfg(all(feature = "alloc", feature = "ecdsa"))]
141pub use self::sk::SkEcdsaSha2NistP256;
142
143use crate::{Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, public};
144use cipher::Tag;
145use core::str;
146use encoding::{
147 CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
148 pem::{LineEnding, PemLabel},
149};
150use subtle::{Choice, ConstantTimeEq};
151
152#[cfg(feature = "alloc")]
153use {
154 crate::AssociatedHashAlg,
155 alloc::{string::String, vec::Vec},
156 zeroize::Zeroizing,
157};
158
159#[cfg(feature = "encryption")]
160use rand_core::TryCryptoRng;
161
162#[cfg(feature = "rand_core")]
163use rand_core::CryptoRng;
164
165#[cfg(feature = "std")]
166use std::{fs::File, path::Path};
167
168#[cfg(feature = "std")]
169use std::io::{self, Read, Write};
170
171#[cfg(all(unix, feature = "std"))]
172use std::os::unix::fs::OpenOptionsExt;
173
174const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
176
177#[cfg(all(feature = "rand_core", feature = "rsa"))]
179const DEFAULT_RSA_KEY_SIZE: usize = 4096;
180
181const MAX_BLOCK_SIZE: usize = 16;
185
186const PADDING_BYTES: [u8; MAX_BLOCK_SIZE] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
188
189#[cfg(all(unix, feature = "std"))]
191const UNIX_FILE_PERMISSIONS: u32 = 0o600;
192
193#[derive(Clone, Debug)]
195pub struct PrivateKey {
196 cipher: Cipher,
198
199 kdf: Kdf,
201
202 checkint: Option<u32>,
204
205 public_key: PublicKey,
207
208 key_data: KeypairData,
210
211 auth_tag: Option<Tag>,
213}
214
215impl PrivateKey {
216 const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
218
219 #[cfg(feature = "alloc")]
223 pub fn new(key_data: KeypairData, comment: impl Into<Comment>) -> Result<Self> {
224 if key_data.is_encrypted() {
225 return Err(Error::Encrypted);
226 }
227
228 let mut private_key = Self::try_from(key_data)?;
229 private_key.public_key.comment = comment.into();
230 Ok(private_key)
231 }
232
233 pub fn from_openssh(pem: impl AsRef<[u8]>) -> Result<Self> {
241 Self::decode_pem(pem)
242 }
243
244 #[cfg(feature = "ppk")]
252 pub fn from_ppk(ppk: impl AsRef<str>, passphrase: Option<String>) -> Result<Self> {
253 use crate::ppk::PpkContainer;
254
255 let ppk: PpkContainer = PpkContainer::new(ppk.as_ref().try_into()?, passphrase)?;
256
257 Ok(Self {
258 auth_tag: None,
259 checkint: None,
260 cipher: Cipher::None,
261 kdf: Kdf::None,
262 key_data: ppk.keypair_data,
263 public_key: ppk.public_key,
264 })
265 }
266
267 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
269 let reader = &mut bytes;
270 let private_key = Self::decode(reader)?;
271 Ok(reader.finish(private_key)?)
272 }
273
274 pub fn encode_openssh<'o>(
276 &self,
277 line_ending: LineEnding,
278 out: &'o mut [u8],
279 ) -> Result<&'o str> {
280 Ok(self.encode_pem(line_ending, out)?)
281 }
282
283 #[cfg(feature = "alloc")]
286 pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
287 Ok(self.encode_pem_string(line_ending).map(Zeroizing::new)?)
288 }
289
290 #[cfg(feature = "alloc")]
292 pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
293 let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
294 self.encode(&mut private_key_bytes)?;
295 Ok(Zeroizing::new(private_key_bytes))
296 }
297
298 #[cfg_attr(feature = "ed25519", doc = "```")]
314 #[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
315 #[cfg(feature = "alloc")]
345 pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
346 SshSig::sign(self, namespace, hash_alg, msg)
347 }
348
349 #[cfg(feature = "alloc")]
355 pub fn sign_digest<D: AssociatedHashAlg + Digest>(
356 &self,
357 namespace: &str,
358 digest: D,
359 ) -> Result<SshSig> {
360 SshSig::sign_digest(self, namespace, digest)
361 }
362
363 #[cfg(feature = "alloc")]
369 pub fn sign_prehash(
370 &self,
371 namespace: &str,
372 hash_alg: HashAlg,
373 prehash: &[u8],
374 ) -> Result<SshSig> {
375 SshSig::sign_prehash(self, namespace, hash_alg, prehash)
376 }
377
378 #[cfg(feature = "std")]
380 pub fn read_openssh(reader: &mut impl Read) -> Result<Self> {
381 let pem = Zeroizing::new(io::read_to_string(reader)?);
382 Self::from_openssh(&*pem)
383 }
384
385 #[cfg(feature = "std")]
387 pub fn read_openssh_file(path: impl AsRef<Path>) -> Result<Self> {
388 let mut file = File::open(path)?;
390 Self::read_openssh(&mut file)
391 }
392
393 #[cfg(feature = "std")]
395 pub fn write_openssh(&self, writer: &mut impl Write, line_ending: LineEnding) -> Result<()> {
396 let pem = self.to_openssh(line_ending)?;
397 writer.write_all(pem.as_bytes())?;
398 Ok(())
399 }
400
401 #[cfg(feature = "std")]
403 pub fn write_openssh_file(
404 &self,
405 path: impl AsRef<Path>,
406 line_ending: LineEnding,
407 ) -> Result<()> {
408 let mut options = File::options();
409
410 #[cfg(unix)]
411 options.mode(UNIX_FILE_PERMISSIONS);
412
413 let mut file = options.write(true).create(true).truncate(true).open(path)?;
414
415 self.write_openssh(&mut file, line_ending)
416 }
417
418 #[cfg(feature = "encryption")]
423 pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
424 let (key, iv) = self.kdf.derive_key_and_iv(self.cipher, password)?;
425
426 let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
427 let mut buffer = Zeroizing::new(ciphertext.to_vec());
428 self.cipher.decrypt(&key, &iv, &mut buffer, self.auth_tag)?;
429
430 #[allow(clippy::arithmetic_side_effects)] Self::decode_privatekey_comment_pair(
432 &mut &**buffer,
433 self.public_key.key_data.clone(),
434 self.cipher.block_size(),
435 self.cipher.block_size() - 1,
436 )
437 }
438
439 #[cfg(feature = "encryption")]
448 pub fn encrypt<R: TryCryptoRng + ?Sized>(
449 &self,
450 rng: &mut R,
451 password: impl AsRef<[u8]>,
452 ) -> Result<Self> {
453 self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password)
454 }
455
456 #[cfg(feature = "encryption")]
461 pub fn encrypt_with_cipher<R: TryCryptoRng + ?Sized>(
462 &self,
463 rng: &mut R,
464 cipher: Cipher,
465 password: impl AsRef<[u8]>,
466 ) -> Result<Self> {
467 let checkint = rng.try_next_u32().map_err(|_| Error::RngFailure)?;
468
469 self.encrypt_with(
470 cipher,
471 Kdf::new(Default::default(), rng)?,
472 checkint,
473 password,
474 )
475 }
476
477 #[cfg(feature = "encryption")]
482 pub fn encrypt_with(
483 &self,
484 cipher: Cipher,
485 kdf: Kdf,
486 checkint: u32,
487 password: impl AsRef<[u8]>,
488 ) -> Result<Self> {
489 if self.is_encrypted() {
490 return Err(Error::Encrypted);
491 }
492
493 let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
494 let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
495 let mut out = Vec::with_capacity(msg_len);
496
497 self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
499 let auth_tag = cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
500
501 Ok(Self {
502 cipher,
503 kdf,
504 checkint: None,
505 public_key: self.public_key.key_data.clone().into(),
506 key_data: KeypairData::Encrypted(out),
507 auth_tag,
508 })
509 }
510
511 pub fn algorithm(&self) -> Algorithm {
513 self.public_key.algorithm()
514 }
515
516 #[cfg(feature = "alloc")]
518 pub fn comment(&self) -> &Comment {
519 self.public_key.comment()
520 }
521
522 pub fn cipher(&self) -> Cipher {
524 self.cipher
525 }
526
527 pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
531 self.public_key.fingerprint(hash_alg)
532 }
533
534 pub fn is_encrypted(&self) -> bool {
536 let ret = self.key_data.is_encrypted();
537 debug_assert_eq!(ret, self.cipher.is_some());
538 ret
539 }
540
541 pub fn kdf(&self) -> &Kdf {
545 &self.kdf
546 }
547
548 pub fn key_data(&self) -> &KeypairData {
550 &self.key_data
551 }
552
553 pub fn public_key(&self) -> &PublicKey {
555 &self.public_key
556 }
557
558 #[cfg(feature = "rand_core")]
563 #[allow(unreachable_code, unused_variables)]
564 pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, algorithm: Algorithm) -> Result<Self> {
565 let checkint = rng.next_u32();
566
567 let key_data = match algorithm {
568 #[cfg(feature = "dsa")]
569 Algorithm::Dsa => KeypairData::from(DsaKeypair::random(rng)?),
570 #[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
571 Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
572 #[cfg(feature = "ed25519")]
573 Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
574 #[cfg(feature = "rsa")]
575 Algorithm::Rsa { .. } => {
576 KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
577 }
578 _ => return Err(Error::AlgorithmUnknown),
579 };
580 let public_key = public::KeyData::try_from(&key_data)?;
581
582 Ok(Self {
583 cipher: Cipher::None,
584 kdf: Kdf::None,
585 checkint: Some(checkint),
586 public_key: public_key.into(),
587 key_data,
588 auth_tag: None,
589 })
590 }
591
592 #[cfg(feature = "alloc")]
594 pub fn set_comment(&mut self, comment: impl Into<Comment>) {
595 self.public_key.set_comment(comment);
596 }
597
598 fn decode_privatekey_comment_pair(
625 reader: &mut impl Reader,
626 public_key: public::KeyData,
627 block_size: usize,
628 max_padding_size: usize,
629 ) -> Result<Self> {
630 debug_assert!(block_size <= MAX_BLOCK_SIZE);
631 debug_assert!(max_padding_size <= MAX_BLOCK_SIZE);
632
633 if reader.remaining_len().checked_rem(block_size) != Some(0) {
635 return Err(encoding::Error::Length.into());
636 }
637
638 let checkint1 = u32::decode(reader)?;
639 let checkint2 = u32::decode(reader)?;
640
641 if checkint1 != checkint2 {
642 return Err(Error::Crypto);
643 }
644
645 let key_data = KeypairData::decode(reader)?;
646
647 if public_key != public::KeyData::try_from(&key_data)? {
649 return Err(Error::PublicKey);
650 }
651
652 let mut public_key = PublicKey::from(public_key);
653 public_key.decode_comment(reader)?;
654
655 let padding_len = reader.remaining_len();
656
657 if padding_len > max_padding_size {
658 return Err(encoding::Error::Length.into());
659 }
660
661 if padding_len != 0 {
662 let mut padding = [0u8; MAX_BLOCK_SIZE];
663 reader.read(&mut padding[..padding_len])?;
664
665 if PADDING_BYTES[..padding_len] != padding[..padding_len] {
666 return Err(Error::FormatEncoding);
667 }
668 }
669
670 if !reader.is_finished() {
671 return Err(Error::TrailingData {
672 remaining: reader.remaining_len(),
673 });
674 }
675
676 Ok(Self {
677 cipher: Cipher::None,
678 kdf: Kdf::None,
679 checkint: Some(checkint1),
680 public_key,
681 key_data,
682 auth_tag: None,
683 })
684 }
685
686 fn encode_privatekey_comment_pair(
689 &self,
690 writer: &mut impl Writer,
691 cipher: Cipher,
692 checkint: u32,
693 ) -> encoding::Result<()> {
694 let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
695 let padding_len = cipher.padding_len(unpadded_len);
696
697 checkint.encode(writer)?;
698 checkint.encode(writer)?;
699 self.key_data.encode(writer)?;
700
701 #[cfg(not(feature = "alloc"))]
703 b"".encode(writer)?;
704 #[cfg(feature = "alloc")]
705 self.comment().encode(writer)?;
706
707 writer.write(&PADDING_BYTES[..padding_len])?;
708 Ok(())
709 }
710
711 fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> encoding::Result<usize> {
714 let len = self.unpadded_privatekey_comment_pair_len()?;
715 [len, cipher.padding_len(len)].checked_sum()
716 }
717
718 fn unpadded_privatekey_comment_pair_len(&self) -> encoding::Result<usize> {
723 debug_assert!(!self.is_encrypted(), "called on encrypted key");
725
726 #[cfg(not(feature = "alloc"))]
727 let comment_len = 0;
728 #[cfg(feature = "alloc")]
729 let comment_len = self.comment().encoded_len()?;
730
731 [
732 8, self.key_data.encoded_len()?,
734 comment_len,
735 ]
736 .checked_sum()
737 }
738}
739
740impl ConstantTimeEq for PrivateKey {
741 fn ct_eq(&self, other: &Self) -> Choice {
742 self.key_data.ct_eq(&other.key_data)
744 & Choice::from(
745 (self.cipher == other.cipher
746 && self.kdf == other.kdf
747 && self.public_key == other.public_key) as u8,
748 )
749 }
750}
751
752impl Eq for PrivateKey {}
753
754impl PartialEq for PrivateKey {
755 fn eq(&self, other: &Self) -> bool {
756 self.ct_eq(other).into()
757 }
758}
759
760impl Decode for PrivateKey {
761 type Error = Error;
762
763 fn decode(reader: &mut impl Reader) -> Result<Self> {
764 let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
765 reader.read(&mut auth_magic)?;
766
767 if auth_magic != Self::AUTH_MAGIC {
768 return Err(Error::FormatEncoding);
769 }
770
771 let cipher = Cipher::decode(reader)?;
772 let kdf = Kdf::decode(reader)?;
773 let nkeys = usize::decode(reader)?;
774
775 if nkeys != 1 {
777 return Err(encoding::Error::Length.into());
778 }
779
780 let public_key = reader.read_prefixed(public::KeyData::decode)?;
781
782 #[cfg(not(feature = "alloc"))]
784 if cipher.is_some() {
785 return Err(Error::Encrypted);
786 }
787 #[cfg(feature = "alloc")]
788 if cipher.is_some() {
789 let ciphertext = Vec::decode(reader)?;
790
791 if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
793 return Err(Error::Crypto);
794 }
795
796 let auth_tag = if cipher.has_tag() {
797 let mut tag = Tag::default();
798 reader.read(&mut tag)?;
799 Some(tag)
800 } else {
801 None
802 };
803
804 if !reader.is_finished() {
805 return Err(Error::TrailingData {
806 remaining: reader.remaining_len(),
807 });
808 }
809
810 return Ok(Self {
811 cipher,
812 kdf,
813 checkint: None,
814 public_key: public_key.into(),
815 key_data: KeypairData::Encrypted(ciphertext),
816 auth_tag,
817 });
818 }
819
820 if kdf.is_some() {
822 return Err(Error::Crypto);
823 }
824
825 reader.read_prefixed(|reader| {
826 let max_padding_size = match cipher {
835 Cipher::None => 16,
836 #[allow(clippy::arithmetic_side_effects)] _ => cipher.block_size() - 1,
838 };
839 Self::decode_privatekey_comment_pair(
840 reader,
841 public_key,
842 cipher.block_size(),
843 max_padding_size,
844 )
845 })
846 }
847}
848
849impl Encode for PrivateKey {
850 fn encoded_len(&self) -> encoding::Result<usize> {
851 let private_key_len = if self.is_encrypted() {
852 self.key_data.encoded_len_prefixed()?
853 } else {
854 [4, self.encoded_privatekey_comment_pair_len(Cipher::None)?].checked_sum()?
855 };
856
857 [
858 Self::AUTH_MAGIC.len(),
859 self.cipher.encoded_len()?,
860 self.kdf.encoded_len()?,
861 4, self.public_key.key_data().encoded_len_prefixed()?,
863 private_key_len,
864 self.auth_tag.map(|tag| tag.len()).unwrap_or(0),
865 ]
866 .checked_sum()
867 }
868
869 fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
870 writer.write(Self::AUTH_MAGIC)?;
871 self.cipher.encode(writer)?;
872 self.kdf.encode(writer)?;
873
874 1usize.encode(writer)?;
876
877 self.public_key.key_data().encode_prefixed(writer)?;
879
880 if self.is_encrypted() {
882 self.key_data.encode_prefixed(writer)?;
883
884 if let Some(tag) = &self.auth_tag {
885 writer.write(tag)?;
886 }
887 } else {
888 self.encoded_privatekey_comment_pair_len(Cipher::None)?
889 .encode(writer)?;
890
891 let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
892 self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
893 }
894
895 Ok(())
896 }
897}
898
899impl From<PrivateKey> for PublicKey {
900 fn from(private_key: PrivateKey) -> PublicKey {
901 private_key.public_key
902 }
903}
904
905impl From<&PrivateKey> for PublicKey {
906 fn from(private_key: &PrivateKey) -> PublicKey {
907 private_key.public_key.clone()
908 }
909}
910
911impl From<PrivateKey> for public::KeyData {
912 fn from(private_key: PrivateKey) -> public::KeyData {
913 private_key.public_key.key_data
914 }
915}
916
917impl From<&PrivateKey> for public::KeyData {
918 fn from(private_key: &PrivateKey) -> public::KeyData {
919 private_key.public_key.key_data.clone()
920 }
921}
922
923#[cfg(feature = "alloc")]
924impl From<DsaKeypair> for PrivateKey {
925 fn from(keypair: DsaKeypair) -> PrivateKey {
926 KeypairData::from(keypair)
927 .try_into()
928 .expect(CONVERSION_ERROR_MSG)
929 }
930}
931
932#[cfg(feature = "ecdsa")]
933impl From<EcdsaKeypair> for PrivateKey {
934 fn from(keypair: EcdsaKeypair) -> PrivateKey {
935 KeypairData::from(keypair)
936 .try_into()
937 .expect(CONVERSION_ERROR_MSG)
938 }
939}
940
941impl From<Ed25519Keypair> for PrivateKey {
942 fn from(keypair: Ed25519Keypair) -> PrivateKey {
943 KeypairData::from(keypair)
944 .try_into()
945 .expect(CONVERSION_ERROR_MSG)
946 }
947}
948
949#[cfg(feature = "alloc")]
950impl From<RsaKeypair> for PrivateKey {
951 fn from(keypair: RsaKeypair) -> PrivateKey {
952 KeypairData::from(keypair)
953 .try_into()
954 .expect(CONVERSION_ERROR_MSG)
955 }
956}
957
958#[cfg(all(feature = "alloc", feature = "ecdsa"))]
959impl From<SkEcdsaSha2NistP256> for PrivateKey {
960 fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
961 KeypairData::from(keypair)
962 .try_into()
963 .expect(CONVERSION_ERROR_MSG)
964 }
965}
966
967#[cfg(feature = "alloc")]
968impl From<SkEd25519> for PrivateKey {
969 fn from(keypair: SkEd25519) -> PrivateKey {
970 KeypairData::from(keypair)
971 .try_into()
972 .expect(CONVERSION_ERROR_MSG)
973 }
974}
975
976impl TryFrom<KeypairData> for PrivateKey {
977 type Error = Error;
978
979 fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
980 let public_key = public::KeyData::try_from(&key_data)?;
981
982 Ok(Self {
983 cipher: Cipher::None,
984 kdf: Kdf::None,
985 checkint: None,
986 public_key: public_key.into(),
987 key_data,
988 auth_tag: None,
989 })
990 }
991}
992
993impl PemLabel for PrivateKey {
994 const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
995}
996
997impl str::FromStr for PrivateKey {
998 type Err = Error;
999
1000 fn from_str(s: &str) -> Result<Self> {
1001 Self::from_openssh(s)
1002 }
1003}