voa_openpgp/
lookup.rs

1//! Helper functionality to find appropriate [`SignatureVerifier`]s from a set of [`OpenpgpCert`].
2
3use std::collections::HashMap;
4
5use log::{debug, trace, warn};
6use pgp::{
7    packet::Signature,
8    types::{Fingerprint, KeyDetails, KeyId},
9};
10use rpgpie::certificate::SignatureVerifier;
11
12use crate::{OpenpgpCert, OpenpgpSignature};
13
14/// Helper to efficiently look up [`OpenpgpCert`]s by [`Fingerprint`] or by [`KeyId`].
15///
16/// This struct keeps a lookup entry by the [`Fingerprint`] and [`KeyId`] of *each* component key
17/// of each indexed [`OpenpgpCert`] (including ones that don't have the data signing key flag, and
18/// invalid ones).
19///
20/// As a consequence, the resulting lookup configuration doesn't make any semantical guarantees
21/// about lookup results, beyond that one component key bears the queried [`Fingerprint`] or
22/// [`KeyId`].
23///
24/// Downstream users must thus perform additional checks if a certificate is actually
25/// appropriate to use for a particular purpose.
26#[derive(Debug)]
27struct CertLookup<'a> {
28    by_fingerprint: HashMap<Fingerprint, Vec<&'a OpenpgpCert>>,
29    by_key_id: HashMap<KeyId, Vec<&'a OpenpgpCert>>,
30}
31
32impl<'a> From<&'a [OpenpgpCert]> for CertLookup<'a> {
33    fn from(certs: &'a [OpenpgpCert]) -> Self {
34        let mut lookup = Self {
35            by_fingerprint: HashMap::new(),
36            by_key_id: HashMap::new(),
37        };
38
39        // Insert lookup entries for the Fingerprint and KeyId of each component key of each cert
40        certs.iter().for_each(|cert| lookup.insert_cert(cert));
41
42        lookup
43    }
44}
45
46impl<'a> CertLookup<'a> {
47    /// Inserts an entry for the [`Fingerprint`] and [`KeyId`] of _each_ component key of an
48    /// [`OpenpgpCert`].
49    fn insert_cert(&mut self, cert: &'a OpenpgpCert) {
50        let primary = &cert.certificate.as_signed_public_key().primary_key;
51        self.insert_by_fingerprint(cert, primary.fingerprint());
52        self.insert_by_key_id(cert, primary.key_id());
53
54        for subkey in &cert.certificate.as_signed_public_key().public_subkeys {
55            self.insert_by_fingerprint(cert, subkey.fingerprint());
56            self.insert_by_key_id(cert, subkey.key_id());
57        }
58    }
59
60    /// Inserts a lookup entry for an [`OpenpgpCert`] and an accompanying [`Fingerprint`].
61    fn insert_by_fingerprint(&mut self, cert: &'a OpenpgpCert, fingerprint: Fingerprint) {
62        self.by_fingerprint
63            .entry(fingerprint)
64            .or_default()
65            .push(cert);
66    }
67
68    /// Inserts a lookup entry for an [`OpenpgpCert`] and an accompanying [`KeyId`].
69    fn insert_by_key_id(&mut self, cert: &'a OpenpgpCert, key_id: KeyId) {
70        self.by_key_id.entry(key_id).or_default().push(cert);
71    }
72
73    /// Returns a list of [`OpenpgpCert`]s that match a provided [`Fingerprint`].
74    pub(crate) fn by_fingerprint(&self, fingerprint: &Fingerprint) -> &[&'a OpenpgpCert] {
75        self.by_fingerprint
76            .get(fingerprint)
77            .map_or(&[], |cert| cert)
78    }
79
80    /// Returns a list of [`OpenpgpCert`]s that match a provided [`KeyId`].
81    pub(crate) fn by_key_id(&self, key_id: &KeyId) -> &[&'a OpenpgpCert] {
82        self.by_key_id.get(key_id).map_or(&[], |cert| cert)
83    }
84}
85
86/// An abstraction over a set of [`OpenpgpCert`], to facilitate signature verification.
87///
88/// It can efficiently look up component keys that are appropriate for attempting validation of
89/// specific signatures.
90#[derive(Debug)]
91pub struct VerifierLookup<'a> {
92    index: CertLookup<'a>,
93}
94
95impl<'a> VerifierLookup<'a> {
96    /// Creates a new [`VerifierLookup`] from a list of [`OpenpgpCert`]s.
97    pub fn new(certs: &'a [OpenpgpCert]) -> Self {
98        Self {
99            index: certs.into(),
100        }
101    }
102
103    /// Returns a list of [`SignatureVerifier`] and [`OpenpgpCert`] tuples, that are reasonable
104    /// candidates for attempting to verify an [`OpenpgpSignature`].
105    ///
106    /// Looks up all component keys that match the [IssuerFingerprint] and/or [Issuer] subpackets in
107    /// `signature`, and returns them as [`SignatureVerifier`] objects.
108    /// For informational purposes, each [`SignatureVerifier`] is accompanied by a reference to the
109    /// [`OpenpgpCert`] that contains it.
110    ///
111    /// Callers of this function will usually want to validate `signature` with the returned
112    /// [`SignatureVerifier`]s.
113    ///
114    /// [IssuerFingerprint]: https://www.rfc-editor.org/rfc/rfc9580.html#issuer-fingerprint-subpacket
115    /// [Issuer]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-key-id
116    pub(crate) fn get_matching_verifiers(
117        &self,
118        signature: &OpenpgpSignature,
119    ) -> Vec<(SignatureVerifier, &'a OpenpgpCert)> {
120        let Some(created) = signature.creation_time() else {
121            warn!("Skipping signature without creation time");
122            return Vec::new();
123        };
124
125        let mut verifiers = Vec::new();
126
127        // Iterate over all candidate certificates that contain component keys that match
128        // an issuer identity that `signature` lists.
129        for cert in self.candidate_certs(&signature.detached.signature) {
130            // OpenPGP semantics: enumerate all component keys that are valid for data signing
131            // purposes at the creation time of this signature.
132            for verifier in cert
133                .certificate
134                .valid_signing_capable_component_keys_at(&created.into())
135            {
136                // This set of valid component keys may contain some that do not match the issuer
137                // hints in the signature.
138                // Here, we filter the valid verifiers by the signature's issuer hints again.
139                if Self::matches_issuer(signature, &verifier) {
140                    verifiers.push((verifier, cert))
141                }
142            }
143        }
144
145        debug!(
146            "get_matching_verifiers for {:?}: {}",
147            &signature.source,
148            verifiers
149                .iter()
150                .map(|(verifier, cert)| format!(
151                    "{}/{}",
152                    verifier.as_componentkey().fingerprint(),
153                    cert.fingerprint()
154                ))
155                .collect::<Vec<_>>()
156                .join(", ")
157        );
158
159        verifiers
160    }
161
162    /// Returns a list of [`OpenpgpCert`]s that match either the
163    /// [IssuerFingerprint] or [Issuer] subpackets in a [`Signature`].
164    ///
165    /// All component keys of each [`OpenpgpCert`] are considered when matching against the
166    /// [IssuerFingerprint] and [Issuer] subpackets of the `signature`. The returned
167    /// [`OpenpgpCert`]s are deduplicated by their primary [`Fingerprint`].
168    ///
169    /// ## Note
170    ///
171    /// The lookup does not enforce any semantics constraints. It does not guarantee
172    /// validity of certificates or component keys for any particular purpose. Semantics checks
173    /// must be performed separately, after this lookup.
174    ///
175    /// [IssuerFingerprint]: https://www.rfc-editor.org/rfc/rfc9580.html#issuer-fingerprint-subpacket
176    /// [Issuer]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-key-id
177    fn candidate_certs(&self, signature: &Signature) -> Vec<&'a OpenpgpCert> {
178        let mut candidates = HashMap::new();
179
180        // Try to find the signer by Issuer Fingerprint subpacket
181        for fp in signature.issuer_fingerprint() {
182            for &cert in self.index.by_fingerprint(fp) {
183                candidates.insert(cert.fingerprint(), cert);
184            }
185        }
186
187        // Try to find the signer by Issuer KeyId subpacket
188        for key_id in signature.issuer() {
189            for &cert in self.index.by_key_id(key_id) {
190                candidates.insert(cert.fingerprint(), cert);
191            }
192        }
193
194        trace!(
195            "candidate_certs for {signature:#?}: {:?}",
196            candidates.keys().cloned().collect::<Vec<_>>().join(", ")
197        );
198
199        candidates.into_values().collect()
200    }
201
202    /// Checks if a [`SignatureVerifier`] matches any [IssuerFingerprint] or [Issuer] subpacket in
203    /// an [`OpenpgpSignature`].
204    ///
205    /// [IssuerFingerprint]: https://www.rfc-editor.org/rfc/rfc9580.html#issuer-fingerprint-subpacket
206    /// [Issuer]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-key-id
207    fn matches_issuer(signature: &OpenpgpSignature, verifier: &SignatureVerifier) -> bool {
208        let issuer_fingerprint = signature.detached.signature.issuer_fingerprint();
209        let issuer_key_id = signature.detached.signature.issuer();
210
211        issuer_fingerprint.contains(&&verifier.as_componentkey().fingerprint())
212            || issuer_key_id.contains(&&verifier.as_componentkey().key_id())
213    }
214}