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}