voa_openpgp/lib.rs
1#![doc = include_str!("../README.md")]
2
3mod cert;
4mod error;
5mod voa;
6
7pub mod import;
8mod signature;
9
10use std::{fs::read, path::Path};
11
12pub use error::Error;
13pub use import::OpenPgpImport;
14use log::debug;
15use pgp::types::KeyDetails;
16
17pub use crate::{
18 cert::OpenpgpCert,
19 signature::{OpenpgpSignature, OpenpgpSignatureCheck, SignerInfo},
20 voa::VoaOpenpgp,
21};
22
23/// File name suffix for verifiers in the VOA "openpgp" technology
24pub(crate) const FILE_SUFFIX: &str = ".openpgp";
25
26/// Verifies one or more `signatures` for a `signed_file` with one or more `certs`.
27///
28/// All `signatures` are used to check the data in `signed_file`.
29/// For each signature, all `certs` are considered for finding a matching component key for signing.
30///
31/// The return value is a list of tuples, one entry for each signature (in order).
32/// Signatures that could not be positively verified have a `None` entry.
33/// Successfully verified signatures have a `Some` entry with a tuple of a signature, certificate
34/// and the fingerprint of the component key which verified the data in `signed_file`.
35///
36/// # Note
37///
38/// Does not consider OpenPGP signatures with an unset creation time and instead emits a warning for
39/// them.
40///
41/// # Errors
42///
43/// Returns an error, if the `signed_file` cannot be read.
44pub fn verify_from_file<'a>(
45 signed_file: impl AsRef<Path>,
46 certs: &'a [OpenpgpCert],
47 signatures: &'a [OpenpgpSignature],
48) -> Result<Vec<OpenpgpSignatureCheck<'a>>, Error> {
49 let signed_file = signed_file.as_ref();
50 let mut result = Vec::new();
51
52 let data = read(signed_file).map_err(|source| Error::IoPath {
53 path: signed_file.into(),
54 source,
55 context: "reading signed_file",
56 })?;
57
58 'sigs: for sig in signatures {
59 let Some(created) = sig.creation_time() else {
60 // this signature is not ok, we'll skip it
61 debug!("Skipping signature with unset creation time: {sig:?}");
62
63 result.push(OpenpgpSignatureCheck::new(sig, None));
64
65 continue;
66 };
67
68 for cert in certs {
69 // The set of all component keys in this verifier that can be used for data
70 // signature validation, using the signature creation time as the
71 // reference time
72 let valid_keys = cert
73 .certificate
74 .valid_signing_capable_component_keys_at(&created.into());
75
76 for key in valid_keys {
77 if sig.verifiable_with(&key) && key.verify(&sig.detached.signature, &data).is_ok() {
78 let component_fp = format!("{:02x?}", key.as_componentkey().fingerprint());
79
80 result.push(OpenpgpSignatureCheck::new(
81 sig,
82 Some(SignerInfo::new(cert, component_fp)),
83 ));
84 continue 'sigs; // we're done with this signature
85 }
86 }
87
88 // We failed to validate this signature as correct
89 }
90
91 result.push(OpenpgpSignatureCheck::new(sig, None));
92 }
93
94 Ok(result)
95}