Skip to main content

luct_core/
cert_chain.rs

1use crate::{
2    Certificate, CertificateError,
3    cert::{CT_POISON, SCT_V1},
4    utils::codec::CodecError,
5    v1,
6};
7use itertools::Itertools;
8use sha2::{Digest, Sha256};
9use x509_cert::{
10    Certificate as Cert,
11    der::{Decode, Encode},
12};
13use x509_verify::VerifyingKey;
14
15/// A [`CertificateChain`] chain of trust
16///
17/// These chains are what gets presented by TLS.
18/// They consist of a number of X.509 [`Certificates`](Certificate),
19/// from the source to a root of trust.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct CertificateChain(Vec<Certificate>);
22
23impl From<Vec<Certificate>> for CertificateChain {
24    fn from(value: Vec<Certificate>) -> Self {
25        Self(value)
26    }
27}
28
29impl CertificateChain {
30    pub fn from_pem_chain(input: &str) -> Result<Self, CertificateError> {
31        let chain = Cert::load_pem_chain(input.as_bytes()).map_err(CodecError::DerError)?;
32
33        // We need at least a chain of depth 2 (root + leaf), since root certs themselves
34        // can not be logged in this way
35        if chain.len() < 2 {
36            return Err(CertificateError::InvalidChain);
37        }
38
39        let chain = Self(chain.into_iter().map(Certificate).collect());
40        Ok(chain)
41    }
42
43    pub fn as_pem_chain(&self) -> String {
44        self.0.iter().map(|cert| cert.as_pem()).join("")
45    }
46
47    pub fn from_der_chain(input: &[Vec<u8>]) -> Result<Self, CertificateError> {
48        let chain = input
49            .iter()
50            .map(|bytes| Cert::from_der(bytes))
51            .collect::<Result<Vec<_>, _>>()
52            .map_err(CodecError::DerError)?;
53
54        // We need at least a chain of depth 2 (root + leaf), since root certs themselves
55        // can not be logged in this way
56        if chain.len() < 2 {
57            return Err(CertificateError::InvalidChain);
58        }
59
60        let chain = Self(chain.into_iter().map(Certificate).collect());
61        Ok(chain)
62    }
63
64    pub fn verify_chain(&self) -> Result<(), CertificateError> {
65        self.verify_chain_inner(None)
66    }
67
68    pub fn verify_chain_against_root(&self, root: &Certificate) -> Result<(), CertificateError> {
69        self.verify_chain_inner(Some(root))
70    }
71
72    fn verify_chain_inner(&self, maybe_root: Option<&Certificate>) -> Result<(), CertificateError> {
73        for idx in 1..self.0.len() {
74            let key = VerifyingKey::try_from(&self.0[idx].0)?;
75            key.verify(&self.0[idx - 1].0)?;
76        }
77
78        if let Some(root) = maybe_root {
79            let key = VerifyingKey::try_from(&self.0.last().unwrap().0)?;
80            key.verify(&root.0)?;
81        }
82
83        Ok(())
84    }
85
86    pub fn cert(&self) -> &Certificate {
87        &self.0[0]
88    }
89
90    pub fn root(&self) -> &Certificate {
91        self.0.last().unwrap()
92    }
93
94    pub(crate) fn as_log_entry_v1(&self, as_precert: bool) -> Result<v1::LogEntry, CodecError> {
95        if !as_precert {
96            return Ok(v1::LogEntry::X509(self.cert().0.clone()));
97        }
98
99        // Get the hash of the issuers subject public key info
100        let mut subject_public_key_bytes = vec![];
101        // TODO: Support precert signing certificate
102        // If the parrent of the precert is a precert signing certificate,
103        // the issuer should be the subject public key info of that certificates parent
104        self.0[1]
105            .0
106            .tbs_certificate
107            .subject_public_key_info
108            .encode_to_vec(&mut subject_public_key_bytes)
109            .map_err(CodecError::DerError)?;
110        let issuer_key_hash: [u8; 32] = Sha256::digest(&subject_public_key_bytes).into();
111
112        let mut tbs_certificate = self.cert().0.tbs_certificate.clone();
113        tbs_certificate.extensions = tbs_certificate.extensions.map(|extensions| {
114            extensions
115                .into_iter()
116                // NOTE: We need to remove all SCT and POISON extensions
117                .filter(|extension| extension.extn_id != SCT_V1 && extension.extn_id != CT_POISON)
118                .collect::<Vec<_>>()
119        });
120
121        Ok(v1::LogEntry::PreCert(v1::PreCert {
122            issuer_key_hash,
123            tbs_certificate,
124        }))
125    }
126
127    /// Return the [leaf](v1::MerkleTreeLeaf) of the [SCT](v1::SignedCertificateTimestamp)
128    ///
129    /// # Arguments
130    /// -`sct`: The [`v1::SignedCertificateTimestamp`] for which the [leaf](v1::MerkleTreeLeaf) should be generated
131    /// -`as_precert`: Whether the [leaf](v1::MerkleTreeLeaf) should contain a precert entry or the certificate itself
132    ///
133    /// # Note:
134    /// If the [SCT](v1::SignedCertificateTimestamp) was obtained by extracting it out of the [`Certificate`] itself
135    /// via [`Certificate::extract_scts_v1`], then the corresponding leaf must be a precertificate and `is_precert` should
136    /// be set to true.
137    pub fn as_leaf_v1(
138        &self,
139        sct: &v1::SignedCertificateTimestamp,
140        as_precert: bool,
141    ) -> Result<v1::MerkleTreeLeaf, CodecError> {
142        Ok(v1::MerkleTreeLeaf {
143            version: sct.sct_version.clone(),
144            leaf: v1::tree::Leaf::TimestampedEntry(v1::tree::TimestampedEntry {
145                timestamp: sct.timestamp,
146                log_entry: self.as_log_entry_v1(as_precert)?,
147                extensions: sct.extensions.clone(),
148            }),
149        })
150    }
151}