voa_openpgp/
voa.rs

1//! VOA technology implementation for OpenPGP
2
3use std::{
4    collections::{HashMap, hash_map::Entry},
5    fs::File,
6};
7
8use log::{debug, info, warn};
9use rpgpie::certificate::Certificate;
10use voa_core::{
11    Voa,
12    identifiers::{Context, Os, Purpose, Technology},
13};
14
15use crate::{FILE_SUFFIX, OpenpgpCert, cert::UncheckedOpenpgpCert};
16
17/// An OpenPGP-specific view of a VOA hierarchy
18#[derive(Debug)]
19pub struct VoaOpenpgp(Voa);
20
21impl Default for VoaOpenpgp {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl VoaOpenpgp {
28    /// Initializes a new [`VoaOpenpgp`] instance.
29    ///
30    /// # Note
31    ///
32    /// Depending on the effective user ID of the calling process, [`VoaOpenpgp::lookup`] later
33    /// happens either in system or in user mode.
34    pub fn new() -> VoaOpenpgp {
35        info!("Initializing VOA OpenPGP instance");
36
37        Self(Voa::new())
38    }
39
40    /// Retrieves OpenPGP verifiers from available VOA hierarchies based on a query.
41    ///
42    /// Verifiers are returned as OpenPGP certificates.
43    /// Valid verifiers are considered, if they match the VOA identifiers `os`, `purpose` and
44    /// `context`. Invalid verifiers are skipped.
45    ///
46    /// # Note
47    ///
48    /// Skips OpenPGP verifiers under certain conditions and emits warning messages instead:
49    ///
50    /// - an OpenPGP verifier file name is not valid UTF-8,
51    /// - an OpenPGP verifier file cannot be opened for reading,
52    /// - an OpenPGP certificate cannot be read from an OpenPGP verifier file,
53    /// - an OpenPGP verifier file contains more than one OpenPGP certificate,
54    /// - or an OpenPGP verifier file name does not match the primary fingerprint of the OpenPGP
55    ///   certificate contained in it.
56    pub fn lookup(&self, os: Os, purpose: Purpose, context: Context) -> Vec<OpenpgpCert> {
57        let verifiers = self.0.lookup(os, purpose, context, Technology::Openpgp);
58
59        // Map of Fingerprint -> OpenPGPCert
60        let mut certs: HashMap<String, UncheckedOpenpgpCert> = HashMap::new();
61
62        for (canonicalized, verifiers) in verifiers {
63            debug!("Processing VOA file {canonicalized:?}");
64
65            // -- load cert from file --
66            let Ok(mut file) = File::open(&canonicalized) else {
67                warn!("Couldn't open verifier file {canonicalized:?}, skipping",);
68
69                continue;
70            };
71
72            let Ok(mut loaded) = Certificate::load(&mut file) else {
73                warn!("Failed to deserialize OpenPGP certificate {canonicalized:?}, skipping",);
74
75                continue;
76            };
77
78            if loaded.len() != 1 {
79                warn!(
80                    "File {file:?} contained {} certificates, skipping",
81                    loaded.len()
82                );
83
84                continue;
85            }
86
87            let certificate = loaded.remove(0);
88
89            // -- check that filename and certificate content align --
90
91            let Some(source_file_name) = canonicalized.file_name() else {
92                // The path of the verifier is not valid UTF-8.
93                warn!("The file name {canonicalized:?} is not valid UTF-8, skipping");
94                continue;
95            };
96
97            let fingerprint = format!("{:02x?}", certificate.fingerprint());
98            let expected_filename = format!("{fingerprint}{FILE_SUFFIX}");
99
100            if *source_file_name != *expected_filename {
101                warn!(
102                    "Verifier filename {source_file_name:?} doesn't match expectation ({expected_filename}), skipping",
103                );
104
105                continue;
106            }
107
108            // -- merge if necessary, store in map --
109            match certs.entry(fingerprint) {
110                Entry::Vacant(ve) => {
111                    // We don't have a certificate for that fingerprint yet, so we start a new entry
112                    ve.insert(UncheckedOpenpgpCert {
113                        certificate,
114                        sources: verifiers,
115                    });
116                }
117                Entry::Occupied(oe) => {
118                    // We obtained data for a pre-existing fingerprint, merge in the new data
119                    oe.into_mut().merge(certificate, verifiers);
120                }
121            }
122        }
123
124        // -- transform map into Vec of OpenPGPCert --
125        certs.into_values().map(Into::into).collect()
126    }
127}