#[cfg(feature = "__rustls")]
use rustls::{
client::danger::HandshakeSignatureValid, client::danger::ServerCertVerified,
client::danger::ServerCertVerifier, crypto::WebPkiSupportedAlgorithms,
server::ParsedCertificate, DigitallySignedStruct, Error as TLSError, RootCertStore,
SignatureScheme,
};
#[cfg(feature = "__rustls")]
use rustls_pki_types::{ServerName, UnixTime};
use std::{
fmt,
io::{BufRead, BufReader},
};
#[cfg(feature = "__rustls")]
pub struct CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer<'static>,
}
#[derive(Clone)]
pub struct Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate,
#[cfg(feature = "__rustls")]
original: Cert,
}
#[cfg(feature = "__rustls")]
#[derive(Clone)]
enum Cert {
Der(Vec<u8>),
Pem(Vec<u8>),
}
#[derive(Clone)]
pub struct Identity {
#[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
inner: ClientCert,
}
enum ClientCert {
#[cfg(feature = "native-tls")]
Pkcs12(native_tls_crate::Identity),
#[cfg(feature = "native-tls")]
Pkcs8(native_tls_crate::Identity),
#[cfg(feature = "__rustls")]
Pem {
key: rustls_pki_types::PrivateKeyDer<'static>,
certs: Vec<rustls_pki_types::CertificateDer<'static>>,
},
}
impl Clone for ClientCert {
fn clone(&self) -> Self {
match self {
#[cfg(feature = "native-tls")]
Self::Pkcs8(i) => Self::Pkcs8(i.clone()),
#[cfg(feature = "native-tls")]
Self::Pkcs12(i) => Self::Pkcs12(i.clone()),
#[cfg(feature = "__rustls")]
ClientCert::Pem { key, certs } => ClientCert::Pem {
key: key.clone_key(),
certs: certs.clone(),
},
#[cfg_attr(
any(feature = "native-tls", feature = "__rustls"),
allow(unreachable_patterns)
)]
_ => unreachable!(),
}
}
}
impl Certificate {
pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Der(der.to_owned()),
})
}
pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Pem(pem.to_owned()),
})
}
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<Certificate>> {
let mut reader = BufReader::new(pem_bundle);
Self::read_pem_certs(&mut reader)?
.iter()
.map(|cert_vec| Certificate::from_der(&cert_vec))
.collect::<crate::Result<Vec<Certificate>>>()
}
#[cfg(feature = "default-tls")]
pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
tls.add_root_certificate(self.native);
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
root_cert_store: &mut rustls::RootCertStore,
) -> crate::Result<()> {
use std::io::Cursor;
match self.original {
Cert::Der(buf) => root_cert_store
.add(buf.into())
.map_err(crate::error::builder)?,
Cert::Pem(buf) => {
let mut reader = Cursor::new(buf);
let certs = Self::read_pem_certs(&mut reader)?;
for c in certs {
root_cert_store
.add(c.into())
.map_err(crate::error::builder)?;
}
}
}
Ok(())
}
fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result<Vec<Vec<u8>>> {
rustls_pemfile::certs(reader)
.map(|result| match result {
Ok(cert) => Ok(cert.as_ref().to_vec()),
Err(_) => Err(crate::error::builder("invalid certificate encoding")),
})
.collect()
}
}
impl Identity {
#[cfg(feature = "native-tls")]
pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs12(
native_tls_crate::Identity::from_pkcs12(der, password)
.map_err(crate::error::builder)?,
),
})
}
#[cfg(feature = "native-tls")]
pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs8(
native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
),
})
}
#[cfg(feature = "__rustls")]
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
use rustls_pemfile::Item;
use std::io::Cursor;
let (key, certs) = {
let mut pem = Cursor::new(buf);
let mut sk = Vec::<rustls_pki_types::PrivateKeyDer>::new();
let mut certs = Vec::<rustls_pki_types::CertificateDer>::new();
for result in rustls_pemfile::read_all(&mut pem) {
match result {
Ok(Item::X509Certificate(cert)) => certs.push(cert),
Ok(Item::Pkcs1Key(key)) => sk.push(key.into()),
Ok(Item::Pkcs8Key(key)) => sk.push(key.into()),
Ok(Item::Sec1Key(key)) => sk.push(key.into()),
Ok(_) => {
return Err(crate::error::builder(TLSError::General(String::from(
"No valid certificate was found",
))))
}
Err(_) => {
return Err(crate::error::builder(TLSError::General(String::from(
"Invalid identity PEM file",
))))
}
}
}
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(crate::error::builder(TLSError::General(String::from(
"private key or certificate not found",
))));
}
};
Ok(Identity {
inner: ClientCert::Pem { key, certs },
})
}
#[cfg(feature = "native-tls")]
pub(crate) fn add_to_native_tls(
self,
tls: &mut native_tls_crate::TlsConnectorBuilder,
) -> crate::Result<()> {
match self.inner {
ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
tls.identity(id);
Ok(())
}
#[cfg(feature = "__rustls")]
ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
config_builder: rustls::ConfigBuilder<
rustls::ClientConfig,
rustls::client::WantsClientCert,
>,
) -> crate::Result<rustls::ClientConfig> {
match self.inner {
ClientCert::Pem { key, certs } => config_builder
.with_client_auth_cert(certs, key)
.map_err(crate::error::builder),
#[cfg(feature = "native-tls")]
ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
Err(crate::error::builder("incompatible TLS identity type"))
}
}
}
}
#[cfg(feature = "__rustls")]
impl CertificateRevocationList {
#[cfg(feature = "__rustls")]
pub fn from_pem(pem: &[u8]) -> crate::Result<CertificateRevocationList> {
Ok(CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()),
})
}
#[cfg(feature = "__rustls")]
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<CertificateRevocationList>> {
let mut reader = BufReader::new(pem_bundle);
rustls_pemfile::crls(&mut reader)
.map(|result| match result {
Ok(crl) => Ok(CertificateRevocationList { inner: crl }),
Err(_) => Err(crate::error::builder("invalid crl encoding")),
})
.collect::<crate::Result<Vec<CertificateRevocationList>>>()
}
#[cfg(feature = "__rustls")]
pub(crate) fn as_rustls_crl<'a>(&self) -> rustls_pki_types::CertificateRevocationListDer<'a> {
self.inner.clone()
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate").finish()
}
}
impl fmt::Debug for Identity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Identity").finish()
}
}
#[cfg(feature = "__rustls")]
impl fmt::Debug for CertificateRevocationList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CertificateRevocationList").finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version(InnerVersion);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
enum InnerVersion {
Tls1_0,
Tls1_1,
Tls1_2,
Tls1_3,
}
impl Version {
pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
#[cfg(feature = "default-tls")]
pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
match self.0 {
InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
InnerVersion::Tls1_3 => None,
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
match version {
rustls::ProtocolVersion::SSLv2 => None,
rustls::ProtocolVersion::SSLv3 => None,
rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
_ => None,
}
}
}
pub(crate) enum TlsBackend {
#[allow(dead_code)]
#[cfg(feature = "default-tls")]
Default,
#[cfg(feature = "native-tls")]
BuiltNativeTls(native_tls_crate::TlsConnector),
#[cfg(feature = "__rustls")]
Rustls,
#[cfg(feature = "__rustls")]
BuiltRustls(rustls::ClientConfig),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
UnknownPreconfigured,
}
impl fmt::Debug for TlsBackend {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "default-tls")]
TlsBackend::Default => write!(f, "Default"),
#[cfg(feature = "native-tls")]
TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
#[cfg(feature = "__rustls")]
TlsBackend::Rustls => write!(f, "Rustls"),
#[cfg(feature = "__rustls")]
TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
}
}
}
impl Default for TlsBackend {
fn default() -> TlsBackend {
#[cfg(all(feature = "default-tls", not(feature = "http3")))]
{
TlsBackend::Default
}
#[cfg(any(
all(feature = "__rustls", not(feature = "default-tls")),
feature = "http3"
))]
{
TlsBackend::Rustls
}
}
}
#[cfg(feature = "__rustls")]
#[derive(Debug)]
pub(crate) struct NoVerifier;
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls_pki_types::CertificateDer,
_intermediates: &[rustls_pki_types::CertificateDer],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<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 = "__rustls")]
#[derive(Debug)]
pub(crate) struct IgnoreHostname {
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
}
#[cfg(feature = "__rustls")]
impl IgnoreHostname {
pub(crate) fn new(
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
) -> Self {
Self {
roots,
signature_algorithms,
}
}
}
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for IgnoreHostname {
fn verify_server_cert(
&self,
end_entity: &rustls_pki_types::CertificateDer<'_>,
intermediates: &[rustls_pki_types::CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
let cert = ParsedCertificate::try_from(end_entity)?;
rustls::client::verify_server_cert_signed_by_trust_anchor(
&cert,
&self.roots,
intermediates,
now,
self.signature_algorithms.all,
)?;
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls12_signature(message, cert, dss, &self.signature_algorithms)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls13_signature(message, cert, dss, &self.signature_algorithms)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.signature_algorithms.supported_schemes()
}
}
#[derive(Clone)]
pub struct TlsInfo {
pub(crate) peer_certificate: Option<Vec<u8>>,
}
impl TlsInfo {
pub fn peer_certificate(&self) -> Option<&[u8]> {
self.peer_certificate.as_ref().map(|der| &der[..])
}
}
impl std::fmt::Debug for TlsInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("TlsInfo").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_der_invalid() {
Certificate::from_der(b"not der").unwrap_err();
}
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_pem_invalid() {
Certificate::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs12_der_invalid() {
Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs8_pem_invalid() {
Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_invalid() {
Identity::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_pkcs1_key() {
let pem = b"-----BEGIN CERTIFICATE-----\n\
-----END CERTIFICATE-----\n\
-----BEGIN RSA PRIVATE KEY-----\n\
-----END RSA PRIVATE KEY-----\n";
Identity::from_pem(pem).unwrap();
}
#[test]
fn certificates_from_pem_bundle() {
const PEM_BUNDLE: &[u8] = b"
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
YyRIHN8wfdVoOw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
";
assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem() {
let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n";
CertificateRevocationList::from_pem(pem).unwrap();
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem_bundle() {
let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap();
let result = CertificateRevocationList::from_pem_bundle(&pem_bundle);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.len(), 1);
}
}