use crate::error::WSError;
use rustls_pki_types::{CertificateDer, TrustAnchor, UnixTime};
use std::time::Duration;
use webpki::{EndEntityCert, KeyUsage};
pub struct OfflineVerifier {
root_anchor: TrustAnchor<'static>,
intermediates: Vec<CertificateDer<'static>>,
}
impl OfflineVerifier {
pub fn new(root_cert_der: &[u8]) -> Result<Self, WSError> {
let root_cert = CertificateDer::from(root_cert_der);
let root_anchor = webpki::anchor_from_trusted_cert(&root_cert)
.map_err(|e| WSError::X509Error(format!("Invalid root certificate: {:?}", e)))?
.to_owned();
Ok(Self {
root_anchor,
intermediates: Vec::new(),
})
}
pub fn add_intermediate(&mut self, intermediate_cert_der: &[u8]) -> Result<(), WSError> {
let intermediate = CertificateDer::from(intermediate_cert_der.to_vec());
let _ = EndEntityCert::try_from(&intermediate).map_err(|e| {
WSError::X509Error(format!("Invalid intermediate certificate: {:?}", e))
})?;
self.intermediates.push(intermediate);
Ok(())
}
pub fn verify_device_certificate(
&self,
device_cert_der: &[u8],
verification_time: Option<u64>,
) -> Result<(), WSError> {
let device_cert = CertificateDer::from(device_cert_der);
let cert = EndEntityCert::try_from(&device_cert)
.map_err(|e| WSError::X509Error(format!("Invalid device certificate: {:?}", e)))?;
let time = if let Some(ts) = verification_time {
UnixTime::since_unix_epoch(Duration::from_secs(ts))
} else {
UnixTime::now()
};
self.verify_cert_with_time(&cert, time)?;
Ok(())
}
fn verify_cert_with_time(
&self,
cert: &EndEntityCert,
verification_time: UnixTime,
) -> Result<(), WSError> {
let signing_algs = webpki::ALL_VERIFICATION_ALGS;
let eku_code_signing = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03];
cert.verify_for_usage(
signing_algs,
std::slice::from_ref(&self.root_anchor),
&self.intermediates,
verification_time,
KeyUsage::required(eku_code_signing),
None, None, )
.map_err(|e| {
WSError::VerificationError(format!("Certificate chain verification failed: {:?}", e))
})?;
Ok(())
}
pub fn verify_certificate_chain(
&self,
cert_chain: &[Vec<u8>],
verification_time: Option<u64>,
) -> Result<(), WSError> {
if cert_chain.is_empty() {
return Err(WSError::InvalidArgument);
}
let device_cert = &cert_chain[0];
let mut verifier = OfflineVerifier {
root_anchor: self.root_anchor.clone(),
intermediates: self.intermediates.clone(),
};
for intermediate_der in &cert_chain[1..] {
if intermediate_der != self.root_anchor_der() {
verifier.add_intermediate(intermediate_der)?;
}
}
verifier.verify_device_certificate(device_cert, verification_time)
}
fn root_anchor_der(&self) -> &[u8] {
&[]
}
}
pub struct OfflineVerifierBuilder {
root_cert: Option<Vec<u8>>,
intermediates: Vec<Vec<u8>>,
}
impl OfflineVerifierBuilder {
pub fn new() -> Self {
Self {
root_cert: None,
intermediates: Vec::new(),
}
}
pub fn with_root(mut self, root_cert_der: &[u8]) -> Result<Self, WSError> {
self.root_cert = Some(root_cert_der.to_vec());
Ok(self)
}
pub fn with_intermediate(mut self, intermediate_cert_der: &[u8]) -> Self {
self.intermediates.push(intermediate_cert_der.to_vec());
self
}
pub fn build(self) -> Result<OfflineVerifier, WSError> {
let root_cert = self.root_cert.ok_or(WSError::InvalidArgument)?;
let mut verifier = OfflineVerifier::new(&root_cert)?;
for intermediate in &self.intermediates {
verifier.add_intermediate(intermediate)?;
}
Ok(verifier)
}
}
impl Default for OfflineVerifierBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offline_verifier_builder() {
let builder = OfflineVerifierBuilder::new();
assert!(builder.root_cert.is_none());
assert!(builder.intermediates.is_empty());
}
#[test]
fn test_builder_requires_root() {
let builder = OfflineVerifierBuilder::new();
let result = builder.build();
assert!(result.is_err()); }
}