1use std::convert::TryInto;
2use std::fmt;
3use std::fs::File;
4use std::io::{self, Read};
5use std::path::Path;
6
7use ring::signature::Ed25519KeyPair;
8use ring::{rand, signature, signature::KeyPair};
9
10use zeroize::Zeroize;
11
12use crate::{
13 error::Error,
14 ssh::{
15 keytype::{Curve, KeyType, KeyTypeKind},
16 reader::Reader,
17 writer::Writer,
18 CurveKind, EcdsaPublicKey, Ed25519PublicKey, PublicKey, PublicKeyKind, RsaPublicKey,
19 },
20 utils::format_signature_for_ssh,
21 Result,
22};
23
24#[cfg(feature = "rsa-signing")]
25use num_bigint::{BigInt, BigUint, Sign};
26
27#[cfg(feature = "rsa-signing")]
28use simple_asn1::{ASN1Block, ASN1Class, ToASN1};
29
30#[cfg(feature = "encrypted-keys")]
31use aes::{
32 cipher::{generic_array::GenericArray, NewCipher, StreamCipher},
33 Aes256Ctr,
34};
35
36#[cfg(feature = "encrypted-keys")]
37use bcrypt_pbkdf::bcrypt_pbkdf;
38
39const SSH_SK_USER_PRESENCE_REQD: u8 = 0x01;
40
41#[derive(Debug, PartialEq, Eq, Clone, Copy)]
43pub enum TouchRequirement {
44 Required,
46
47 NotRequired,
49
50 Unknown,
52}
53
54impl TouchRequirement {
55 pub fn is_required(self) -> bool {
57 matches!(self, TouchRequirement::Required)
58 }
59}
60
61fn touch_requirement_from_flags(flags: u8) -> TouchRequirement {
62 if (flags & SSH_SK_USER_PRESENCE_REQD) != 0 {
63 TouchRequirement::Required
64 } else {
65 TouchRequirement::NotRequired
66 }
67}
68
69#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
71pub struct RsaPrivateKey {
72 pub n: Vec<u8>,
74
75 pub e: Vec<u8>,
77
78 pub d: Vec<u8>,
80
81 pub coefficient: Vec<u8>,
83
84 pub p: Vec<u8>,
86
87 pub q: Vec<u8>,
89
90 pub exp: Option<Vec<u8>>,
92
93 pub exq: Option<Vec<u8>>,
95}
96
97#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
99pub struct EcdsaPrivateKey {
100 pub curve: Curve,
102
103 pub key: Vec<u8>,
105}
106
107#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
109pub struct EcdsaSkPrivateKey {
110 pub flags: u8,
112
113 pub handle: Vec<u8>,
115
116 pub reserved: Vec<u8>,
118
119 pub pin: Option<String>,
122
123 pub device_path: Option<String>,
127}
128
129#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
131pub struct Ed25519PrivateKey {
132 pub key: Vec<u8>,
134}
135
136#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
138pub struct Ed25519SkPrivateKey {
139 pub flags: u8,
141
142 pub handle: Vec<u8>,
144
145 pub reserved: Vec<u8>,
147
148 pub pin: Option<String>,
151
152 pub device_path: Option<String>,
156}
157
158impl EcdsaSkPrivateKey {
159 pub fn touch_requirement(&self) -> TouchRequirement {
161 touch_requirement_from_flags(self.flags)
162 }
163}
164
165impl Ed25519SkPrivateKey {
166 pub fn touch_requirement(&self) -> TouchRequirement {
168 touch_requirement_from_flags(self.flags)
169 }
170}
171
172#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
174pub enum PrivateKeyKind {
175 Rsa(RsaPrivateKey),
177
178 Ecdsa(EcdsaPrivateKey),
180
181 EcdsaSk(EcdsaSkPrivateKey),
183
184 Ed25519(Ed25519PrivateKey),
186
187 Ed25519Sk(Ed25519SkPrivateKey),
189}
190
191#[derive(Debug, PartialEq, Eq, Clone)]
193pub struct PrivateKey {
194 pub key_type: KeyType,
196
197 pub kind: PrivateKeyKind,
199
200 pub pubkey: PublicKey,
202
203 pub magic: u32,
207
208 pub comment: String,
211}
212
213#[cfg(feature = "rsa-signing")]
214impl ToASN1 for RsaPrivateKey {
215 type Error = Error;
216
217 fn to_asn1_class(&self, _class: ASN1Class) -> std::result::Result<Vec<ASN1Block>, Error> {
218 Ok(vec![ASN1Block::Sequence(
219 0,
220 [
221 vec![ASN1Block::Integer(0, BigInt::new(Sign::Plus, vec![0]))],
222 vec![ASN1Block::Integer(
223 0,
224 BigInt::from_bytes_be(Sign::Plus, &self.n),
225 )],
226 vec![ASN1Block::Integer(
227 0,
228 BigInt::from_bytes_be(Sign::Plus, &self.e),
229 )],
230 vec![ASN1Block::Integer(
231 0,
232 BigInt::from_bytes_be(Sign::Plus, &self.d),
233 )],
234 vec![ASN1Block::Integer(
235 0,
236 BigInt::from_bytes_be(Sign::Plus, &self.p),
237 )],
238 vec![ASN1Block::Integer(
239 0,
240 BigInt::from_bytes_be(Sign::Plus, &self.q),
241 )],
242 vec![ASN1Block::Integer(
243 0,
244 BigInt::from_bytes_be(Sign::Plus, self.exp.as_ref().unwrap()),
245 )],
246 vec![ASN1Block::Integer(
247 0,
248 BigInt::from_bytes_be(Sign::Plus, self.exq.as_ref().unwrap()),
249 )],
250 vec![ASN1Block::Integer(
251 0,
252 BigInt::from_bytes_be(Sign::Plus, &self.coefficient),
253 )],
254 Vec::new(),
255 ]
256 .concat(),
257 )])
258 }
259}
260
261impl super::SSHCertificateSigner for PrivateKey {
262 fn sign(&self, buffer: &[u8]) -> Option<Vec<u8>> {
263 let rng = rand::SystemRandom::new();
264
265 match &self.kind {
266 #[cfg(feature = "rsa-signing")]
267 PrivateKeyKind::Rsa(key) => {
268 let asn_privkey = match simple_asn1::der_encode(key) {
269 Ok(apk) => apk,
270 Err(_) => return None,
271 };
272
273 let keypair = match signature::RsaKeyPair::from_der(&asn_privkey) {
274 Ok(kp) => kp,
275 Err(_) => return None,
276 };
277
278 let mut signature = vec![0; keypair.public().modulus_len()];
279
280 keypair
281 .sign(&signature::RSA_PKCS1_SHA512, &rng, buffer, &mut signature)
282 .ok()?;
283
284 format_signature_for_ssh(&self.pubkey, &signature)
285 }
286 #[cfg(not(feature = "rsa-signing"))]
287 PrivateKeyKind::Rsa(_) => return None,
288 PrivateKeyKind::Ecdsa(key) => {
289 let alg = match key.curve.kind {
290 CurveKind::Nistp256 => &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
291 CurveKind::Nistp384 => &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
292 CurveKind::Nistp521 => return None,
293 };
294
295 let pubkey = match &self.pubkey.kind {
296 PublicKeyKind::Ecdsa(key) => &key.key,
297 _ => return None,
298 };
299
300 let key = if key.key[0] == 0x0_u8 {
301 &key.key[1..]
302 } else {
303 &key.key
304 };
305 let key_pair = match signature::EcdsaKeyPair::from_private_key_and_public_key(
306 alg, key, pubkey, &rng,
307 ) {
308 Ok(kp) => kp,
309 Err(_) => return None,
310 };
311
312 format_signature_for_ssh(&self.pubkey, key_pair.sign(&rng, buffer).ok()?.as_ref())
313 }
314 PrivateKeyKind::Ed25519(key) => {
315 let public_key = match &self.pubkey.kind {
316 PublicKeyKind::Ed25519(key) => &key.key,
317 _ => return None,
318 };
319
320 let key_pair =
321 match Ed25519KeyPair::from_seed_and_public_key(&key.key[..32], public_key) {
322 Ok(kp) => kp,
323 Err(_) => return None,
324 };
325
326 format_signature_for_ssh(&self.pubkey, key_pair.sign(buffer).as_ref())
327 }
328 #[cfg(any(feature = "fido-support", feature = "fido-support-mozilla"))]
329 PrivateKeyKind::Ed25519Sk(_) | PrivateKeyKind::EcdsaSk(_) => {
330 crate::fido::signing::sign_with_private_key(&self, buffer)
331 }
332 #[cfg(not(any(feature = "fido-support", feature = "fido-support-mozilla")))]
333 PrivateKeyKind::Ed25519Sk(_) | PrivateKeyKind::EcdsaSk(_) => None,
334 }
335 }
336}
337impl PrivateKey {
338 pub fn read_private_key(reader: &mut Reader<'_>) -> Result<Self> {
340 let key_type = reader.read_string()?;
341 let kt = KeyType::from_name(&key_type)?;
342
343 let (kind, pubkey) = match kt.kind {
344 KeyTypeKind::Rsa => {
345 let n = reader.read_positive_mpint()?;
346 let e = reader.read_positive_mpint()?;
347 let d = reader.read_positive_mpint()?;
348 let coefficient = reader.read_positive_mpint()?;
349 let p = reader.read_positive_mpint()?;
350 let q = reader.read_positive_mpint()?;
351
352 #[cfg(feature = "rsa-signing")]
353 let exp = Some(
354 BigUint::from_bytes_be(&d)
355 .modpow(
356 &BigUint::from_slice(&[0x1]),
357 &(BigUint::from_bytes_be(&p) - 1_u8),
358 )
359 .to_bytes_be(),
360 );
361 #[cfg(not(feature = "rsa-signing"))]
362 let exp = None;
363
364 #[cfg(feature = "rsa-signing")]
365 let exq = Some(
366 BigUint::from_bytes_be(&d)
367 .modpow(
368 &BigUint::from_slice(&[0x1]),
369 &(BigUint::from_bytes_be(&q) - 1_u8),
370 )
371 .to_bytes_be(),
372 );
373 #[cfg(not(feature = "rsa-signing"))]
374 let exq = None;
375
376 (
377 PrivateKeyKind::Rsa(RsaPrivateKey {
378 n: n.clone(),
379 e: e.clone(),
380 d,
381 coefficient,
382 p,
383 q,
384 exp,
385 exq,
386 }),
387 PublicKey {
388 key_type: kt.clone(),
389 kind: PublicKeyKind::Rsa(RsaPublicKey { e, n }),
390 comment: None,
391 },
392 )
393 }
394 KeyTypeKind::Ecdsa => {
395 let identifier = reader.read_string()?;
396 let curve = Curve::from_identifier(&identifier)?;
397 let pubkey = reader.read_bytes()?;
398
399 let (private_key, sk_application) = match kt.is_sk {
400 true => {
401 let sk_application = Some(reader.read_string()?);
402 let k = EcdsaSkPrivateKey {
403 flags: reader.read_raw_bytes(1)?[0],
404 handle: reader.read_bytes()?,
405 reserved: reader.read_bytes()?,
406 pin: None,
407 device_path: None,
408 };
409
410 (PrivateKeyKind::EcdsaSk(k), sk_application)
411 }
412 false => {
413 let key = reader.read_bytes()?;
414 let k = EcdsaPrivateKey {
415 curve: curve.clone(),
416 key,
417 };
418 (PrivateKeyKind::Ecdsa(k), None)
419 }
420 };
421 (
422 private_key,
423 PublicKey {
424 key_type: kt.clone(),
425 kind: PublicKeyKind::Ecdsa(EcdsaPublicKey {
426 curve,
427 key: pubkey,
428 sk_application,
429 }),
430 comment: None,
431 },
432 )
433 }
434 KeyTypeKind::Ed25519 => {
435 let pubkey = reader.read_bytes()?;
436
437 let (private_key, sk_application) = match kt.is_sk {
438 true => {
439 let sk_application = Some(reader.read_string()?);
440 let k = Ed25519SkPrivateKey {
441 flags: reader.read_raw_bytes(1)?[0],
442 handle: reader.read_bytes()?,
443 reserved: reader.read_bytes()?,
444 pin: None,
445 device_path: None,
446 };
447
448 (PrivateKeyKind::Ed25519Sk(k), sk_application)
449 }
450 false => {
451 let k = Ed25519PrivateKey {
452 key: reader.read_bytes()?,
453 };
454
455 (PrivateKeyKind::Ed25519(k), None)
456 }
457 };
458 (
459 private_key,
460 PublicKey {
461 key_type: kt.clone(),
462 kind: PublicKeyKind::Ed25519(Ed25519PublicKey {
463 key: pubkey,
464 sk_application,
465 }),
466 comment: None,
467 },
468 )
469 }
470 _ => return Err(Error::UnknownKeyType(kt.name.to_string())),
471 };
472
473 let comment = reader.read_string()?;
474
475 Ok(Self {
476 key_type: kt,
477 kind,
478 pubkey,
479 magic: 0x0,
480 comment,
481 })
482 }
483
484 pub fn new(kind: KeyTypeKind, comment: &str) -> Result<Self> {
486 let rng = rand::SystemRandom::new();
487 match kind {
488 KeyTypeKind::Ed25519 => {
489 let key_type = KeyType::from_name("ssh-ed25519").unwrap();
490 let seed: [u8; 32] = rand::generate(&rng).unwrap().expose();
494 let magic: [u8; 4] = rand::generate(&rng).unwrap().expose();
495 let public_key_bytes = Ed25519KeyPair::from_seed_unchecked(&seed)
496 .unwrap()
497 .public_key()
498 .as_ref()
499 .to_vec();
500 let key = Ed25519PrivateKey { key: seed.to_vec() };
501
502 let pubkey = PublicKey {
503 key_type: key_type.clone(),
504 kind: PublicKeyKind::Ed25519(Ed25519PublicKey {
505 key: public_key_bytes,
506 sk_application: None,
507 }),
508 comment: None,
509 };
510
511 Ok(Self {
512 key_type,
513 kind: PrivateKeyKind::Ed25519(key),
514 pubkey,
515 magic: u32::from_be_bytes(magic),
516 comment: comment.to_owned(),
517 })
518 }
519 KeyTypeKind::Ecdsa => Err(Error::Unsupported),
520 KeyTypeKind::Rsa => Err(Error::Unsupported),
521 _ => Err(Error::KeyTypeMismatch),
522 }
523 }
524
525 pub fn from_path_with_passphrase<P: AsRef<Path>>(
527 path: P,
528 passphrase: Option<String>,
529 ) -> Result<Self> {
530 let mut contents = String::new();
531 File::open(path)?.read_to_string(&mut contents)?;
532
533 PrivateKey::from_string_with_passphrase(&contents, passphrase)
534 }
535
536 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
538 PrivateKey::from_path_with_passphrase(path, None)
539 }
540
541 pub fn from_string_with_passphrase(contents: &str, passphrase: Option<String>) -> Result<Self> {
543 let mut iter = contents.lines();
544 let header = iter.next().unwrap_or("");
545 if header != "-----BEGIN OPENSSH PRIVATE KEY-----" {
546 return Err(Error::InvalidFormat);
547 }
548
549 let mut encoded_key = String::new();
550 loop {
551 let part = match iter.next() {
552 Some(p) => p,
553 None => return Err(Error::InvalidFormat),
554 };
555
556 if part == "-----END OPENSSH PRIVATE KEY-----" {
557 break;
558 }
559 encoded_key.push_str(part);
560 }
561
562 let decoded = base64::decode(encoded_key)?;
563 let mut reader = Reader::new(&decoded);
564 let k = PrivateKey::from_reader(&mut reader, passphrase)?;
566
567 Ok(k)
568 }
569
570 pub fn from_string(contents: &str) -> Result<PrivateKey> {
572 PrivateKey::from_string_with_passphrase(contents, None)
573 }
574
575 pub fn from_bytes<T: ?Sized + AsRef<[u8]>>(buffer: &T) -> Result<PrivateKey> {
577 let mut reader = Reader::new(buffer);
578 PrivateKey::read_private_key(&mut reader)
579 }
580
581 pub(crate) fn from_reader(
583 reader: &mut Reader<'_>,
584 passphrase: Option<String>,
585 ) -> Result<PrivateKey> {
586 let preamble = reader.read_cstring()?;
587
588 if preamble != "openssh-key-v1" {
589 return Err(Error::InvalidFormat);
590 }
591
592 let cipher_name = reader.read_string()?;
594 let kdf = reader.read_string()?;
595
596 #[allow(unused_variables)]
597 let encryption_data = reader.read_bytes()?;
598
599 let number_of_keys = reader.read_u32()?;
601 if number_of_keys != 1 {
602 return Err(Error::InvalidFormat);
603 }
604
605 let pubkey = reader
607 .read_bytes()
608 .and_then(|v| PublicKey::from_bytes(&v))?;
609
610 let remaining_length = match reader.read_u32()?.try_into() {
611 Ok(rl) => rl,
612 Err(_) => return Err(Error::InvalidFormat),
613 };
614
615 #[allow(unused_mut)]
616 let mut remaining_bytes = reader.read_raw_bytes(remaining_length)?;
617
618 match (cipher_name.as_str(), kdf.as_str(), passphrase) {
619 ("none", "none", _) => (),
620 #[cfg(feature = "encrypted-keys")]
621 ("aes256-ctr", "bcrypt", Some(passphrase)) => {
622 let mut enc_reader = Reader::new(&encryption_data);
623 let salt = enc_reader.read_bytes()?;
624 let rounds = enc_reader.read_u32()?;
625 let mut output = [0; 48];
626 if bcrypt_pbkdf(passphrase.as_str(), &salt, rounds, &mut output).is_err() {
627 return Err(Error::InvalidFormat);
628 }
629
630 let mut cipher = Aes256Ctr::new(
631 GenericArray::from_slice(&output[..32]),
632 GenericArray::from_slice(&output[32..]),
633 );
634
635 match cipher.try_apply_keystream(&mut remaining_bytes) {
636 Ok(_) => (),
637 Err(_) => return Err(Error::InvalidFormat),
638 }
639 }
640 ("aes256-ctr", "bcrypt", None) => return Err(Error::EncryptedPrivateKey),
641 _ => return Err(Error::EncryptedPrivateKeyNotSupported),
642 };
643
644 let mut reader = Reader::new(&remaining_bytes);
645
646 let m1 = reader.read_u32()?;
649 let m2 = reader.read_u32()?;
650
651 if m1 != m2 {
652 return Err(Error::InvalidFormat);
653 }
654
655 let mut private_key = PrivateKey::read_private_key(&mut reader)?;
656 private_key.magic = m1;
657
658 if private_key.pubkey != pubkey {
659 return Err(Error::InvalidFormat);
660 }
661
662 Ok(private_key)
663 }
664
665 pub fn set_pin(&mut self, pin: &str) {
668 match &mut self.kind {
669 PrivateKeyKind::EcdsaSk(key) => {
670 key.pin = Some(pin.to_string());
671 }
672 PrivateKeyKind::Ed25519Sk(key) => {
673 key.pin = Some(pin.to_string());
674 }
675 _ => (),
676 };
677 }
678
679 pub fn set_device_path(&mut self, path: &str) {
682 match &mut self.kind {
683 PrivateKeyKind::EcdsaSk(key) => {
684 key.device_path = Some(path.to_string());
685 }
686 PrivateKeyKind::Ed25519Sk(key) => {
687 key.device_path = Some(path.to_string());
688 }
689 _ => (),
690 };
691 }
692
693 pub fn touch_requirement(&self) -> TouchRequirement {
697 match &self.kind {
698 PrivateKeyKind::EcdsaSk(key) => key.touch_requirement(),
699 PrivateKeyKind::Ed25519Sk(key) => key.touch_requirement(),
700 _ => TouchRequirement::NotRequired,
701 }
702 }
703
704 pub fn encode(&self) -> Vec<u8> {
706 let mut serializer = Writer::new();
707 serializer.write_cstring("openssh-key-v1"); serializer.write_string("none"); serializer.write_string("none"); serializer.write_bytes(&[]); serializer.write_u32(1); serializer.write_pub_key(&self.pubkey); let mut w = Writer::new();
715 w.write_u32(self.magic); w.write_u32(self.magic); w.write_string(self.pubkey.key_type.name);
719 match &self.kind {
720 PrivateKeyKind::Rsa(rsa) => {
721 w.write_mpint(&rsa.n); w.write_mpint(&rsa.e); w.write_mpint(&rsa.d);
724 w.write_mpint(&rsa.coefficient);
725 w.write_mpint(&rsa.p);
726 w.write_mpint(&rsa.q);
727 }
728 PrivateKeyKind::Ecdsa(ecdsa) => {
729 w.write_pub_key_data(&self.pubkey);
730 w.write_bytes(&ecdsa.key);
731 }
732 PrivateKeyKind::EcdsaSk(ecdsask) => {
733 w.write_pub_key_data(&self.pubkey);
734 w.write_raw_bytes(&[ecdsask.flags]);
735 w.write_bytes(&ecdsask.handle);
736 w.write_bytes(&[]);
737 }
738 PrivateKeyKind::Ed25519(ed25519) => {
739 w.write_pub_key_data(&self.pubkey);
740 w.write_bytes(&ed25519.key);
741 }
742 PrivateKeyKind::Ed25519Sk(ed25519sk) => {
743 w.write_pub_key_data(&self.pubkey);
744 w.write_raw_bytes(&[ed25519sk.flags]);
745 w.write_bytes(&ed25519sk.handle);
746 w.write_bytes(&[]);
747 }
748 };
749
750 w.write_string(&self.comment);
751
752 let pad_bytes = (8 - (w.as_bytes().len() % 8)) % 8;
755 let padding = vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7];
756 w.write_raw_bytes(&padding[..pad_bytes]);
757
758 serializer.write_bytes(w.as_bytes());
759 serializer.into_bytes()
760 }
761
762 pub fn write<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
764 let encoded = self.encode();
765 let data = base64::encode(&encoded);
766 let split = data
767 .chars()
768 .enumerate()
769 .flat_map(|(i, c)| {
770 if i != 0 && i % 70 == 0 {
771 Some('\n')
772 } else {
773 None
774 }
775 .into_iter()
776 .chain(std::iter::once(c))
777 })
778 .collect::<String>();
779 write!(
780 w,
781 "-----BEGIN OPENSSH PRIVATE KEY-----\n{}\n-----END OPENSSH PRIVATE KEY-----\n",
782 split
783 )
784 }
785}
786
787impl fmt::Display for PrivateKey {
788 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789 write!(f, "{} {}", &self.pubkey.fingerprint(), self.comment)
790 }
791}
792
793impl Drop for PrivateKey {
794 fn drop(&mut self) {
795 self.kind.zeroize();
796 }
797}