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