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