voa_openpgp/trust/
model.rs1use std::{collections::HashSet, fs::read, path::Path, time::SystemTime};
2
3use log::{debug, error, info, warn};
4use pgp::types::KeyDetails;
5use voa_config::openpgp::{NumDataSignatures, OpenpgpSettings, VerificationMethod};
6
7use crate::{
8 Error,
9 OpenpgpCert,
10 OpenpgpSignature,
11 OpenpgpSignatureCheck,
12 SignerInfo,
13 lookup::VerifierLookup,
14 trust::{
15 anchor::TrustAnchorModel,
16 plain::PlainTrustModel,
17 util::{log_cert_data, log_sig_data},
18 wot::WebOfTrustModel,
19 },
20};
21
22#[derive(Debug)]
24enum TrustModel<'a> {
25 Plain(PlainTrustModel<'a>),
26 TrustAnchor(TrustAnchorModel<'a>),
27 WebOfTrust(WebOfTrustModel<'a>),
28}
29
30#[derive(Debug)]
32pub struct ModelBasedVerifier<'a> {
33 num_data_signatures: NumDataSignatures,
36
37 trust_model: TrustModel<'a>,
39}
40
41impl<'a> ModelBasedVerifier<'a> {
42 pub fn new(
47 config: &'a OpenpgpSettings,
48 artifact_verifiers: &'a [OpenpgpCert],
49 trust_anchors: &'a [OpenpgpCert],
50 ) -> Self {
51 let trust_model = match config.verification_method() {
52 VerificationMethod::Plain(plain) => {
53 if !trust_anchors.is_empty() {
54 warn!(
55 "VerificationMethod is 'Plain', but {} trust_anchors exist",
56 trust_anchors.len()
57 );
58 }
59
60 TrustModel::Plain(PlainTrustModel::new(plain, artifact_verifiers))
61 }
62
63 VerificationMethod::TrustAnchor(anchor) => TrustModel::TrustAnchor(
64 TrustAnchorModel::new(anchor, artifact_verifiers, trust_anchors),
65 ),
66
67 VerificationMethod::WebOfTrust(wot) => {
68 TrustModel::WebOfTrust(WebOfTrustModel::new(wot, artifact_verifiers, trust_anchors))
69 }
70 };
71
72 Self {
73 num_data_signatures: config.num_data_signatures(),
74 trust_model,
75 }
76 }
77
78 pub fn verify_file_with_signatures(
100 &'a self,
101 signed_file: impl AsRef<Path>,
102 signatures: &'a [OpenpgpSignature],
103 ) -> Result<Vec<OpenpgpSignatureCheck<'a>>, Error> {
104 let signed_file = signed_file.as_ref();
105
106 let checks = match &self.trust_model {
108 TrustModel::Plain(plain) => verify(plain, signatures, signed_file),
109 TrustModel::TrustAnchor(anchor) => verify(anchor, signatures, signed_file),
110 TrustModel::WebOfTrust(_wot) => {
111 return Err(Error::UnimplementedVerificationMethod {
112 verification_method: "OpenPGP Web of Trust".to_string(),
113 });
114 }
115 }?;
116
117 let signer_fingerprints: HashSet<_> = checks
125 .iter()
126 .filter_map(|check| check.signer_info())
127 .map(|si| si.certificate().certificate.fingerprint())
128 .collect();
129
130 let num_required_signatures = self.num_data_signatures.get().get();
133 let num_successful_signatures = signer_fingerprints.len();
134 if num_successful_signatures < num_required_signatures {
135 let failed_signatures = checks
136 .iter()
137 .filter_map(|check| {
138 if check.signer_info().is_none() {
139 Some(check.signature().clone())
140 } else {
141 None
142 }
143 })
144 .collect::<Vec<_>>();
145
146 return Err(Error::FileVerificationFailed {
147 path: signed_file.to_path_buf(),
148 num_required_signatures,
149 num_successful_signatures,
150 failed_signatures,
151 });
152 };
153
154 Ok(checks)
155 }
156}
157
158pub(crate) trait ArtifactVerifierValidity {
164 fn check(&self, cert: &OpenpgpCert, reference_time: SystemTime) -> bool;
167
168 fn lookup(&self) -> &VerifierLookup<'_>;
170}
171
172fn verify<'b>(
175 artifact_verifiers: &'b impl ArtifactVerifierValidity,
176 signatures: &'b [OpenpgpSignature],
177 signed_file: &Path,
178) -> Result<Vec<OpenpgpSignatureCheck<'b>>, Error> {
179 let data = read(signed_file).map_err(|source| Error::IoPath {
181 path: signed_file.into(),
182 source,
183 context: "reading a signed file",
184 })?;
185
186 let mut checks = Vec::new();
187
188 'sigs: for signature in signatures {
189 let log_sig_data = log_sig_data(signature);
190 info!("Verifying OpenPGP signature ({log_sig_data}) for file {signed_file:?}");
191
192 let Some(sig_created) = signature.creation_time() else {
193 warn!("Skipping signature without creation time");
194 checks.push(OpenpgpSignatureCheck::new(signature, None));
195 continue;
196 };
197
198 let verifiers = artifact_verifiers
199 .lookup()
200 .get_matching_verifiers(signature);
201 debug!("Found {} candidate verifiers", verifiers.len());
202
203 for (verifier, cert) in &verifiers {
204 if !artifact_verifiers.check(cert, sig_created) {
205 warn!(
207 "We can't use cert {} to verify, under the current policy, at {:?}",
208 cert.certificate.fingerprint(),
209 sig_created
210 );
211
212 continue;
213 }
214
215 let log_cert_data = log_cert_data(cert);
216 let component_fp = verifier.as_componentkey().fingerprint().to_string();
217
218 if let Err(error) = verifier.verify(&signature.detached.signature, &data) {
220 error!(
229 "Signature verification error for {log_sig_data} for file {signed_file:?} with component key {component_fp} in OpenPGP certificate {log_cert_data}: {error}"
230 );
231
232 continue;
234 }
235
236 info!(
237 "Successfully verified OpenPGP signature {log_sig_data} for file {signed_file:?} with component key {component_fp} in OpenPGP certificate {log_cert_data}"
238 );
239
240 checks.push(OpenpgpSignatureCheck::new(
241 signature,
242 Some(SignerInfo::new(cert, component_fp)),
243 ));
244
245 continue 'sigs;
247 }
248
249 warn!(
250 "Signature {log_sig_data} and file {signed_file:?} could not be verified (tried {} candidate verifiers)",
251 verifiers.len()
252 );
253 checks.push(OpenpgpSignatureCheck::new(signature, None));
254 }
255
256 Ok(checks)
257}