1#![allow(clippy::missing_safety_doc)]
4use anyhow::Context as _;
5use libc::{c_char, c_int, size_t};
6use openpgp::cert::prelude::*;
7use openpgp::parse::{stream::*, Parse};
8use openpgp::policy::StandardPolicy;
9use openpgp::serialize::stream::{LiteralWriter, Message, Signer};
10use openpgp::KeyHandle;
11use sequoia_cert_store::{Store as _, StoreUpdate as _};
12use sequoia_openpgp as openpgp;
13use sequoia_policy_config::ConfiguredStandardPolicy;
14use std::ffi::{CStr, CString, OsStr};
15use std::fs;
16use std::io::{Read, Write};
17use std::os::unix::ffi::OsStrExt;
18use std::path::Path;
19use std::ptr;
20use std::slice;
21use std::sync::Arc;
22
23use crate::{set_error_from, SequoiaError};
24
25pub struct SequoiaMechanism<'a> {
26 keystore: Option<sequoia_keystore::Keystore>,
27 certstore: Arc<sequoia_cert_store::CertStore<'a>>,
28 policy: StandardPolicy<'a>,
29}
30
31impl<'a> SequoiaMechanism<'a> {
32 fn from_directory(dir: Option<impl AsRef<Path>>) -> Result<Self, anyhow::Error> {
33 let home_path = dir.map(|s| s.as_ref().to_path_buf());
34 let sequoia_home = sequoia_directories::Home::new(home_path)?;
35
36 let keystore_dir = sequoia_home.data_dir(sequoia_directories::Component::Keystore);
37 let context = sequoia_keystore::Context::configure()
38 .home(&keystore_dir)
39 .build()?;
41 let keystore = sequoia_keystore::Keystore::connect(&context)?;
42
43 let certstore_dir = sequoia_home.data_dir(sequoia_directories::Component::CertD);
44 fs::create_dir_all(&certstore_dir)?;
45 let certstore = sequoia_cert_store::CertStore::open(&certstore_dir)?;
47
48 let policy = crypto_policy()?;
51
52 Ok(Self {
53 keystore: Some(keystore),
54 certstore: Arc::new(certstore),
55 policy,
56 })
57 }
58
59 fn ephemeral() -> Result<Self, anyhow::Error> {
60 let certstore = Arc::new(sequoia_cert_store::CertStore::empty());
61 let policy = crypto_policy()?;
64 Ok(Self {
65 keystore: None,
66 certstore,
67 policy,
68 })
69 }
70
71 fn import_keys(&mut self, blob: &[u8]) -> Result<SequoiaImportResult, anyhow::Error> {
72 let mut key_handles = vec![];
73 for r in CertParser::from_bytes(blob)? {
74 let cert = r.context("Error parsing certificate")?;
79
80 key_handles.push(CString::new(cert.fingerprint().to_hex().as_bytes()).unwrap());
81 self.certstore
82 .update(Arc::new(sequoia_cert_store::LazyCert::from(cert)))?;
83 }
84 Ok(SequoiaImportResult { key_handles })
85 }
86
87 fn sign(
88 &mut self,
89 key_handle: &str,
90 password: Option<&str>,
91 data: &[u8],
92 ) -> Result<Vec<u8>, anyhow::Error> {
93 let primary_key_handle: KeyHandle = key_handle.parse()?; let certs = self
95 .certstore
96 .lookup_by_cert(&primary_key_handle)
97 .with_context(|| format!("Failed to look up {key_handle} in certificate store"))?;
98 if certs.len() != 1 {
99 return Err(anyhow::anyhow!(
114 "Ambiguous input, multiple certificates match {key_handle}"
115 ));
116 }
117
118 let cert = certs[0]
119 .to_cert()
120 .with_context(|| format!("Parsing certificate for {key_handle}"))?;
127
128 let keystore = self.keystore.as_mut().ok_or_else(|| {
129 anyhow::anyhow!("Caller error: attempting to sign with an ephemeral mechanism")
130 })?;
131
132 let mut key: Option<sequoia_keystore::Key> = None;
133 let mut rejected_key_errors: Vec<String> = vec![];
134 let ka = cert
135 .with_policy(&self.policy, None)
136 .with_context(|| format!("No acceptable signing key for {key_handle}"))?;
137 for ka in ka.keys().for_signing() {
138 if ka.alive().is_err() {
139 rejected_key_errors.push(format!("key {} is expired", ka.key().fingerprint()));
140 } else if matches!(
141 ka.revocation_status(),
142 openpgp::types::RevocationStatus::Revoked(_)
143 ) {
144 rejected_key_errors.push(format!("key {} is revoked", ka.key().fingerprint()));
145 } else if !ka.key().pk_algo().is_supported() {
146 rejected_key_errors
147 .push(format!("key {} is not supported", ka.key().fingerprint()));
148 } else {
149 let mut keys = keystore.find_key(ka.key().key_handle())?;
151 if keys.is_empty() {
152 rejected_key_errors.push(format!(
153 "private key for key {} not found",
154 ka.key().fingerprint()
155 ));
156 } else {
157 key = Some(keys.swap_remove(0));
161 break; }
163 }
164 }
165 let mut key = match key {
166 Some(key) => key,
167 None => {
168 if !rejected_key_errors.is_empty() {
169 return Err(anyhow::anyhow!(
170 "No acceptable signing key for {key_handle}: {}",
171 rejected_key_errors.join(", ")
172 ));
173 } else {
174 return Err(anyhow::anyhow!("Key {key_handle} does not support signing"));
179 }
180 }
181 };
182
183 if let Some(password) = password {
184 key.unlock(password.into())?;
185 }
186
187 let mut sink = vec![];
188 {
189 let message = Message::new(&mut sink);
190 let message = Signer::new(message, &mut key)?
192 .build()?;
198 let mut message = LiteralWriter::new(message).build()?;
200 message.write_all(data)?;
202 message.finalize()?;
203 }
204 Ok(sink)
205 }
206
207 fn verify(&mut self, signature: &[u8]) -> Result<SequoiaVerificationResult, anyhow::Error> {
212 let h = Helper {
213 certstore: self.certstore.clone(),
214 signer: Default::default(),
215 };
216
217 let mut v = VerifierBuilder::from_bytes(signature)?.with_policy(&self.policy, None, h)?;
219 let mut content = Vec::new();
220 v.read_to_end(&mut content)?;
221
222 assert!(v.message_processed());
223
224 match &v.helper_ref().signer {
225 Some(signer) => Ok(SequoiaVerificationResult {
226 content,
227 signer: CString::new(signer.fingerprint().to_hex().as_bytes()).unwrap(),
228 }),
229 None => Err(anyhow::anyhow!("No valid signer")), }
231 }
232}
233
234struct Helper<'a> {
235 certstore: Arc<sequoia_cert_store::CertStore<'a>>,
236 signer: Option<openpgp::Cert>,
237}
238
239impl<'a> VerificationHelper for Helper<'a> {
240 fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> {
241 let mut certs = Vec::new();
242 for id in ids {
243 match self.certstore.lookup_by_cert_or_subkey(id) {
244 Ok(matches) => {
245 for lc in matches {
246 certs.push(lc.to_cert()?.clone());
251 }
252 }
253 Err(e) => {
254 if let Some(sequoia_cert_store::store::StoreError::NotFound(_)) =
255 e.downcast_ref()
256 {
257 } else {
259 return Err(e);
260 }
261 }
262 };
263 }
264 Ok(certs)
265 }
266
267 fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
268 let mut signature_errors: Vec<String> = Vec::new();
269 for layer in structure {
270 match layer {
271 MessageLayer::Compression { algo: _ } => (),
272 MessageLayer::Encryption {
273 sym_algo: _,
274 aead_algo: _,
275 } => {
276 return Err(anyhow::anyhow!(
279 "internal error: MessageLayer::Encryption should never have happened"
280 ));
281 }
282 MessageLayer::SignatureGroup { ref results } => {
283 for result in results {
284 match result {
285 Ok(good_checksum) => {
286 self.signer = Some(good_checksum.ka.cert().to_owned());
292 return Ok(());
293 }
294 Err(verification_error) => {
295 signature_errors.push(pretty_error(verification_error));
296 }
297 }
298 }
299 }
300 }
301 }
302 let err = match signature_errors.len() {
303 0 => anyhow::anyhow!("No valid signature"),
304 1 => anyhow::anyhow!("{}", &signature_errors[0]),
305 _ => anyhow::anyhow!(
306 "Multiple signature errors:\n{}",
307 signature_errors.join("\n")
308 ),
309 };
310 Err(err)
311 }
312}
313
314fn pretty_error_chain(err: &anyhow::Error) -> String {
315 let causes = err
316 .chain()
317 .skip(1)
318 .map(|cause| format!("because: {cause}"))
319 .collect::<Vec<_>>()
320 .join("\n");
321 format!(" {err}\n{causes}") }
323
324fn pretty_error(verification_error: &VerificationError) -> String {
325 fn indent_error_chain(err: &anyhow::Error) -> String {
326 pretty_error_chain(err)
327 .lines()
328 .map(|line| format!(" {line}"))
329 .collect::<Vec<_>>()
330 .join("\n")
331 }
332 use openpgp::parse::stream::VerificationError::*;
333 match verification_error {
334 MissingKey { sig, .. } => format!("Missing key {:X}", sig.get_issuers().first().unwrap()),
335 UnboundKey { cert, error, .. } => format!(
336 "Signing key on {:X} is not bound:\n{}",
337 cert.fingerprint(),
338 indent_error_chain(error)
339 ),
340 BadKey { ka, error, .. } => format!(
341 "Signing key on {:X} is bad:\n{}",
342 ka.cert().fingerprint(),
343 indent_error_chain(error)
344 ),
345 BadSignature { error, .. } => format!("Bad signature:\n{}", indent_error_chain(error)),
346 MalformedSignature { error, .. } => {
347 format!("Signature is malformed:\n{}", indent_error_chain(error))
348 }
349 UnknownSignature { sig, .. } => {
350 format!("Unknown signature:\n{}", indent_error_chain(sig.error()))
351 }
352 _ => verification_error.to_string(),
353 }
354}
355
356fn crypto_policy<'a>() -> Result<StandardPolicy<'a>, anyhow::Error> {
358 let mut policy = ConfiguredStandardPolicy::new();
359 policy.parse_default_config()?;
362 Ok(policy.build())
363}
364
365pub struct SequoiaSignature {
366 data: Vec<u8>,
367}
368
369pub struct SequoiaVerificationResult {
370 content: Vec<u8>,
371 signer: CString,
372}
373
374#[derive(Default)]
375pub struct SequoiaImportResult {
376 key_handles: Vec<CString>,
377}
378
379#[no_mangle]
380pub unsafe extern "C" fn sequoia_mechanism_new_from_directory<'a>(
381 dir_ptr: *const c_char,
382 err_ptr: *mut *mut SequoiaError,
383) -> *mut SequoiaMechanism<'a> {
384 let c_dir = if dir_ptr.is_null() {
385 None
388 } else {
389 Some(CStr::from_ptr(dir_ptr))
390 };
391 let os_dir = c_dir.map(|s| OsStr::from_bytes(s.to_bytes()));
392 match SequoiaMechanism::from_directory(os_dir) {
393 Ok(mechanism) => Box::into_raw(Box::new(mechanism)),
394 Err(e) => {
395 set_error_from(err_ptr, e);
396 ptr::null_mut()
397 }
398 }
399}
400
401#[no_mangle]
402pub unsafe extern "C" fn sequoia_mechanism_new_ephemeral<'a>(
403 err_ptr: *mut *mut SequoiaError,
404) -> *mut SequoiaMechanism<'a> {
405 match SequoiaMechanism::ephemeral() {
406 Ok(mechanism) => Box::into_raw(Box::new(mechanism)),
407 Err(e) => {
408 set_error_from(err_ptr, e);
411 ptr::null_mut()
412 }
413 }
414}
415
416#[no_mangle]
417pub unsafe extern "C" fn sequoia_mechanism_free(mechanism_ptr: *mut SequoiaMechanism) {
418 drop(Box::from_raw(mechanism_ptr))
419}
420
421#[no_mangle]
422pub unsafe extern "C" fn sequoia_signature_free(signature_ptr: *mut SequoiaSignature) {
423 drop(Box::from_raw(signature_ptr))
424}
425
426#[no_mangle]
427pub unsafe extern "C" fn sequoia_signature_get_data(
428 signature_ptr: *const SequoiaSignature,
429 data_len: *mut size_t,
430) -> *const u8 {
431 assert!(!signature_ptr.is_null());
432 *data_len = (*signature_ptr).data.len();
433 (*signature_ptr).data.as_ptr()
434}
435
436#[no_mangle]
437pub unsafe extern "C" fn sequoia_verification_result_free(
438 result_ptr: *mut SequoiaVerificationResult,
439) {
440 assert!(!result_ptr.is_null());
441 drop(Box::from_raw(result_ptr))
442}
443
444#[no_mangle]
445pub unsafe extern "C" fn sequoia_verification_result_get_content(
446 result_ptr: *const SequoiaVerificationResult,
447 data_len: *mut size_t,
448) -> *const u8 {
449 assert!(!result_ptr.is_null());
450 *data_len = (*result_ptr).content.len();
451 (*result_ptr).content.as_ptr()
452}
453
454#[no_mangle]
455pub unsafe extern "C" fn sequoia_verification_result_get_signer(
456 result_ptr: *const SequoiaVerificationResult,
457) -> *const c_char {
458 assert!(!result_ptr.is_null());
459 (*result_ptr).signer.as_ptr()
460}
461
462#[no_mangle]
463pub unsafe extern "C" fn sequoia_sign(
464 mechanism_ptr: *mut SequoiaMechanism,
465 key_handle_ptr: *const c_char,
466 password_ptr: *const c_char,
467 data_ptr: *const u8,
468 data_len: size_t,
469 err_ptr: *mut *mut SequoiaError,
470) -> *mut SequoiaSignature {
471 assert!(!mechanism_ptr.is_null());
472 assert!(!key_handle_ptr.is_null());
473 assert!(!data_ptr.is_null());
474
475 let key_handle = match CStr::from_ptr(key_handle_ptr).to_str() {
476 Ok(key_handle) => key_handle,
477 Err(e) => {
478 set_error_from(err_ptr, e.into());
479 return ptr::null_mut();
480 }
481 };
482
483 let password = if password_ptr.is_null() {
484 None
485 } else {
486 match CStr::from_ptr(password_ptr).to_str() {
487 Ok(password) => Some(password),
488 Err(e) => {
489 set_error_from(err_ptr, e.into());
490 return ptr::null_mut();
491 }
492 }
493 };
494
495 let data = slice::from_raw_parts(data_ptr, data_len);
496 match (*mechanism_ptr).sign(key_handle, password, data) {
497 Ok(signature) => Box::into_raw(Box::new(SequoiaSignature { data: signature })),
498 Err(e) => {
499 set_error_from(err_ptr, e);
500 ptr::null_mut()
501 }
502 }
503}
504
505#[no_mangle]
506pub unsafe extern "C" fn sequoia_verify(
507 mechanism_ptr: *mut SequoiaMechanism,
508 signature_ptr: *const u8,
509 signature_len: size_t,
510 err_ptr: *mut *mut SequoiaError,
511) -> *mut SequoiaVerificationResult {
512 assert!(!mechanism_ptr.is_null());
513
514 let signature = slice::from_raw_parts(signature_ptr, signature_len);
515 match (*mechanism_ptr).verify(signature) {
516 Ok(result) => Box::into_raw(Box::new(result)),
517 Err(e) => {
518 set_error_from(err_ptr, e);
519 ptr::null_mut()
520 }
521 }
522}
523
524#[no_mangle]
525pub unsafe extern "C" fn sequoia_import_result_free(result_ptr: *mut SequoiaImportResult) {
526 drop(Box::from_raw(result_ptr))
527}
528
529#[no_mangle]
530pub unsafe extern "C" fn sequoia_import_result_get_count(
531 result_ptr: *const SequoiaImportResult,
532) -> size_t {
533 assert!(!result_ptr.is_null());
534
535 (*result_ptr).key_handles.len()
536}
537
538#[no_mangle]
539pub unsafe extern "C" fn sequoia_import_result_get_content(
540 result_ptr: *const SequoiaImportResult,
541 index: size_t,
542 err_ptr: *mut *mut SequoiaError,
543) -> *const c_char {
544 assert!(!result_ptr.is_null());
545
546 if index >= (*result_ptr).key_handles.len() {
547 set_error_from(err_ptr, anyhow::anyhow!("No matching key handle"));
548 return ptr::null();
549 }
550 let key_handle = &(&(*result_ptr)).key_handles[index];
551 key_handle.as_ptr()
552}
553
554#[no_mangle]
555pub unsafe extern "C" fn sequoia_import_keys(
556 mechanism_ptr: *mut SequoiaMechanism,
557 blob_ptr: *const u8,
558 blob_len: size_t,
559 err_ptr: *mut *mut SequoiaError,
560) -> *mut SequoiaImportResult {
561 assert!(!mechanism_ptr.is_null());
562
563 let blob = slice::from_raw_parts(blob_ptr, blob_len);
564 match (*mechanism_ptr).import_keys(blob) {
565 Ok(result) => Box::into_raw(Box::new(result)),
566 Err(e) => {
567 set_error_from(err_ptr, e);
568 ptr::null_mut()
569 }
570 }
571}
572
573#[derive(Eq, PartialEq, Debug)]
575#[repr(C)]
576pub enum SequoiaLogLevel {
579 Unknown,
580 Error,
581 Warn,
582 Info,
583 Debug,
584 Trace,
585}
586
587struct SequoiaLogger {
589 consumer: unsafe extern "C" fn(level: SequoiaLogLevel, message: *const c_char),
590}
591
592impl log::Log for SequoiaLogger {
593 fn enabled(&self, _: &log::Metadata) -> bool {
594 true
595 }
596
597 fn log(&self, record: &log::Record) {
598 let level = match record.level() {
599 log::Level::Error => SequoiaLogLevel::Error,
600 log::Level::Warn => SequoiaLogLevel::Warn,
601 log::Level::Info => SequoiaLogLevel::Info,
602 log::Level::Debug => SequoiaLogLevel::Debug,
603 log::Level::Trace => SequoiaLogLevel::Trace,
604 };
605 let text = match CString::new(record.args().to_string()) {
606 Ok(text) => text,
607 Err(_) => {
608 return;
609 }
610 };
611 unsafe { (self.consumer)(level, text.as_ptr()) };
612 }
613
614 fn flush(&self) {}
615}
616
617#[no_mangle]
621pub unsafe extern "C" fn sequoia_set_logger_consumer(
622 consumer: unsafe extern "C" fn(level: SequoiaLogLevel, message: *const c_char),
623 err_ptr: *mut *mut SequoiaError,
624) -> c_int {
625 let logger = SequoiaLogger { consumer };
626 match log::set_boxed_logger(Box::new(logger)) {
627 Ok(_) => {}
633 Err(e) => {
634 set_error_from(err_ptr, e.into());
635 return -1;
636 }
637 }
638
639 log::set_max_level(log::LevelFilter::Trace); 0
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646
647 use log::Log as _;
648 use openpgp::serialize::SerializeInto as _;
649
650 const TEST_KEY: &[u8] = include_bytes!("./data/no-passphrase.pub");
651 const TEST_KEY_FINGERPRINT: &str = "50DDE898DF4E48755C8C2B7AF6F908B6FA48A229";
652 const TEST_KEY_WITH_PASSPHRASE: &[u8] = include_bytes!("./data/with-passphrase.pub");
653 const TEST_KEY_WITH_PASSPHRASE_FINGERPRINT: &str = "1F5825285B785E1DB13BF36D2D11A19ABA41C6AE";
656 const INVALID_PUBLIC_KEY_BLOB: &[u8] = b"\xC6\x09this is not a valid public key";
657
658 #[test]
659 fn primary_workflow() {
660 let input = b"Hello, world!";
662
663 let signed = with_c_fixture_mechanism(|m1| {
664 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
665
666 let c_fingerprint = CString::new(TEST_KEY_FINGERPRINT).unwrap();
667 let sig = unsafe {
668 super::sequoia_sign(
669 m1,
670 c_fingerprint.as_ptr(),
671 ptr::null(),
672 input.as_ptr(),
673 input.len(),
674 &mut err_ptr,
675 )
676 };
677 assert!(!sig.is_null());
678 assert!(err_ptr.is_null());
679 let mut sig_size: size_t = 0;
680 let c_sig_data = unsafe { sequoia_signature_get_data(sig, &mut sig_size) };
681 let sig_slice = unsafe { slice::from_raw_parts(c_sig_data, sig_size) };
682 let signed = sig_slice.to_vec();
683 unsafe { sequoia_signature_free(sig) };
684
685 signed
686 });
687
688 with_c_ephemeral_mechanism(|m2| {
689 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
690
691 let res = unsafe { sequoia_verify(m2, signed.as_ptr(), signed.len(), &mut err_ptr) };
693 assert!(res.is_null());
694 assert!(!err_ptr.is_null());
695 unsafe { crate::sequoia_error_free(err_ptr) };
696 err_ptr = ptr::null_mut();
697
698 let mut fingerprints: Vec<String> = Vec::new();
699 {
700 let import_result = unsafe {
701 super::sequoia_import_keys(m2, TEST_KEY.as_ptr(), TEST_KEY.len(), &mut err_ptr)
702 };
703 assert!(!import_result.is_null());
704 assert!(err_ptr.is_null());
705 let count = unsafe { sequoia_import_result_get_count(import_result) };
706 for i in 0..count {
707 let c_fingerprint = unsafe {
708 super::sequoia_import_result_get_content(import_result, i, &mut err_ptr)
709 };
710 let fingerprint = unsafe { CStr::from_ptr(c_fingerprint) };
711 fingerprints.push(fingerprint.to_str().unwrap().to_owned());
712 }
713 unsafe { sequoia_import_result_free(import_result) };
714 }
715 assert_eq!(fingerprints.len(), 1);
716 assert_eq!(fingerprints[0], TEST_KEY_FINGERPRINT);
717
718 {
719 let res =
720 unsafe { sequoia_verify(m2, signed.as_ptr(), signed.len(), &mut err_ptr) };
721 assert!(!res.is_null());
722 assert!(err_ptr.is_null());
723
724 let mut contents_size: size_t = 0;
725 let c_contents =
726 unsafe { sequoia_verification_result_get_content(res, &mut contents_size) };
727 let contents_slice = unsafe { slice::from_raw_parts(c_contents, contents_size) };
728 assert_eq!(contents_slice, input);
729
730 let c_signer = unsafe { sequoia_verification_result_get_signer(res) };
731 let signer = unsafe { CStr::from_ptr(c_signer) };
732 assert_eq!(signer.to_str().unwrap(), TEST_KEY_FINGERPRINT);
733
734 unsafe { sequoia_verification_result_free(res) };
735 }
736 });
737 }
738
739 #[test]
740 fn sequoia_mechanism_from_directory() {
741 {
748 let original_dir = std::env::current_dir().unwrap();
749 let temp_dir = tempfile::tempdir().unwrap();
750 std::env::set_current_dir(&temp_dir).unwrap();
751 std::os::unix::fs::symlink("/var/empty/this/does/not/exist", "unreachable-symlink")
752 .unwrap();
753 let res =
754 SequoiaMechanism::from_directory(Some(Path::new("unreachable-symlink/subdir/dir")));
755 assert!(res.is_err());
756 std::env::set_current_dir(original_dir).unwrap();
757 temp_dir.close().unwrap();
758 }
759
760 let res =
764 SequoiaMechanism::from_directory(Some(Path::new("/dev/null/this/does/not/exist")));
765 assert!(res.is_err());
766
767 {
770 let temp_dir = tempfile::tempdir().unwrap();
771 let certstore_dir = sequoia_directories::Home::new(temp_dir.path().to_path_buf())
772 .unwrap()
773 .data_dir(sequoia_directories::Component::CertD);
774 fs::create_dir_all(certstore_dir.parent().unwrap()).unwrap();
775 std::os::unix::fs::symlink("/var/empty/this/does/not/exist", certstore_dir).unwrap();
776 let res = SequoiaMechanism::from_directory(Some(temp_dir.path()));
777 assert!(res.is_err());
778 temp_dir.close().unwrap();
779 }
780 }
781
782 #[test]
783 fn sequoia_mechanism_new_from_directory() {
784 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
790 let c_sequoia_home = CString::new("/dev/null/this/does/not/exist").unwrap();
791 let m = unsafe {
792 super::sequoia_mechanism_new_from_directory(c_sequoia_home.as_ptr(), &mut err_ptr)
793 };
794 assert!(m.is_null());
795 assert!(!err_ptr.is_null());
796 unsafe { crate::sequoia_error_free(err_ptr) };
797 }
798
799 #[test]
800 fn sign() {
801 fn with_temporary_mechanism<R>(f: impl FnOnce(&mut SequoiaMechanism) -> R) -> R {
804 let temp_dir = tempfile::tempdir().unwrap();
805 let mut mech = SequoiaMechanism::from_directory(Some(temp_dir.path())).unwrap();
806 let res = f(&mut mech);
807 temp_dir.close().unwrap();
808 res
809 }
810
811 const LOCAL_PASSPHRASE: &str = "a single-use passphrase";
817 let (cert, signature) = with_temporary_mechanism(|mut mech| {
818 let (cert, _) = CertBuilder::new()
819 .set_primary_key_flags(
820 openpgp::types::KeyFlags::empty()
821 .set_certification()
822 .set_signing(),
823 )
824 .add_userid("passphrase-protected")
825 .set_password(Some(LOCAL_PASSPHRASE.into()))
826 .generate()
827 .unwrap();
828 import_private_key(&mut mech, &cert);
829 let res = mech.sign(&cert.fingerprint().to_hex(), None, b"contents");
831 assert!(res.is_err());
832 let res = mech.sign(
833 &cert.fingerprint().to_hex(),
834 Some(LOCAL_PASSPHRASE),
835 b"contents",
836 );
837 let signature = res.expect("signature should be returned");
838 (cert, signature)
839 });
840 let fingerprint = cert.fingerprint();
841 let mut mech = SequoiaMechanism::ephemeral().unwrap();
842 mech.import_keys(
843 cert.strip_secret_key_material()
844 .export_to_vec()
845 .unwrap()
846 .as_slice(),
847 )
848 .unwrap();
849 let res = mech.verify(&signature).expect("verify should succeed");
850 assert_eq!(res.content, b"contents");
851 assert_eq!(res.signer, CString::new(fingerprint.to_hex()).unwrap());
852
853 with_fixture_sequoia_home_locked(|fixture_dir| {
855 let mut mech = SequoiaMechanism::from_directory(Some(fixture_dir)).unwrap();
856 let res = mech.sign("should-be-hexadecimal", None, b"contents");
857 assert!(res.is_err());
858 });
859
860 with_fixture_sequoia_home_locked(|fixture_dir| {
862 let mut mech = SequoiaMechanism::from_directory(Some(fixture_dir)).unwrap();
863 let res = mech.sign(
864 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
865 None,
866 b"contents",
867 );
868 assert!(res.is_err());
869 });
870
871 let mut mech = SequoiaMechanism::ephemeral().unwrap();
878 mech.import_keys(&TEST_KEY).unwrap();
879 let res = mech.sign(TEST_KEY_FINGERPRINT, None, b"contents");
880 assert!(res.is_err());
881
882 with_temporary_mechanism(|mut mech| {
884 let cert = generate_cert_with_expired_self_signature();
885 import_private_key(&mut mech, &cert);
886 let res = mech.sign(&cert.fingerprint().to_hex(), None, b"contents");
887 assert!(res.is_err());
888 });
889
890 with_temporary_mechanism(|mut mech| {
892 let (cert, _) = CertBuilder::new()
895 .set_primary_key_flags(
896 openpgp::types::KeyFlags::empty()
897 .set_certification()
898 .set_signing(),
899 )
900 .add_userid("key expired")
901 .set_creation_time(
902 std::time::SystemTime::now()
903 - std::time::Duration::from_secs(365 * 24 * 60 * 60),
904 )
905 .set_validity_period(std::time::Duration::from_secs(60 * 60))
906 .generate()
907 .unwrap();
908 import_private_key(&mut mech, &cert);
909 let res = mech.sign(&cert.fingerprint().to_hex(), None, b"contents");
910 assert!(res.is_err());
911 });
912
913 with_temporary_mechanism(|mut mech| {
915 let (cert, _) = CertBuilder::new()
916 .add_userid("signing subkey revoked")
917 .set_creation_time(
918 std::time::SystemTime::now()
919 - std::time::Duration::from_secs(365 * 24 * 60 * 60),
920 )
921 .add_signing_subkey()
922 .generate()
923 .unwrap();
924 let mut signer = cert
925 .primary_key()
926 .key()
927 .clone()
928 .parts_into_secret()
929 .unwrap()
930 .into_keypair()
931 .unwrap();
932 let subkey = cert.keys().subkeys().nth(0).unwrap();
933 let sig = SubkeyRevocationBuilder::new()
934 .set_reason_for_revocation(openpgp::types::ReasonForRevocation::KeyCompromised, b"")
935 .unwrap()
936 .build(&mut signer, &cert, subkey.key(), None)
937 .unwrap();
938 let cert = cert.insert_packets(sig).unwrap().0;
939 import_private_key(&mut mech, &cert);
940 let res = mech.sign(&cert.fingerprint().to_hex(), None, b"contents");
941 assert!(res.is_err());
942 });
943
944 with_temporary_mechanism(|mech| {
949 mech.import_keys(TEST_KEY).unwrap();
950 let res = mech.sign(TEST_KEY_FINGERPRINT, None, b"contents");
951 assert!(res.is_err());
952 });
953
954 with_temporary_mechanism(|mut mech| {
956 let (cert, _) = CertBuilder::new()
959 .add_userid("no signing capability")
960 .generate()
961 .unwrap();
962 import_private_key(&mut mech, &cert);
963 let res = mech.sign(&cert.fingerprint().to_hex(), None, b"contents");
964 assert!(res.is_err());
965 });
966
967 with_fixture_sequoia_home_locked(|fixture_dir| {
969 let mut mech = SequoiaMechanism::from_directory(Some(fixture_dir)).unwrap();
970 let res = mech.sign(TEST_KEY_WITH_PASSPHRASE_FINGERPRINT, None, b"contents");
971 assert!(res.is_err());
972 });
973
974 with_fixture_sequoia_home_locked(|fixture_dir| {
976 let mut mech = SequoiaMechanism::from_directory(Some(fixture_dir)).unwrap();
977 let res = mech.sign(
978 TEST_KEY_WITH_PASSPHRASE_FINGERPRINT,
979 Some("incorrect passphrase"),
980 b"contents",
981 );
982 assert!(res.is_err());
983 });
984 }
985
986 pub fn generate_cert_with_expired_self_signature() -> Cert {
988 let creation_time =
997 std::time::SystemTime::now() - std::time::Duration::from_secs(365 * 24 * 60 * 60);
998
999 let mut primary = openpgp::packet::key::Key::V4(
1001 openpgp::packet::key::Key4::<
1002 openpgp::packet::key::SecretParts,
1003 openpgp::packet::key::PrimaryRole,
1004 >::generate_ecc(true, openpgp::types::Curve::Ed25519)
1005 .unwrap(),
1006 );
1007 primary.set_creation_time(creation_time).unwrap();
1008 let mut signer = primary.clone().into_keypair().unwrap();
1009
1010 let our_signature_builder =
1011 |typ: openpgp::types::SignatureType| -> openpgp::packet::prelude::SignatureBuilder {
1012 openpgp::packet::prelude::SignatureBuilder::new(typ)
1013 .set_signature_creation_time(creation_time)
1014 .unwrap()
1015 .set_signature_validity_period(std::time::Duration::from_secs(24 * 60 * 60))
1016 .unwrap()
1017 .set_key_flags(
1018 openpgp::types::KeyFlags::empty()
1019 .set_certification()
1020 .set_signing(),
1021 )
1022 .unwrap()
1023 };
1024
1025 let cert = Cert::try_from(vec![openpgp::Packet::SecretKey(primary.clone())]).unwrap();
1026 let direct_sig = our_signature_builder(openpgp::types::SignatureType::DirectKey)
1027 .sign_direct_key(&mut signer, primary.parts_as_public())
1028 .unwrap();
1029 let uid = openpgp::packet::UserID::from("expired binding signature");
1030 let sig = our_signature_builder(openpgp::types::SignatureType::PositiveCertification)
1031 .set_primary_userid(true)
1032 .unwrap();
1033 let uid_signature = uid.bind(&mut signer, &cert, sig).unwrap();
1034 let cert = cert
1035 .insert_packets(vec![
1036 openpgp::Packet::Signature(direct_sig),
1037 uid.into(),
1038 uid_signature.into(),
1039 ])
1040 .unwrap()
1041 .0;
1042 cert
1043 }
1044
1045 #[test]
1046 fn sequoia_sign() {
1047 let plaintext = b"contents";
1050
1051 let (cert, signature) = {
1055 const LOCAL_PASSPHRASE: &str = "a single-use passphrase";
1056
1057 let temp_dir = tempfile::tempdir().unwrap();
1058 let cert = {
1059 let mut mech = SequoiaMechanism::from_directory(Some(temp_dir.path())).unwrap();
1060
1061 let (cert, _) = CertBuilder::new()
1062 .set_primary_key_flags(
1063 openpgp::types::KeyFlags::empty()
1064 .set_certification()
1065 .set_signing(),
1066 )
1067 .add_userid("passphrase-protected")
1068 .set_password(Some(LOCAL_PASSPHRASE.into()))
1069 .generate()
1070 .unwrap();
1071 import_private_key(&mut mech, &cert);
1072 let res = mech.sign(&cert.fingerprint().to_hex(), None, plaintext);
1074 assert!(res.is_err());
1075 cert
1076 };
1077 let signature = with_c_mechanism_from_directory(temp_dir.path(), |mech| {
1078 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1079
1080 let c_fingerprint = CString::new(cert.fingerprint().to_hex()).unwrap();
1081 let c_passphrase = CString::new(LOCAL_PASSPHRASE).unwrap();
1082 let sig = unsafe {
1083 super::sequoia_sign(
1084 mech,
1085 c_fingerprint.as_ptr(),
1086 c_passphrase.as_ptr(),
1087 plaintext.as_ptr(),
1088 plaintext.len(),
1089 &mut err_ptr,
1090 )
1091 };
1092 assert!(!sig.is_null());
1093 assert!(err_ptr.is_null());
1094 let mut sig_size: size_t = 0;
1095 let c_sig_data = unsafe { sequoia_signature_get_data(sig, &mut sig_size) };
1096 let sig_slice = unsafe { slice::from_raw_parts(c_sig_data, sig_size) };
1097 let signature = sig_slice.to_vec();
1098 unsafe { sequoia_signature_free(sig) }
1099 signature
1100 });
1101 (cert, signature)
1102 };
1103 let fingerprint = cert.fingerprint();
1104 let mut mech = SequoiaMechanism::ephemeral().unwrap();
1105 mech.import_keys(
1106 cert.strip_secret_key_material()
1107 .export_to_vec()
1108 .unwrap()
1109 .as_slice(),
1110 )
1111 .unwrap();
1112 let res = mech.verify(&signature).expect("verify should succeed");
1113 assert_eq!(res.content, plaintext);
1114 assert_eq!(res.signer, CString::new(fingerprint.to_hex()).unwrap());
1115
1116 with_c_fixture_mechanism(|m| {
1118 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1119
1120 let c_fingerprint = CString::new(b"invalid UTF-8: \x80\x80").unwrap();
1121 let res = unsafe {
1122 super::sequoia_sign(
1123 m,
1124 c_fingerprint.as_ptr(),
1125 ptr::null(),
1126 plaintext.as_ptr(),
1127 plaintext.len(),
1128 &mut err_ptr,
1129 )
1130 };
1131 assert!(res.is_null());
1132 assert!(!err_ptr.is_null());
1133 unsafe { crate::sequoia_error_free(err_ptr) };
1134 });
1135
1136 with_c_fixture_mechanism(|m| {
1138 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1139
1140 let c_fingerprint = CString::new(TEST_KEY_FINGERPRINT).unwrap();
1143 let c_passphrase = CString::new(b"invalid UTF-8: \x80\x80").unwrap();
1144 let res = unsafe {
1145 super::sequoia_sign(
1146 m,
1147 c_fingerprint.as_ptr(),
1148 c_passphrase.as_ptr(),
1149 plaintext.as_ptr(),
1150 plaintext.len(),
1151 &mut err_ptr,
1152 )
1153 };
1154 assert!(res.is_null());
1155 assert!(!err_ptr.is_null());
1156 unsafe { crate::sequoia_error_free(err_ptr) };
1157 });
1158
1159 with_c_fixture_mechanism(|m| {
1161 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1162
1163 let c_fingerprint = CString::new(b"this is not a valid fingerprint").unwrap();
1164 let res = unsafe {
1165 super::sequoia_sign(
1166 m,
1167 c_fingerprint.as_ptr(),
1168 ptr::null(),
1169 plaintext.as_ptr(),
1170 plaintext.len(),
1171 &mut err_ptr,
1172 )
1173 };
1174 assert!(res.is_null());
1175 assert!(!err_ptr.is_null());
1176 unsafe { crate::sequoia_error_free(err_ptr) };
1177 });
1178 }
1179
1180 #[test]
1181 fn import_keys() {
1182 let mut mech = SequoiaMechanism::ephemeral().unwrap();
1186 let res = mech.import_keys(&[]).expect("import should succeed");
1187 assert!(res.key_handles.is_empty());
1188
1189 let pk1 = &TEST_KEY[..];
1191 let pk2 = &TEST_KEY_WITH_PASSPHRASE[..];
1192 let mut mech = SequoiaMechanism::ephemeral().unwrap();
1193 let res = mech
1194 .import_keys(&[pk1, pk2].concat())
1195 .expect("import should succeed");
1196 assert_eq!(
1197 res.key_handles,
1198 [
1199 CString::new(TEST_KEY_FINGERPRINT).unwrap(),
1200 CString::new(TEST_KEY_WITH_PASSPHRASE_FINGERPRINT).unwrap(),
1201 ],
1202 );
1203
1204 let mut mech = SequoiaMechanism::ephemeral().unwrap();
1205 let res = mech.import_keys(b"this is not a valid public key");
1206 assert!(res.is_err());
1209
1210 let mut mech = SequoiaMechanism::ephemeral().unwrap();
1211 let res = mech.import_keys(INVALID_PUBLIC_KEY_BLOB);
1212 assert!(res.is_err());
1214
1215 if cfg!(unix) {
1221 let sequoia_home = tempfile::tempdir().unwrap();
1222 let certstore_dir = sequoia_directories::Home::new(sequoia_home.path().to_path_buf())
1223 .unwrap()
1224 .data_dir(sequoia_directories::Component::CertD);
1225 let mut mech = SequoiaMechanism::from_directory(Some(sequoia_home.path())).unwrap();
1226 fs::remove_dir_all(&certstore_dir).unwrap();
1228 std::os::unix::fs::symlink("/var/empty/this/does/not/exist", &certstore_dir).unwrap();
1229 let res = mech.import_keys(pk1);
1230 assert!(res.is_err());
1231 }
1232 }
1233
1234 #[test]
1235 fn sequoia_import_result_get_content() {
1236 with_c_ephemeral_mechanism(|m| {
1240 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1241
1242 let no_public_key = b"";
1243 let import_result = unsafe {
1244 super::sequoia_import_keys(
1245 m,
1246 no_public_key.as_ptr(),
1247 no_public_key.len(),
1248 &mut err_ptr,
1249 )
1250 };
1251 assert!(!import_result.is_null());
1252 assert!(err_ptr.is_null());
1253 let count = unsafe { sequoia_import_result_get_count(import_result) };
1254 assert_eq!(count, 0);
1255
1256 let c_fingerprint = unsafe {
1257 super::sequoia_import_result_get_content(import_result, 9999, &mut err_ptr)
1258 };
1259 assert!(c_fingerprint.is_null());
1260 assert!(!err_ptr.is_null());
1261 unsafe { crate::sequoia_error_free(err_ptr) };
1262 unsafe { sequoia_import_result_free(import_result) };
1265 });
1266 }
1267
1268 #[test]
1269 fn sequoia_import_keys_invalid_public_key() {
1270 with_c_ephemeral_mechanism(|m| {
1274 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1275
1276 let import_result = unsafe {
1277 super::sequoia_import_keys(
1278 m,
1279 INVALID_PUBLIC_KEY_BLOB.as_ptr(),
1280 INVALID_PUBLIC_KEY_BLOB.len(),
1281 &mut err_ptr,
1282 )
1283 };
1284 assert!(import_result.is_null());
1285 assert!(!err_ptr.is_null());
1286 unsafe { crate::sequoia_error_free(err_ptr) };
1287 });
1288 }
1289
1290 #[test]
1291 fn verify() {
1292 let mut m = SequoiaMechanism::ephemeral().unwrap();
1296 m.import_keys(TEST_KEY).unwrap();
1297 let res = m.verify(b"");
1298 assert!(res.is_err());
1299
1300 let large_signature = with_fixture_sequoia_home_locked(|fixture_dir| {
1303 let mut m = SequoiaMechanism::from_directory(Some(fixture_dir.as_path())).unwrap();
1304 let large_contents: Vec<u8> = vec![0; 2 * openpgp::parse::stream::DEFAULT_BUFFER_SIZE];
1305 let large_signature = m.sign(TEST_KEY_FINGERPRINT, None, &large_contents).unwrap();
1306 let res = m.verify(&large_signature).expect("verify should succeed");
1307 assert_eq!(res.content, large_contents);
1308 large_signature
1309 });
1310 let mut m = SequoiaMechanism::ephemeral().unwrap();
1312 let res = m.verify(&large_signature);
1313 assert!(res.is_err());
1314 }
1315
1316 #[test]
1317 fn verification_helper_get_certs() {
1318 let valid_signature = include_bytes!("./data/sequoia.signature");
1321
1322 let mut m = SequoiaMechanism::ephemeral().unwrap(); let res = m.verify(valid_signature);
1325 assert!(res.is_err());
1326
1327 let mut store = sequoia_cert_store::CertStore::empty();
1334 store.add_backend(
1335 Box::new(FailingCertStore {}),
1336 sequoia_cert_store::AccessMode::Always,
1337 );
1338 let mut m = SequoiaMechanism {
1339 keystore: None,
1340 certstore: Arc::new(store),
1341 policy: crypto_policy().unwrap(),
1342 };
1343 let res = m.verify(valid_signature);
1344 assert!(res.is_err());
1345 }
1346
1347 struct FailingCertStore {}
1349 impl<'a> sequoia_cert_store::Store<'a> for FailingCertStore {
1350 fn lookup_by_cert(
1351 &self,
1352 _: &KeyHandle,
1353 ) -> openpgp::Result<Vec<Arc<sequoia_cert_store::LazyCert<'a>>>> {
1354 Err(anyhow::anyhow!("test error"))
1355 }
1356 fn lookup_by_cert_or_subkey(
1357 &self,
1358 _: &KeyHandle,
1359 ) -> openpgp::Result<Vec<Arc<sequoia_cert_store::LazyCert<'a>>>> {
1360 Err(anyhow::anyhow!("test error"))
1361 }
1362 fn select_userid(
1363 &self,
1364 _: &sequoia_cert_store::store::UserIDQueryParams,
1365 _: &str,
1366 ) -> openpgp::Result<Vec<Arc<sequoia_cert_store::LazyCert<'a>>>> {
1367 Err(anyhow::anyhow!("test error"))
1368 }
1369 fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item = openpgp::Fingerprint> + 'b> {
1370 Box::new(std::iter::empty())
1371 }
1372 }
1373
1374 #[test]
1375 fn verification_helper_check() {
1376 let mut m = SequoiaMechanism::ephemeral().unwrap();
1380 m.import_keys(include_bytes!("./data/public-key.gpg"))
1381 .unwrap();
1382 let res = m
1383 .verify(include_bytes!("./data/invalid-blob.signature"))
1384 .expect("verify should succeed");
1385 assert_eq!(res.content, b"This is not JSON\n");
1386 assert_eq!(
1387 res.signer,
1388 CString::new("08CD26E446E2E95249B7A405E932F44B23E8DD43").unwrap()
1389 );
1390
1391 let mut m = SequoiaMechanism::ephemeral().unwrap();
1395 let res = m.verify(include_bytes!("./data/unsigned-encrypted.signature"));
1396 assert!(res.is_err());
1398
1399 let mut m = SequoiaMechanism::ephemeral().unwrap();
1401 let res = m.verify(include_bytes!("./data/unsigned-literal.signature"));
1402 assert!(res.is_err()); let double_signed_signature = include_bytes!("./data/double-signed.signature");
1414 let mut m = SequoiaMechanism::ephemeral().unwrap();
1415 let res = m.verify(double_signed_signature);
1416 assert!(res.is_err()); m.import_keys(TEST_KEY_WITH_PASSPHRASE).unwrap();
1418 m.verify(double_signed_signature)
1419 .expect("verify should succeed");
1420 }
1421
1422 #[test]
1423 fn sequoia_logger() {
1424 struct Recording {
1428 level: Option<SequoiaLogLevel>,
1429 message: Option<String>,
1430 }
1431 static RECORDED: std::sync::Mutex<Recording> = std::sync::Mutex::new(Recording {
1435 level: None,
1436 message: None,
1437 });
1438
1439 extern "C" fn record(level: SequoiaLogLevel, message: *const c_char) {
1440 let c_message = unsafe { CStr::from_ptr(message) };
1441 let mut recorded = RECORDED.lock().unwrap();
1442 assert!(recorded.level.is_none());
1443 assert!(recorded.message.is_none());
1444 recorded.level = Some(level);
1445 recorded.message = Some(c_message.to_str().unwrap().to_owned());
1446 }
1447 fn recorded() -> (Option<SequoiaLogLevel>, Option<String>) {
1448 let mut recorded = RECORDED.lock().unwrap();
1449 (recorded.level.take(), recorded.message.take())
1450 }
1451
1452 let logger = SequoiaLogger { consumer: record };
1454 for mb in [
1455 &mut log::MetadataBuilder::new(),
1457 log::MetadataBuilder::new().level(log::Level::Info),
1458 log::MetadataBuilder::new()
1459 .level(log::Level::Trace)
1460 .target("some target"),
1461 ] {
1462 let res = logger.enabled(&mb.build());
1463 assert_eq!(res, true);
1464 assert_eq!(recorded(), (None, None));
1465 }
1466
1467 for (rust_level, c_level) in [
1469 (log::Level::Error, SequoiaLogLevel::Error),
1470 (log::Level::Warn, SequoiaLogLevel::Warn),
1471 (log::Level::Info, SequoiaLogLevel::Info),
1472 (log::Level::Debug, SequoiaLogLevel::Debug),
1473 (log::Level::Trace, SequoiaLogLevel::Trace),
1474 ] {
1475 let record = log::RecordBuilder::new()
1476 .args(format_args!("text"))
1477 .level(rust_level)
1478 .build();
1479 logger.log(&record);
1480 assert_eq!(recorded(), (Some(c_level), Some("text".into())));
1481 }
1482 let record = log::RecordBuilder::new()
1484 .args(format_args!("NUL:'\0'"))
1485 .level(log::Level::Info)
1486 .build();
1487 logger.log(&record);
1488 assert_eq!(recorded(), (None, None));
1489
1490 logger.flush();
1492 assert_eq!(recorded(), (None, None));
1493 logger.log(&log::RecordBuilder::new().args(format_args!("text")).build());
1494 _ = recorded();
1496 logger.flush();
1497 assert_eq!(recorded(), (None, None));
1498 }
1499
1500 #[test]
1501 fn sequoia_set_logger_consumer() {
1502 extern "C" fn noop_consumer(_: SequoiaLogLevel, _: *const c_char) {}
1509 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1510 _ = unsafe { super::sequoia_set_logger_consumer(noop_consumer, &mut err_ptr) };
1511 if !err_ptr.is_null() {
1512 unsafe { crate::sequoia_error_free(err_ptr) };
1513 err_ptr = ptr::null_mut();
1514 }
1515 let res = unsafe { super::sequoia_set_logger_consumer(noop_consumer, &mut err_ptr) };
1516 assert_ne!(res, 0);
1517 assert!(!err_ptr.is_null());
1518 unsafe { crate::sequoia_error_free(err_ptr) };
1519 }
1520
1521 fn with_fixture_sequoia_home_locked<R>(f: impl FnOnce(std::path::PathBuf) -> R) -> R {
1524 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1525
1526 let fixture_path_buf = Path::new(env!("CARGO_MANIFEST_DIR")).join("./src/data");
1527 let _guard = LOCK.lock().unwrap();
1528 return f(fixture_path_buf);
1529 }
1530
1531 fn with_c_mechanism_from_directory<R>(
1534 path: impl AsRef<Path>,
1535 f: impl FnOnce(*mut SequoiaMechanism) -> R,
1536 ) -> R {
1537 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1538
1539 let c_sequoia_home = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
1540 let m = unsafe {
1541 super::sequoia_mechanism_new_from_directory(c_sequoia_home.as_ptr(), &mut err_ptr)
1542 };
1543 assert!(!m.is_null());
1544 assert!(err_ptr.is_null());
1545
1546 let res = f(m);
1547
1548 unsafe { sequoia_mechanism_free(m) };
1549
1550 res
1551 }
1552
1553 fn with_c_fixture_mechanism<R>(f: impl FnOnce(*mut SequoiaMechanism) -> R) -> R {
1556 return with_fixture_sequoia_home_locked(|fixture_dir| {
1557 return with_c_mechanism_from_directory(fixture_dir, f);
1558 });
1559 }
1560
1561 fn with_c_ephemeral_mechanism(f: impl FnOnce(*mut SequoiaMechanism)) {
1564 let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1565
1566 let m = unsafe { sequoia_mechanism_new_ephemeral(&mut err_ptr) };
1567 assert!(!m.is_null());
1568 assert!(err_ptr.is_null());
1569
1570 f(m);
1571
1572 unsafe { sequoia_mechanism_free(m) };
1573 }
1574
1575 fn import_private_key(mech: &mut SequoiaMechanism, cert: &Cert) {
1577 let mut softkeys = None;
1578 for mut backend in mech
1579 .keystore
1580 .as_mut()
1581 .unwrap()
1582 .backends()
1583 .unwrap()
1584 .into_iter()
1585 {
1586 if backend.id().unwrap() == "softkeys" {
1587 softkeys = Some(backend);
1588 break;
1589 }
1590 }
1591 let mut softkeys = softkeys.unwrap();
1592 softkeys.import(&cert).unwrap();
1593 let cert = cert.clone().strip_secret_key_material();
1594 mech.certstore
1595 .update(Arc::new(sequoia_cert_store::LazyCert::from(cert)))
1596 .unwrap();
1597 }
1598}