use std::io::Cursor;
use std::sync::Arc;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::sign::CertifiedKey;
use super::TlsConfigError;
pub(crate) fn crypto_provider() -> Result<Arc<rustls::crypto::CryptoProvider>, TlsConfigError> {
rustls::crypto::CryptoProvider::get_default()
.ok_or(TlsConfigError::NoCryptoProvider)
.map(Arc::clone)
}
pub fn pem_from_env_pair(prefix: &str) -> Result<(String, String), TlsConfigError> {
let cert = pem_from_env(
&format!("{prefix}_CERT_PEM"),
&format!("{prefix}_CERT_PATH"),
)?;
let key = pem_from_env(
&format!("{prefix}_KEY_PEM"),
&format!("{prefix}_KEY_PATH"),
)?;
Ok((cert, key))
}
pub fn pem_from_env(inline: &str, path_var: &str) -> Result<String, TlsConfigError> {
if let Ok(pem) = std::env::var(inline) {
if !pem.is_empty() {
return Ok(pem);
}
}
if let Ok(path) = std::env::var(path_var) {
if !path.is_empty() {
return std::fs::read_to_string(&path).map_err(|source| TlsConfigError::Io {
path: path.clone(),
source,
});
}
}
if inline.contains("CLIENT_CA") {
Err(TlsConfigError::MissingClientCa)
} else if inline.contains("CERT") || inline.ends_with("_PEM") && inline.contains("CERT") {
Err(TlsConfigError::MissingCert)
} else if inline.contains("KEY") {
Err(TlsConfigError::MissingKey)
} else if path_var.contains("CERT") {
Err(TlsConfigError::MissingCert)
} else {
Err(TlsConfigError::MissingKey)
}
}
pub fn load_certs(pem: &str) -> Result<Vec<CertificateDer<'static>>, TlsConfigError> {
load_pem_certs(pem, TlsConfigError::BadCert)
}
pub fn load_ca_certs(pem: &str) -> Result<Vec<CertificateDer<'static>>, TlsConfigError> {
load_pem_certs(pem, TlsConfigError::BadClientCa)
}
fn load_pem_certs(
pem: &str,
map_err: fn(String) -> TlsConfigError,
) -> Result<Vec<CertificateDer<'static>>, TlsConfigError> {
let mut reader = Cursor::new(pem.as_bytes());
let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| map_err(e.to_string()))?;
if certs.is_empty() {
return Err(map_err("no certificates in PEM".into()));
}
Ok(certs)
}
pub fn load_private_key(pem: &str) -> Result<PrivateKeyDer<'static>, TlsConfigError> {
let mut reader = Cursor::new(pem.as_bytes());
for item in rustls_pemfile::read_all(&mut reader) {
match item.map_err(|e| TlsConfigError::BadKey(e.to_string()))? {
rustls_pemfile::Item::Pkcs1Key(key) => {
return Ok(PrivateKeyDer::Pkcs1(key));
}
rustls_pemfile::Item::Pkcs8Key(key) => {
return Ok(PrivateKeyDer::Pkcs8(key));
}
rustls_pemfile::Item::Sec1Key(key) => {
return Ok(PrivateKeyDer::Sec1(key));
}
_ => {}
}
}
Err(TlsConfigError::BadKey("no private key in PEM".into()))
}
pub fn certified_key_from_pem(
cert_pem: &str,
key_pem: &str,
provider: Arc<rustls::crypto::CryptoProvider>,
) -> Result<CertifiedKey, TlsConfigError> {
let certs = load_certs(cert_pem)?;
let key = load_private_key(key_pem)?;
let signing = provider
.key_provider
.load_private_key(key)
.map_err(|e| TlsConfigError::BadKey(e.to_string()))?;
Ok(CertifiedKey::new(certs, signing))
}
#[must_use]
pub fn h2_cert_env_prefix() -> &'static str {
if split_cert_env_touched("NAUTALID_TLS_H2") {
"NAUTALID_TLS_H2"
} else {
"NAUTALID_TLS"
}
}
#[must_use]
pub fn h3_cert_env_prefix() -> &'static str {
if split_cert_env_touched("NAUTALID_TLS_H3") {
"NAUTALID_TLS_H3"
} else {
"NAUTALID_TLS"
}
}
fn split_cert_env_touched(prefix: &str) -> bool {
[
format!("{prefix}_CERT_PEM"),
format!("{prefix}_CERT_PATH"),
format!("{prefix}_KEY_PEM"),
format!("{prefix}_KEY_PATH"),
]
.iter()
.any(|name| {
std::env::var(name)
.ok()
.is_some_and(|s| !s.trim().is_empty())
})
}