use std::sync::Arc;
use rustls_pki_types::CertificateDer;
use crate::connection::PgConnection;
use crate::error::PgError;
#[derive(Clone, Default)]
#[allow(missing_debug_implementations)]
pub enum TlsMode {
#[default]
Disabled,
Rustls(Arc<rustls::ClientConfig>),
}
impl TlsMode {
pub fn skip_verify() -> Result<Self, PgError> {
let provider = oxitls::pure_provider();
let verifier = Arc::new(NoCertVerifier);
let cfg = rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| PgError::Tls(e.to_string()))?
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth();
Ok(TlsMode::Rustls(Arc::new(cfg)))
}
pub fn with_ca_pem(ca_pem: Vec<u8>) -> Result<Self, PgError> {
use std::io::BufReader;
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(oxitls::webpki_root_certs().roots);
let mut reader = BufReader::new(ca_pem.as_slice());
let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| PgError::Tls(format!("PEM parse error: {e}")))?;
if certs.is_empty() {
return Err(PgError::Tls("no certificates found in ca_pem".to_string()));
}
for cert in certs {
root_store
.add(cert)
.map_err(|e| PgError::Tls(format!("bad CA cert: {e}")))?;
}
let provider = oxitls::pure_provider();
let cfg = rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| PgError::Tls(e.to_string()))?
.with_root_certificates(root_store)
.with_no_client_auth();
Ok(TlsMode::Rustls(Arc::new(cfg)))
}
}
#[derive(Debug)]
struct NoCertVerifier;
impl rustls::client::danger::ServerCertVerifier for NoCertVerifier {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &rustls_pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls_pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
oxitls::pure_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
#[derive(Default)]
pub struct PgConnectionBuilder {
host: Option<String>,
port: Option<u16>,
dbname: Option<String>,
user: Option<String>,
password: Option<String>,
connect_timeout_secs: Option<u64>,
tls_mode: TlsMode,
}
impl PgConnectionBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn host(mut self, h: impl Into<String>) -> Self {
self.host = Some(h.into());
self
}
pub fn port(mut self, p: u16) -> Self {
self.port = Some(p);
self
}
pub fn dbname(mut self, db: impl Into<String>) -> Self {
self.dbname = Some(db.into());
self
}
pub fn user(mut self, u: impl Into<String>) -> Self {
self.user = Some(u.into());
self
}
pub fn password(mut self, pw: impl Into<String>) -> Self {
self.password = Some(pw.into());
self
}
pub fn connect_timeout_secs(mut self, secs: u64) -> Self {
self.connect_timeout_secs = Some(secs);
self
}
pub fn tls_mode(mut self, mode: TlsMode) -> Self {
self.tls_mode = mode;
self
}
pub fn tls_skip_verify(mut self) -> Result<Self, PgError> {
self.tls_mode = TlsMode::skip_verify()?;
Ok(self)
}
pub fn tls_with_ca_pem(mut self, ca_pem: Vec<u8>) -> Result<Self, PgError> {
self.tls_mode = TlsMode::with_ca_pem(ca_pem)?;
Ok(self)
}
pub async fn connect(self) -> Result<PgConnection, PgError> {
let mut parts = Vec::new();
if let Some(h) = &self.host {
parts.push(format!("host={h}"));
}
if let Some(p) = self.port {
parts.push(format!("port={p}"));
}
if let Some(db) = &self.dbname {
parts.push(format!("dbname={db}"));
}
if let Some(u) = &self.user {
parts.push(format!("user={u}"));
}
if let Some(pw) = &self.password {
parts.push(format!("password={pw}"));
}
if let Some(t) = self.connect_timeout_secs {
parts.push(format!("connect_timeout={t}"));
}
let conn_str = parts.join(" ");
PgConnection::connect(&conn_str, self.tls_mode).await
}
}