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}