Skip to main content

podman_sequoia/
signature.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#![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            // Coverage: .build() can never fail if .home() is set.
40            .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        // Coverage: CertStore::open currently never fails.
46        let certstore = sequoia_cert_store::CertStore::open(&certstore_dir)?;
47
48        // Coverage: To trigger this failure, we would need to set ConfiguredStandardPolicy::ENV_VAR
49        // but that’s not safe to do in multi-threaded tests (or to overwrite the system-wide config file).
50        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        // Coverage: To trigger this failure, we would need to set ConfiguredStandardPolicy::ENV_VAR
62        // but that’s not safe to do in multi-threaded tests (or to overwrite the system-wide config file).
63        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            // NOTE that we might have successfully imported something by now;
75            // in that case we just return an error and don't report what we have imported.
76            // That's fine for containers/image, which creates an one-use ephemeral mechanism
77            // and imports keys into it, i.e. there is no benefit in handling partial success specially.
78            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()?; // FIXME: For gpgme, allow lookup by user ID? grep_userid, or what is the compatible semantics?
94        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            // This should not happen when looking up by fingerprint, lookup_by_cert documentation says
100            // > The caller may assume that looking up a fingerprint returns at
101            // > most one certificate.
102            // and the implementation merges certificates with the same fingerprint.
103            //
104            // This _is_ reachable by using a key ID (not a fingerprint) that matches multiple fingerprints.
105            // In such a situation, we want to fail: We need the user to be precise about which key
106            // we should be signing with.
107            //
108            // The c/image caller documents the parameter as a fingerprint, but does not restrict the format
109            // (it would have to hard-code a copy of Sequoia-PGP’s string format decisions).
110            // Alternatively, we could, above, parse primary_key_handle explicitly as a Fingerprint —
111            // but for user convenience, letting a key ID through is a bit nicer — and we would _still_
112            // want this certs.len() != 1 check, just to be sure.
113            return Err(anyhow::anyhow!(
114                "Ambiguous input, multiple certificates match {key_handle}"
115            ));
116        }
117
118        let cert = certs[0]
119            .to_cert()
120            // Coverage: If LazyCert is not a Cert already, it is a RawCert, and that ensures
121            // that it starts with a primary key packet, and contains only expected (or unknown) packets.
122            // The Cert parsing requires that only expected packet types are present, and that they
123            // "follow the grammar", but it turns out that any sequence that starts with a primary key
124            // satisfies that. So, it seems that to_cert should never fail and this error
125            // handling path is unreachable.
126            .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                // Coverage: find_key() never fails.
150                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                    // sq might try all elements of keys — but only if the user aborts passphrase prompting.
158                    // We have no way to associate the provided password with a specific subkey, so assume
159                    // it is intended for the first one, to behave predictably.
160                    key = Some(keys.swap_remove(0));
161                    break; // We are done.
162                }
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                    // ka.keys().for_signing() only returns keys with the signing flag,
175                    // and we found none. (The OpenPGP RFC seems not to make it mandatory
176                    // to include the "key flags" subpacket?! Anyway, this is consistent with
177                    // (sq sign).)
178                    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            // Coverage: Signer::new() never fails.
191            let message = Signer::new(message, &mut key)?
192                // Coverage: Signer::build() could fail
193                // - With a caller-chosen unsupported hash algorithm (not our case)
194                // - If random number generation fails (possible)
195                // - If the key in the signer used an unimplemented version (no way to create that)
196                // - If writing failed (impossible when writing to memory)
197                .build()?;
198            // Coverage: LiteralWriter::build() could fail if writing failed (impossible when writing to memory)
199            let mut message = LiteralWriter::new(message).build()?;
200            // Coverage: This could fail only if writing failed (impossible when writing to memory)
201            message.write_all(data)?;
202            message.finalize()?;
203        }
204        Ok(sink)
205    }
206
207    /// Verifies a signature against _any_ public key known to the mechanism,
208    /// and returns the signed contents, along with the signing key’s (primary) fingerprint, on success.
209    ///
210    /// Note that this does not implement the web of trust, or any other policy.
211    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        // Coverage: VerifierBuilder::from_bytes (via VerifierBuilder::new) never fails.
218        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")), // Coverage: Should not happen, Helper should have rejected this.
230        }
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                        // Coverage: lc.to_cert() should never fail for ephemeral SequoiaMechanism, where the in-memory cert store always creates
247                        // a LazyCert from a parsed Cert. It could fail for non-ephemeral contexts, where the LazyCert typically originates
248                        // as RawCert and the parsed Cert would be created here — but see the discusison of LazyCert::to_cert() in
249                        // SigningMechanism::sign(), it seems that this can not actually fail.
250                        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                        // Don’t immediately abort, maybe can verify the signature with some other key.
258                    } 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                    // Coverage: MessageLayer::Encryption is only created when the message parser is invoked using a DecryptorBuilder,
277                    // not using a VerifierBuilder like we do.
278                    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                                // NOTE: We are not imposing any trust policy - as long as a public key is found,
287                                // this succeeds and the key’s fingerprint is returned to the caller.
288                                // This is fine for the expected user, which constructs an ephemeral mechanism
289                                // and imports only the keys trusted in that situation — but it might not be suitable
290                                // for more general use cases.
291                                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}") // align err to "because: " length
322}
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
356/// Creates a StandardPolicy with the policy we desire, primarily based on the system’s configuration.
357fn crypto_policy<'a>() -> Result<StandardPolicy<'a>, anyhow::Error> {
358    let mut policy = ConfiguredStandardPolicy::new();
359    // Coverage: To trigger this failure, we would need to set ConfiguredStandardPolicy::ENV_VAR
360    // but that’s not safe to do in multi-threaded tests (or to overwrite the system-wide config file).
361    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        // Coverage: Testing this might affect users’ primary configuration — or we would have
386        // to set $HOME, which is not safe to do in multi-threaded tests.
387        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            // Coverage: To trigger this failure, we would need to set ConfiguredStandardPolicy::ENV_VAR
409            // but that’s not safe to do in multi-threaded tests (or to overwrite the system-wide config file).
410            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// SequoiaLogLevel is a C-compatible version of log::Level.
574#[derive(Eq, PartialEq, Debug)]
575#[repr(C)]
576/// cbindgen:rename-all=ScreamingSnakeCase
577/// cbindgen:prefix-with-name
578pub enum SequoiaLogLevel {
579    Unknown,
580    Error,
581    Warn,
582    Info,
583    Debug,
584    Trace,
585}
586
587// SequoiaLogger implements log::Log.
588struct 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// sequoia_set_logger_consumer sets the process-wide Rust logger to the provided simple string consumer.
618// More sophisticated logging interfaces may be added in the future as an alternative.
619// Note that the logger is a per-process global; it is up to the caller to coordinate.
620#[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        // Leaks the logger, but this is explicitly an once-per-process API.
628        //
629        // Coverage: set_boxed_logger can only succeed once per process, and internally,
630        // sequoia-keystore does env_logger::Builder::from_default_env().try_init(),
631        // i.e. unrelated test cases can set the logger, and we can’t reliably test the success path.
632        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); // We’ll let the consumer do the filtering, if any.
640    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    // Note that the tests never unlock this key, because that would affect per-process state
654    // and interfere with any other tests referring to this key.
655    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        // The typical successful usage of this library.
661        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            // With no public key, verification should fail
692            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        // Success is tested in primary_workflow().
742
743        // Error preparing home directory path in sequoia_directories::Home::new:
744        // Failures to access an absolute path are ignored, but a relative path triggers
745        // an attempt to create all parent directories. If one of them is an unresolvable symlink
746        // the whole operation fails.
747        {
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        // Error creating a keystore:
761        // Use a non-directory component in the home directory path (merely using a non-existent path
762        // could succeed when running as root, auto-creating the parents).
763        let res =
764            SequoiaMechanism::from_directory(Some(Path::new("/dev/null/this/does/not/exist")));
765        assert!(res.is_err());
766
767        // Error creating a certstore directory:
768        // Place a dangling symlink at the certstore path.
769        {
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        // Success is tested in primary_workflow().
785
786        // Failure:
787        // Use a non-directory component in the home directory path (merely using a non-existent path
788        // could succeed when running as root, auto-creating the parents).
789        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        // Success is tested in primary_workflow().
802
803        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        // Successful signing with a passphrase:
812        // We want to do this on a temporary mechanism, because seqoia-keystore::server::Servers maintains
813        // a per-process singleton for each home directory; so, unlocking a key is permanent.
814        // OTOH we want to also test not unlocking, or unlocking with a wrong passphrase — so we need
815        // at least two mechanisms, one where unlocking succeeds, one where it fails.
816        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            // Sanity-check that signing without a passphrase fails, first
830            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        // Invalid key handle format:
854        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        // Key handle not found in certificate store:
861        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        // We don’t test the case where a key matches multiple certificates.
872        // This is reachable by using a key ID (not a fingerprint) that matches multiple fingerprints,
873        // we are not going to try generating such a collision. Also, the external c/image API
874        // is documented to accept fingerprints, not key IDs (although, actually, key IDs are not rejected).
875
876        // Attempting to sign with an ephemeral mechanism (no keystore):
877        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        // Trying to sign with a key where even the primary key is invalid (in this case, the binding signature is expired):
883        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        // Trying to sign with an expired key
891        with_temporary_mechanism(|mut mech| {
892            // For simplicity, we generate a single-key certificate where the primary
893            // key supports signing and is expired.
894            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        // Trying to sign with a revoked key
914        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        // We do not test trying to sign with an unsupported key: that would require creating
945        // and importing an ElGamal key somehow.
946
947        // Trying to sign when we have a certificate but not the private key.
948        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        // Trying to sign using a key which is not capable of signing:
955        with_temporary_mechanism(|mut mech| {
956            // For simplicity, we generate a single-key certificate where the primary
957            // key only supports certification.
958            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        // Trying to sign without providing a required passphrase:
968        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        // Trying to sign with a wrong passphrase:
975        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    // generate_cert_with_expired_self_signature is a helper for the sign() test.
987    pub fn generate_cert_with_expired_self_signature() -> Cert {
988        // This is surprisingly tedious.
989        //
990        // Ordinarily, CertBuilder.set_validity_period() sets the _key_ validity period;
991        // that does not invalidate the validity of the self-signature, and that’s
992        // the only thing Cert::with_policy() cares about. (In particular, at least for a primary
993        // key, it ignores revoked binding signatures as well as binding signatuers with expired keys).
994        // So, this is basically CertBuilder::generate(), specialized for our parameters,
995        // with the extra set_signature_validity_period() calls we need.
996        let creation_time =
997            std::time::SystemTime::now() - std::time::Duration::from_secs(365 * 24 * 60 * 60);
998
999        // Generate & self-sign primary key.
1000        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        // Success is tested in primary_workflow().
1048
1049        let plaintext = b"contents";
1050
1051        // Successful signing with a passphrase:
1052        // See the comment in the sign() test about not unlocking the fixture home directory
1053        // in tests.
1054        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                // Sanity-check that signing without a passphrase fails, first
1073                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        // Invalid UTF-8 in key_handle:
1117        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        // Invalid UTF-8 in password:
1137        with_c_fixture_mechanism(|m| {
1138            let mut err_ptr: *mut SequoiaError = ptr::null_mut();
1139
1140            // This key doesn’t require a passphrase at all — we fail anyway, before
1141            // we could even get the opportunity to determine that.
1142            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        // Signing failed (in this case, invalid fingerprint):
1160        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        // The basic case of import of a single key is tested in primary_workflow().
1183
1184        // Empty input.
1185        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        // A valid import of multiple keys.
1190        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        // "unexpected EOF": When the input does not look like binary OpenPGP, the code tries to parse it as ASCII-armored,
1207        // and looks for a BEGIN… header.
1208        assert!(res.is_err());
1209
1210        let mut mech = SequoiaMechanism::ephemeral().unwrap();
1211        let res = mech.import_keys(INVALID_PUBLIC_KEY_BLOB);
1212        // "Error parsing certificate" Malformed packet: Truncated packet": The input starts with a valid enough OpenPGP packet header.
1213        assert!(res.is_err());
1214
1215        // Generally, the certstore.update call should never fail for ephemeral mechanisms; it might fail
1216        // - if the provided LazyCert can’t be parsed, but we already have a parsed form
1217        // - on an internal inconsistency of CertStore, if it tries to merge two certificates with different fingerprints
1218        // but, purely for test purposes, we can trigger a write failure by using a non-ephemeral mechanism
1219        // (which is not expected to happen in practice).
1220        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            // Forcefully delete the contents of certstore_dir, and replace it with a dangling symlink.
1227            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        // Success is tested in primary_workflow().
1237
1238        // Index out of range
1239        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            // err_ptr = ptr::null_mut();
1263
1264            unsafe { sequoia_import_result_free(import_result) };
1265        });
1266    }
1267
1268    #[test]
1269    fn sequoia_import_keys_invalid_public_key() {
1270        // Success is tested in primary_workflow().
1271
1272        // Import failed.
1273        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        // Basic success is tested in primary_workflow().
1293
1294        // Empty signature
1295        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        // A very large signature, where verification happens in read_to_end, not already in VerifierBuilder::with_policy.
1301        // Success:
1302        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        // Failure: (using a mechanism which doesn’t trust the key)
1311        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        // Success is tested in primary_workflow().
1319
1320        let valid_signature = include_bytes!("./data/sequoia.signature");
1321
1322        // Certificate not found
1323        let mut m = SequoiaMechanism::ephemeral().unwrap(); // No public keys
1324        let res = m.verify(valid_signature);
1325        assert!(res.is_err());
1326
1327        // Other error:
1328        // Generally, the certstore.lookup_by_cert_or_subkey call should only fail by reporting that
1329        // nothing was found.
1330        // We also can’t very easily trigger a read I/O error, because sequoia-cert-store::store::certd::CertD
1331        // reads all files on creation already.
1332        // So, *sigh*, mock a failing cert store.
1333        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    // FailingCertStore exists for the verification_helper_get_certs test.
1348    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        // Basic success is tested in primary_workflow().
1377
1378        // Signature uses a compressed data packet (as GnuPG does), and it is valid:
1379        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        // Encrypted, but unsigned, data:
1392        // This does not create MessageLayer::Encryption, the code never creates that when verifying
1393        // (we’d have to explicitly be decrypting, not verifying).
1394        let mut m = SequoiaMechanism::ephemeral().unwrap();
1395        let res = m.verify(include_bytes!("./data/unsigned-encrypted.signature"));
1396        // "Malformed Message: Malformed OpenPGP message" because encrypted data is only processed when decrypting, and ignored by Verifier.
1397        assert!(res.is_err());
1398
1399        // Literal data with no signature:
1400        let mut m = SequoiaMechanism::ephemeral().unwrap();
1401        let res = m.verify(include_bytes!("./data/unsigned-literal.signature"));
1402        assert!(res.is_err()); // "No valid signature" by our Helper
1403
1404        // Double-signed signature:
1405        // Created using
1406        //  let message = Message::new(&mut sink);
1407        //  let message = Signer::new(message, &mut key1).unwrap().build().unwrap();
1408        //  let message = Signer::new(message, &mut key2).unwrap().build().unwrap();
1409        //  let mut message = LiteralWriter::new(message).build().unwrap();
1410        //  message.write_all(b"double-signed").unwrap();
1411        //  message.finalize().unwrap();
1412        // with key1 and key2 being TEST_KEY_FINGERPRINT_WITH_PASSPHRASE.
1413        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()); // "Multiple signature errors: [Missing key …, Missing key …]" by our Helper
1417        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        // See the discussion in sequoia_set_logger_consumer below explaining that we can’t test
1425        // logging end-to-end.
1426
1427        struct Recording {
1428            level: Option<SequoiaLogLevel>,
1429            message: Option<String>,
1430        }
1431        // RECORDED allows logging _one_ message, and records the data for later inspection.
1432        // This must be static because the "consumer" parameter is not accompanied
1433        // with any way to pass context to the C closure.
1434        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        // Log::enabled
1453        let logger = SequoiaLogger { consumer: record };
1454        for mb in [
1455            // A random set of examples, the implementation ignores the value.
1456            &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        // Log::log
1468        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        // Entries with embedded NULs are discarded
1483        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        // Log::flush
1491        logger.flush();
1492        assert_eq!(recorded(), (None, None));
1493        logger.log(&log::RecordBuilder::new().args(format_args!("text")).build());
1494        // Here we don't care about the value, we just want known state to verify that flush() does not call the callback.
1495        _ = recorded();
1496        logger.flush();
1497        assert_eq!(recorded(), (None, None));
1498    }
1499
1500    #[test]
1501    fn sequoia_set_logger_consumer() {
1502        // set_boxed_logger can only succeed once per process, and internally,
1503        // sequoia-keystore does env_logger::Builder::from_default_env().try_init(),
1504        // i.e. unrelated test cases can set the logger, and we can’t reliably test the success path.
1505        // (Therefore we also can't test logging end-to-end, by calling log::… and verifying the
1506        // C consumer receives the data.)
1507        // So, test just the failure path, by invoking the function twice.
1508        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    // with_fixture_sequoia_home_locked runs the provided function with a lock that serializes
1522    // accesses to the fixture Sequoia home.
1523    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    // with_c_mechanism_from_directory runs the provided function with a C-interface mechanism
1532    // in path, as a convenience for tests of other aspects of the C bindings.
1533    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    // with_c_fixture_mechanism runs the provided function with a C-interface mechanism
1554    // in fixture_path_buf(), as a convenience for tests of other aspects of the C bindings.
1555    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    // with_c_ephemeral_mechanism runs the provided function with a C-interface ephemeral mechanism,
1562    // as a convenience for tests of other aspects of the C bindings.
1563    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    // import_private_key imports a certificate and the included private key into a mechanism.
1576    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}