#![allow(clippy::doc_lazy_continuation)]
use std::{collections::HashSet, error::Error, fmt, io::BufRead, str::FromStr};
use asn1_rs::{oid, Oid};
use async_generic::async_generic;
use thiserror::Error;
use x509_parser::{extensions::ExtendedKeyUsage, pem::Pem};
use crate::{base64, hash::sha256};
#[derive(Debug)]
pub struct CertificateTrustPolicy {
trust_anchor_ders: Vec<Vec<u8>>,
end_entity_cert_set: HashSet<String>,
additional_ekus: HashSet<String>,
}
impl Default for CertificateTrustPolicy {
fn default() -> Self {
let mut this = CertificateTrustPolicy {
trust_anchor_ders: vec![],
end_entity_cert_set: HashSet::default(),
additional_ekus: HashSet::default(),
};
this.add_valid_ekus(include_bytes!("./valid_eku_oids.cfg"));
#[cfg(test)]
{
let _ = this.add_trust_anchors(include_bytes!(
"../tests/fixtures/raw_signature/test_cert_root_bundle.pem"
));
}
this
}
}
impl CertificateTrustPolicy {
pub fn new() -> Self {
CertificateTrustPolicy {
trust_anchor_ders: vec![],
end_entity_cert_set: HashSet::default(),
additional_ekus: HashSet::default(),
}
}
#[allow(unused)] #[async_generic]
pub fn check_certificate_trust(
&self,
chain_der: &[Vec<u8>],
end_entity_cert_der: &[u8],
signing_time_epoch: Option<i64>,
) -> Result<(), CertificateTrustError> {
let cert_hash = base64_sha256_cert_der(end_entity_cert_der);
if self.end_entity_cert_set.contains(&cert_hash) {
return Ok(());
}
#[cfg(any(target_arch = "wasm32", feature = "rust_native_crypto", test))]
{
return crate::raw_signature::rust_native::check_certificate_trust::check_certificate_trust(
self,
chain_der,
end_entity_cert_der,
signing_time_epoch,
);
}
#[cfg(not(target_arch = "wasm32"))]
{
return crate::raw_signature::openssl::check_certificate_trust::check_certificate_trust(
self,
chain_der,
end_entity_cert_der,
signing_time_epoch,
);
}
Err(CertificateTrustError::InternalError(
"no implementation for certificate evaluation available".to_string(),
))
}
pub fn add_trust_anchors(
&mut self,
trust_anchor_pems: &[u8],
) -> Result<(), InvalidCertificateError> {
for maybe_pem in Pem::iter_from_buffer(trust_anchor_pems) {
match maybe_pem {
Ok(pem) => self.trust_anchor_ders.push(pem.contents),
Err(e) => {
return Err(InvalidCertificateError(e.to_string()));
}
}
}
Ok(())
}
pub fn add_end_entity_credentials(
&mut self,
end_entity_cert_pems: &[u8],
) -> Result<(), InvalidCertificateError> {
let mut inside_pem_block = false;
for line in end_entity_cert_pems.lines().map_while(Result::ok) {
if line.contains("-----BEGIN") {
inside_pem_block = true;
}
if line.contains("-----END") {
inside_pem_block = false;
}
if !inside_pem_block && line.len() == 44 && base64::decode(&line).is_ok() {
self.end_entity_cert_set.insert(line);
}
}
for maybe_pem in Pem::iter_from_buffer(end_entity_cert_pems) {
match maybe_pem {
Ok(pem) => {
self.end_entity_cert_set
.insert(base64_sha256_cert_der(&pem.contents));
}
Err(e) => {
return Err(InvalidCertificateError(e.to_string()));
}
}
}
Ok(())
}
pub fn add_valid_ekus(&mut self, eku_oids: &[u8]) {
let Ok(eku_oids) = std::str::from_utf8(eku_oids) else {
return;
};
for line in eku_oids.lines() {
if let Ok(_oid) = Oid::from_str(line) {
self.additional_ekus.insert(line.to_string());
}
}
}
pub fn clear(&mut self) {
self.trust_anchor_ders.clear();
self.end_entity_cert_set.clear();
self.additional_ekus.clear();
}
pub(crate) fn trust_anchor_ders(&self) -> impl Iterator<Item = &'_ Vec<u8>> {
self.trust_anchor_ders.iter()
}
pub(crate) fn has_allowed_eku<'a>(&self, eku: &'a ExtendedKeyUsage) -> Option<Oid<'a>> {
if eku.email_protection {
return Some(EMAIL_PROTECTION_OID.clone());
}
if eku.time_stamping {
return Some(TIMESTAMPING_OID.clone());
}
if eku.ocsp_signing {
return Some(OCSP_SIGNING_OID.clone());
}
for extra_oid in eku.other.iter().as_ref() {
let extra_oid_str = extra_oid.to_string();
if self.additional_ekus.contains(&extra_oid_str) {
return Some(extra_oid.clone());
}
}
None
}
}
fn base64_sha256_cert_der(cert_der: &[u8]) -> String {
let cert_sha256 = sha256(cert_der);
base64::encode(&cert_sha256)
}
#[derive(Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum CertificateTrustError {
#[error("the certificate is not trusted")]
CertificateNotTrusted,
#[error("the certificate contains an invalid extended key usage (EKU) value")]
InvalidEku,
#[error("an error was reported by the cryptography library: {0}")]
CryptoLibraryError(String),
#[error("the certificate or certificate chain is invalid")]
InvalidCertificate,
#[error("internal error ({0})")]
InternalError(String),
}
#[cfg(not(target_arch = "wasm32"))]
impl From<openssl::error::ErrorStack> for CertificateTrustError {
fn from(err: openssl::error::ErrorStack) -> Self {
Self::CryptoLibraryError(err.to_string())
}
}
#[cfg(not(target_arch = "wasm32"))]
impl From<crate::raw_signature::openssl::OpenSslMutexUnavailable> for CertificateTrustError {
fn from(err: crate::raw_signature::openssl::OpenSslMutexUnavailable) -> Self {
Self::InternalError(err.to_string())
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct InvalidCertificateError(pub(crate) String);
impl fmt::Display for InvalidCertificateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unable to parse certificate list: {}", self.0)
}
}
impl Error for InvalidCertificateError {}
static EMAIL_PROTECTION_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .4);
static TIMESTAMPING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .8);
static OCSP_SIGNING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .9);