diff options
Diffstat (limited to 'packet-detector/src/validator.rs')
| -rw-r--r-- | packet-detector/src/validator.rs | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/packet-detector/src/validator.rs b/packet-detector/src/validator.rs new file mode 100644 index 0000000..92e64d7 --- /dev/null +++ b/packet-detector/src/validator.rs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | //! Certificate chain validation - signature only | ||
| 2 | |||
| 3 | use anyhow::{anyhow, Result}; | ||
| 4 | use rustls::pki_types::CertificateDer; | ||
| 5 | use x509_parser::prelude::*; | ||
| 6 | |||
| 7 | pub struct ValidationResult { | ||
| 8 | pub valid: bool, | ||
| 9 | pub subject: String, | ||
| 10 | pub issuer: String, | ||
| 11 | pub error: Option<String>, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl ValidationResult { | ||
| 15 | fn fail(subject: String, issuer: String, err: impl ToString) -> Self { | ||
| 16 | Self { valid: false, subject, issuer, error: Some(err.to_string()) } | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | pub struct CertValidator { | ||
| 21 | ca_der: Vec<u8>, | ||
| 22 | } | ||
| 23 | |||
| 24 | impl CertValidator { | ||
| 25 | pub fn with_ca_file(path: &str) -> Result<Self> { | ||
| 26 | let pem = std::fs::read_to_string(path)?; | ||
| 27 | let der = rustls_pemfile::certs(&mut pem.as_bytes()) | ||
| 28 | .next() | ||
| 29 | .ok_or_else(|| anyhow!("No cert in PEM"))??; | ||
| 30 | Ok(Self { ca_der: der.to_vec() }) | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn validate(&self, chain: &[Vec<u8>]) -> ValidationResult { | ||
| 34 | let Some(ee_der) = chain.first() else { | ||
| 35 | return ValidationResult::fail(String::new(), String::new(), "Empty chain"); | ||
| 36 | }; | ||
| 37 | |||
| 38 | let (subject, issuer) = match X509Certificate::from_der(ee_der) { | ||
| 39 | Ok((_, c)) => (c.subject().to_string(), c.issuer().to_string()), | ||
| 40 | Err(e) => return ValidationResult::fail(String::new(), String::new(), format!("{e:?}")), | ||
| 41 | }; | ||
| 42 | |||
| 43 | let ca = CertificateDer::from(self.ca_der.clone()); | ||
| 44 | let anchor = match webpki::anchor_from_trusted_cert(&ca) { | ||
| 45 | Ok(a) => a, | ||
| 46 | Err(e) => return ValidationResult::fail(subject, issuer, format!("CA: {e:?}")), | ||
| 47 | }; | ||
| 48 | |||
| 49 | let cert = CertificateDer::from(ee_der.clone()); | ||
| 50 | let ee = match webpki::EndEntityCert::try_from(&cert) { | ||
| 51 | Ok(c) => c, | ||
| 52 | Err(e) => return ValidationResult::fail(subject, issuer, format!("{e:?}")), | ||
| 53 | }; | ||
| 54 | |||
| 55 | let intermediates: Vec<_> = chain[1..].iter().map(|c| CertificateDer::from(c.clone())).collect(); | ||
| 56 | let algos = webpki::ALL_VERIFICATION_ALGS; | ||
| 57 | let time = webpki::types::UnixTime::since_unix_epoch(std::time::Duration::from_secs(4102444800)); // 2100 | ||
| 58 | |||
| 59 | match ee.verify_for_usage(algos, &[anchor], &intermediates, time, webpki::KeyUsage::client_auth(), None, None) { | ||
| 60 | Ok(_) => ValidationResult { valid: true, subject, issuer, error: None }, | ||
| 61 | Err(e) => ValidationResult::fail(subject, issuer, format!("{e:?}")), | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
