voa_openpgp/trust/plain.rs
1//! Implementation of the "plain" verification method.
2//!
3//! This verification method only considers artifact verifiers, but no trust anchors.
4
5use std::{collections::HashSet, time::SystemTime};
6
7use log::{trace, warn};
8use voa_config::openpgp::{DomainName, PlainMode};
9
10use crate::{
11 OpenpgpCert,
12 lookup::VerifierLookup,
13 trust::{
14 model::ArtifactVerifierValidity,
15 util::{fingerprint_matches, identity_bound_at, user_id_matches_any},
16 },
17};
18
19/// Signature verification object that uses a plain artifact verifier-based trust model.
20#[derive(Debug)]
21pub(crate) struct PlainTrustModel<'a> {
22 /// Artifact verifiers are only considered if they contain a validly self-bound identity in
23 /// one of these domain names, at the reference time.
24 pub(crate) identity_domain_names: &'a HashSet<DomainName>,
25
26 /// Lookup table for the set of artifact verifiers
27 pub(crate) artifact_verifier_lookup: VerifierLookup<'a>,
28}
29
30impl<'a> PlainTrustModel<'a> {
31 /// Creates a new [`PlainTrustModel`].
32 ///
33 /// Applies OpenPGP fingerprint based filters from `config` on `artifact_verifiers`.
34 /// Further filters for `artifact_verifiers` are only applied during signature verification.
35 pub(crate) fn new(config: &'a PlainMode, artifact_verifiers: &'a [OpenpgpCert]) -> Self {
36 // NOTE: We don't filter certificates for validity here.
37 // (For data signature verification, we care about certificate validity at signature
38 // creation time.)
39
40 let fingerprints = config.fingerprint_matches();
41
42 // Filter artifact verifiers by fingerprint
43 let artifact_verifier_lookup = VerifierLookup::from_iter(
44 artifact_verifiers
45 .iter()
46 .filter(|cert| fingerprint_matches(cert, fingerprints)),
47 );
48
49 PlainTrustModel {
50 identity_domain_names: config.identity_domain_matches(),
51 artifact_verifier_lookup,
52 }
53 }
54}
55
56impl ArtifactVerifierValidity for PlainTrustModel<'_> {
57 /// Check if `cert` is valid under the "plain" trust model.
58 ///
59 /// If `identity_domain_names` is not empty, this checks that `cert` has a validly
60 /// self-bound identity in one of the legal domain names, at `reference_time`.
61 fn check(&self, cert: &OpenpgpCert, reference_time: SystemTime) -> bool {
62 if self.identity_domain_names.is_empty() {
63 // We don't explicitly require any particular identity
64 //
65 // (However, note that elsewhere, certificate validity is checked! In that context, for
66 // some certificates, a valid self-certification on a user id may be required.)
67
68 return true;
69 }
70
71 for user in cert.certificate.user_ids() {
72 // Extract the user id as a UTF-8 string
73 let Ok(userid) = String::from_utf8(user.id.id().to_vec()) else {
74 warn!(
75 "Invalid non-UTF8 User ID {} in certificate {}",
76 String::from_utf8_lossy(user.id.id()),
77 cert.certificate.fingerprint()
78 );
79 continue;
80 };
81
82 // Check that the identity matches a domain in `identity_domain_names`
83 if !user_id_matches_any(&userid, self.identity_domain_names) {
84 trace!(
85 "Skipping User ID {}, because it doesn't match {:?}",
86 userid, self.identity_domain_names
87 );
88 continue;
89 }
90
91 // Check that the identity is validly self-bound at `reference_time`
92 if !identity_bound_at(cert, &user.signatures, reference_time) {
93 trace!(
94 "Skipping User ID {userid}, because it is not validly self-bound at {reference_time:?}",
95 );
96 continue;
97 }
98
99 // This identity matches `identity_domain_names`, and is validly self-bound
100 trace!("Accepted User ID {userid}");
101 return true;
102 }
103
104 // We found no valid identity that matches `identity_domain_names`
105 trace!("No accepted User ID was found");
106 false
107 }
108
109 fn lookup(&self) -> &VerifierLookup<'_> {
110 &self.artifact_verifier_lookup
111 }
112}