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}