use crate::der_parser::Oid;
use crate::oid_registry::OID_X509_EXT_EXTENDED_KEY_USAGE;
use crate::unverified_chain::UnverifiedCertificateChain;
use crate::{Certificate, PolicyEvaluationResult, PolicyFailureReason, ValidationPolicy};
pub mod eku_oids {
use crate::der_parser::{Oid, oid};
pub fn server_auth() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.1)
}
pub fn client_auth() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.2)
}
pub fn code_signing() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.3)
}
pub fn email_protection() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.4)
}
pub fn time_stamping() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.8)
}
pub fn ocsp_signing() -> Oid<'static> {
oid!(1.3.6.1.5.5.7.3.9)
}
pub fn any_extended_key_usage() -> Oid<'static> {
oid!(2.5.29.37.0)
}
}
fn extended_key_usage_oid() -> Oid<'static> {
OID_X509_EXT_EXTENDED_KEY_USAGE
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CertificateRole {
EndEntity,
Issuers,
IssuersExcludingAnchor,
EntireChain,
}
impl CertificateRole {
fn covers(self, index: usize, length: usize) -> bool {
let is_end_entity = index == 0;
let is_anchor = index + 1 == length;
match self {
Self::EndEntity => is_end_entity,
Self::Issuers => !is_end_entity,
Self::IssuersExcludingAnchor => !is_end_entity && !is_anchor,
Self::EntireChain => true,
}
}
}
pub struct EkuPolicy {
accepted_purposes: Vec<Oid<'static>>,
role: CertificateRole,
extension_required: bool,
}
impl EkuPolicy {
pub fn new(purpose: Oid<'static>) -> Self {
Self::key_purposes([purpose])
}
pub fn key_purposes(purposes: impl IntoIterator<Item = Oid<'static>>) -> Self {
Self {
accepted_purposes: purposes.into_iter().collect(),
role: CertificateRole::EntireChain,
extension_required: false,
}
}
pub fn server_auth() -> Self {
Self::new(eku_oids::server_auth())
}
pub fn client_auth() -> Self {
Self::new(eku_oids::client_auth())
}
pub fn applies_to(mut self, role: CertificateRole) -> Self {
self.role = role;
self
}
pub fn require_extension(mut self) -> Self {
self.extension_required = true;
self
}
fn certificate_meets_requirement(
&self,
certificate: &Certificate<'_>,
) -> PolicyEvaluationResult {
let extension = certificate
.tbs_certificate
.get_extension_unique(&extended_key_usage_oid())
.map_err(|error| {
PolicyFailureReason::new(format!(
"error processing extended key usage for {:?}: {}",
certificate, error
))
})?;
let Some(extension) = extension else {
return if self.extension_required {
Err(PolicyFailureReason::new(format!(
"certificate {:?} carries no extended key usage extension",
certificate
)))
} else {
Ok(())
};
};
if is_empty_sequence(extension.value) {
return Err(PolicyFailureReason::new(format!(
"certificate {:?} has an empty extended key usage extension",
certificate
)));
}
let usage = certificate
.tbs_certificate
.extended_key_usage()
.map_err(|error| {
PolicyFailureReason::new(format!(
"error processing extended key usage for {:?}: {}",
certificate, error
))
})?
.ok_or_else(|| {
PolicyFailureReason::new(format!(
"error processing extended key usage for {:?}",
certificate
))
})?;
if self.accepts(usage.value) {
Ok(())
} else {
Err(PolicyFailureReason::new(format!(
"certificate {:?} names none of the accepted extended key usages {}",
certificate,
self.accepted_purposes_display()
)))
}
}
fn accepts(&self, usage: &crate::extensions::ExtendedKeyUsage<'_>) -> bool {
let named = [
(usage.server_auth, eku_oids::server_auth()),
(usage.client_auth, eku_oids::client_auth()),
(usage.code_signing, eku_oids::code_signing()),
(usage.email_protection, eku_oids::email_protection()),
(usage.time_stamping, eku_oids::time_stamping()),
(usage.ocsp_signing, eku_oids::ocsp_signing()),
(usage.any, eku_oids::any_extended_key_usage()),
];
self.accepted_purposes
.iter()
.any(|accepted| {
named
.iter()
.any(|(present, oid)| *present && oid == accepted)
|| usage.other.contains(accepted)
})
}
fn accepted_purposes_display(&self) -> String {
self.accepted_purposes
.iter()
.map(|oid| oid.to_string())
.collect::<Vec<_>>()
.join(", ")
}
}
fn is_empty_sequence(value: &[u8]) -> bool {
value == [0x30, 0x00]
}
impl ValidationPolicy for EkuPolicy {
fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
vec![extended_key_usage_oid()]
}
fn chain_meets_policy_requirements(
&self,
chain: &UnverifiedCertificateChain<'_>,
) -> PolicyEvaluationResult {
let length = chain.len();
for (index, certificate) in chain.iter().enumerate() {
if self.role.covers(index, length) {
self.certificate_meets_requirement(certificate)?;
}
}
Ok(())
}
}