use std::sync::Arc;
use thiserror::Error;
use tokio_rustls::rustls::crypto::CryptoProvider as RustlsCryptoProvider;
use tokio_rustls::rustls::pki_types::pem::PemObject;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::rustls::version::{TLS12, TLS13};
use tokio_rustls::rustls::{CipherSuite, ServerConfig, SupportedProtocolVersion};
pub const FIPS_APPROVED_SUITES: &[CipherSuite] = &[
CipherSuite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS13_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
];
const FIPS_VERSIONS: &[&SupportedProtocolVersion] = &[&TLS13, &TLS12];
pub trait CryptoProvider: Send + Sync + 'static {
fn server_config(&self) -> Arc<ServerConfig>;
fn fips_mode(&self) -> bool;
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum TlsError {
#[error("invalid PEM: {0}")]
Pem(&'static str),
#[error("no private key found")]
NoKey,
#[error("rustls configuration error: {0}")]
Config(String),
}
#[cfg(feature = "non-fips")]
#[derive(Debug, Clone)]
pub struct RingProvider {
server_config: Arc<ServerConfig>,
}
#[cfg(feature = "non-fips")]
impl RingProvider {
pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<Self, TlsError> {
let base = tokio_rustls::rustls::crypto::ring::default_provider();
Ok(Self {
server_config: build_server_config(base, cert_pem, key_pem, None)?,
})
}
pub fn from_pem_mtls(
cert_pem: &[u8],
key_pem: &[u8],
client_ca_pem: &[u8],
) -> Result<Self, TlsError> {
let base = tokio_rustls::rustls::crypto::ring::default_provider();
Ok(Self {
server_config: build_server_config(base, cert_pem, key_pem, Some(client_ca_pem))?,
})
}
}
#[cfg(feature = "non-fips")]
impl CryptoProvider for RingProvider {
fn server_config(&self) -> Arc<ServerConfig> {
Arc::clone(&self.server_config)
}
fn fips_mode(&self) -> bool {
false
}
}
#[cfg(feature = "fips")]
#[derive(Debug, Clone)]
pub struct AwsLcFipsProvider {
server_config: Arc<ServerConfig>,
}
#[cfg(feature = "fips")]
impl AwsLcFipsProvider {
pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<Self, TlsError> {
Ok(Self {
server_config: build_server_config(fips_base()?, cert_pem, key_pem, None)?,
})
}
pub fn from_pem_mtls(
cert_pem: &[u8],
key_pem: &[u8],
client_ca_pem: &[u8],
) -> Result<Self, TlsError> {
Ok(Self {
server_config: build_server_config(
fips_base()?,
cert_pem,
key_pem,
Some(client_ca_pem),
)?,
})
}
}
#[cfg(feature = "fips")]
fn fips_base() -> Result<RustlsCryptoProvider, TlsError> {
let base = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider();
if base.fips() {
Ok(base)
} else {
Err(TlsError::Config(
"aws-lc-rs is not operating in FIPS mode: the `fips` build did not engage \
AWS-LC-FIPS (check the AWS-LC-FIPS toolchain and that the `fips` feature \
is active)"
.to_owned(),
))
}
}
#[cfg(feature = "fips")]
impl CryptoProvider for AwsLcFipsProvider {
fn server_config(&self) -> Arc<ServerConfig> {
Arc::clone(&self.server_config)
}
fn fips_mode(&self) -> bool {
self.server_config.crypto_provider().fips()
}
}
fn build_server_config(
base: RustlsCryptoProvider,
cert_pem: &[u8],
key_pem: &[u8],
client_ca_pem: Option<&[u8]>,
) -> Result<Arc<ServerConfig>, TlsError> {
let provider = fips_pinned_provider(base);
let versions = ServerConfig::builder_with_provider(provider.clone())
.with_protocol_versions(FIPS_VERSIONS)
.map_err(|e| TlsError::Config(e.to_string()))?;
let builder = match client_ca_pem {
None => versions.with_no_client_auth(),
Some(ca_pem) => {
let mut roots = tokio_rustls::rustls::RootCertStore::empty();
for ca in parse_certs(ca_pem)? {
roots.add(ca).map_err(|e| TlsError::Config(e.to_string()))?;
}
let verifier =
tokio_rustls::rustls::server::WebPkiClientVerifier::builder_with_provider(
Arc::new(roots),
provider,
)
.build()
.map_err(|e| TlsError::Config(e.to_string()))?;
versions.with_client_cert_verifier(verifier)
}
};
let certs = parse_certs(cert_pem)?;
let key = parse_key(key_pem)?;
let mut config = builder
.with_single_cert(certs, key)
.map_err(|e| TlsError::Config(e.to_string()))?;
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
Ok(Arc::new(config))
}
fn fips_pinned_provider(base: RustlsCryptoProvider) -> Arc<RustlsCryptoProvider> {
let cipher_suites = base
.cipher_suites
.iter()
.copied()
.filter(|cs| FIPS_APPROVED_SUITES.contains(&cs.suite()))
.collect();
Arc::new(RustlsCryptoProvider {
cipher_suites,
..base
})
}
#[must_use]
pub(crate) fn client_subject_from_tls(
tls: &tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
) -> Option<String> {
let (_, conn) = tls.get_ref();
conn.peer_certificates()
.and_then(<[_]>::first)
.map(|cert| format!("cert:{}", cert_fingerprint(cert.as_ref())))
}
#[must_use]
pub(crate) fn cert_fingerprint(der: &[u8]) -> String {
#[cfg(feature = "non-fips")]
let digest = ring::digest::digest(&ring::digest::SHA256, der);
#[cfg(feature = "fips")]
let digest = aws_lc_rs::digest::digest(&aws_lc_rs::digest::SHA256, der);
let mut out = String::with_capacity(digest.as_ref().len() * 2);
for byte in digest.as_ref() {
out.push(char::from_digit(u32::from(byte >> 4), 16).unwrap_or('0'));
out.push(char::from_digit(u32::from(byte & 0x0f), 16).unwrap_or('0'));
}
out
}
fn parse_certs(pem: &[u8]) -> Result<Vec<CertificateDer<'static>>, TlsError> {
let certs: Result<Vec<_>, _> = CertificateDer::pem_slice_iter(pem).collect();
let certs = certs.map_err(|_| TlsError::Pem("certificate"))?;
if certs.is_empty() {
return Err(TlsError::Pem("certificate (none found)"));
}
Ok(certs)
}
fn parse_key(pem: &[u8]) -> Result<PrivateKeyDer<'static>, TlsError> {
PrivateKeyDer::from_pem_slice(pem).map_err(|_| TlsError::NoKey)
}