diff --git a/src/auth/crl.rs b/src/auth/crl.rs index c1213f0..0a87088 100644 --- a/src/auth/crl.rs +++ b/src/auth/crl.rs @@ -165,7 +165,16 @@ pub fn load_crl(crl_path: &Path, ca_cert_der: &[u8]) -> CrlState { }; // Verify CRL signature against CA - let (_, ca_cert) = match x509_parser::parse_x509_certificate(ca_cert_der) { + // Extract DER from PEM if the CA cert is PEM-encoded + let ca_der = match extract_pem_cert_der(ca_cert_der) { + Some(der) => der, + None => { + // Not PEM — assume it's already DER + ca_cert_der.to_vec() + } + }; + + let (_, ca_cert) = match x509_parser::parse_x509_certificate(&ca_der) { Ok(r) => r, Err(e) => { error!(error = %e, "Failed to parse CA cert for CRL signature verification"); @@ -220,6 +229,29 @@ pub fn load_crl(crl_path: &Path, ca_cert_der: &[u8]) -> CrlState { } } +/// Extract DER bytes from a PEM-encoded certificate. +/// Looks for `-----BEGIN CERTIFICATE-----` / `-----END CERTIFICATE-----` markers +/// and base64-decodes the content between them. +pub fn extract_pem_cert_der(pem_bytes: &[u8]) -> Option> { + let pem_str = String::from_utf8_lossy(pem_bytes); + let begin_marker = "-----BEGIN CERTIFICATE-----"; + let end_marker = "-----END CERTIFICATE-----"; + + let begin_idx = pem_str.find(begin_marker)?; + let after_begin = begin_idx + begin_marker.len(); + let end_idx = pem_str[after_begin..].find(end_marker)?; + // Strip all whitespace (including newlines) from the base64 block + // before decoding, since PEM format wraps lines at 64 characters. + let b64_block: String = pem_str[after_begin..after_begin + end_idx] + .split_whitespace() + .collect(); + + use base64::Engine; + base64::engine::general_purpose::STANDARD + .decode(&b64_block) + .ok() +} + /// Extract DER bytes from a PEM-encoded CRL. /// Looks for `-----BEGIN X509 CRL-----` / `-----END X509 CRL-----` blocks. fn extract_pem_crl_der(pem_bytes: &[u8]) -> Option> { @@ -688,4 +720,47 @@ mod tests { "Invalid CRL should not match any serial" ); } + + #[test] + fn test_extract_pem_cert_der_invalid() { + // Not PEM + assert!(extract_pem_cert_der(b"not pem").is_none()); + // PEM but wrong type (CRL instead of CERTIFICATE) + assert!( + extract_pem_cert_der(b"-----BEGIN X509 CRL-----\nAA==\n-----END X509 CRL-----") + .is_none() + ); + } + + #[test] + fn test_extract_pem_cert_der_valid() { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let (_, ca_cert) = generate_test_ca(); + let cert_pem = ca_cert.pem(); + + // Verify PEM extraction succeeds + let der = extract_pem_cert_der(cert_pem.as_bytes()); + assert!( + der.is_some(), + "PEM extraction should succeed for valid certificate PEM" + ); + + // Verify the DER can be parsed as an X.509 certificate + let der_bytes = der.unwrap(); + let parsed = x509_parser::parse_x509_certificate(&der_bytes); + assert!( + parsed.is_ok(), + "DER should parse as a valid X.509 certificate" + ); + } + + #[test] + fn test_extract_pem_cert_der_rejects_crl_pem() { + // CERTIFICATE extraction should reject CRL PEM + let crl_pem = "-----BEGIN X509 CRL-----\nAA==\n-----END X509 CRL-----"; + assert!( + extract_pem_cert_der(crl_pem.as_bytes()).is_none(), + "CRL PEM should not extract as CERTIFICATE" + ); + } }