1#![cfg_attr(feature = "encryption", doc = " ```")]
14#![cfg_attr(not(feature = "encryption"), doc = " ```ignore")]
15#![cfg_attr(
50 all(feature = "ed25519", feature = "encryption", feature = "getrandom",),
51 doc = " ```"
52)]
53#![cfg_attr(
54 not(all(feature = "ed25519", feature = "encryption", feature = "getrandom",)),
55 doc = " ```ignore"
56)]
57#![cfg_attr(all(feature = "ed25519", feature = "getrandom"), doc = " ```")]
82#![cfg_attr(
83 not(all(feature = "ed25519", feature = "getrandom")),
84 doc = " ```ignore"
85)]
86#[cfg(feature = "alloc")]
95mod dsa;
96#[cfg(feature = "ecdsa")]
97mod ecdsa;
98mod ed25519;
99mod keypair;
100#[cfg(feature = "alloc")]
101mod opaque;
102#[cfg(feature = "alloc")]
103mod rsa;
104#[cfg(feature = "alloc")]
105mod sk;
106
107pub use self::{
108 ed25519::{Ed25519Keypair, Ed25519PrivateKey},
109 keypair::KeypairData,
110};
111
112#[cfg(feature = "alloc")]
113pub use crate::{
114 Comment, SshSig,
115 private::{
116 dsa::{DsaKeypair, DsaPrivateKey},
117 opaque::{OpaqueKeypair, OpaqueKeypairBytes, OpaquePrivateKeyBytes},
118 rsa::{RsaKeypair, RsaPrivateKey},
119 sk::SkEd25519,
120 },
121 sha2::Digest,
122};
123
124#[cfg(feature = "ecdsa")]
125pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
126
127#[cfg(all(feature = "alloc", feature = "ecdsa"))]
128pub use self::sk::SkEcdsaSha2NistP256;
129
130use crate::{Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, public};
131use cipher::Tag;
132use core::str;
133use ctutils::{Choice, CtEq};
134use encoding::{
135 CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
136 pem::{LineEnding, PemLabel},
137};
138
139#[cfg(feature = "alloc")]
140use {
141 crate::AssociatedHashAlg,
142 alloc::{string::String, vec::Vec},
143 zeroize::Zeroizing,
144};
145
146#[cfg(feature = "encryption")]
147use rand_core::TryCryptoRng;
148
149#[cfg(feature = "rand_core")]
150use rand_core::CryptoRng;
151
152#[cfg(feature = "std")]
153use std::{fs::File, path::Path};
154
155#[cfg(feature = "std")]
156use std::io::{self, Read, Write};
157
158#[cfg(all(unix, feature = "std"))]
159use std::os::unix::fs::OpenOptionsExt;
160
161const CONVERSION_ERROR_MSG: &str = "SSH private key conversion error";
163
164#[cfg(all(feature = "rand_core", feature = "rsa"))]
166const DEFAULT_RSA_KEY_SIZE: usize = 4096;
167
168const MAX_BLOCK_SIZE: usize = 16;
172
173const PADDING_BYTES: [u8; MAX_BLOCK_SIZE] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
175
176#[cfg(all(unix, feature = "std"))]
178const UNIX_FILE_PERMISSIONS: u32 = 0o600;
179
180#[derive(Clone, Debug)]
182pub struct PrivateKey {
183 cipher: Cipher,
185
186 kdf: Kdf,
188
189 checkint: Option<u32>,
191
192 public_key: PublicKey,
194
195 key_data: KeypairData,
197
198 auth_tag: Option<Tag>,
200}
201
202impl PrivateKey {
203 const AUTH_MAGIC: &'static [u8] = b"openssh-key-v1\0";
205
206 #[cfg(feature = "alloc")]
213 pub fn new(key_data: KeypairData, comment: impl Into<Comment>) -> Result<Self> {
214 if key_data.is_encrypted() {
215 return Err(Error::Encrypted);
216 }
217
218 let mut private_key = Self::try_from(key_data)?;
219 private_key.public_key.comment = comment.into();
220 Ok(private_key)
221 }
222
223 pub fn from_openssh(pem: impl AsRef<[u8]>) -> Result<Self> {
234 Self::decode_pem(pem)
235 }
236
237 #[cfg(feature = "ppk")]
248 pub fn from_ppk(ppk: impl AsRef<str>, passphrase: Option<String>) -> Result<Self> {
249 use crate::ppk::PpkContainer;
250
251 let ppk: PpkContainer = PpkContainer::new(ppk.as_ref().try_into()?, passphrase)?;
252
253 Ok(Self {
254 auth_tag: None,
255 checkint: None,
256 cipher: Cipher::None,
257 kdf: Kdf::None,
258 key_data: ppk.keypair_data,
259 public_key: ppk.public_key,
260 })
261 }
262
263 pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
268 let reader = &mut bytes;
269 let private_key = Self::decode(reader)?;
270 Ok(reader.finish(private_key)?)
271 }
272
273 pub fn encode_openssh<'o>(
278 &self,
279 line_ending: LineEnding,
280 out: &'o mut [u8],
281 ) -> Result<&'o str> {
282 Ok(self.encode_pem(line_ending, out)?)
283 }
284
285 #[cfg(feature = "alloc")]
291 pub fn to_openssh(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
292 Ok(self.encode_pem_string(line_ending).map(Zeroizing::new)?)
293 }
294
295 #[cfg(feature = "alloc")]
300 pub fn to_bytes(&self) -> Result<Zeroizing<Vec<u8>>> {
301 let mut private_key_bytes = Vec::with_capacity(self.encoded_len()?);
302 self.encode(&mut private_key_bytes)?;
303 Ok(Zeroizing::new(private_key_bytes))
304 }
305
306 #[cfg_attr(feature = "ed25519", doc = "```")]
322 #[cfg_attr(not(feature = "ed25519"), doc = "```ignore")]
323 #[cfg(feature = "alloc")]
356 pub fn sign(&self, namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<SshSig> {
357 SshSig::sign(self, namespace, hash_alg, msg)
358 }
359
360 #[cfg(feature = "alloc")]
369 pub fn sign_digest<D: AssociatedHashAlg + Digest>(
370 &self,
371 namespace: &str,
372 digest: D,
373 ) -> Result<SshSig> {
374 SshSig::sign_digest(self, namespace, digest)
375 }
376
377 #[cfg(feature = "alloc")]
386 pub fn sign_prehash(
387 &self,
388 namespace: &str,
389 hash_alg: HashAlg,
390 prehash: &[u8],
391 ) -> Result<SshSig> {
392 SshSig::sign_prehash(self, namespace, hash_alg, prehash)
393 }
394
395 #[cfg(feature = "std")]
401 pub fn read_openssh(reader: &mut impl Read) -> Result<Self> {
402 let pem = Zeroizing::new(io::read_to_string(reader)?);
403 Self::from_openssh(&*pem)
404 }
405
406 #[cfg(feature = "std")]
412 pub fn read_openssh_file(path: impl AsRef<Path>) -> Result<Self> {
413 let mut file = File::open(path)?;
415 Self::read_openssh(&mut file)
416 }
417
418 #[cfg(feature = "std")]
424 pub fn write_openssh(&self, writer: &mut impl Write, line_ending: LineEnding) -> Result<()> {
425 let pem = self.to_openssh(line_ending)?;
426 writer.write_all(pem.as_bytes())?;
427 Ok(())
428 }
429
430 #[cfg(feature = "std")]
436 pub fn write_openssh_file(
437 &self,
438 path: impl AsRef<Path>,
439 line_ending: LineEnding,
440 ) -> Result<()> {
441 let mut options = File::options();
442
443 #[cfg(unix)]
444 options.mode(UNIX_FILE_PERMISSIONS);
445
446 let mut file = options.write(true).create(true).truncate(true).open(path)?;
447
448 self.write_openssh(&mut file, line_ending)
449 }
450
451 #[cfg(feature = "encryption")]
457 pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<Self> {
458 let (key, iv) = self.kdf.derive_key_and_iv(self.cipher, password)?;
459
460 let ciphertext = self.key_data.encrypted().ok_or(Error::Decrypted)?;
461 let mut buffer = Zeroizing::new(ciphertext.to_vec());
462 self.cipher.decrypt(&key, &iv, &mut buffer, self.auth_tag)?;
463
464 #[allow(clippy::arithmetic_side_effects)] Self::decode_privatekey_comment_pair(
466 &mut &**buffer,
467 self.public_key.key_data.clone(),
468 self.cipher.block_size(),
469 self.cipher.block_size() - 1,
470 )
471 }
472
473 #[cfg(feature = "encryption")]
483 pub fn encrypt<R: TryCryptoRng + ?Sized>(
484 &self,
485 rng: &mut R,
486 password: impl AsRef<[u8]>,
487 ) -> Result<Self> {
488 self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password)
489 }
490
491 #[cfg(feature = "encryption")]
497 pub fn encrypt_with_cipher<R: TryCryptoRng + ?Sized>(
498 &self,
499 rng: &mut R,
500 cipher: Cipher,
501 password: impl AsRef<[u8]>,
502 ) -> Result<Self> {
503 let checkint = rng.try_next_u32().map_err(|_| Error::RngFailure)?;
504
505 self.encrypt_with(
506 cipher,
507 Kdf::new(Default::default(), rng)?,
508 checkint,
509 password,
510 )
511 }
512
513 #[cfg(feature = "encryption")]
519 pub fn encrypt_with(
520 &self,
521 cipher: Cipher,
522 kdf: Kdf,
523 checkint: u32,
524 password: impl AsRef<[u8]>,
525 ) -> Result<Self> {
526 if self.is_encrypted() {
527 return Err(Error::Encrypted);
528 }
529
530 let (key_bytes, iv_bytes) = kdf.derive_key_and_iv(cipher, password)?;
531 let msg_len = self.encoded_privatekey_comment_pair_len(cipher)?;
532 let mut out = Vec::with_capacity(msg_len);
533
534 self.encode_privatekey_comment_pair(&mut out, cipher, checkint)?;
536 let auth_tag = cipher.encrypt(&key_bytes, &iv_bytes, out.as_mut_slice())?;
537
538 Ok(Self {
539 cipher,
540 kdf,
541 checkint: None,
542 public_key: self.public_key.key_data.clone().into(),
543 key_data: KeypairData::Encrypted(out),
544 auth_tag,
545 })
546 }
547
548 #[must_use]
550 pub fn algorithm(&self) -> Algorithm {
551 self.public_key.algorithm()
552 }
553
554 #[cfg(feature = "alloc")]
556 #[must_use]
557 pub fn comment(&self) -> &Comment {
558 self.public_key.comment()
559 }
560
561 #[must_use]
563 pub fn cipher(&self) -> Cipher {
564 self.cipher
565 }
566
567 #[must_use]
571 pub fn fingerprint(&self, hash_alg: HashAlg) -> Fingerprint {
572 self.public_key.fingerprint(hash_alg)
573 }
574
575 #[must_use]
577 pub fn is_encrypted(&self) -> bool {
578 let ret = self.key_data.is_encrypted();
579 debug_assert_eq!(ret, self.cipher.is_some());
580 ret
581 }
582
583 #[must_use]
587 pub fn kdf(&self) -> &Kdf {
588 &self.kdf
589 }
590
591 #[must_use]
593 pub fn key_data(&self) -> &KeypairData {
594 &self.key_data
595 }
596
597 #[must_use]
599 pub fn public_key(&self) -> &PublicKey {
600 &self.public_key
601 }
602
603 #[cfg(feature = "rand_core")]
608 #[allow(unreachable_code, unused_variables)]
609 pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, algorithm: Algorithm) -> Result<Self> {
610 let checkint = rng.next_u32();
611
612 let key_data = match algorithm {
613 #[cfg(feature = "dsa")]
614 Algorithm::Dsa => KeypairData::from(DsaKeypair::random(rng)?),
615 #[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
616 Algorithm::Ecdsa { curve } => KeypairData::from(EcdsaKeypair::random(rng, curve)?),
617 #[cfg(feature = "ed25519")]
618 Algorithm::Ed25519 => KeypairData::from(Ed25519Keypair::random(rng)),
619 #[cfg(feature = "rsa")]
620 Algorithm::Rsa { .. } => {
621 KeypairData::from(RsaKeypair::random(rng, DEFAULT_RSA_KEY_SIZE)?)
622 }
623 _ => return Err(Error::AlgorithmUnknown),
624 };
625 let public_key = public::KeyData::try_from(&key_data)?;
626
627 Ok(Self {
628 cipher: Cipher::None,
629 kdf: Kdf::None,
630 checkint: Some(checkint),
631 public_key: public_key.into(),
632 key_data,
633 auth_tag: None,
634 })
635 }
636
637 #[cfg(feature = "alloc")]
639 pub fn set_comment(&mut self, comment: impl Into<Comment>) {
640 self.public_key.set_comment(comment);
641 }
642
643 fn decode_privatekey_comment_pair(
670 reader: &mut impl Reader,
671 public_key: public::KeyData,
672 block_size: usize,
673 max_padding_size: usize,
674 ) -> Result<Self> {
675 debug_assert!(block_size <= MAX_BLOCK_SIZE);
676 debug_assert!(max_padding_size <= MAX_BLOCK_SIZE);
677
678 if reader.remaining_len().checked_rem(block_size) != Some(0) {
680 return Err(encoding::Error::Length.into());
681 }
682
683 let checkint1 = u32::decode(reader)?;
684 let checkint2 = u32::decode(reader)?;
685
686 if checkint1 != checkint2 {
687 return Err(Error::Crypto);
688 }
689
690 let key_data = KeypairData::decode(reader)?;
691
692 if public_key != public::KeyData::try_from(&key_data)? {
694 return Err(Error::PublicKey);
695 }
696
697 let mut public_key = PublicKey::from(public_key);
698 public_key.decode_comment(reader)?;
699
700 let padding_len = reader.remaining_len();
701
702 if padding_len > max_padding_size {
703 return Err(encoding::Error::Length.into());
704 }
705
706 if padding_len != 0 {
707 let mut padding = [0u8; MAX_BLOCK_SIZE];
708 reader.read(&mut padding[..padding_len])?;
709
710 if PADDING_BYTES[..padding_len] != padding[..padding_len] {
711 return Err(Error::FormatEncoding);
712 }
713 }
714
715 if !reader.is_finished() {
716 return Err(Error::TrailingData {
717 remaining: reader.remaining_len(),
718 });
719 }
720
721 Ok(Self {
722 cipher: Cipher::None,
723 kdf: Kdf::None,
724 checkint: Some(checkint1),
725 public_key,
726 key_data,
727 auth_tag: None,
728 })
729 }
730
731 fn encode_privatekey_comment_pair(
734 &self,
735 writer: &mut impl Writer,
736 cipher: Cipher,
737 checkint: u32,
738 ) -> encoding::Result<()> {
739 let unpadded_len = self.unpadded_privatekey_comment_pair_len()?;
740 let padding_len = cipher.padding_len(unpadded_len);
741
742 checkint.encode(writer)?;
743 checkint.encode(writer)?;
744 self.key_data.encode(writer)?;
745
746 #[cfg(not(feature = "alloc"))]
748 b"".encode(writer)?;
749 #[cfg(feature = "alloc")]
750 self.comment().encode(writer)?;
751
752 writer.write(&PADDING_BYTES[..padding_len])?;
753 Ok(())
754 }
755
756 fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> encoding::Result<usize> {
759 let len = self.unpadded_privatekey_comment_pair_len()?;
760 [len, cipher.padding_len(len)].checked_sum()
761 }
762
763 fn unpadded_privatekey_comment_pair_len(&self) -> encoding::Result<usize> {
768 debug_assert!(!self.is_encrypted(), "called on encrypted key");
770
771 #[cfg(not(feature = "alloc"))]
772 let comment_len = 0;
773 #[cfg(feature = "alloc")]
774 let comment_len = self.comment().encoded_len()?;
775
776 [
777 8, self.key_data.encoded_len()?,
779 comment_len,
780 ]
781 .checked_sum()
782 }
783}
784
785impl CtEq for PrivateKey {
786 fn ct_eq(&self, other: &Self) -> Choice {
787 self.key_data.ct_eq(&other.key_data)
789 & Choice::from(u8::from(
790 self.cipher == other.cipher
791 && self.kdf == other.kdf
792 && self.public_key == other.public_key,
793 ))
794 }
795}
796
797impl Eq for PrivateKey {}
798
799impl PartialEq for PrivateKey {
800 fn eq(&self, other: &Self) -> bool {
801 self.ct_eq(other).into()
802 }
803}
804
805impl Decode for PrivateKey {
806 type Error = Error;
807
808 fn decode(reader: &mut impl Reader) -> Result<Self> {
809 let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
810 reader.read(&mut auth_magic)?;
811
812 if auth_magic != Self::AUTH_MAGIC {
813 return Err(Error::FormatEncoding);
814 }
815
816 let cipher = Cipher::decode(reader)?;
817 let kdf = Kdf::decode(reader)?;
818 let nkeys = usize::decode(reader)?;
819
820 if nkeys != 1 {
822 return Err(encoding::Error::Length.into());
823 }
824
825 let public_key = reader.read_prefixed(public::KeyData::decode)?;
826
827 #[cfg(not(feature = "alloc"))]
829 if cipher.is_some() {
830 return Err(Error::Encrypted);
831 }
832 #[cfg(feature = "alloc")]
833 if cipher.is_some() {
834 let ciphertext = Vec::decode(reader)?;
835
836 if ciphertext.len().checked_rem(cipher.block_size()) != Some(0) {
838 return Err(Error::Crypto);
839 }
840
841 let auth_tag = if cipher.has_tag() {
842 let mut tag = Tag::default();
843 reader.read(&mut tag)?;
844 Some(tag)
845 } else {
846 None
847 };
848
849 if !reader.is_finished() {
850 return Err(Error::TrailingData {
851 remaining: reader.remaining_len(),
852 });
853 }
854
855 return Ok(Self {
856 cipher,
857 kdf,
858 checkint: None,
859 public_key: public_key.into(),
860 key_data: KeypairData::Encrypted(ciphertext),
861 auth_tag,
862 });
863 }
864
865 if kdf.is_some() {
867 return Err(Error::Crypto);
868 }
869
870 reader.read_prefixed(|reader| {
871 let max_padding_size = match cipher {
880 Cipher::None => 16,
881 #[allow(clippy::arithmetic_side_effects)] _ => cipher.block_size() - 1,
883 };
884 Self::decode_privatekey_comment_pair(
885 reader,
886 public_key,
887 cipher.block_size(),
888 max_padding_size,
889 )
890 })
891 }
892}
893
894impl Encode for PrivateKey {
895 fn encoded_len(&self) -> encoding::Result<usize> {
896 let private_key_len = if self.is_encrypted() {
897 self.key_data.encoded_len_prefixed()?
898 } else {
899 [4, self.encoded_privatekey_comment_pair_len(Cipher::None)?].checked_sum()?
900 };
901
902 [
903 Self::AUTH_MAGIC.len(),
904 self.cipher.encoded_len()?,
905 self.kdf.encoded_len()?,
906 4, self.public_key.key_data().encoded_len_prefixed()?,
908 private_key_len,
909 self.auth_tag.map_or(0, |tag| tag.len()),
910 ]
911 .checked_sum()
912 }
913
914 fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
915 writer.write(Self::AUTH_MAGIC)?;
916 self.cipher.encode(writer)?;
917 self.kdf.encode(writer)?;
918
919 1usize.encode(writer)?;
921
922 self.public_key.key_data().encode_prefixed(writer)?;
924
925 if self.is_encrypted() {
927 self.key_data.encode_prefixed(writer)?;
928
929 if let Some(tag) = &self.auth_tag {
930 writer.write(tag)?;
931 }
932 } else {
933 self.encoded_privatekey_comment_pair_len(Cipher::None)?
934 .encode(writer)?;
935
936 let checkint = self.checkint.unwrap_or_else(|| self.key_data.checkint());
937 self.encode_privatekey_comment_pair(writer, Cipher::None, checkint)?;
938 }
939
940 Ok(())
941 }
942}
943
944impl From<PrivateKey> for PublicKey {
945 fn from(private_key: PrivateKey) -> PublicKey {
946 private_key.public_key
947 }
948}
949
950impl From<&PrivateKey> for PublicKey {
951 fn from(private_key: &PrivateKey) -> PublicKey {
952 private_key.public_key.clone()
953 }
954}
955
956impl From<PrivateKey> for public::KeyData {
957 fn from(private_key: PrivateKey) -> public::KeyData {
958 private_key.public_key.key_data
959 }
960}
961
962impl From<&PrivateKey> for public::KeyData {
963 fn from(private_key: &PrivateKey) -> public::KeyData {
964 private_key.public_key.key_data.clone()
965 }
966}
967
968#[cfg(feature = "alloc")]
969impl From<DsaKeypair> for PrivateKey {
970 fn from(keypair: DsaKeypair) -> PrivateKey {
971 KeypairData::from(keypair)
972 .try_into()
973 .expect(CONVERSION_ERROR_MSG)
974 }
975}
976
977#[cfg(feature = "ecdsa")]
978impl From<EcdsaKeypair> for PrivateKey {
979 fn from(keypair: EcdsaKeypair) -> PrivateKey {
980 KeypairData::from(keypair)
981 .try_into()
982 .expect(CONVERSION_ERROR_MSG)
983 }
984}
985
986impl From<Ed25519Keypair> for PrivateKey {
987 fn from(keypair: Ed25519Keypair) -> PrivateKey {
988 KeypairData::from(keypair)
989 .try_into()
990 .expect(CONVERSION_ERROR_MSG)
991 }
992}
993
994#[cfg(feature = "alloc")]
995impl From<RsaKeypair> for PrivateKey {
996 fn from(keypair: RsaKeypair) -> PrivateKey {
997 KeypairData::from(keypair)
998 .try_into()
999 .expect(CONVERSION_ERROR_MSG)
1000 }
1001}
1002
1003#[cfg(all(feature = "alloc", feature = "ecdsa"))]
1004impl From<SkEcdsaSha2NistP256> for PrivateKey {
1005 fn from(keypair: SkEcdsaSha2NistP256) -> PrivateKey {
1006 KeypairData::from(keypair)
1007 .try_into()
1008 .expect(CONVERSION_ERROR_MSG)
1009 }
1010}
1011
1012#[cfg(feature = "alloc")]
1013impl From<SkEd25519> for PrivateKey {
1014 fn from(keypair: SkEd25519) -> PrivateKey {
1015 KeypairData::from(keypair)
1016 .try_into()
1017 .expect(CONVERSION_ERROR_MSG)
1018 }
1019}
1020
1021impl TryFrom<KeypairData> for PrivateKey {
1022 type Error = Error;
1023
1024 fn try_from(key_data: KeypairData) -> Result<PrivateKey> {
1025 let public_key = public::KeyData::try_from(&key_data)?;
1026
1027 Ok(Self {
1028 cipher: Cipher::None,
1029 kdf: Kdf::None,
1030 checkint: None,
1031 public_key: public_key.into(),
1032 key_data,
1033 auth_tag: None,
1034 })
1035 }
1036}
1037
1038impl PemLabel for PrivateKey {
1039 const PEM_LABEL: &'static str = "OPENSSH PRIVATE KEY";
1040}
1041
1042impl str::FromStr for PrivateKey {
1043 type Err = Error;
1044
1045 fn from_str(s: &str) -> Result<Self> {
1046 Self::from_openssh(s)
1047 }
1048}