voa_openpgp/trust/anchor.rs
1//! Implementation of the basic "trust anchor" verification method.
2
3use std::{collections::HashSet, time::SystemTime};
4
5use log::{debug, error, trace, warn};
6use pgp::packet::SignatureType;
7use voa_config::openpgp::{DomainName, NumCertifications, TrustAnchorMode};
8
9use crate::{
10 OpenpgpCert,
11 lookup::VerifierLookup,
12 trust::{
13 model::ArtifactVerifierValidity,
14 util::{find_signed_user, fingerprint_matches, identity_bound_at, user_id_matches_any},
15 },
16};
17
18/// Signature verification object that uses a basic trust anchor-based trust model.
19#[derive(Debug)]
20pub(crate) struct TrustAnchorModel<'a> {
21 /// Lookup table for the set of trust anchors
22 pub(crate) trust_anchor_lookup: VerifierLookup<'a>,
23
24 /// Artifact verifiers are only considered if they contain a validly self-bound identity in
25 /// one of these domain names, at the reference time.
26 /// In addition, such an identity must be certified by the required number of trust
27 /// anchors.
28 pub(crate) identity_domain_names: &'a HashSet<DomainName>,
29
30 /// Number of valid certifications an artifact verifiers needs to be used for signature
31 /// verification
32 pub(crate) required_certifications: NumCertifications,
33
34 /// Lookup table for the set of artifact verifiers
35 pub(crate) artifact_verifier_lookup: VerifierLookup<'a>,
36}
37
38impl<'a> TrustAnchorModel<'a> {
39 /// Creates a new [`TrustAnchorModel`].
40 ///
41 /// Applies filters from `config` on `trust_anchors`.
42 /// Filters for `artifact_verifiers` are only applied during signature verification.
43 pub(crate) fn new(
44 config: &'a TrustAnchorMode,
45 artifact_verifiers: &'a [OpenpgpCert],
46 trust_anchors: &'a [OpenpgpCert],
47 ) -> Self {
48 // NOTE: We don't filter certificates for validity here.
49 // (Both for certifications and data signature verification, we care about certificate
50 // validity at signature creation time.)
51
52 let anchor_fingerprints = config.trust_anchor_fingerprint_matches();
53
54 // Filter trust anchors by fingerprint
55 let trust_anchor_lookup = VerifierLookup::from_iter(
56 trust_anchors
57 .iter()
58 .filter(|cert| fingerprint_matches(cert, anchor_fingerprints)),
59 );
60
61 TrustAnchorModel {
62 trust_anchor_lookup,
63 identity_domain_names: config.artifact_verifier_identity_domain_matches(),
64 required_certifications: config.required_certifications(),
65 artifact_verifier_lookup: VerifierLookup::new(artifact_verifiers),
66 }
67 }
68}
69
70impl ArtifactVerifierValidity for TrustAnchorModel<'_> {
71 /// Check if `cert` is valid under the basic "trust anchor" model.
72 ///
73 /// This checks for an acceptable identity, which must:
74 /// - Be validly self-bound.
75 /// - Match `identity_domain_names`, if that set has any entries.
76 /// - Have the required number of certifications from valid trust anchors.
77 ///
78 /// Note that this function considers third-party certification revocations permanent.
79 /// If a certifier revokes his third-party certification on a specific user id, this function
80 /// will not accept any future certifications from that third party over the same user id.
81 fn check(&self, cert: &OpenpgpCert, reference_time: SystemTime) -> bool {
82 // Iterate over all user ids, and the third-party signatures over them
83 for (uid, sigs) in cert.certificate.user_id_third_party_certifications() {
84 // Check that the UserId matches a domain in `identity_domain_names`
85
86 // Transform the user id into a UTF-8 string
87 let Ok(userid) = String::from_utf8(uid.id().to_vec()) else {
88 warn!(
89 "Invalid non-UTF8 User ID {} in certificate {}",
90 String::from_utf8_lossy(uid.id()),
91 cert.certificate.fingerprint()
92 );
93 continue;
94 };
95
96 // Check if userid is acceptable, based on `identity_domain_names`
97 if !user_id_matches_any(&userid, self.identity_domain_names) {
98 trace!(
99 "Skipping User ID {userid}, because it doesn't match one of {}",
100 self.identity_domain_names
101 .iter()
102 .map(ToString::to_string)
103 .collect::<Vec<_>>()
104 .join(", ")
105 );
106 continue;
107 }
108
109 debug!(
110 "The User ID {userid} has {} third-party certifications in total.",
111 sigs.len()
112 );
113
114 // Get self-signatures for this user id
115 if let Some(su) = find_signed_user(cert, &uid) {
116 // Check that the identity is validly self-bound at `reference_time`
117 if !identity_bound_at(cert, &su.signatures, reference_time) {
118 trace!(
119 "Skipping User ID {userid}, because it is not validly self-bound at {reference_time:?}",
120 );
121 continue;
122 }
123 } else {
124 error!("Couldn't find SignedUser for {userid}. This should never happen.");
125 continue;
126 }
127
128 // Third-party certifications by valid certifiers (from our trust anchor set).
129 // These certifications are cryptographically valid, and passed rpgpie's policy and
130 // temporal checks.
131 let valid_certifications = self.trust_anchor_lookup.valid_userid_certifications(
132 &sigs,
133 cert,
134 &uid,
135 reference_time,
136 );
137
138 debug!(
139 "The UserID {userid} has {} valid third-party certifications.",
140 valid_certifications.len()
141 );
142
143 // Produce a list of confirming certifiers.
144 // If a signer has revoked their certification, or not issued a certification that is
145 // valid at the reference time, then that signer is not added to this list.
146 let active_certifiers = {
147 let mut active = Vec::new();
148
149 for (signer, sigs) in valid_certifications {
150 // We consider this signer's certification revoked if any revocation exists
151 // (at reference time)
152 if sigs
153 .iter()
154 .any(|s| s.typ() == Some(SignatureType::CertRevocation))
155 {
156 trace!(
157 "Signer {} has revoked their certification of {userid}",
158 signer.certificate.fingerprint()
159 );
160 continue;
161 }
162
163 // If there is any certifying signature from this signer we consider that signer
164 // an active certifier of this user id.
165 if !sigs.is_empty() {
166 active.push(signer);
167 }
168 }
169
170 active
171 };
172
173 if !active_certifiers.is_empty() {
174 trace!(
175 "Found third-party certifications on UserID {userid} from {}",
176 active_certifiers
177 .iter()
178 .map(|cert| cert.certificate.fingerprint())
179 .map(ToString::to_string)
180 .collect::<Vec<_>>()
181 .join(", ")
182 );
183 }
184
185 // We consider each `active` certifier as one unit of trust in `target`.
186 // If we have less third-party certifications than
187 // `required_certifications`, we continue to the next UserID.
188 if active_certifiers.len() < self.required_certifications.get().get() {
189 debug!(
190 "Too few certifications {} on User ID {userid} of {} at {:?} ",
191 active_certifiers.len(),
192 cert.certificate.fingerprint(),
193 reference_time.duration_since(std::time::UNIX_EPOCH),
194 );
195 continue;
196 }
197
198 // We have found the required number of third-party certifications (or more)
199 return true;
200 }
201
202 false
203 }
204
205 fn lookup(&self) -> &VerifierLookup<'_> {
206 &self.artifact_verifier_lookup
207 }
208}