1pub(crate) mod certs;
6pub(crate) mod err;
7pub(crate) mod ssh;
8
9use std::io::{self};
10use std::path::Path;
11use std::result::Result as StdResult;
12use std::str::FromStr;
13use std::sync::Arc;
14
15use crate::keystore::fs_utils::{checked_op, FilesystemAction, FilesystemError, RelKeyPath};
16use crate::keystore::{EncodableItem, ErasedKey, KeySpecifier, Keystore};
17use crate::{
18 arti_path, ArtiPath, ArtiPathUnavailableError, KeyPath, KeystoreId, Result,
19 UnknownKeyTypeError, UnrecognizedEntryError, UnrecognizedEntryId,
20};
21use certs::UnparsedCert;
22use err::ArtiNativeKeystoreError;
23use ssh::UnparsedOpenSshKey;
24
25use fs_mistrust::{CheckedDir, Mistrust};
26use itertools::Itertools;
27use tor_error::internal;
28use walkdir::WalkDir;
29
30use tor_basic_utils::PathExt as _;
31use tor_key_forge::{CertData, KeystoreItem, KeystoreItemType};
32
33use super::KeystoreEntryResult;
34
35#[derive(Debug)]
55pub struct ArtiNativeKeystore {
56 keystore_dir: CheckedDir,
60 id: KeystoreId,
62}
63
64impl ArtiNativeKeystore {
65 pub fn from_path_and_mistrust(
73 keystore_dir: impl AsRef<Path>,
74 mistrust: &Mistrust,
75 ) -> Result<Self> {
76 let keystore_dir = mistrust
77 .verifier()
78 .check_content()
79 .make_secure_dir(&keystore_dir)
80 .map_err(|e| FilesystemError::FsMistrust {
81 action: FilesystemAction::Init,
82 path: keystore_dir.as_ref().into(),
83 err: e.into(),
84 })
85 .map_err(ArtiNativeKeystoreError::Filesystem)?;
86
87 let id = KeystoreId::from_str("arti")?;
89 Ok(Self { keystore_dir, id })
90 }
91
92 fn rel_path(
95 &self,
96 key_spec: &dyn KeySpecifier,
97 item_type: &KeystoreItemType,
98 ) -> StdResult<RelKeyPath, ArtiPathUnavailableError> {
99 RelKeyPath::arti(&self.keystore_dir, key_spec, item_type)
100 }
101}
102
103macro_rules! rel_path_if_supported {
109 ($res:expr, $ret:expr) => {{
110 use ArtiPathUnavailableError::*;
111
112 match $res {
113 Ok(path) => path,
114 Err(ArtiPathUnavailable) => return $ret,
115 Err(e) => return Err(tor_error::internal!("invalid ArtiPath: {e}").into()),
116 }
117 }};
118}
119
120impl Keystore for ArtiNativeKeystore {
121 fn id(&self) -> &KeystoreId {
122 &self.id
123 }
124
125 fn contains(&self, key_spec: &dyn KeySpecifier, item_type: &KeystoreItemType) -> Result<bool> {
126 let path = rel_path_if_supported!(self.rel_path(key_spec, item_type), Ok(false));
127
128 let meta = match checked_op!(metadata, path) {
129 Ok(meta) => meta,
130 Err(fs_mistrust::Error::NotFound(_)) => return Ok(false),
131 Err(e) => {
132 return Err(FilesystemError::FsMistrust {
133 action: FilesystemAction::Read,
134 path: path.rel_path_unchecked().into(),
135 err: e.into(),
136 })
137 .map_err(|e| ArtiNativeKeystoreError::Filesystem(e).into());
138 }
139 };
140
141 if meta.is_file() {
143 Ok(true)
144 } else {
145 Err(
146 ArtiNativeKeystoreError::Filesystem(FilesystemError::NotARegularFile(
147 path.rel_path_unchecked().into(),
148 ))
149 .into(),
150 )
151 }
152 }
153
154 fn get(
155 &self,
156 key_spec: &dyn KeySpecifier,
157 item_type: &KeystoreItemType,
158 ) -> Result<Option<ErasedKey>> {
159 let path = rel_path_if_supported!(self.rel_path(key_spec, item_type), Ok(None));
160
161 let inner = match checked_op!(read, path) {
162 Err(fs_mistrust::Error::NotFound(_)) => return Ok(None),
163 res => res
164 .map_err(|err| FilesystemError::FsMistrust {
165 action: FilesystemAction::Read,
166 path: path.rel_path_unchecked().into(),
167 err: err.into(),
168 })
169 .map_err(ArtiNativeKeystoreError::Filesystem)?,
170 };
171
172 let abs_path = path
173 .checked_path()
174 .map_err(ArtiNativeKeystoreError::Filesystem)?;
175
176 match item_type {
177 KeystoreItemType::Key(key_type) => {
178 let inner = String::from_utf8(inner).map_err(|_| {
179 let err = io::Error::new(
180 io::ErrorKind::InvalidData,
181 "OpenSSH key is not valid UTF-8".to_string(),
182 );
183
184 ArtiNativeKeystoreError::Filesystem(FilesystemError::Io {
185 action: FilesystemAction::Read,
186 path: abs_path.clone(),
187 err: err.into(),
188 })
189 })?;
190
191 UnparsedOpenSshKey::new(inner, abs_path)
192 .parse_ssh_format_erased(key_type)
193 .map(Some)
194 }
195 KeystoreItemType::Cert(cert_type) => UnparsedCert::new(inner, abs_path)
196 .parse_certificate_erased(cert_type)
197 .map(Some),
198 KeystoreItemType::Unknown { arti_extension } => Err(
199 ArtiNativeKeystoreError::UnknownKeyType(UnknownKeyTypeError {
200 arti_extension: arti_extension.clone(),
201 })
202 .into(),
203 ),
204 _ => Err(internal!("unknown item type {item_type:?}").into()),
205 }
206 }
207
208 fn insert(&self, key: &dyn EncodableItem, key_spec: &dyn KeySpecifier) -> Result<()> {
209 let keystore_item = key.as_keystore_item()?;
210 let item_type = keystore_item.item_type()?;
211 let path = self
212 .rel_path(key_spec, &item_type)
213 .map_err(|e| tor_error::internal!("{e}"))?;
214 let unchecked_path = path.rel_path_unchecked();
215
216 if let Some(parent) = unchecked_path.parent() {
218 self.keystore_dir
219 .make_directory(parent)
220 .map_err(|err| FilesystemError::FsMistrust {
221 action: FilesystemAction::Write,
222 path: parent.to_path_buf(),
223 err: err.into(),
224 })
225 .map_err(ArtiNativeKeystoreError::Filesystem)?;
226 }
227
228 let item_bytes: Vec<u8> = match keystore_item {
229 KeystoreItem::Key(key) => {
230 let comment = "";
232 key.to_openssh_string(comment)?.into_bytes()
233 }
234 KeystoreItem::Cert(cert) => match cert {
235 CertData::TorEd25519Cert(cert) => cert.into(),
236 _ => return Err(internal!("unknown cert type {item_type:?}").into()),
237 },
238 _ => return Err(internal!("unknown item type {item_type:?}").into()),
239 };
240
241 Ok(checked_op!(write_and_replace, path, item_bytes)
242 .map_err(|err| FilesystemError::FsMistrust {
243 action: FilesystemAction::Write,
244 path: unchecked_path.into(),
245 err: err.into(),
246 })
247 .map_err(ArtiNativeKeystoreError::Filesystem)?)
248 }
249
250 fn remove(
251 &self,
252 key_spec: &dyn KeySpecifier,
253 item_type: &KeystoreItemType,
254 ) -> Result<Option<()>> {
255 let rel_path = self
256 .rel_path(key_spec, item_type)
257 .map_err(|e| tor_error::internal!("{e}"))?;
258
259 match checked_op!(remove_file, rel_path) {
260 Ok(()) => Ok(Some(())),
261 Err(fs_mistrust::Error::NotFound(_)) => Ok(None),
262 Err(e) => Err(ArtiNativeKeystoreError::Filesystem(
263 FilesystemError::FsMistrust {
264 action: FilesystemAction::Remove,
265 path: rel_path.rel_path_unchecked().into(),
266 err: e.into(),
267 },
268 ))?,
269 }
270 }
271
272 fn list(&self) -> Result<Vec<KeystoreEntryResult<(KeyPath, KeystoreItemType)>>> {
273 WalkDir::new(self.keystore_dir.as_path())
274 .into_iter()
275 .map(|entry| {
276 let entry = entry
277 .map_err(|e| {
278 let msg = e.to_string();
279 FilesystemError::Io {
280 action: FilesystemAction::Read,
281 path: self.keystore_dir.as_path().into(),
282 err: e
283 .into_io_error()
284 .unwrap_or_else(|| io::Error::other(msg.to_string()))
285 .into(),
286 }
287 })
288 .map_err(ArtiNativeKeystoreError::Filesystem)?;
289
290 let path = entry.path();
291
292 if entry.file_type().is_dir() {
296 return Ok(None);
297 }
298
299 let path = path
300 .strip_prefix(self.keystore_dir.as_path())
301 .map_err(|_| {
302 tor_error::internal!(
304 "found key {} outside of keystore_dir {}?!",
305 path.display_lossy(),
306 self.keystore_dir.as_path().display_lossy()
307 )
308 })?;
309
310 if let Some(parent) = path.parent() {
311 self.keystore_dir
314 .read_directory(parent)
315 .map_err(|e| FilesystemError::FsMistrust {
316 action: FilesystemAction::Read,
317 path: parent.into(),
318 err: e.into(),
319 })
320 .map_err(ArtiNativeKeystoreError::Filesystem)?;
321 }
322
323 let unrecognized_entry_err = |path: &Path, err| {
324 let error = ArtiNativeKeystoreError::MalformedPath {
325 path: path.into(),
326 err,
327 };
328 let entry = UnrecognizedEntryId::Path(path.into());
329 Some(Err(UnrecognizedEntryError::new(entry, Arc::new(error))))
330 };
331
332 let Some(ext) = path.extension() else {
333 return Ok(unrecognized_entry_err(
334 path,
335 err::MalformedPathError::NoExtension,
336 ));
337 };
338
339 let Some(extension) = ext.to_str() else {
340 return Ok(unrecognized_entry_err(path, err::MalformedPathError::Utf8));
341 };
342
343 let item_type = KeystoreItemType::from(extension);
344 let p = path.with_extension("");
346 let slugs = p
348 .components()
349 .map(|component| component.as_os_str().to_string_lossy())
350 .collect::<Vec<_>>()
351 .join(&arti_path::PATH_SEP.to_string());
352 let opt = match ArtiPath::new(slugs) {
353 Ok(path) => Some(Ok((path.into(), item_type))),
354 Err(e) => {
355 unrecognized_entry_err(path, err::MalformedPathError::InvalidArtiPath(e))
356 }
357 };
358 Ok(opt)
359 })
360 .flatten_ok()
361 .collect()
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 #![allow(clippy::bool_assert_comparison)]
369 #![allow(clippy::clone_on_copy)]
370 #![allow(clippy::dbg_macro)]
371 #![allow(clippy::mixed_attributes_style)]
372 #![allow(clippy::print_stderr)]
373 #![allow(clippy::print_stdout)]
374 #![allow(clippy::single_char_pattern)]
375 #![allow(clippy::unwrap_used)]
376 #![allow(clippy::unchecked_duration_subtraction)]
377 #![allow(clippy::useless_vec)]
378 #![allow(clippy::needless_pass_by_value)]
379 use super::*;
381 use crate::test_utils::ssh_keys::*;
382 use crate::test_utils::sshkeygen_ed25519_strings;
383 use crate::test_utils::TEST_SPECIFIER_PATH;
384 use crate::test_utils::{assert_found, TestSpecifier};
385 use crate::KeyPath;
386 use std::cmp::Ordering;
387 use std::fs;
388 use std::path::PathBuf;
389 use std::time::{Duration, SystemTime};
390 use tempfile::{tempdir, TempDir};
391 use tor_cert::{CertifiedKey, Ed25519Cert};
392 use tor_checkable::{SelfSigned, Timebound};
393 use tor_key_forge::{CertType, KeyType, ParsedEd25519Cert};
394 use tor_llcrypto::pk::ed25519::{self, Ed25519PublicKey as _};
395
396 #[cfg(unix)]
397 use std::os::unix::fs::PermissionsExt;
398
399 impl Ord for KeyPath {
400 fn cmp(&self, other: &Self) -> Ordering {
401 match (self, other) {
402 (KeyPath::Arti(path1), KeyPath::Arti(path2)) => path1.cmp(path2),
403 _ => unimplemented!("not supported"),
404 }
405 }
406 }
407
408 impl PartialOrd for KeyPath {
409 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
410 Some(self.cmp(other))
411 }
412 }
413
414 fn key_path(key_store: &ArtiNativeKeystore, key_type: &KeyType) -> PathBuf {
415 let rel_key_path = key_store
416 .rel_path(&TestSpecifier::default(), &key_type.clone().into())
417 .unwrap();
418
419 rel_key_path.checked_path().unwrap()
420 }
421
422 fn init_keystore(gen_keys: bool) -> (ArtiNativeKeystore, TempDir) {
423 let keystore_dir = tempdir().unwrap();
424
425 #[cfg(unix)]
426 fs::set_permissions(&keystore_dir, fs::Permissions::from_mode(0o700)).unwrap();
427
428 let key_store =
429 ArtiNativeKeystore::from_path_and_mistrust(&keystore_dir, &Mistrust::default())
430 .unwrap();
431
432 if gen_keys {
433 let key_path = key_path(&key_store, &KeyType::Ed25519Keypair);
434 let parent = key_path.parent().unwrap();
435 fs::create_dir_all(parent).unwrap();
436 #[cfg(unix)]
437 fs::set_permissions(parent, fs::Permissions::from_mode(0o700)).unwrap();
438
439 fs::write(key_path, ED25519_OPENSSH).unwrap();
440 }
441
442 (key_store, keystore_dir)
443 }
444
445 macro_rules! assert_contains_arti_paths {
447 ($expected:expr, $list:expr) => {{
448 let mut expected = Vec::from_iter($expected.iter().cloned().map(KeyPath::Arti));
449 expected.sort();
450
451 let mut sorted_list = $list
452 .iter()
453 .filter_map(|entry| {
454 if let Ok((path, _)) = entry {
455 Some(path.clone())
456 } else {
457 None
458 }
459 })
460 .collect::<Vec<_>>();
461 sorted_list.sort();
462
463 assert_eq!(expected, sorted_list);
464 }};
465 }
466
467 #[test]
468 #[cfg(unix)]
469 fn init_failure_perms() {
470 use std::os::unix::fs::PermissionsExt;
471
472 let keystore_dir = tempdir().unwrap();
473
474 let mode = 0o777;
476
477 fs::set_permissions(&keystore_dir, fs::Permissions::from_mode(mode)).unwrap();
478 let err = ArtiNativeKeystore::from_path_and_mistrust(&keystore_dir, &Mistrust::default())
479 .expect_err(&format!("expected failure (perms = {mode:o})"));
480
481 assert_eq!(
482 err.to_string(),
483 format!(
484 "Inaccessible path or bad permissions on {} while attempting to Init",
485 keystore_dir.path().display_lossy()
486 ),
487 "expected keystore init failure (perms = {:o})",
488 mode
489 );
490 }
491
492 #[test]
493 fn key_path_repr() {
494 let (key_store, _) = init_keystore(false);
495
496 assert_eq!(
497 key_store
498 .rel_path(&TestSpecifier::default(), &KeyType::Ed25519Keypair.into())
499 .unwrap()
500 .rel_path_unchecked(),
501 PathBuf::from("parent1/parent2/parent3/test-specifier.ed25519_private")
502 );
503
504 assert_eq!(
505 key_store
506 .rel_path(
507 &TestSpecifier::default(),
508 &KeyType::X25519StaticKeypair.into()
509 )
510 .unwrap()
511 .rel_path_unchecked(),
512 PathBuf::from("parent1/parent2/parent3/test-specifier.x25519_private")
513 );
514 }
515
516 #[cfg(unix)]
517 #[test]
518 fn get_and_rm_bad_perms() {
519 use std::os::unix::fs::PermissionsExt;
520
521 let (key_store, _keystore_dir) = init_keystore(true);
522
523 let key_path = key_path(&key_store, &KeyType::Ed25519Keypair);
524
525 fs::set_permissions(&key_path, fs::Permissions::from_mode(0o777)).unwrap();
527 assert!(key_store
528 .get(&TestSpecifier::default(), &KeyType::Ed25519Keypair.into())
529 .is_err());
530
531 fs::set_permissions(
533 key_path.parent().unwrap(),
534 fs::Permissions::from_mode(0o777),
535 )
536 .unwrap();
537
538 assert!(key_store.list().is_err());
539
540 let key_spec = TestSpecifier::default();
541 let ed_key_type = &KeyType::Ed25519Keypair.into();
542 assert_eq!(
543 key_store
544 .remove(&key_spec, ed_key_type)
545 .unwrap_err()
546 .to_string(),
547 format!(
548 "Inaccessible path or bad permissions on {} while attempting to Remove",
549 key_store
550 .rel_path(&key_spec, ed_key_type)
551 .unwrap()
552 .rel_path_unchecked()
553 .display_lossy()
554 ),
555 );
556 }
557
558 #[test]
559 fn get() {
560 let (key_store, _keystore_dir) = init_keystore(false);
562
563 let mut expected_arti_paths = Vec::new();
564
565 assert_found!(
567 key_store,
568 &TestSpecifier::default(),
569 &KeyType::Ed25519Keypair,
570 false
571 );
572 assert!(key_store.list().unwrap().is_empty());
573
574 let (key_store, _keystore_dir) = init_keystore(true);
576
577 expected_arti_paths.push(TestSpecifier::default().arti_path().unwrap());
578
579 assert_found!(
581 key_store,
582 &TestSpecifier::default(),
583 &KeyType::Ed25519Keypair,
584 true
585 );
586
587 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
588 }
589
590 #[test]
591 fn insert() {
592 let (key_store, keystore_dir) = init_keystore(false);
594
595 let mut expected_arti_paths = Vec::new();
596
597 assert_found!(
599 key_store,
600 &TestSpecifier::default(),
601 &KeyType::Ed25519Keypair,
602 false
603 );
604 assert!(key_store.list().unwrap().is_empty());
605
606 let mut keys_and_specs = vec![(ED25519_OPENSSH.into(), TestSpecifier::default())];
607
608 if let Ok((key, _)) = sshkeygen_ed25519_strings() {
609 keys_and_specs.push((key, TestSpecifier::new("-sshkeygen")));
610 }
611
612 for (i, (key, key_spec)) in keys_and_specs.iter().enumerate() {
613 let key = UnparsedOpenSshKey::new(key.into(), PathBuf::from("/test/path"));
615 let erased_kp = key
616 .parse_ssh_format_erased(&KeyType::Ed25519Keypair)
617 .unwrap();
618
619 let Ok(key) = erased_kp.downcast::<ed25519::Keypair>() else {
620 panic!("failed to downcast key to ed25519::Keypair")
621 };
622
623 let path = keystore_dir.as_ref().join(
624 key_store
625 .rel_path(key_spec, &KeyType::Ed25519Keypair.into())
626 .unwrap()
627 .rel_path_unchecked(),
628 );
629
630 assert_eq!(!path.parent().unwrap().try_exists().unwrap(), i == 0);
633
634 assert!(key_store.insert(&*key, key_spec).is_ok());
635
636 expected_arti_paths.push(key_spec.arti_path().unwrap());
638
639 assert!(path.parent().unwrap().try_exists().unwrap());
641
642 assert_found!(key_store, key_spec, &KeyType::Ed25519Keypair, true);
644
645 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
647 }
648 }
649
650 #[test]
651 fn remove() {
652 let (key_store, _keystore_dir) = init_keystore(true);
654
655 let mut expected_arti_paths = vec![TestSpecifier::default().arti_path().unwrap()];
656 let mut specs = vec![TestSpecifier::default()];
657
658 assert_found!(
659 key_store,
660 &TestSpecifier::default(),
661 &KeyType::Ed25519Keypair,
662 true
663 );
664
665 if let Ok((key, _)) = sshkeygen_ed25519_strings() {
666 let key = UnparsedOpenSshKey::new(key, PathBuf::from("/test/path"));
668 let erased_kp = key
669 .parse_ssh_format_erased(&KeyType::Ed25519Keypair)
670 .unwrap();
671
672 let Ok(key) = erased_kp.downcast::<ed25519::Keypair>() else {
673 panic!("failed to downcast key to ed25519::Keypair")
674 };
675
676 let key_spec = TestSpecifier::new("-sshkeygen");
677
678 assert!(key_store.insert(&*key, &key_spec).is_ok());
679
680 expected_arti_paths.push(key_spec.arti_path().unwrap());
681 specs.push(key_spec);
682 }
683
684 let ed_key_type = &KeyType::Ed25519Keypair.into();
685
686 for spec in specs {
687 assert_found!(key_store, &spec, &KeyType::Ed25519Keypair, true);
689
690 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
692
693 assert_eq!(key_store.remove(&spec, ed_key_type).unwrap(), Some(()));
695
696 expected_arti_paths.retain(|arti_path| *arti_path != spec.arti_path().unwrap());
698
699 assert_found!(key_store, &spec, &KeyType::Ed25519Keypair, false);
701
702 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
704
705 assert!(key_store.remove(&spec, ed_key_type).unwrap().is_none());
707 }
708
709 assert!(key_store.list().unwrap().is_empty());
710 }
711
712 #[test]
713 fn list() {
714 let (key_store, keystore_dir) = init_keystore(true);
716
717 let mut expected_arti_paths = vec![TestSpecifier::default().arti_path().unwrap()];
718
719 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
720
721 let mut keys_and_specs =
722 vec![(ED25519_OPENSSH.into(), TestSpecifier::new("-i-am-a-suffix"))];
723
724 if let Ok((key, _)) = sshkeygen_ed25519_strings() {
725 keys_and_specs.push((key, TestSpecifier::new("-sshkeygen")));
726 }
727
728 for (key, key_spec) in keys_and_specs {
730 let key = UnparsedOpenSshKey::new(key, PathBuf::from("/test/path"));
731 let erased_kp = key
732 .parse_ssh_format_erased(&KeyType::Ed25519Keypair)
733 .unwrap();
734
735 let Ok(key) = erased_kp.downcast::<ed25519::Keypair>() else {
736 panic!("failed to downcast key to ed25519::Keypair")
737 };
738
739 assert!(key_store.insert(&*key, &key_spec).is_ok());
740
741 expected_arti_paths.push(key_spec.arti_path().unwrap());
742
743 assert_contains_arti_paths!(expected_arti_paths, key_store.list().unwrap());
744 }
745
746 let _ = fs::File::create(keystore_dir.path().join(TEST_SPECIFIER_PATH)).unwrap();
748 let entries = key_store.list().unwrap();
749 let mut unrecognized_entries = entries.iter().filter_map(|e| {
750 let Err(entry) = e else {
751 return None;
752 };
753 #[allow(irrefutable_let_patterns)]
754 let UnrecognizedEntryId::Path(path) = entry.entry() else {
755 return None;
756 };
757 Some(path)
758 });
759 assert_eq!(
760 unrecognized_entries.next().unwrap(),
761 Path::new(TEST_SPECIFIER_PATH)
762 );
763 assert!(unrecognized_entries.next().is_none());
764 }
765
766 #[test]
767 fn key_path_not_regular_file() {
768 let (key_store, _keystore_dir) = init_keystore(false);
769
770 let key_path = key_path(&key_store, &KeyType::Ed25519Keypair);
771 fs::create_dir_all(&key_path).unwrap();
773 assert!(key_path.try_exists().unwrap());
774 let parent = key_path.parent().unwrap();
775 #[cfg(unix)]
776 fs::set_permissions(parent, fs::Permissions::from_mode(0o700)).unwrap();
777
778 let err = key_store
779 .contains(&TestSpecifier::default(), &KeyType::Ed25519Keypair.into())
780 .unwrap_err();
781 assert!(err.to_string().contains("not a regular file"), "{err}");
782 }
783
784 #[test]
785 fn certs() {
786 let (key_store, _keystore_dir) = init_keystore(false);
787
788 let mut rng = rand::rng();
789 let subject_key = ed25519::Keypair::generate(&mut rng);
790 let signing_key = ed25519::Keypair::generate(&mut rng);
791
792 let cert_exp = SystemTime::UNIX_EPOCH + Duration::from_secs(60 * 60);
795
796 let encoded_cert = Ed25519Cert::constructor()
797 .cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
798 .expiration(cert_exp)
799 .signing_key(signing_key.public_key().into())
800 .cert_key(CertifiedKey::Ed25519(subject_key.public_key().into()))
801 .encode_and_sign(&signing_key)
802 .unwrap();
803
804 let cert_spec = TestSpecifier::default();
806 assert!(key_store.insert(&encoded_cert, &cert_spec).is_ok());
807
808 let erased_cert = key_store
809 .get(&cert_spec, &CertType::Ed25519TorCert.into())
810 .unwrap()
811 .unwrap();
812 let Ok(found_cert) = erased_cert.downcast::<ParsedEd25519Cert>() else {
813 panic!("failed to downcast cert to KewUnknownCert")
814 };
815
816 let found_cert = found_cert
817 .should_be_signed_with(&signing_key.public_key().into())
818 .unwrap()
819 .dangerously_assume_wellsigned()
820 .dangerously_assume_timely();
821
822 assert_eq!(
823 found_cert.as_ref().cert_type(),
824 tor_cert::CertType::IDENTITY_V_SIGNING
825 );
826 assert_eq!(found_cert.as_ref().expiry(), cert_exp);
827 assert_eq!(
828 found_cert.as_ref().signing_key(),
829 Some(&signing_key.public_key().into())
830 );
831 assert_eq!(
832 found_cert.subject_key().unwrap(),
833 &subject_key.public_key().into()
834 );
835 }
836}