use std::sync::Arc;
use anyhow::{anyhow, Context, Result};
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
use rustls::version::TLS12;
use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme};
pub fn build_client_config(
pfx: Option<&str>,
pfx_pass: Option<&str>,
crt: Option<&str>,
key: Option<&str>,
) -> Result<Arc<ClientConfig>> {
let (certs, key_der) = match (pfx, crt, key) {
(Some(pfx_path), _, _) => load_pfx(pfx_path, pfx_pass.unwrap_or(""))?,
(None, Some(crt_path), Some(key_path)) => load_pem(crt_path, key_path)?,
_ => return Err(anyhow!("certificate auth requires --pfx, or both --crt and --key")),
};
let config = ClientConfig::builder_with_protocol_versions(&[&TLS12])
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoServerVerify))
.with_client_auth_cert(certs, key_der)
.map_err(|e| anyhow!("client auth cert: {e}"))?;
Ok(Arc::new(config))
}
fn load_pfx(
path: &str,
password: &str,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
use p12_keystore::{KeyStore, KeyStoreEntry, Pkcs12ImportPolicy};
let data = std::fs::read(path).with_context(|| format!("read pfx {path}"))?;
let ks = KeyStore::from_pkcs12(&data, password, Pkcs12ImportPolicy::default())
.map_err(|e| anyhow!("parse pfx (convert to PEM with `openssl pkcs12` if this fails): {e:?}"))?;
for (_alias, entry) in ks.entries() {
if let KeyStoreEntry::PrivateKeyChain(chain) = entry {
let key = PrivateKeyDer::try_from(chain.key().as_der().to_vec())
.map_err(|e| anyhow!("pfx private key: {e}"))?;
let certs: Vec<CertificateDer<'static>> = chain
.certs()
.iter()
.map(|c| CertificateDer::from(c.as_der().to_vec()))
.collect();
if certs.is_empty() {
return Err(anyhow!("pfx has a key but no certificate"));
}
return Ok((certs, key));
}
}
Err(anyhow!("no private-key entry found in pfx"))
}
fn load_pem(
crt: &str,
key: &str,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
let crt_bytes = std::fs::read(crt).with_context(|| format!("read crt {crt}"))?;
let key_bytes = std::fs::read(key).with_context(|| format!("read key {key}"))?;
let certs = rustls_pemfile::certs(&mut &crt_bytes[..])
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| anyhow!("parse crt: {e}"))?;
if certs.is_empty() {
return Err(anyhow!("no certificate in {crt}"));
}
let key_der = rustls_pemfile::private_key(&mut &key_bytes[..])
.map_err(|e| anyhow!("parse key: {e}"))?
.ok_or_else(|| anyhow!("no private key in {key}"))?;
Ok((certs, key_der))
}
#[derive(Debug)]
struct NoServerVerify;
impl ServerCertVerifier for NoServerVerify {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> std::result::Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
]
}
}