1use crate::{
2 utils::{
3 codec::{CodecError, Decode},
4 extract_oid_from_rdn, hex_with_colons,
5 },
6 v1,
7};
8use chrono::{DateTime, Utc};
9use const_oid::db::rfc4519::{CN, COMMON_NAME, O, ORGANIZATION, ORGANIZATION_NAME};
10use p256::pkcs8::ObjectIdentifier;
11use sha2::{Digest, Sha256};
12use std::{
13 fmt::{self, Display},
14 io::Cursor,
15};
16use thiserror::Error;
17use x509_cert::{
18 Certificate as Cert,
19 der::{Decode as CertDecode, DecodePem, Encode as CertEncode, EncodePem, asn1::OctetString},
20 ext::pkix::{AuthorityKeyIdentifier, SubjectKeyIdentifier},
21};
22
23pub(crate) const SCT_V1: ObjectIdentifier = const_oid::db::rfc6962::CT_PRECERT_SCTS;
24pub(crate) const CT_POISON: ObjectIdentifier = const_oid::db::rfc6962::CT_PRECERT_POISON;
25
26pub(crate) const SUBJECT_KEY_ID: ObjectIdentifier =
27 const_oid::db::rfc5280::ID_CE_SUBJECT_KEY_IDENTIFIER;
28pub(crate) const AUTH_KEY_ID: ObjectIdentifier =
29 const_oid::db::rfc5280::ID_CE_AUTHORITY_KEY_IDENTIFIER;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Certificate(pub(crate) Cert);
34
35impl Certificate {
36 pub fn from_pem(input: &str) -> Result<Self, CertificateError> {
38 Ok(Self(
39 Cert::from_pem(input.as_bytes()).map_err(CodecError::DerError)?,
40 ))
41 }
42
43 pub fn as_pem(&self) -> String {
44 self.0.to_pem(p256::pkcs8::LineEnding::LF).unwrap()
45 }
46
47 pub fn from_der(input: &[u8]) -> Result<Self, CertificateError> {
49 Ok(Self(Cert::from_der(input).map_err(CodecError::DerError)?))
50 }
51
52 pub fn extract_scts_v1(&self) -> Result<Vec<v1::SignedCertificateTimestamp>, CertificateError> {
54 let Some(extensions) = &self.0.tbs_certificate.extensions else {
55 return Ok(vec![]);
56 };
57
58 let sct_lists = extensions
59 .iter()
60 .filter(|extension| extension.extn_id == SCT_V1)
61 .map(|sct| &sct.extn_value)
62 .map(|sct| {
63 let sct = OctetString::from_der(sct.as_bytes()).unwrap();
64 let mut reader = Cursor::new(sct.as_bytes());
65 v1::SctList::decode(&mut reader)
66 })
67 .collect::<Result<Vec<_>, _>>()?;
68
69 let scts = sct_lists
70 .into_iter()
71 .flat_map(|list| list.into_inner())
72 .collect();
73
74 Ok(scts)
75 }
76
77 pub fn is_precert(&self) -> Result<bool, CertificateError> {
78 let Some(extensions) = &self.0.tbs_certificate.extensions else {
79 return Ok(false);
80 };
81
82 let scts = extensions
83 .iter()
84 .filter(|extension| extension.extn_id == SCT_V1)
85 .count();
86
87 let poisons = extensions
88 .iter()
89 .filter(|extension| extension.extn_id == CT_POISON && extension.critical)
90 .filter(|extension| extension.extn_value.as_bytes() == [0x05, 0x00])
91 .count();
92
93 match (poisons, scts) {
94 (1, 0) => Ok(true),
95 (0, _) => Ok(false),
96 _ => Err(CertificateError::InvalidPreCert),
97 }
98 }
99
100 pub fn fingerprint_sha256(&self) -> Fingerprint {
101 let mut cert_bytes = vec![];
102 self.0.encode_to_vec(&mut cert_bytes).unwrap();
103
104 let hash: [u8; 32] = Sha256::digest(&cert_bytes).into();
105 Fingerprint(hash)
106 }
107
108 pub fn get_issuer_name(&self) -> String {
109 let issuer = &self.0.tbs_certificate.issuer;
110 extract_oid_from_rdn(issuer, O)
111 .or_else(|| extract_oid_from_rdn(issuer, ORGANIZATION))
112 .or_else(|| extract_oid_from_rdn(issuer, ORGANIZATION_NAME))
113 .unwrap_or_else(|| issuer.to_string())
114 }
115
116 pub fn get_subject_name(&self) -> String {
117 let subject = &self.0.tbs_certificate.subject;
118 extract_oid_from_rdn(subject, CN)
119 .or_else(|| extract_oid_from_rdn(subject, COMMON_NAME))
120 .unwrap_or_else(|| subject.to_string())
121 }
122
123 pub fn get_validity(&self) -> (DateTime<Utc>, DateTime<Utc>) {
124 (
125 DateTime::from(self.0.tbs_certificate.validity.not_before.to_system_time()),
126 DateTime::from(self.0.tbs_certificate.validity.not_after.to_system_time()),
127 )
128 }
129
130 pub fn get_subject_key_info(&self) -> Option<Vec<u8>> {
131 let Some(extensions) = &self.0.tbs_certificate.extensions else {
132 return None;
133 };
134
135 extensions
136 .iter()
137 .find(|extension| extension.extn_id == SUBJECT_KEY_ID)
138 .and_then(|extension| {
139 SubjectKeyIdentifier::from_der(extension.extn_value.as_bytes()).ok()
140 })
141 .map(|key_id| key_id.0.as_bytes().to_vec())
142 }
143
144 pub fn get_authority_key_info(&self) -> Option<Vec<u8>> {
145 let Some(extensions) = &self.0.tbs_certificate.extensions else {
146 return None;
147 };
148
149 extensions
150 .iter()
151 .find(|extension| extension.extn_id == AUTH_KEY_ID)
152 .and_then(|extension| {
153 AuthorityKeyIdentifier::from_der(extension.extn_value.as_bytes()).ok()
154 })
155 .and_then(|key_id| key_id.key_identifier)
156 .map(|key_id| key_id.as_bytes().to_vec())
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
161pub struct Fingerprint(pub [u8; 32]);
162
163impl Display for Fingerprint {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(f, "{}", hex_with_colons(&self.0))
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Error)]
171pub enum CertificateError {
172 #[error("A precert can't have SCTs or more than one poison value")]
173 InvalidPreCert,
174
175 #[error("The certificate chain is malformed")]
176 InvalidChain,
177
178 #[error("Failed to decode a value: {0}")]
179 CodecError(#[from] CodecError),
180
181 #[error("Failed to verify certificate: {0}")]
182 VerificationError(x509_verify::Error),
183}
184
185impl From<x509_verify::Error> for CertificateError {
186 fn from(value: x509_verify::Error) -> Self {
187 Self::VerificationError(value)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::{
195 CertificateChain,
196 tests::{CERT_CHAIN_GOOGLE_COM, get_log_argon2025h2},
197 utils::codec::Encode,
198 };
199
200 const CERT_GOOGLE_COM: &str = include_str!("../../testdata/google-cert.pem");
201 const PRE_CERT_GOOGLE_COM: &str = include_str!("../../testdata/google-precert.pem");
202 const GOOGLE_COM_FINGERPRINT: &str = "4B:4F:46:F8:E1:78:B4:08:F9:A7:AF:2B:CE:31:0A:6A:9F:BD:59:37:BD:F8:5B:C5:9B:45:D6:3C:81:61:73:67";
203
204 const CERT_GEOMYS_ORG: &str = include_str!("../../testdata/geomys-org.pem");
206
207 #[test]
208 fn sct_list_codec_roundtrip() {
209 let cert = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
210 cert.verify_chain().unwrap();
211 let scts = cert.cert().extract_scts_v1().unwrap();
212
213 let mut writer = Cursor::new(vec![]);
214 v1::SctList::new(scts.clone()).encode(&mut writer).unwrap();
215 let scts2 = v1::SctList::decode(Cursor::new(writer.into_inner()))
216 .unwrap()
217 .into_inner();
218
219 assert_eq!(scts, scts2)
220 }
221
222 #[test]
223 fn validate_scts() {
224 let cert = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
225 cert.verify_chain().unwrap();
226 let scts = cert.cert().extract_scts_v1().unwrap();
227
228 let log = get_log_argon2025h2();
229 assert_eq!(log.log_id(), &scts[0].log_id());
230
231 log.validate_sct_v1(&cert, &scts[0], true).unwrap();
232 }
233
234 #[test]
235 fn precert_transformation() {
236 let cert1 = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
237 cert1.verify_chain().unwrap();
238 let cert2 = Certificate::from_pem(CERT_GOOGLE_COM).unwrap();
239
240 assert_eq!(cert1.cert(), &cert2);
241 assert!(!cert1.cert().is_precert().unwrap());
242
243 let precert = Certificate::from_pem(PRE_CERT_GOOGLE_COM).unwrap();
244 assert!(precert.is_precert().unwrap());
245 }
246
247 #[test]
248 fn fingerprint() {
249 let cert = Certificate::from_pem(CERT_GOOGLE_COM).unwrap();
250 let fp = cert.fingerprint_sha256();
251 assert_eq!(format!("{fp}"), GOOGLE_COM_FINGERPRINT);
252 }
253
254 #[test]
255 fn leaf_index() {
256 let cert = Certificate::from_pem(CERT_GEOMYS_ORG).unwrap();
257 let scts = cert.extract_scts_v1().unwrap();
258
259 assert_eq!(scts.len(), 2);
260 assert!(scts[0].extensions.leaf_index().is_none());
261 assert!(scts[1].extensions.leaf_index().is_some());
262 }
263}