use anyhow::{Context, anyhow};
use log::debug;
use rustls::pki_types::pem::PemObject;
use std::{path::PathBuf, sync::Arc};
use tokio_rustls::rustls::{
ClientConfig, RootCertStore, ServerConfig, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
pki_types::{CertificateDer, PrivateKeyDer, ServerName},
};
pub fn make_tls_acceptor(
certs: Vec<CertificateDer<'static>>,
key: PrivateKeyDer<'static>,
) -> anyhow::Result<tokio_rustls::TlsAcceptor> {
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)
.context("bad certificates/private key")?;
Ok(tokio_rustls::TlsAcceptor::from(Arc::new(config)))
}
pub fn make_tls_client_config(insecure: bool) -> ClientConfig {
let mut root_cert_store = RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
if insecure {
config
.dangerous()
.set_certificate_verifier(Arc::new(NoVerifier));
}
config
}
pub fn make_tls_connector(insecure: bool) -> tokio_rustls::TlsConnector {
tokio_rustls::TlsConnector::from(Arc::new(make_tls_client_config(insecure)))
}
pub fn make_server_name<'a>(domain: &str) -> anyhow::Result<ServerName<'a>> {
let domain = domain
.split(':')
.next()
.ok_or_else(|| anyhow!("domain parse error : {}", domain))?;
debug!("server name parse is : {}", domain);
let server_name = ServerName::try_from(domain)
.map_err(|e| anyhow!("try from domain [{}] to server name err : {}", &domain, e))?
.to_owned();
Ok(server_name)
}
pub fn load_certs(path: PathBuf) -> anyhow::Result<Vec<CertificateDer<'static>>> {
CertificateDer::pem_file_iter(&path)
.context("pem_file_iter failed")?
.collect::<Result<Vec<_>, _>>()
.context("pem_file_iter collect failed")
}
pub fn load_private_key(path: PathBuf) -> anyhow::Result<PrivateKeyDer<'static>> {
PrivateKeyDer::from_pem_file(&path).context("from_pem_file failed")
}
#[derive(Debug)]
pub(crate) struct NoVerifier;
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: tokio_rustls::rustls::pki_types::UnixTime,
) -> Result<tokio_rustls::rustls::client::danger::ServerCertVerified, tokio_rustls::rustls::Error>
{
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &tokio_rustls::rustls::DigitallySignedStruct,
) -> Result<
tokio_rustls::rustls::client::danger::HandshakeSignatureValid,
tokio_rustls::rustls::Error,
> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &tokio_rustls::rustls::DigitallySignedStruct,
) -> Result<
tokio_rustls::rustls::client::danger::HandshakeSignatureValid,
tokio_rustls::rustls::Error,
> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
vec![
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ED25519,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA256,
]
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use tokio_rustls::rustls::pki_types::ServerName;
use super::{load_certs, load_private_key, make_server_name, make_tls_client_config};
fn example_path(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../borer-cli/examples")
.join(name)
}
#[test]
fn make_server_name_strips_port() {
let server_name = make_server_name("example.com:443").unwrap();
assert_eq!(server_name, ServerName::try_from("example.com").unwrap());
}
#[test]
fn make_server_name_accepts_plain_domain() {
let server_name = make_server_name("example.com").unwrap();
assert_eq!(server_name, ServerName::try_from("example.com").unwrap());
}
#[test]
fn make_server_name_rejects_invalid_input() {
assert!(make_server_name(":443").is_err());
}
#[test]
fn make_tls_client_config_builds_for_both_modes() {
let _secure = make_tls_client_config(false);
let _insecure = make_tls_client_config(true);
}
#[test]
fn load_certs_and_private_key_from_example_files() {
let certs = load_certs(example_path("cert.pem")).unwrap();
let key = load_private_key(example_path("key.pem")).unwrap();
assert!(!certs.is_empty());
assert!(matches!(
key,
tokio_rustls::rustls::pki_types::PrivateKeyDer::Pkcs8(_)
| tokio_rustls::rustls::pki_types::PrivateKeyDer::Pkcs1(_)
| tokio_rustls::rustls::pki_types::PrivateKeyDer::Sec1(_)
));
}
#[test]
fn load_certs_and_private_key_fail_for_missing_files() {
assert!(load_certs(PathBuf::from("missing-cert.pem")).is_err());
assert!(load_private_key(PathBuf::from("missing-key.pem")).is_err());
}
}