voa_openpgp/
cert.rs

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