confium_log_server/
cert.rs1use anyhow::{Context, Result};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CertMetadata {
12 pub fingerprint_sha256: String,
13 pub issuer_distinguished_name: String,
14 pub subject_distinguished_name: String,
15 pub valid_from: String,
16 pub valid_to: String,
17 pub serial_hex: String,
18}
19
20pub fn parse_der(der_bytes: &[u8]) -> Result<CertMetadata> {
24 let cert =
25 confium_pki::cert::Certificate::from_der(der_bytes).context("parsing DER certificate")?;
26 let issuer = cert.as_inner().tbs_certificate().issuer().to_string();
27 let subject = cert.as_inner().tbs_certificate().subject().to_string();
28 Ok(CertMetadata {
29 fingerprint_sha256: cert.fingerprint_sha256(),
30 issuer_distinguished_name: issuer,
31 subject_distinguished_name: subject,
32 valid_from: cert.not_before_chrono().to_rfc3339(),
33 valid_to: cert.not_after_chrono().to_rfc3339(),
34 serial_hex: hex::encode(cert.serial_bytes()),
35 })
36}
37
38pub fn fingerprint(bytes: &[u8]) -> [u8; 32] {
41 use sha2::{Digest, Sha256};
42 let mut h = Sha256::new();
43 h.update(bytes);
44 let digest: [u8; 32] = h.finalize().into();
45 digest
46}
47
48pub fn classify_cert(_der_bytes: &[u8], meta: &CertMetadata) -> String {
53 let subj = meta.subject_distinguished_name.to_lowercase();
56 if subj.contains("cnml") || subj.contains("certificate of conformity") {
57 "cnml_certificate".to_string()
58 } else if subj.contains("code sign") || subj.contains("codesign") {
59 "code_signing_certificate".to_string()
60 } else if subj.contains("email") || subj.contains("smime") {
61 "email_signing_certificate".to_string()
62 } else if subj.contains("document") {
63 "document_signing_certificate".to_string()
64 } else if subj.contains("tsa") || subj.contains("timestamping") {
65 "timestamping_certificate".to_string()
66 } else {
67 "x509_certificate".to_string()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn classify_cnml_cert_by_subject_dn() {
77 let meta = CertMetadata {
78 fingerprint_sha256: "00".repeat(32),
79 issuer_distinguished_name: "CNML Root CA".to_string(),
80 subject_distinguished_name: "Acme CNML Cert".to_string(),
81 valid_from: "2026-01-01T00:00:00Z".to_string(),
82 valid_to: "2027-01-01T00:00:00Z".to_string(),
83 serial_hex: "00".to_string(),
84 };
85 assert_eq!(classify_cert(&[], &meta), "cnml_certificate");
86 }
87
88 #[test]
89 fn fingerprint_is_32_bytes() {
90 let fp = fingerprint(b"hello");
91 assert_eq!(fp.len(), 32);
92 }
93
94 #[test]
95 fn fingerprint_is_deterministic() {
96 assert_eq!(fingerprint(b"hello"), fingerprint(b"hello"));
97 }
98}