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::{info, trace, warn};
15use pgp::types::KeyDetails;
16
17pub use crate::{
18 cert::OpenpgpCert,
19 signature::{OpenpgpSignature, OpenpgpSignatureCheck, SignerInfo},
20 voa::VoaOpenpgp,
21};
22
23pub(crate) const FILE_SUFFIX: &str = ".openpgp";
25
26fn log_sig_data(signature: &OpenpgpSignature) -> String {
32 format!(
33 "{}{}",
34 signature
35 .detached
36 .signature
37 .issuer_fingerprint()
38 .iter()
39 .map(|fingerprint| fingerprint.to_string())
40 .collect::<Vec<String>>()
41 .join(", "),
42 if let Some(path) = signature.source() {
43 format!(" ({})", path.to_string_lossy())
44 } else {
45 "".to_string()
46 }
47 )
48}
49
50fn log_cert_data(cert: &OpenpgpCert) -> String {
56 format!(
57 "{} ({})",
58 cert.fingerprint(),
59 cert.sources
60 .iter()
61 .map(|verifier| verifier.canonicalized().to_string_lossy().to_string())
62 .collect::<Vec<_>>()
63 .join(",")
64 )
65}
66
67pub fn verify_from_file<'a>(
86 signed_file: impl AsRef<Path>,
87 certs: &'a [OpenpgpCert],
88 signatures: &'a [OpenpgpSignature],
89) -> Result<Vec<OpenpgpSignatureCheck<'a>>, Error> {
90 let signed_file = signed_file.as_ref();
91 let mut result = Vec::new();
92
93 let data = read(signed_file).map_err(|source| Error::IoPath {
94 path: signed_file.into(),
95 source,
96 context: "reading signed_file",
97 })?;
98
99 'sigs: for signature in signatures {
100 let log_sig_data = log_sig_data(signature);
101 info!("Verifying OpenPGP signature ({log_sig_data}) for file {signed_file:?}");
102
103 let Some(created) = signature.creation_time() else {
104 warn!("Skipping signature without creation time: {log_sig_data}");
105 result.push(OpenpgpSignatureCheck::new(signature, None));
106
107 continue;
108 };
109
110 for cert in certs {
111 let log_cert_data = log_cert_data(cert);
112 trace!("Considering certificate {log_cert_data}");
113
114 let valid_keys = cert
118 .certificate
119 .valid_signing_capable_component_keys_at(&created.into());
120
121 for key in valid_keys {
122 if signature.verifiable_with(&key)
123 && key.verify(&signature.detached.signature, &data).is_ok()
124 {
125 let component_fp = format!("{:02x?}", key.as_componentkey().fingerprint());
126 trace!(
127 "Considering component key {component_fp} in certificate {log_cert_data}"
128 );
129
130 info!(
131 "Successfully verified OpenPGP signature {log_sig_data} for file {signed_file:?} with component key {component_fp} in OpenPGP certificate {log_cert_data}"
132 );
133 result.push(OpenpgpSignatureCheck::new(
134 signature,
135 Some(SignerInfo::new(cert, component_fp)),
136 ));
137 continue 'sigs; }
139 }
140 }
141
142 warn!(
143 "No valid signing key found for signature {log_sig_data} and file {signed_file:?} at signature creation time {created:?}"
144 );
145 result.push(OpenpgpSignatureCheck::new(signature, None));
146 }
147
148 Ok(result)
149}