use std::sync::Arc;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::crypto::CryptoProvider;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
use rustls::{DigitallySignedStruct, SignatureScheme};
use super::{HttpError, TrustRoots};
pub trait CertificateCheck: Send + Sync {
fn check(&self, server: &ServerCertificate<'_>) -> CertificateDecision;
}
impl<F> CertificateCheck for F
where
F: Fn(&ServerCertificate<'_>) -> CertificateDecision + Send + Sync,
{
fn check(&self, server: &ServerCertificate<'_>) -> CertificateDecision {
self(server)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CertificateDecision {
Accept,
Reject,
UseDefault,
}
#[derive(Debug)]
pub struct ServerCertificate<'a> {
host: &'a str,
end_entity: &'a [u8],
intermediates: &'a [CertificateDer<'a>],
verification: Result<(), CertificateError>,
}
impl ServerCertificate<'_> {
pub fn host(&self) -> &str {
self.host
}
pub fn end_entity(&self) -> &[u8] {
self.end_entity
}
pub fn intermediates(&self) -> impl ExactSizeIterator<Item = &[u8]> {
self.intermediates.iter().map(|der| der.as_ref())
}
pub fn verification(&self) -> Result<(), &CertificateError> {
self.verification.as_ref().map(|_| ())
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("{0}")]
pub struct CertificateError(String);
struct Checked {
inner: Arc<dyn ServerCertVerifier>,
check: Arc<dyn CertificateCheck>,
provider: Arc<CryptoProvider>,
}
impl std::fmt::Debug for Checked {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Checked").finish_non_exhaustive()
}
}
impl ServerCertVerifier for Checked {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
server_name: &ServerName<'_>,
ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let verdict = self.inner.verify_server_cert(
end_entity,
intermediates,
server_name,
ocsp_response,
now,
);
let host = server_name.to_str();
let server = ServerCertificate {
host: &host,
end_entity: end_entity.as_ref(),
intermediates,
verification: verdict
.as_ref()
.map(|_| ())
.map_err(|error| CertificateError(error.to_string())),
};
match self.check.check(&server) {
CertificateDecision::Accept => Ok(ServerCertVerified::assertion()),
CertificateDecision::Reject => Err(rustls::Error::General(
"certificate rejected by the application".to_owned(),
)),
CertificateDecision::UseDefault => verdict,
}
}
fn verify_tls12_signature(
&self,
message: &[u8],
certificate: &CertificateDer<'_>,
signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
certificate,
signature,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
certificate: &CertificateDer<'_>,
signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
certificate,
signature,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.provider
.signature_verification_algorithms
.supported_schemes()
}
}
#[derive(Debug)]
struct Unverified;
impl ServerCertVerifier for Unverified {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_certificate: &CertificateDer<'_>,
_signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
unreachable!("Checked verifies handshake signatures")
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_certificate: &CertificateDer<'_>,
_signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
unreachable!("Checked verifies handshake signatures")
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
Vec::new()
}
}
pub(super) fn client_config(
roots: &TrustRoots<'_>,
identity: Option<&[u8]>,
verify: bool,
check: Arc<dyn CertificateCheck>,
) -> Result<rustls::ClientConfig, HttpError> {
let provider = CryptoProvider::get_default()
.cloned()
.unwrap_or_else(|| Arc::new(rustls::crypto::aws_lc_rs::default_provider()));
let failure = |_| HttpError::Configuration("TLS client");
let inner: Arc<dyn ServerCertVerifier> = if !verify {
Arc::new(Unverified)
} else {
match roots {
TrustRoots::Configured(bundles) if !bundles.is_empty() => {
let mut store = rustls::RootCertStore::empty();
for certificate in super::pem_certificates(bundles)? {
store
.add(certificate)
.map_err(|_| HttpError::Configuration("trust roots"))?;
}
rustls::client::WebPkiServerVerifier::builder_with_provider(
Arc::new(store),
provider.clone(),
)
.build()
.map_err(|_| HttpError::Configuration("trust roots"))?
}
TrustRoots::Additional(bundles) if !bundles.is_empty() => Arc::new(
rustls_platform_verifier::Verifier::new_with_extra_roots(
super::pem_certificates(bundles)?,
provider.clone(),
)
.map_err(failure)?,
),
_ => Arc::new(
rustls_platform_verifier::Verifier::new(provider.clone()).map_err(failure)?,
),
}
};
let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.map_err(failure)?
.dangerous()
.with_custom_certificate_verifier(Arc::new(Checked {
inner,
check,
provider,
}));
let mut config = match identity {
None => builder.with_no_client_auth(),
Some(pem) => {
let invalid = |_| HttpError::Configuration("TLS client certificate");
let certificates = CertificateDer::pem_slice_iter(pem)
.collect::<Result<Vec<_>, _>>()
.map_err(invalid)?;
let key = PrivateKeyDer::from_pem_slice(pem).map_err(invalid)?;
builder
.with_client_auth_cert(certificates, key)
.map_err(|_| HttpError::Configuration("TLS client certificate"))?
}
};
config.alpn_protocols = vec![b"http/1.1".to_vec()];
config.resumption = rustls::client::Resumption::disabled();
Ok(config)
}