1use std::fs;
51use std::os::unix::fs::PermissionsExt;
52use std::path::{Path, PathBuf};
53
54use bytes::Bytes;
55use ndn_packet::{Name, tlv_type};
56use ndn_tlv::TlvWriter;
57use sha2::{Digest, Sha256};
58
59use crate::TrustError;
60
61#[derive(Debug, thiserror::Error)]
66pub enum FileTpmError {
67 #[error("I/O error: {0}")]
68 Io(#[from] std::io::Error),
69 #[error("key not found: {0}")]
70 KeyNotFound(String),
71 #[error("invalid key encoding: {0}")]
72 InvalidKey(String),
73 #[error("base64 decode error: {0}")]
74 Base64(String),
75 #[error("unsupported algorithm in tpm-file: {0}")]
76 UnsupportedAlgorithm(String),
77 #[error("signing error: {0}")]
78 Sign(String),
79}
80
81impl From<FileTpmError> for TrustError {
82 fn from(e: FileTpmError) -> Self {
83 TrustError::KeyStore(e.to_string())
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum TpmKeyKind {
93 Rsa,
96 EcdsaP256,
99 Ed25519,
103}
104
105impl TpmKeyKind {
106 fn extension(self) -> &'static str {
107 match self {
108 TpmKeyKind::Rsa | TpmKeyKind::EcdsaP256 => "privkey",
109 TpmKeyKind::Ed25519 => "privkey-ed25519",
110 }
111 }
112}
113
114fn name_wire_encode(name: &Name) -> Vec<u8> {
119 let mut w = TlvWriter::new();
120 w.write_nested(tlv_type::NAME, |w| {
121 for c in name.components() {
122 w.write_tlv(c.typ, &c.value);
123 }
124 });
125 w.finish().to_vec()
126}
127
128fn upper_hex(bytes: &[u8]) -> String {
131 let mut s = String::with_capacity(bytes.len() * 2);
132 for b in bytes {
133 s.push_str(&format!("{b:02X}"));
134 }
135 s
136}
137
138fn filename_stem(key_name: &Name) -> String {
142 let wire = name_wire_encode(key_name);
143 let digest = Sha256::digest(&wire);
144 upper_hex(&digest)
145}
146
147fn b64_encode(bytes: &[u8]) -> String {
150 use base64::Engine;
151 base64::engine::general_purpose::STANDARD.encode(bytes)
152}
153fn b64_decode(s: &str) -> Result<Vec<u8>, FileTpmError> {
154 use base64::Engine;
155 let cleaned: String = s.chars().filter(|c| !c.is_whitespace()).collect();
159 base64::engine::general_purpose::STANDARD
160 .decode(cleaned.as_bytes())
161 .map_err(|e| FileTpmError::Base64(e.to_string()))
162}
163
164pub struct FileTpm {
171 root: PathBuf,
172}
173
174impl FileTpm {
175 pub fn open(root: impl AsRef<Path>) -> Result<Self, FileTpmError> {
178 let root = root.as_ref().to_path_buf();
179 fs::create_dir_all(&root)?;
180 #[cfg(unix)]
184 {
185 let _ = fs::set_permissions(&root, fs::Permissions::from_mode(0o700));
186 }
187 Ok(Self { root })
188 }
189
190 pub fn open_default() -> Result<Self, FileTpmError> {
193 let dir = if let Ok(p) = std::env::var("TEST_HOME") {
194 PathBuf::from(p).join(".ndn").join("ndnsec-key-file")
195 } else if let Ok(p) = std::env::var("HOME") {
196 PathBuf::from(p).join(".ndn").join("ndnsec-key-file")
197 } else {
198 std::env::current_dir()?
199 .join(".ndn")
200 .join("ndnsec-key-file")
201 };
202 Self::open(dir)
203 }
204
205 pub fn locator(&self) -> String {
211 format!("tpm-file:{}", self.root.display())
215 }
216
217 fn path_for(&self, key_name: &Name, kind: TpmKeyKind) -> PathBuf {
219 let stem = filename_stem(key_name);
220 self.root.join(format!("{stem}.{}", kind.extension()))
221 }
222
223 pub fn save_raw(
231 &self,
232 key_name: &Name,
233 kind: TpmKeyKind,
234 der: &[u8],
235 ) -> Result<(), FileTpmError> {
236 let path = self.path_for(key_name, kind);
237 let body = b64_encode(der);
238 fs::write(&path, body.as_bytes())?;
239 #[cfg(unix)]
240 {
241 fs::set_permissions(&path, fs::Permissions::from_mode(0o400))?;
243 }
244 Ok(())
245 }
246
247 pub fn load_raw(&self, key_name: &Name) -> Result<(TpmKeyKind, Vec<u8>), FileTpmError> {
251 let stem = filename_stem(key_name);
252
253 let primary = self.root.join(format!("{stem}.privkey"));
256 if let Ok(body) = fs::read_to_string(&primary) {
257 let der = b64_decode(&body)?;
258 let kind = autodetect_pkcs1_or_sec1(&der)?;
259 return Ok((kind, der));
260 }
261
262 let secondary = self.root.join(format!("{stem}.privkey-ed25519"));
264 if let Ok(body) = fs::read_to_string(&secondary) {
265 let der = b64_decode(&body)?;
266 return Ok((TpmKeyKind::Ed25519, der));
267 }
268
269 Err(FileTpmError::KeyNotFound(format!("{key_name}")))
270 }
271
272 pub fn delete(&self, key_name: &Name) -> Result<(), FileTpmError> {
274 let stem = filename_stem(key_name);
275 for ext in ["privkey", "privkey-ed25519"] {
276 let p = self.root.join(format!("{stem}.{ext}"));
277 if p.exists() {
278 fs::remove_file(p)?;
279 }
280 }
281 Ok(())
282 }
283
284 pub fn has_key(&self, key_name: &Name) -> bool {
286 let stem = filename_stem(key_name);
287 self.root.join(format!("{stem}.privkey")).exists()
288 || self.root.join(format!("{stem}.privkey-ed25519")).exists()
289 }
290
291 pub fn generate_ed25519(&self, key_name: &Name) -> Result<[u8; 32], FileTpmError> {
297 use ed25519_dalek::SigningKey;
298 use ed25519_dalek::pkcs8::EncodePrivateKey;
299
300 let mut seed = [0u8; 32];
303 ring::rand::SecureRandom::fill(&ring::rand::SystemRandom::new(), &mut seed)
304 .map_err(|_| FileTpmError::Sign("rng failure".into()))?;
305 let sk = SigningKey::from_bytes(&seed);
306
307 let pkcs8 = sk
309 .to_pkcs8_der()
310 .map_err(|e| FileTpmError::InvalidKey(format!("ed25519 pkcs8: {e}")))?;
311 self.save_raw(key_name, TpmKeyKind::Ed25519, pkcs8.as_bytes())?;
312 Ok(seed)
313 }
314
315 pub fn sign(&self, key_name: &Name, region: &[u8]) -> Result<Bytes, FileTpmError> {
319 let (kind, der) = self.load_raw(key_name)?;
320 match kind {
321 TpmKeyKind::Rsa => sign_rsa(&der, region),
322 TpmKeyKind::EcdsaP256 => sign_ecdsa_p256(&der, region),
323 TpmKeyKind::Ed25519 => sign_ed25519(&der, region),
324 }
325 }
326
327 pub fn public_key(&self, key_name: &Name) -> Result<Vec<u8>, FileTpmError> {
331 let (kind, der) = self.load_raw(key_name)?;
332 match kind {
333 TpmKeyKind::Rsa => public_key_rsa(&der),
334 TpmKeyKind::EcdsaP256 => public_key_ecdsa_p256(&der),
335 TpmKeyKind::Ed25519 => public_key_ed25519(&der),
336 }
337 }
338
339 pub fn export_to_safebag(
360 &self,
361 key_name: &Name,
362 certificate: Bytes,
363 password: &[u8],
364 ) -> Result<crate::safe_bag::SafeBag, crate::safe_bag::SafeBagError> {
365 let (kind, der) = self.load_raw(key_name)?;
366 let pkcs8_der: Vec<u8> = match kind {
367 TpmKeyKind::Rsa => crate::safe_bag::rsa_pkcs1_to_pkcs8(&der)?,
368 TpmKeyKind::EcdsaP256 => crate::safe_bag::ec_sec1_to_pkcs8(&der)?,
369 TpmKeyKind::Ed25519 => der, };
371 crate::safe_bag::SafeBag::encrypt(certificate, &pkcs8_der, password)
372 }
373
374 pub fn import_from_safebag(
390 &self,
391 safebag: &crate::safe_bag::SafeBag,
392 key_name: &Name,
393 password: &[u8],
394 ) -> Result<Bytes, crate::safe_bag::SafeBagError> {
395 let pkcs8_der = safebag.decrypt_key(password)?;
396 let kind = crate::safe_bag::detect_pkcs8_algorithm(&pkcs8_der)?;
397 let on_disk: Vec<u8> = match kind {
398 TpmKeyKind::Rsa => crate::safe_bag::rsa_pkcs8_to_pkcs1(&pkcs8_der)?,
399 TpmKeyKind::EcdsaP256 => crate::safe_bag::ec_pkcs8_to_sec1(&pkcs8_der)?,
400 TpmKeyKind::Ed25519 => pkcs8_der, };
402 self.save_raw(key_name, kind, &on_disk)?;
403 Ok(safebag.certificate.clone())
404 }
405}
406
407fn autodetect_pkcs1_or_sec1(der: &[u8]) -> Result<TpmKeyKind, FileTpmError> {
424 if der.len() < 6 || der[0] != 0x30 {
425 return Err(FileTpmError::InvalidKey("not a DER SEQUENCE".into()));
426 }
427 let mut i = 1usize;
429 let len_byte = der[i];
430 i += 1;
431 if len_byte & 0x80 != 0 {
432 i += (len_byte & 0x7F) as usize;
433 }
434 if i + 3 > der.len() || der[i] != 0x02 || der[i + 1] != 0x01 {
436 return Err(FileTpmError::InvalidKey(
437 "inner version field missing".into(),
438 ));
439 }
440 let next_tag_idx = i + 3;
441 if next_tag_idx >= der.len() {
442 return Err(FileTpmError::InvalidKey("DER too short".into()));
443 }
444 match der[next_tag_idx] {
445 0x02 => Ok(TpmKeyKind::Rsa), 0x04 => Ok(TpmKeyKind::EcdsaP256), b => Err(FileTpmError::UnsupportedAlgorithm(format!(
448 "unknown second-element tag 0x{b:02x}"
449 ))),
450 }
451}
452
453fn sign_rsa(pkcs1_der: &[u8], region: &[u8]) -> Result<Bytes, FileTpmError> {
456 use pkcs1::DecodeRsaPrivateKey;
457 use rsa::sha2::{Digest, Sha256};
467 use rsa::{Pkcs1v15Sign, RsaPrivateKey};
468
469 let sk = RsaPrivateKey::from_pkcs1_der(pkcs1_der)
470 .map_err(|e| FileTpmError::InvalidKey(format!("rsa pkcs1: {e}")))?;
471
472 let hash = Sha256::digest(region);
475 let sig = sk
476 .sign(Pkcs1v15Sign::new::<Sha256>(), &hash)
477 .map_err(|e| FileTpmError::Sign(format!("rsa sign: {e}")))?;
478 Ok(Bytes::from(sig))
479}
480
481fn public_key_rsa(pkcs1_der: &[u8]) -> Result<Vec<u8>, FileTpmError> {
482 use pkcs1::DecodeRsaPrivateKey;
483 use pkcs8::EncodePublicKey;
484 use rsa::RsaPrivateKey;
485
486 let sk = RsaPrivateKey::from_pkcs1_der(pkcs1_der)
487 .map_err(|e| FileTpmError::InvalidKey(format!("rsa pkcs1: {e}")))?;
488 let pk = sk.to_public_key();
489 pk.to_public_key_der()
491 .map(|d| d.as_bytes().to_vec())
492 .map_err(|e| FileTpmError::InvalidKey(format!("rsa spki: {e}")))
493}
494
495pub(crate) fn parse_sec1_p256_priv_scalar(sec1: &[u8]) -> Result<[u8; 32], FileTpmError> {
515 if sec1.len() < 9 || sec1[0] != 0x30 {
516 return Err(FileTpmError::InvalidKey("not a SEC1 SEQUENCE".into()));
517 }
518 let mut i = 1usize;
519 let len_byte = sec1[i];
520 i += 1;
521 if len_byte & 0x80 != 0 {
522 i += (len_byte & 0x7F) as usize;
524 }
525 if i + 3 > sec1.len() || sec1[i] != 0x02 || sec1[i + 1] != 0x01 {
526 return Err(FileTpmError::InvalidKey("expected version INTEGER".into()));
527 }
528 i += 3; if i + 2 > sec1.len() || sec1[i] != 0x04 {
530 return Err(FileTpmError::InvalidKey(
531 "expected privateKey OCTET STRING".into(),
532 ));
533 }
534 let key_len = sec1[i + 1] as usize;
535 if key_len != 32 {
536 return Err(FileTpmError::InvalidKey(format!(
537 "expected 32-byte P-256 scalar, got {key_len}"
538 )));
539 }
540 i += 2;
541 if i + 32 > sec1.len() {
542 return Err(FileTpmError::InvalidKey(
543 "SEC1 truncated in privateKey".into(),
544 ));
545 }
546 let mut out = [0u8; 32];
547 out.copy_from_slice(&sec1[i..i + 32]);
548 Ok(out)
549}
550
551fn signing_key_from_sec1(sec1_der: &[u8]) -> Result<p256_ecdsa::ecdsa::SigningKey, FileTpmError> {
554 use p256_ecdsa::ecdsa::SigningKey;
555 let scalar = parse_sec1_p256_priv_scalar(sec1_der)?;
556 SigningKey::from_bytes((&scalar).into())
557 .map_err(|e| FileTpmError::InvalidKey(format!("ecdsa scalar: {e}")))
558}
559
560fn sign_ecdsa_p256(sec1_der: &[u8], region: &[u8]) -> Result<Bytes, FileTpmError> {
561 use p256_ecdsa::ecdsa::{Signature, signature::Signer};
562
563 let sk = signing_key_from_sec1(sec1_der)?;
564 let sig: Signature = sk.sign(region);
566 Ok(Bytes::from(sig.to_der().as_bytes().to_vec()))
567}
568
569fn public_key_ecdsa_p256(sec1_der: &[u8]) -> Result<Vec<u8>, FileTpmError> {
570 let sk = signing_key_from_sec1(sec1_der)?;
571 let point = sk.verifying_key().to_encoded_point(false);
573 let sec1_bytes = point.as_bytes();
574 debug_assert_eq!(sec1_bytes.len(), 65);
575 debug_assert_eq!(sec1_bytes[0], 0x04);
576 Ok(p256_spki_wrap(sec1_bytes))
577}
578
579fn p256_spki_wrap(sec1_uncompressed: &[u8]) -> Vec<u8> {
601 const PREFIX: [u8; 26] = [
602 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, ];
609 let mut out = Vec::with_capacity(PREFIX.len() + sec1_uncompressed.len());
610 out.extend_from_slice(&PREFIX);
611 out.extend_from_slice(sec1_uncompressed);
612 out
613}
614
615fn sign_ed25519(pkcs8_der: &[u8], region: &[u8]) -> Result<Bytes, FileTpmError> {
616 use ed25519_dalek::Signer;
617 use ed25519_dalek::SigningKey;
618 use ed25519_dalek::pkcs8::DecodePrivateKey;
619
620 let sk = SigningKey::from_pkcs8_der(pkcs8_der)
621 .map_err(|e| FileTpmError::InvalidKey(format!("ed25519 pkcs8: {e}")))?;
622 let sig = sk.sign(region);
623 Ok(Bytes::copy_from_slice(&sig.to_bytes()))
624}
625
626fn public_key_ed25519(pkcs8_der: &[u8]) -> Result<Vec<u8>, FileTpmError> {
627 use ed25519_dalek::SigningKey;
628 use ed25519_dalek::pkcs8::DecodePrivateKey;
629
630 let sk = SigningKey::from_pkcs8_der(pkcs8_der)
631 .map_err(|e| FileTpmError::InvalidKey(format!("ed25519 pkcs8: {e}")))?;
632 Ok(sk.verifying_key().to_bytes().to_vec())
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use ndn_packet::NameComponent;
639 use tempfile::tempdir;
640
641 fn comp(s: &'static str) -> NameComponent {
642 NameComponent::generic(Bytes::from_static(s.as_bytes()))
643 }
644 fn name(parts: &[&'static str]) -> Name {
645 Name::from_components(parts.iter().map(|p| comp(p)))
646 }
647
648 #[test]
649 fn filename_stem_is_uppercase_sha256_of_wire() {
650 let n = name(&["alice", "KEY", "k1"]);
653 let stem = filename_stem(&n);
654 let mut wire = Vec::new();
656 for c in n.components() {
659 wire.push(c.typ as u8);
660 wire.push(c.value.len() as u8);
661 wire.extend_from_slice(&c.value);
662 }
663 let inner_len = wire.len();
664 let mut full = Vec::new();
665 full.push(0x07);
666 full.push(inner_len as u8);
667 full.extend_from_slice(&wire);
668 let expected = upper_hex(&sha2::Sha256::digest(&full));
669 assert_eq!(stem, expected);
670 assert_eq!(stem.len(), 64);
672 assert!(
674 stem.chars()
675 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_lowercase())
676 );
677 }
678
679 #[test]
680 fn ed25519_save_load_sign_roundtrip() {
681 let dir = tempdir().unwrap();
682 let tpm = FileTpm::open(dir.path()).unwrap();
683 let kn = name(&["alice", "KEY", "k1"]);
684 let _seed = tpm.generate_ed25519(&kn).unwrap();
685 assert!(tpm.has_key(&kn));
686
687 let region = b"hello ndn-rs file tpm";
688 let sig = tpm.sign(&kn, region).unwrap();
689 assert_eq!(sig.len(), 64);
690
691 use ed25519_dalek::Verifier;
693 use ed25519_dalek::{Signature, VerifyingKey};
694 let pk_bytes = tpm.public_key(&kn).unwrap();
695 let pk = VerifyingKey::from_bytes(&pk_bytes.as_slice().try_into().unwrap()).unwrap();
696 let sig_obj = Signature::from_bytes(&sig.as_ref().try_into().unwrap());
697 pk.verify(region, &sig_obj).unwrap();
698 }
699
700 #[test]
701 fn ecdsa_p256_save_load_sign_roundtrip() {
702 use p256_ecdsa::SecretKey;
703
704 let dir = tempdir().unwrap();
705 let tpm = FileTpm::open(dir.path()).unwrap();
706 let kn = name(&["bob", "KEY", "k1"]);
707
708 let sk = SecretKey::random(&mut rand_core_compat());
713 let der = sk.to_sec1_der().unwrap();
714 tpm.save_raw(&kn, TpmKeyKind::EcdsaP256, der.as_slice())
715 .unwrap();
716
717 let (kind, _der) = tpm.load_raw(&kn).unwrap();
719 assert_eq!(kind, TpmKeyKind::EcdsaP256);
720
721 let region = b"ecdsa test region";
722 let sig = tpm.sign(&kn, region).unwrap();
723 assert!(!sig.is_empty(), "sig must be non-empty");
724
725 use p256_ecdsa::ecdsa::{Signature, VerifyingKey, signature::Verifier};
727 use pkcs8::DecodePublicKey;
728 let pk_der = tpm.public_key(&kn).unwrap();
729 let vk = VerifyingKey::from_public_key_der(&pk_der).unwrap();
730 let sig_obj = Signature::from_der(&sig).unwrap();
731 vk.verify(region, &sig_obj).unwrap();
732 }
733
734 #[test]
735 fn rsa_save_load_sign_roundtrip() {
736 use pkcs1::EncodeRsaPrivateKey;
737 use rsa::RsaPrivateKey;
738
739 let dir = tempdir().unwrap();
740 let tpm = FileTpm::open(dir.path()).unwrap();
741 let kn = name(&["carol", "KEY", "k1"]);
742
743 let mut rng = rand_core_compat();
745 let sk = RsaPrivateKey::new(&mut rng, 2048).unwrap();
746 let der = sk.to_pkcs1_der().unwrap();
747 tpm.save_raw(&kn, TpmKeyKind::Rsa, der.as_bytes()).unwrap();
748
749 let (kind, _) = tpm.load_raw(&kn).unwrap();
750 assert_eq!(kind, TpmKeyKind::Rsa);
751
752 let region = b"rsa test region";
753 let sig = tpm.sign(&kn, region).unwrap();
754 assert_eq!(sig.len(), 256);
756
757 use pkcs8::DecodePublicKey;
763 use rsa::sha2::{Digest, Sha256};
764 use rsa::{Pkcs1v15Sign, RsaPublicKey};
765 let pk_der = tpm.public_key(&kn).unwrap();
766 let pk = RsaPublicKey::from_public_key_der(&pk_der).unwrap();
767 let hash = Sha256::digest(region);
768 pk.verify(Pkcs1v15Sign::new::<Sha256>(), &hash, &sig)
769 .unwrap();
770 }
771
772 #[test]
773 fn delete_removes_both_extensions() {
774 let dir = tempdir().unwrap();
775 let tpm = FileTpm::open(dir.path()).unwrap();
776 let kn = name(&["alice", "KEY", "k1"]);
777 tpm.generate_ed25519(&kn).unwrap();
778 assert!(tpm.has_key(&kn));
779 tpm.delete(&kn).unwrap();
780 assert!(!tpm.has_key(&kn));
781 }
782
783 #[test]
784 fn load_missing_key_returns_not_found() {
785 let dir = tempdir().unwrap();
786 let tpm = FileTpm::open(dir.path()).unwrap();
787 let kn = name(&["nobody"]);
788 match tpm.load_raw(&kn) {
789 Err(FileTpmError::KeyNotFound(_)) => {}
790 other => panic!("expected KeyNotFound, got {other:?}"),
791 }
792 }
793
794 #[test]
795 fn locator_string_is_canonical() {
796 let dir = tempdir().unwrap();
797 let tpm = FileTpm::open(dir.path()).unwrap();
798 let loc = tpm.locator();
799 assert!(loc.starts_with("tpm-file:"));
800 assert!(loc.contains(&dir.path().display().to_string()));
801 }
802
803 #[test]
804 fn autodetect_distinguishes_rsa_and_ecdsa() {
805 let rsa_like = [0x30, 0x82, 0x01, 0x00, 0x02, 0x01, 0x00, 0x02, 0x82];
807 assert_eq!(
808 autodetect_pkcs1_or_sec1(&rsa_like).unwrap(),
809 TpmKeyKind::Rsa
810 );
811 let ec_like = [0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20];
813 assert_eq!(
814 autodetect_pkcs1_or_sec1(&ec_like).unwrap(),
815 TpmKeyKind::EcdsaP256
816 );
817 }
818
819 fn rand_core_compat() -> rsa::rand_core::OsRng {
824 rsa::rand_core::OsRng
825 }
826
827 fn fake_cert_bytes() -> Bytes {
843 use ndn_tlv::TlvWriter;
846 let mut w = TlvWriter::new();
847 w.write_tlv(0x06, b"placeholder cert body");
848 w.finish()
849 }
850
851 #[test]
852 fn safebag_ed25519_roundtrip() {
853 let dir_a = tempdir().unwrap();
854 let dir_b = tempdir().unwrap();
855 let tpm_a = FileTpm::open(dir_a.path()).unwrap();
856 let tpm_b = FileTpm::open(dir_b.path()).unwrap();
857 let kn = name(&["alice", "KEY", "k1"]);
858 let pw = b"transfer-password";
859
860 tpm_a.generate_ed25519(&kn).unwrap();
863 let region = b"hello safe bag";
864 let sig_a = tpm_a.sign(&kn, region).unwrap();
865
866 let sb = tpm_a.export_to_safebag(&kn, fake_cert_bytes(), pw).unwrap();
867 let wire = sb.encode();
868 let sb2 = crate::safe_bag::SafeBag::decode(&wire).unwrap();
869 let cert_back = tpm_b.import_from_safebag(&sb2, &kn, pw).unwrap();
870 assert_eq!(cert_back, fake_cert_bytes());
871
872 let sig_b = tpm_b.sign(&kn, region).unwrap();
875 assert_eq!(sig_a, sig_b, "imported Ed25519 must produce same sig");
876 }
877
878 #[test]
879 fn safebag_ecdsa_roundtrip() {
880 use p256_ecdsa::SecretKey;
881
882 let dir_a = tempdir().unwrap();
883 let dir_b = tempdir().unwrap();
884 let tpm_a = FileTpm::open(dir_a.path()).unwrap();
885 let tpm_b = FileTpm::open(dir_b.path()).unwrap();
886 let kn = name(&["bob", "KEY", "k1"]);
887 let pw = b"transfer-password";
888
889 let sk = SecretKey::random(&mut rand_core_compat());
891 let der = sk.to_sec1_der().unwrap();
892 tpm_a
893 .save_raw(&kn, TpmKeyKind::EcdsaP256, der.as_slice())
894 .unwrap();
895
896 let sb = tpm_a.export_to_safebag(&kn, fake_cert_bytes(), pw).unwrap();
898 let wire = sb.encode();
899 let sb2 = crate::safe_bag::SafeBag::decode(&wire).unwrap();
900 tpm_b.import_from_safebag(&sb2, &kn, pw).unwrap();
901
902 let region = b"ecdsa safe bag region";
905 let sig_b = tpm_b.sign(&kn, region).unwrap();
906
907 use p256_ecdsa::ecdsa::{Signature, VerifyingKey, signature::Verifier};
911 use pkcs8::DecodePublicKey;
912 let pk_a_der = tpm_a.public_key(&kn).unwrap();
913 let vk_a = VerifyingKey::from_public_key_der(&pk_a_der).unwrap();
914 let sig_obj = Signature::from_der(&sig_b).unwrap();
915 vk_a.verify(region, &sig_obj)
916 .expect("imported ECDSA signature must verify against original public key");
917 }
918
919 #[test]
920 fn safebag_rsa_roundtrip() {
921 use pkcs1::EncodeRsaPrivateKey;
922 use rsa::RsaPrivateKey;
923
924 let dir_a = tempdir().unwrap();
925 let dir_b = tempdir().unwrap();
926 let tpm_a = FileTpm::open(dir_a.path()).unwrap();
927 let tpm_b = FileTpm::open(dir_b.path()).unwrap();
928 let kn = name(&["carol", "KEY", "k1"]);
929 let pw = b"transfer-password";
930
931 let mut rng = rand_core_compat();
933 let sk = RsaPrivateKey::new(&mut rng, 1024).unwrap();
934 let der = sk.to_pkcs1_der().unwrap();
935 tpm_a
936 .save_raw(&kn, TpmKeyKind::Rsa, der.as_bytes())
937 .unwrap();
938
939 let sb = tpm_a.export_to_safebag(&kn, fake_cert_bytes(), pw).unwrap();
940 let wire = sb.encode();
941 let sb2 = crate::safe_bag::SafeBag::decode(&wire).unwrap();
942 tpm_b.import_from_safebag(&sb2, &kn, pw).unwrap();
943
944 let region = b"rsa safe bag region";
947 let sig_a = tpm_a.sign(&kn, region).unwrap();
948 let sig_b = tpm_b.sign(&kn, region).unwrap();
949 assert_eq!(
950 sig_a, sig_b,
951 "imported RSA must produce same deterministic sig"
952 );
953 }
954
955 #[test]
956 fn safebag_wrong_password_fails_import() {
957 let dir_a = tempdir().unwrap();
958 let dir_b = tempdir().unwrap();
959 let tpm_a = FileTpm::open(dir_a.path()).unwrap();
960 let tpm_b = FileTpm::open(dir_b.path()).unwrap();
961 let kn = name(&["alice", "KEY", "k1"]);
962
963 tpm_a.generate_ed25519(&kn).unwrap();
964 let sb = tpm_a
965 .export_to_safebag(&kn, fake_cert_bytes(), b"correct")
966 .unwrap();
967
968 match tpm_b.import_from_safebag(&sb, &kn, b"wrong") {
969 Err(crate::safe_bag::SafeBagError::Pkcs8(_)) => {}
970 other => panic!("expected Pkcs8 decrypt error, got {other:?}"),
971 }
972 }
973}