voa_openpgp/
cert.rs

1//! OpenPGP certificate used as a verifier in VOA
2
3use std::fmt::{Debug, Formatter};
4
5use rpgpie::{
6    certificate::{Certificate, Checked},
7    merge::CertificateInfo,
8};
9use voa_core::Verifier;
10
11/// An OpenPGP certificate for "Verification of OS Artifacts (VOA)"
12#[derive(Clone)]
13pub struct OpenpgpCert {
14    /// OpenPGP certificate that is synthesized from the data in `sources` below.
15    ///
16    /// This is a "checked" view of the certificate, which applies policy and checks validity of
17    /// self-signatures.
18    pub certificate: Checked,
19
20    /// Pointers to verifier files.
21    ///
22    /// There may be multiple sources for one OpenPGP certificate, if multiple files contain data
23    /// about one common Certificate (as defined by a shared primary key fingerprint).
24    pub sources: Vec<Verifier>,
25}
26
27impl From<UncheckedOpenpgpCert> for OpenpgpCert {
28    fn from(value: UncheckedOpenpgpCert) -> Self {
29        let certificate = (&value.certificate).into();
30
31        OpenpgpCert {
32            certificate,
33            sources: value.sources,
34        }
35    }
36}
37
38impl Debug for OpenpgpCert {
39    /// The output consists of two blocks:
40    ///
41    /// 1) Information about the (merged) certificate: Information about the primary key, all valid
42    ///    subkeys that can be used for data signatures, and all valid User IDs.
43    ///
44    /// 2) A list of sources that the certificate information was loaded from.
45    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46        let mut status = rpgpie::model::status_summary(&self.certificate);
47
48        // filter out any subkeys that don't have the signing key flag
49        status.subkeys.retain(|sk| {
50            if let Some(flags) = &sk.key_flags {
51                // TODO: the "Sign" string is not guaranteed to be stable in rpgpie
52                flags.contains(&"Sign".to_string())
53            } else {
54                // don't keep subkeys with no key flag subpacket
55                false
56            }
57        });
58
59        // filter out invalid subkeys
60        status.subkeys.retain(|sk| sk.status.is_valid());
61
62        // filter out invalid user ids
63        status.user_ids.retain(|uid| uid.status.is_valid());
64
65        // Show (filtered) certificate information
66        write!(f, "{}", &status)?;
67
68        // Show information about the sources the certificate was loaded and merged from
69        writeln!(f, "  Source(s):")?;
70        for source in &self.sources {
71            writeln!(f, "  - {:?}", source.canonicalized())?;
72        }
73
74        Ok(())
75    }
76}
77
78impl OpenpgpCert {
79    /// Returns the (primary key) fingerprint of the certificate, as lower-case hex string.
80    pub fn fingerprint(&self) -> String {
81        format!("{:02x?}", self.certificate.fingerprint())
82    }
83}
84
85/// Intermediate form of an OpenPGP certificate while assembling and merging inputs from VOA.
86///
87/// Once merging is complete, an [`OpenpgpCert`] is constructed from this type.
88pub(crate) struct UncheckedOpenpgpCert {
89    /// The raw OpenPGP Certificate, synthesized from the data from
90    /// [`UncheckedOpenpgpCert::sources`].
91    pub(crate) certificate: Certificate,
92
93    /// Information on verifier files.
94    ///
95    /// There may be multiple sources for one OpenPGP certificate, if multiple files contain data
96    /// about one common Certificate (as defined by a shared primary key fingerprint).
97    pub(crate) sources: Vec<Verifier>,
98}
99
100impl UncheckedOpenpgpCert {
101    /// Merges the information from `certificate` into [`Self::certificate`], and adds the set of
102    /// `verifiers` to [`Self::sources`].
103    ///
104    /// By repeated calls to [`Self::merge`], an [`UncheckedOpenpgpCert`] can iteratively ingest
105    /// information from an arbitrary number of certificates and act as a unified view onto the
106    /// collective information in them.
107    pub(crate) fn merge(&mut self, certificate: Certificate, verifiers: Vec<Verifier>) {
108        // Merge the certificate contents into our certificate representation
109        self.certificate
110            .merge(vec![CertificateInfo::Cert(certificate)]);
111
112        // Merge the verifier source information into our set of sources
113        verifiers
114            .into_iter()
115            .for_each(|verifier| self.sources.push(verifier));
116    }
117}