1use std::{fmt, str::FromStr};
2
3use hex::{FromHex, FromHexError};
4use serde::de::{self, Visitor};
5use sha2::{Digest, Sha256};
6use x509_parser::{
7 certificate::X509Certificate,
8 extensions::{GeneralName, ParsedExtension},
9 oid_registry::{OID_X509_COMMON_NAME, OID_X509_EXT_SUBJECT_ALT_NAME},
10 parse_x509_certificate, parse_x509_crl,
11 pem::{Pem, parse_x509_pem},
12};
13
14use crate::{
15 config::{Config, ConfigError},
16 proto::command::{CertificateAndKey, TlsVersion},
17};
18
19#[allow(dead_code)]
29const SHA256_FINGERPRINT_LEN: usize = 32;
30
31#[derive(thiserror::Error, Debug)]
35pub enum CertificateError {
36 #[error("Could not parse PEM certificate from bytes: {0}")]
37 ParsePEMCertificate(String),
38 #[error("Could not parse X509 certificate from bytes: {0}")]
39 ParseX509Certificate(String),
40 #[error("failed to parse tls version '{0}'")]
41 InvalidTlsVersion(String),
42 #[error("failed to parse fingerprint, {0}")]
43 InvalidFingerprint(FromHexError),
44 #[error("could not load file on path {path}: {error}")]
51 LoadFile {
52 path: String,
53 error: Box<ConfigError>,
54 },
55 #[error("Failed at decoding the hex encoded certificate: {0}")]
56 DecodeError(FromHexError),
57}
58
59pub fn parse_pem(certificate: &[u8]) -> Result<Pem, CertificateError> {
65 let (_, pem) = parse_x509_pem(certificate)
66 .map_err(|err| CertificateError::ParsePEMCertificate(err.to_string()))?;
67
68 Ok(pem)
69}
70
71pub fn parse_x509(pem_bytes: &[u8]) -> Result<X509Certificate<'_>, CertificateError> {
73 parse_x509_certificate(pem_bytes)
74 .map_err(|nom_e| CertificateError::ParseX509Certificate(nom_e.to_string()))
75 .map(|t| t.1)
76}
77
78pub fn get_cn_and_san_attributes(x509: &X509Certificate) -> Vec<String> {
95 let mut names: Vec<String> = Vec::new();
96 let mut san_dns_seen = false;
97
98 for extension in x509.extensions() {
99 if extension.oid == OID_X509_EXT_SUBJECT_ALT_NAME
100 && let ParsedExtension::SubjectAlternativeName(san) = extension.parsed_extension()
101 {
102 for name in &san.general_names {
103 if let GeneralName::DNSName(name) = name {
104 san_dns_seen = true;
105 names.push(name.to_string());
106 }
107 }
108 }
109 }
110
111 debug_assert_eq!(
117 san_dns_seen,
118 !names.is_empty(),
119 "SAN dNSName presence must match the collected-names state before CN fallback"
120 );
121
122 if !san_dns_seen {
123 for name in x509.subject().iter_by_oid(&OID_X509_COMMON_NAME) {
124 names.push(
125 name.as_str()
126 .map(String::from)
127 .unwrap_or_else(|_| String::from_utf8_lossy(name.as_slice()).to_string()),
128 );
129 }
130 }
131 let before_dedup = names.len();
132 names.dedup();
133 debug_assert!(
136 names.len() <= before_dedup,
137 "dedup must not grow the identity list"
138 );
139 names
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
150 fn san_dns_present_excludes_cn() {
151 let pem = parse_pem(include_str!("../../lib/assets/cn-ne-san-cert.pem").as_bytes())
152 .expect("parse PEM");
153 let x509 = parse_x509(&pem.contents).expect("parse x509");
154 let names = get_cn_and_san_attributes(&x509);
155 assert_eq!(names, vec![String::from("tenant-a.example")]);
156 }
157
158 #[test]
162 fn cn_used_when_san_absent() {
163 let pem = parse_pem(include_str!("../../lib/assets/certificate.pem").as_bytes())
164 .expect("parse PEM");
165 let x509 = parse_x509(&pem.contents).expect("parse x509");
166 let names = get_cn_and_san_attributes(&x509);
167 assert_eq!(names, vec![String::from("lolcatho.st")]);
168 }
169
170 #[test]
175 fn san_dns_present_cn_is_san_member() {
176 let pem = parse_pem(include_str!("../../lib/assets/multi-sni-cert.pem").as_bytes())
177 .expect("parse PEM");
178 let x509 = parse_x509(&pem.contents).expect("parse x509");
179 let names = get_cn_and_san_attributes(&x509);
180 assert!(names.contains(&String::from("foo.example.com")));
181 assert!(names.contains(&String::from("bar.example.com")));
182 assert!(names.contains(&String::from("baz.example.com")));
183 assert!(names.contains(&String::from("localhost")));
184 assert_eq!(names.len(), 4);
185 }
186}
187
188impl FromStr for TlsVersion {
192 type Err = CertificateError;
193
194 fn from_str(s: &str) -> Result<Self, Self::Err> {
195 match s {
196 "SSL_V2" => Ok(TlsVersion::SslV2),
197 "SSL_V3" => Ok(TlsVersion::SslV3),
198 "TLSv1" => Ok(TlsVersion::TlsV10),
199 "TLS_V11" => Ok(TlsVersion::TlsV11),
200 "TLS_V12" => Ok(TlsVersion::TlsV12),
201 "TLS_V13" => Ok(TlsVersion::TlsV13),
202 _ => Err(CertificateError::InvalidTlsVersion(s.to_string())),
203 }
204 }
205}
206
207#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
213pub struct Fingerprint(pub Vec<u8>);
214
215impl FromStr for Fingerprint {
216 type Err = CertificateError;
217
218 fn from_str(s: &str) -> Result<Self, Self::Err> {
219 hex::decode(s)
220 .map_err(CertificateError::InvalidFingerprint)
221 .map(Fingerprint)
222 }
223}
224
225impl fmt::Debug for Fingerprint {
226 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
227 write!(f, "CertificateFingerprint({})", hex::encode(&self.0))
228 }
229}
230
231impl fmt::Display for Fingerprint {
232 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
233 write!(f, "{}", hex::encode(&self.0))
234 }
235}
236
237impl serde::Serialize for Fingerprint {
238 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239 where
240 S: serde::Serializer,
241 {
242 serializer.serialize_str(&hex::encode(&self.0))
243 }
244}
245
246struct FingerprintVisitor;
247
248impl Visitor<'_> for FingerprintVisitor {
249 type Value = Fingerprint;
250
251 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
252 formatter.write_str("the certificate fingerprint must be in hexadecimal format")
253 }
254
255 fn visit_str<E>(self, value: &str) -> Result<Fingerprint, E>
256 where
257 E: de::Error,
258 {
259 FromHex::from_hex(value)
260 .map_err(|e| E::custom(format!("could not deserialize hex: {e:?}")))
261 .map(Fingerprint)
262 }
263}
264
265impl<'de> serde::Deserialize<'de> for Fingerprint {
266 fn deserialize<D>(deserializer: D) -> Result<Fingerprint, D::Error>
267 where
268 D: serde::de::Deserializer<'de>,
269 {
270 deserializer.deserialize_str(FingerprintVisitor {})
271 }
272}
273
274pub fn crl_next_update(crl_der: &[u8]) -> Option<i64> {
279 let (_, crl) = parse_x509_crl(crl_der).ok()?;
280 crl.next_update().map(|next_update| next_update.timestamp())
281}
282
283pub fn calculate_fingerprint_from_der(certificate: &[u8]) -> Vec<u8> {
284 let fingerprint: Vec<u8> = Sha256::digest(certificate).iter().cloned().collect();
285 debug_assert_eq!(
289 fingerprint.len(),
290 SHA256_FINGERPRINT_LEN,
291 "SHA-256 fingerprint must be exactly 32 bytes"
292 );
293 fingerprint
294}
295
296pub fn calculate_fingerprint(certificate: &[u8]) -> Result<Vec<u8>, CertificateError> {
298 let parsed_certificate = parse_pem(certificate)?;
299 let fingerprint = calculate_fingerprint_from_der(&parsed_certificate.contents);
300 debug_assert_eq!(
304 fingerprint.len(),
305 SHA256_FINGERPRINT_LEN,
306 "PEM fingerprint must be a 32-byte SHA-256 digest"
307 );
308 debug_assert_eq!(
309 fingerprint,
310 calculate_fingerprint_from_der(&parsed_certificate.contents),
311 "fingerprint must be a deterministic function of the DER contents"
312 );
313 Ok(fingerprint)
314}
315
316pub fn split_certificate_chain(mut chain: String) -> Vec<String> {
317 let mut v = Vec::new();
318
319 let end = "-----END CERTIFICATE-----";
320 let expected_certs = chain.matches(end).count();
326 loop {
327 if let Some(sz) = chain.find(end) {
328 let cert: String = chain.drain(..sz + end.len()).collect();
329 debug_assert!(
333 cert.contains(end),
334 "each split block must contain its END CERTIFICATE marker"
335 );
336 v.push(cert.trim().to_string());
337 continue;
338 }
339
340 break;
341 }
342
343 debug_assert_eq!(
345 v.len(),
346 expected_certs,
347 "split must yield exactly one certificate per END marker"
348 );
349 v
350}
351
352pub fn get_fingerprint_from_certificate_path(
353 certificate_path: &str,
354) -> Result<Fingerprint, CertificateError> {
355 let bytes =
356 Config::load_file_bytes(certificate_path).map_err(|e| CertificateError::LoadFile {
357 path: certificate_path.to_string(),
358 error: Box::new(e),
359 })?;
360
361 let parsed_bytes = calculate_fingerprint(&bytes)?;
362
363 debug_assert_eq!(
367 parsed_bytes.len(),
368 SHA256_FINGERPRINT_LEN,
369 "fingerprint loaded from a certificate path must be 32 bytes"
370 );
371 Ok(Fingerprint(parsed_bytes))
372}
373
374pub fn decode_fingerprint(fingerprint: &str) -> Result<Fingerprint, CertificateError> {
375 let bytes = hex::decode(fingerprint).map_err(CertificateError::DecodeError)?;
376 Ok(Fingerprint(bytes))
377}
378
379pub fn load_full_certificate(
380 certificate_path: &str,
381 certificate_chain_path: &str,
382 key_path: &str,
383 versions: Vec<TlsVersion>,
384 names: Vec<String>,
385) -> Result<CertificateAndKey, CertificateError> {
386 let certificate =
387 Config::load_file(certificate_path).map_err(|e| CertificateError::LoadFile {
388 path: certificate_path.to_string(),
389 error: Box::new(e),
390 })?;
391
392 let certificate_chain = Config::load_file(certificate_chain_path)
393 .map(split_certificate_chain)
394 .map_err(|e| CertificateError::LoadFile {
395 path: certificate_chain_path.to_string(),
396 error: Box::new(e),
397 })?;
398
399 let key = Config::load_file(key_path).map_err(|e| CertificateError::LoadFile {
400 path: key_path.to_string(),
401 error: Box::new(e),
402 })?;
403
404 let versions_len = versions.len();
405 let names_len = names.len();
406 let versions: Vec<i32> = versions.iter().map(|v| *v as i32).collect();
407
408 debug_assert_eq!(
411 versions.len(),
412 versions_len,
413 "version encoding must preserve the input cardinality"
414 );
415
416 let built = CertificateAndKey {
417 certificate,
418 certificate_chain,
419 key,
420 versions,
421 names,
422 };
423
424 debug_assert_eq!(
428 built.names.len(),
429 names_len,
430 "names must be carried through the builder unchanged"
431 );
432 Ok(built)
433}
434
435impl CertificateAndKey {
436 pub fn fingerprint(&self) -> Result<Fingerprint, CertificateError> {
437 let pem = parse_pem(self.certificate.as_bytes())?;
438 let fingerprint = Fingerprint(Sha256::digest(&pem.contents).iter().cloned().collect());
439 debug_assert_eq!(
444 fingerprint.0.len(),
445 SHA256_FINGERPRINT_LEN,
446 "CertificateAndKey fingerprint must be 32 bytes"
447 );
448 debug_assert_eq!(
449 fingerprint.0,
450 calculate_fingerprint_from_der(&pem.contents),
451 "method and free-function fingerprints must agree on the same DER"
452 );
453 Ok(fingerprint)
454 }
455
456 pub fn get_overriding_names(&self) -> Result<Vec<String>, CertificateError> {
457 if self.names.is_empty() {
458 let pem = parse_pem(self.certificate.as_bytes())?;
459 let x509 = parse_x509(&pem.contents)?;
460
461 let overriding_names = get_cn_and_san_attributes(&x509);
462
463 Ok(overriding_names.into_iter().collect())
464 } else {
465 let names = self.names.to_owned();
466 debug_assert_eq!(
470 names, self.names,
471 "explicit names must be returned unchanged when present"
472 );
473 Ok(names)
474 }
475 }
476
477 pub fn apply_overriding_names(&mut self) -> Result<(), CertificateError> {
478 let resolved = self.get_overriding_names()?;
479 self.names = resolved.clone();
480 debug_assert_eq!(
484 self.names, resolved,
485 "applied names must equal the resolved set"
486 );
487 Ok(())
488 }
489}