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}