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,
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 calculate_fingerprint_from_der(certificate: &[u8]) -> Vec<u8> {
276 let fingerprint: Vec<u8> = Sha256::digest(certificate).iter().cloned().collect();
277 debug_assert_eq!(
281 fingerprint.len(),
282 SHA256_FINGERPRINT_LEN,
283 "SHA-256 fingerprint must be exactly 32 bytes"
284 );
285 fingerprint
286}
287
288pub fn calculate_fingerprint(certificate: &[u8]) -> Result<Vec<u8>, CertificateError> {
290 let parsed_certificate = parse_pem(certificate)?;
291 let fingerprint = calculate_fingerprint_from_der(&parsed_certificate.contents);
292 debug_assert_eq!(
296 fingerprint.len(),
297 SHA256_FINGERPRINT_LEN,
298 "PEM fingerprint must be a 32-byte SHA-256 digest"
299 );
300 debug_assert_eq!(
301 fingerprint,
302 calculate_fingerprint_from_der(&parsed_certificate.contents),
303 "fingerprint must be a deterministic function of the DER contents"
304 );
305 Ok(fingerprint)
306}
307
308pub fn split_certificate_chain(mut chain: String) -> Vec<String> {
309 let mut v = Vec::new();
310
311 let end = "-----END CERTIFICATE-----";
312 let expected_certs = chain.matches(end).count();
318 loop {
319 if let Some(sz) = chain.find(end) {
320 let cert: String = chain.drain(..sz + end.len()).collect();
321 debug_assert!(
325 cert.contains(end),
326 "each split block must contain its END CERTIFICATE marker"
327 );
328 v.push(cert.trim().to_string());
329 continue;
330 }
331
332 break;
333 }
334
335 debug_assert_eq!(
337 v.len(),
338 expected_certs,
339 "split must yield exactly one certificate per END marker"
340 );
341 v
342}
343
344pub fn get_fingerprint_from_certificate_path(
345 certificate_path: &str,
346) -> Result<Fingerprint, CertificateError> {
347 let bytes =
348 Config::load_file_bytes(certificate_path).map_err(|e| CertificateError::LoadFile {
349 path: certificate_path.to_string(),
350 error: Box::new(e),
351 })?;
352
353 let parsed_bytes = calculate_fingerprint(&bytes)?;
354
355 debug_assert_eq!(
359 parsed_bytes.len(),
360 SHA256_FINGERPRINT_LEN,
361 "fingerprint loaded from a certificate path must be 32 bytes"
362 );
363 Ok(Fingerprint(parsed_bytes))
364}
365
366pub fn decode_fingerprint(fingerprint: &str) -> Result<Fingerprint, CertificateError> {
367 let bytes = hex::decode(fingerprint).map_err(CertificateError::DecodeError)?;
368 Ok(Fingerprint(bytes))
369}
370
371pub fn load_full_certificate(
372 certificate_path: &str,
373 certificate_chain_path: &str,
374 key_path: &str,
375 versions: Vec<TlsVersion>,
376 names: Vec<String>,
377) -> Result<CertificateAndKey, CertificateError> {
378 let certificate =
379 Config::load_file(certificate_path).map_err(|e| CertificateError::LoadFile {
380 path: certificate_path.to_string(),
381 error: Box::new(e),
382 })?;
383
384 let certificate_chain = Config::load_file(certificate_chain_path)
385 .map(split_certificate_chain)
386 .map_err(|e| CertificateError::LoadFile {
387 path: certificate_chain_path.to_string(),
388 error: Box::new(e),
389 })?;
390
391 let key = Config::load_file(key_path).map_err(|e| CertificateError::LoadFile {
392 path: key_path.to_string(),
393 error: Box::new(e),
394 })?;
395
396 let versions_len = versions.len();
397 let names_len = names.len();
398 let versions: Vec<i32> = versions.iter().map(|v| *v as i32).collect();
399
400 debug_assert_eq!(
403 versions.len(),
404 versions_len,
405 "version encoding must preserve the input cardinality"
406 );
407
408 let built = CertificateAndKey {
409 certificate,
410 certificate_chain,
411 key,
412 versions,
413 names,
414 };
415
416 debug_assert_eq!(
420 built.names.len(),
421 names_len,
422 "names must be carried through the builder unchanged"
423 );
424 Ok(built)
425}
426
427impl CertificateAndKey {
428 pub fn fingerprint(&self) -> Result<Fingerprint, CertificateError> {
429 let pem = parse_pem(self.certificate.as_bytes())?;
430 let fingerprint = Fingerprint(Sha256::digest(&pem.contents).iter().cloned().collect());
431 debug_assert_eq!(
436 fingerprint.0.len(),
437 SHA256_FINGERPRINT_LEN,
438 "CertificateAndKey fingerprint must be 32 bytes"
439 );
440 debug_assert_eq!(
441 fingerprint.0,
442 calculate_fingerprint_from_der(&pem.contents),
443 "method and free-function fingerprints must agree on the same DER"
444 );
445 Ok(fingerprint)
446 }
447
448 pub fn get_overriding_names(&self) -> Result<Vec<String>, CertificateError> {
449 if self.names.is_empty() {
450 let pem = parse_pem(self.certificate.as_bytes())?;
451 let x509 = parse_x509(&pem.contents)?;
452
453 let overriding_names = get_cn_and_san_attributes(&x509);
454
455 Ok(overriding_names.into_iter().collect())
456 } else {
457 let names = self.names.to_owned();
458 debug_assert_eq!(
462 names, self.names,
463 "explicit names must be returned unchanged when present"
464 );
465 Ok(names)
466 }
467 }
468
469 pub fn apply_overriding_names(&mut self) -> Result<(), CertificateError> {
470 let resolved = self.get_overriding_names()?;
471 self.names = resolved.clone();
472 debug_assert_eq!(
476 self.names, resolved,
477 "applied names must equal the resolved set"
478 );
479 Ok(())
480 }
481}