#[cfg(feature = "rustls-tls")]
pub mod rustls_tls {
use std::{
path::PathBuf,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use hyper_rustls::ConfigBuilderExt;
use rustls::{
self, ClientConfig, DigitallySignedStruct, RootCertStore,
client::{
WebPkiServerVerifier,
danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
},
pki_types::{CertificateDer, InvalidDnsNameError, PrivateKeyDer, ServerName},
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("identity PEM is invalid: {0}")]
InvalidIdentityPem(#[source] rustls::pki_types::pem::Error),
#[error("identity PEM is missing a private key: the key must be PKCS8 or RSA/PKCS1")]
MissingPrivateKey,
#[error("identity PEM is missing certificate")]
MissingCertificate,
#[error("invalid private key: {0}")]
InvalidPrivateKey(#[source] rustls::Error),
#[error("unknown private key format")]
UnknownPrivateKeyFormat,
#[error("failed to add a root certificate: {0}")]
AddRootCertificate(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("no valid native root CA certificates found")]
NoValidNativeRootCA(#[source] std::io::Error),
#[error("invalid server name: {0}")]
InvalidServerName(#[source] InvalidDnsNameError),
}
pub fn rustls_client_config(
identity_pem: Option<&[u8]>,
root_certs: Option<&[Vec<u8>]>,
accept_invalid: bool,
) -> Result<ClientConfig, Error> {
let config_builder = if let Some(certs) = root_certs {
ClientConfig::builder().with_root_certificates(root_store(certs)?)
} else {
#[cfg(feature = "webpki-roots")]
{
ClientConfig::builder().with_webpki_roots()
}
#[cfg(not(feature = "webpki-roots"))]
{
ClientConfig::builder()
.with_native_roots()
.map_err(Error::NoValidNativeRootCA)?
}
};
let mut client_config = if let Some((chain, pkey)) = identity_pem.map(client_auth).transpose()? {
config_builder
.with_client_auth_cert(chain, pkey)
.map_err(Error::InvalidPrivateKey)?
} else {
config_builder.with_no_client_auth()
};
if accept_invalid {
client_config
.dangerous()
.set_certificate_verifier(std::sync::Arc::new(NoCertificateVerification {}));
}
Ok(client_config)
}
fn root_store(root_certs: &[Vec<u8>]) -> Result<rustls::RootCertStore, Error> {
let mut root_store = rustls::RootCertStore::empty();
for der in root_certs {
root_store
.add(CertificateDer::from(der.to_owned()))
.map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
}
Ok(root_store)
}
#[derive(Debug)]
pub(crate) struct ReloadingVerifier {
path: PathBuf,
inner: RwLock<(Arc<WebPkiServerVerifier>, Instant)>,
}
impl ReloadingVerifier {
const RELOAD_INTERVAL: Duration = Duration::from_secs(60);
pub(crate) fn new(path: PathBuf) -> Result<Self, Error> {
let verifier = Self::load(&path)?;
Ok(Self {
path,
inner: RwLock::new((verifier, Instant::now())),
})
}
fn load(path: &PathBuf) -> Result<Arc<WebPkiServerVerifier>, Error> {
let pem = std::fs::read(path).map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
let ders = crate::config::certs(&pem).map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
let mut store = RootCertStore::empty();
for der in ders {
store
.add(CertificateDer::from(der))
.map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
}
WebPkiServerVerifier::builder(Arc::new(store))
.build()
.map_err(|e| Error::AddRootCertificate(Box::new(e)))
}
fn current(&self) -> Arc<WebPkiServerVerifier> {
{
let guard = self.inner.read().unwrap_or_else(|e| e.into_inner());
if guard.1.elapsed() < Self::RELOAD_INTERVAL {
return guard.0.clone();
}
}
let mut guard = self.inner.write().unwrap_or_else(|e| e.into_inner());
if guard.1.elapsed() < Self::RELOAD_INTERVAL {
return guard.0.clone();
}
if let Ok(fresh) = Self::load(&self.path) {
guard.0 = fresh;
} else {
tracing::warn!(path = ?self.path, "failed to reload CA bundle; keeping stale roots");
}
guard.1 = Instant::now();
guard.0.clone()
}
}
impl ServerCertVerifier for ReloadingVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer,
intermediates: &[CertificateDer],
server_name: &ServerName,
ocsp_response: &[u8],
now: rustls::pki_types::UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
self.current()
.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
self.current().verify_tls12_signature(message, cert, dss)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
self.current().verify_tls13_signature(message, cert, dss)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.current().supported_verify_schemes()
}
}
#[cfg(test)]
mod tests {
use super::*;
const CA1: &str = "-----BEGIN CERTIFICATE-----
MIIBgDCCASWgAwIBAgIUVrQf5d//S01a0fbXxYRIx9wc0VQwCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJdGVzdC1jYS0xMCAXDTI2MDMwNDEzMDk1MFoYDzIxMjYwMjA4
MTMwOTUwWjAUMRIwEAYDVQQDDAl0ZXN0LWNhLTEwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAARg57mWJPDsAIEQAgXqMOOfjMQP+PE9HqcZobycO8z94r/uRuV0wKx/
0SvMsKFtnreut0bjgFtmZaWY+6d87Is9o1MwUTAdBgNVHQ4EFgQUjtGuhkM7LtHB
gMPCJIxMwbY69OQwHwYDVR0jBBgwFoAUjtGuhkM7LtHBgMPCJIxMwbY69OQwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAj/WzNVJDg/cBtLqQVM77
tkB+QyIXLG3Vi9Xj1YfW9QECIQDDFW8yFtgLeCg2Zhr4xQNq3/24r/01kI2rjFPO
xBkDMw==
-----END CERTIFICATE-----
";
const CA2: &str = "-----BEGIN CERTIFICATE-----
MIIBfjCCASWgAwIBAgIUZ7Qsiwan2joRz01p25/cy1XNNiwwCgYIKoZIzj0EAwIw
FDESMBAGA1UEAwwJdGVzdC1jYS0yMCAXDTI2MDMwNDEzMDk1MFoYDzIxMjYwMjA4
MTMwOTUwWjAUMRIwEAYDVQQDDAl0ZXN0LWNhLTIwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAARJle2/yiOD5zp0UkjZg9Yy6ZHBItTLrqv/uzB2YMQg03frnqEUMzSV
mFinosBcGpX/dPGfHNPhBMOpHmlocZu9o1MwUTAdBgNVHQ4EFgQUsqG0hSGDYsz2
eGIsLIwJnCR5SFIwHwYDVR0jBBgwFoAUsqG0hSGDYsz2eGIsLIwJnCR5SFIwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiApvLu9DIC3/K/+G9ooOm75
a72Cjw62aM8NfPe7ILs8SgIgL0VHe6ksTyB176RECCm3MJVnlhOop6b1tNvxjrru
FRU=
-----END CERTIFICATE-----
";
fn expire(v: &ReloadingVerifier) {
v.inner.write().unwrap().1 = Instant::now().checked_sub(Duration::from_secs(120)).unwrap();
}
#[test]
fn reloading_verifier() {
#[cfg(feature = "aws-lc-rs")]
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let file = tempfile::NamedTempFile::new().unwrap();
std::fs::write(file.path(), CA1).unwrap();
let verifier = ReloadingVerifier::new(file.path().to_path_buf()).unwrap();
let first = verifier.current();
std::fs::write(file.path(), CA2).unwrap();
assert!(Arc::ptr_eq(&verifier.current(), &first));
expire(&verifier);
let second = verifier.current();
assert!(!Arc::ptr_eq(&second, &first));
drop(file);
expire(&verifier);
assert!(Arc::ptr_eq(&verifier.current(), &second));
}
}
fn client_auth(data: &[u8]) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
use rustls::pki_types::pem::{self, SectionKind};
let mut cert_chain = Vec::new();
let mut pkcs8_key = None;
let mut pkcs1_key = None;
let mut sec1_key = None;
let mut reader = std::io::Cursor::new(data);
while let Some((kind, der)) = pem::from_buf(&mut reader).map_err(Error::InvalidIdentityPem)? {
match kind {
SectionKind::Certificate => cert_chain.push(der.into()),
SectionKind::PrivateKey => pkcs8_key = Some(PrivateKeyDer::Pkcs8(der.into())),
SectionKind::RsaPrivateKey => pkcs1_key = Some(PrivateKeyDer::Pkcs1(der.into())),
SectionKind::EcPrivateKey => sec1_key = Some(PrivateKeyDer::Sec1(der.into())),
_ => return Err(Error::UnknownPrivateKeyFormat),
}
}
let private_key = pkcs8_key
.or(pkcs1_key)
.or(sec1_key)
.ok_or(Error::MissingPrivateKey)?;
if cert_chain.is_empty() {
return Err(Error::MissingCertificate);
}
Ok((cert_chain, private_key))
}
#[derive(Debug)]
struct NoCertificateVerification {}
impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer,
_intermediates: &[CertificateDer],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
tracing::warn!("Server cert bypassed");
Ok(ServerCertVerified::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
use rustls::SignatureScheme;
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
}
#[cfg(feature = "openssl-tls")]
pub mod openssl_tls {
use openssl::{
pkey::PKey,
ssl::{SslConnector, SslConnectorBuilder, SslMethod},
x509::X509,
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to create OpenSSL HTTPS connector: {0}")]
CreateHttpsConnector(#[source] openssl::error::ErrorStack),
#[error("failed to create OpenSSL SSL connector: {0}")]
CreateSslConnector(#[source] SslConnectorError),
}
#[derive(Debug, Error)]
pub enum SslConnectorError {
#[error("failed to build SslConnectorBuilder: {0}")]
CreateBuilder(#[source] openssl::error::ErrorStack),
#[error("failed to deserialize PEM-encoded chain of certificates: {0}")]
DeserializeCertificateChain(#[source] openssl::error::ErrorStack),
#[error("failed to deserialize PEM-encoded private key: {0}")]
DeserializePrivateKey(#[source] openssl::error::ErrorStack),
#[error("failed to set private key: {0}")]
SetPrivateKey(#[source] openssl::error::ErrorStack),
#[error("failed to get a leaf certificate, the certificate chain is empty")]
GetLeafCertificate,
#[error("failed to set the leaf certificate: {0}")]
SetLeafCertificate(#[source] openssl::error::ErrorStack),
#[error("failed to append a certificate to the chain: {0}")]
AppendCertificate(#[source] openssl::error::ErrorStack),
#[error("failed to deserialize DER-encoded root certificate: {0}")]
DeserializeRootCertificate(#[source] openssl::error::ErrorStack),
#[error("failed to add a root certificate: {0}")]
AddRootCertificate(#[source] openssl::error::ErrorStack),
}
pub fn ssl_connector_builder(
identity_pem: Option<&Vec<u8>>,
root_certs: Option<&Vec<Vec<u8>>>,
) -> Result<SslConnectorBuilder, SslConnectorError> {
let mut builder =
SslConnector::builder(SslMethod::tls()).map_err(SslConnectorError::CreateBuilder)?;
if let Some(pem) = identity_pem {
let mut chain = X509::stack_from_pem(pem)
.map_err(SslConnectorError::DeserializeCertificateChain)?
.into_iter();
let leaf_cert = chain.next().ok_or(SslConnectorError::GetLeafCertificate)?;
builder
.set_certificate(&leaf_cert)
.map_err(SslConnectorError::SetLeafCertificate)?;
for cert in chain {
builder
.add_extra_chain_cert(cert)
.map_err(SslConnectorError::AppendCertificate)?;
}
let pkey = PKey::private_key_from_pem(pem).map_err(SslConnectorError::DeserializePrivateKey)?;
builder
.set_private_key(&pkey)
.map_err(SslConnectorError::SetPrivateKey)?;
}
if let Some(ders) = root_certs {
for der in ders {
let cert = X509::from_der(der).map_err(SslConnectorError::DeserializeRootCertificate)?;
builder
.cert_store_mut()
.add_cert(cert)
.map_err(SslConnectorError::AddRootCertificate)?;
}
}
Ok(builder)
}
}