use alloc::vec::Vec;
use pki_types::{CertificateDer, CertificateRevocationListDer, UnixTime};
use webpki::{CertRevocationList, ExpirationPolicy, RevocationCheckDepth, UnknownStatusPolicy};
use super::{VerifierBuilderError, pki_error};
#[cfg(doc)]
use crate::ConfigBuilder;
#[cfg(doc)]
use crate::crypto;
use crate::crypto::{CryptoProvider, WebPkiSupportedAlgorithms};
#[cfg(doc)]
use crate::server::ServerConfig;
use crate::sync::Arc;
use crate::verify::{
ClientCertVerified, ClientCertVerifier, DigitallySignedStruct, HandshakeSignatureValid,
NoClientAuth,
};
use crate::webpki::parse_crls;
use crate::webpki::verify::{ParsedCertificate, verify_tls12_signature, verify_tls13_signature};
use crate::{DistinguishedName, Error, RootCertStore, SignatureScheme};
#[derive(Debug, Clone)]
pub struct ClientCertVerifierBuilder {
roots: Arc<RootCertStore>,
root_hint_subjects: Vec<DistinguishedName>,
crls: Vec<CertificateRevocationListDer<'static>>,
revocation_check_depth: RevocationCheckDepth,
unknown_revocation_policy: UnknownStatusPolicy,
revocation_expiration_policy: ExpirationPolicy,
anon_policy: AnonymousClientPolicy,
supported_algs: WebPkiSupportedAlgorithms,
}
impl ClientCertVerifierBuilder {
pub(crate) fn new(
roots: Arc<RootCertStore>,
supported_algs: WebPkiSupportedAlgorithms,
) -> Self {
Self {
root_hint_subjects: roots.subjects(),
roots,
crls: Vec::new(),
anon_policy: AnonymousClientPolicy::Deny,
revocation_check_depth: RevocationCheckDepth::Chain,
unknown_revocation_policy: UnknownStatusPolicy::Deny,
revocation_expiration_policy: ExpirationPolicy::Ignore,
supported_algs,
}
}
pub fn clear_root_hint_subjects(mut self) -> Self {
self.root_hint_subjects = Vec::default();
self
}
pub fn add_root_hint_subjects(
mut self,
subjects: impl IntoIterator<Item = DistinguishedName>,
) -> Self {
self.root_hint_subjects.extend(subjects);
self
}
pub fn with_crls(
mut self,
crls: impl IntoIterator<Item = CertificateRevocationListDer<'static>>,
) -> Self {
self.crls.extend(crls);
self
}
pub fn only_check_end_entity_revocation(mut self) -> Self {
self.revocation_check_depth = RevocationCheckDepth::EndEntity;
self
}
pub fn allow_unauthenticated(mut self) -> Self {
self.anon_policy = AnonymousClientPolicy::Allow;
self
}
pub fn allow_unknown_revocation_status(mut self) -> Self {
self.unknown_revocation_policy = UnknownStatusPolicy::Allow;
self
}
pub fn enforce_revocation_expiration(mut self) -> Self {
self.revocation_expiration_policy = ExpirationPolicy::Enforce;
self
}
pub fn build(self) -> Result<Arc<dyn ClientCertVerifier>, VerifierBuilderError> {
if self.roots.is_empty() {
return Err(VerifierBuilderError::NoRootAnchors);
}
Ok(Arc::new(WebPkiClientVerifier::new(
self.roots,
self.root_hint_subjects,
parse_crls(self.crls)?,
self.revocation_check_depth,
self.unknown_revocation_policy,
self.revocation_expiration_policy,
self.anon_policy,
self.supported_algs,
)))
}
}
#[derive(Debug)]
pub struct WebPkiClientVerifier {
roots: Arc<RootCertStore>,
root_hint_subjects: Vec<DistinguishedName>,
crls: Vec<CertRevocationList<'static>>,
revocation_check_depth: RevocationCheckDepth,
unknown_revocation_policy: UnknownStatusPolicy,
revocation_expiration_policy: ExpirationPolicy,
anonymous_policy: AnonymousClientPolicy,
supported_algs: WebPkiSupportedAlgorithms,
}
impl WebPkiClientVerifier {
pub fn builder(roots: Arc<RootCertStore>) -> ClientCertVerifierBuilder {
Self::builder_with_provider(
roots,
CryptoProvider::get_default_or_install_from_crate_features().clone(),
)
}
pub fn builder_with_provider(
roots: Arc<RootCertStore>,
provider: Arc<CryptoProvider>,
) -> ClientCertVerifierBuilder {
ClientCertVerifierBuilder::new(roots, provider.signature_verification_algorithms)
}
pub fn no_client_auth() -> Arc<dyn ClientCertVerifier> {
Arc::new(NoClientAuth {})
}
pub(crate) fn new(
roots: Arc<RootCertStore>,
root_hint_subjects: Vec<DistinguishedName>,
crls: Vec<CertRevocationList<'static>>,
revocation_check_depth: RevocationCheckDepth,
unknown_revocation_policy: UnknownStatusPolicy,
revocation_expiration_policy: ExpirationPolicy,
anonymous_policy: AnonymousClientPolicy,
supported_algs: WebPkiSupportedAlgorithms,
) -> Self {
Self {
roots,
root_hint_subjects,
crls,
revocation_check_depth,
unknown_revocation_policy,
revocation_expiration_policy,
anonymous_policy,
supported_algs,
}
}
}
impl ClientCertVerifier for WebPkiClientVerifier {
fn offer_client_auth(&self) -> bool {
true
}
fn client_auth_mandatory(&self) -> bool {
match self.anonymous_policy {
AnonymousClientPolicy::Allow => false,
AnonymousClientPolicy::Deny => true,
}
}
fn root_hint_subjects(&self) -> &[DistinguishedName] {
&self.root_hint_subjects
}
fn verify_client_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
now: UnixTime,
) -> Result<ClientCertVerified, Error> {
let cert = ParsedCertificate::try_from(end_entity)?;
let crl_refs = self.crls.iter().collect::<Vec<_>>();
let revocation = if self.crls.is_empty() {
None
} else {
Some(
webpki::RevocationOptionsBuilder::new(&crl_refs)
.unwrap()
.with_depth(self.revocation_check_depth)
.with_status_policy(self.unknown_revocation_policy)
.with_expiration_policy(self.revocation_expiration_policy)
.build(),
)
};
cert.0
.verify_for_usage(
self.supported_algs.all,
&self.roots.roots,
intermediates,
now,
webpki::KeyUsage::client_auth(),
revocation,
None,
)
.map_err(pki_error)
.map(|_| ClientCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
verify_tls12_signature(message, cert, dss, &self.supported_algs)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
verify_tls13_signature(message, cert, dss, &self.supported_algs)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.supported_algs.supported_schemes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AnonymousClientPolicy {
Allow,
Deny,
}
#[cfg(test)]
#[macro_rules_attribute::apply(test_for_each_provider)]
mod tests {
use std::prelude::v1::*;
use std::{format, println, vec};
use pki_types::pem::PemObject;
use pki_types::{CertificateDer, CertificateRevocationListDer};
use super::{WebPkiClientVerifier, provider};
use crate::RootCertStore;
use crate::server::VerifierBuilderError;
use crate::sync::Arc;
fn load_crls(crls_der: &[&[u8]]) -> Vec<CertificateRevocationListDer<'static>> {
crls_der
.iter()
.map(|pem_bytes| CertificateRevocationListDer::from_pem_slice(pem_bytes).unwrap())
.collect()
}
fn test_crls() -> Vec<CertificateRevocationListDer<'static>> {
load_crls(&[
include_bytes!("../../../test-ca/ecdsa-p256/client.revoked.crl.pem").as_slice(),
include_bytes!("../../../test-ca/rsa-2048/client.revoked.crl.pem").as_slice(),
])
}
fn load_roots(roots_der: &[&[u8]]) -> Arc<RootCertStore> {
let mut roots = RootCertStore::empty();
roots_der.iter().for_each(|der| {
roots
.add(CertificateDer::from(der.to_vec()))
.unwrap()
});
roots.into()
}
fn test_roots() -> Arc<RootCertStore> {
load_roots(&[
include_bytes!("../../../test-ca/ecdsa-p256/ca.der").as_slice(),
include_bytes!("../../../test-ca/rsa-2048/ca.der").as_slice(),
])
}
#[test]
fn test_client_verifier_no_auth() {
WebPkiClientVerifier::no_client_auth();
}
#[test]
fn test_client_verifier_required_auth() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
);
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_optional_auth() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.allow_unauthenticated();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_without_crls_required_auth() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
);
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_without_crls_opptional_auth() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.allow_unauthenticated();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_with_invalid_crls() {
let result = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(vec![CertificateRevocationListDer::from(vec![0xFF])])
.build();
assert!(matches!(result, Err(VerifierBuilderError::InvalidCrl(_))));
}
#[test]
fn test_with_crls_multiple_calls() {
let initial_crls = test_crls();
let extra_crls =
load_crls(&[
include_bytes!("../../../test-ca/eddsa/client.revoked.crl.pem").as_slice(),
]);
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(initial_crls.clone())
.with_crls(extra_crls.clone());
assert_eq!(builder.crls.len(), initial_crls.len() + extra_crls.len());
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_with_crls_required_auth_implicit() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(test_crls());
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_with_crls_optional_auth() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(test_crls())
.allow_unauthenticated();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_ee_only() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(test_crls())
.only_check_end_entity_revocation();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_allow_unknown() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(test_crls())
.allow_unknown_revocation_status();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_client_verifier_enforce_expiration() {
let builder = WebPkiClientVerifier::builder_with_provider(
test_roots(),
provider::default_provider().into(),
)
.with_crls(test_crls())
.enforce_revocation_expiration();
println!("{builder:?}");
builder.build().unwrap();
}
#[test]
fn test_builder_no_roots() {
let result = WebPkiClientVerifier::builder_with_provider(
RootCertStore::empty().into(),
provider::default_provider().into(),
)
.build();
assert!(matches!(result, Err(VerifierBuilderError::NoRootAnchors)));
}
#[test]
fn smoke() {
let all = vec![
VerifierBuilderError::NoRootAnchors,
VerifierBuilderError::InvalidCrl(crate::CertRevocationListError::ParseError),
];
for err in all {
let _ = format!("{err:?}");
let _ = format!("{err}");
}
}
}