From 6ecdb83bc20d259bdce906de08be7415a1b60ea4 Mon Sep 17 00:00:00 2001 From: "alex.berger@nexiot.ch" Date: Fri, 27 Nov 2020 17:43:24 +0100 Subject: [PATCH] Add support for passing in-memory trusted certificates in addition to the already supported path to trusted certificates. --- sqlx-core/src/mysql/connection/tls.rs | 2 +- sqlx-core/src/mysql/options/mod.rs | 21 +++++++-- sqlx-core/src/net/mod.rs | 2 +- sqlx-core/src/net/tls/mod.rs | 55 ++++++++++++++++++++---- sqlx-core/src/net/tls/rustls.rs | 10 ++--- sqlx-core/src/postgres/connection/tls.rs | 2 +- sqlx-core/src/postgres/options/mod.rs | 24 +++++++++-- 7 files changed, 93 insertions(+), 23 deletions(-) diff --git a/sqlx-core/src/mysql/connection/tls.rs b/sqlx-core/src/mysql/connection/tls.rs index 2b2a5b80..468b638f 100644 --- a/sqlx-core/src/mysql/connection/tls.rs +++ b/sqlx-core/src/mysql/connection/tls.rs @@ -52,7 +52,7 @@ async fn upgrade(stream: &mut MySqlStream, options: &MySqlConnectOptions) -> Res &options.host, accept_invalid_certs, accept_invalid_host_names, - options.ssl_ca.as_deref(), + options.ssl_ca.as_ref(), ) .await?; diff --git a/sqlx-core/src/mysql/options/mod.rs b/sqlx-core/src/mysql/options/mod.rs index ce107a13..7c0805c6 100644 --- a/sqlx-core/src/mysql/options/mod.rs +++ b/sqlx-core/src/mysql/options/mod.rs @@ -4,7 +4,7 @@ mod connect; mod parse; mod ssl_mode; -use crate::connection::LogSettings; +use crate::{connection::LogSettings, net::CertificateInput}; pub use ssl_mode::MySqlSslMode; /// Options and flags which can be used to configure a MySQL connection. @@ -62,7 +62,7 @@ pub struct MySqlConnectOptions { pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: MySqlSslMode, - pub(crate) ssl_ca: Option, + pub(crate) ssl_ca: Option, pub(crate) statement_cache_capacity: usize, pub(crate) charset: String, pub(crate) collation: Option, @@ -167,7 +167,22 @@ impl MySqlConnectOptions { /// .ssl_ca("path/to/ca.crt"); /// ``` pub fn ssl_ca(mut self, file_name: impl AsRef) -> Self { - self.ssl_ca = Some(file_name.as_ref().to_owned()); + self.ssl_ca = Some(CertificateInput::File(file_name.as_ref().to_owned())); + self + } + + /// Sets PEM encoded list of trusted SSL Certificate Authorities. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_core::mysql::{MySqlSslMode, MySqlConnectOptions}; + /// let options = MySqlConnectOptions::new() + /// .ssl_mode(MySqlSslMode::VerifyCa) + /// .ssl_ca_from_pem(vec![]); + /// ``` + pub fn ssl_ca_from_pem(mut self, pem_certificate: Vec) -> Self { + self.ssl_ca = Some(CertificateInput::Inline(pem_certificate)); self } diff --git a/sqlx-core/src/net/mod.rs b/sqlx-core/src/net/mod.rs index b3bca645..86d2e1b9 100644 --- a/sqlx-core/src/net/mod.rs +++ b/sqlx-core/src/net/mod.rs @@ -2,4 +2,4 @@ mod socket; mod tls; pub use socket::Socket; -pub use tls::MaybeTlsStream; +pub use tls::{CertificateInput, MaybeTlsStream}; diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 4fb1cfb8..fbf763c9 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -2,7 +2,7 @@ use std::io; use std::ops::{Deref, DerefMut}; -use std::path::Path; +use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; @@ -11,6 +11,48 @@ use sqlx_rt::{AsyncRead, AsyncWrite, TlsStream}; use crate::error::Error; use std::mem::replace; +/// X.509 Certificate input, either a file path or a PEM encoded inline certificate(s). +#[derive(Clone, Debug)] +pub enum CertificateInput { + /// PEM encoded certificate(s) + Inline(Vec), + /// Path to a file containing PEM encoded certificate(s) + File(PathBuf), +} + +impl From for CertificateInput { + fn from(value: String) -> Self { + let trimmed = value.trim(); + // Some heuristics according to https://tools.ietf.org/html/rfc7468 + if trimmed.starts_with("-----BEGIN CERTIFICATE-----") + && trimmed.contains("-----END CERTIFICATE-----") + { + CertificateInput::Inline(value.as_bytes().to_vec()) + } else { + CertificateInput::File(PathBuf::from(value)) + } + } +} + +impl CertificateInput { + async fn data(&self) -> Result, std::io::Error> { + use sqlx_rt::fs; + match self { + CertificateInput::Inline(v) => Ok(v.clone()), + CertificateInput::File(path) => fs::read(path).await, + } + } +} + +impl std::fmt::Display for CertificateInput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CertificateInput::Inline(v) => write!(f, "{}", String::from_utf8_lossy(v.as_slice())), + CertificateInput::File(path) => write!(f, "file: {}", path.display()), + } + } +} + #[cfg(feature = "_tls-rustls")] mod rustls; @@ -37,7 +79,7 @@ where host: &str, accept_invalid_certs: bool, accept_invalid_hostnames: bool, - root_cert_path: Option<&Path>, + root_cert_path: Option<&CertificateInput>, ) -> Result<(), Error> { let connector = configure_tls_connector( accept_invalid_certs, @@ -74,12 +116,9 @@ where async fn configure_tls_connector( accept_invalid_certs: bool, accept_invalid_hostnames: bool, - root_cert_path: Option<&Path>, + root_cert_path: Option<&CertificateInput>, ) -> Result { - use sqlx_rt::{ - fs, - native_tls::{Certificate, TlsConnector}, - }; + use sqlx_rt::native_tls::{Certificate, TlsConnector}; let mut builder = TlsConnector::builder(); builder @@ -88,7 +127,7 @@ async fn configure_tls_connector( if !accept_invalid_certs { if let Some(ca) = root_cert_path { - let data = fs::read(ca).await?; + let data = ca.data().await?; let cert = Certificate::from_pem(&data)?; builder.add_root_certificate(cert); diff --git a/sqlx-core/src/net/tls/rustls.rs b/sqlx-core/src/net/tls/rustls.rs index fcedff75..20044e51 100644 --- a/sqlx-core/src/net/tls/rustls.rs +++ b/sqlx-core/src/net/tls/rustls.rs @@ -2,17 +2,17 @@ use rustls::{ Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError, WebPKIVerifier, }; -use sqlx_rt::fs; use std::sync::Arc; -use std::{io::Cursor, path::Path}; +use std::{io::Cursor}; use webpki::DNSNameRef; +use crate::net::CertificateInput; use crate::error::Error; pub async fn configure_tls_connector( accept_invalid_certs: bool, accept_invalid_hostnames: bool, - root_cert_path: Option<&Path>, + root_cert_path: Option<&CertificateInput>, ) -> Result { let mut config = ClientConfig::new(); @@ -26,10 +26,10 @@ pub async fn configure_tls_connector( .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); if let Some(ca) = root_cert_path { - let data = fs::read(ca).await?; + let data = ca.data().await?; let mut cursor = Cursor::new(data); config.root_store.add_pem_file(&mut cursor).map_err(|_| { - Error::Tls(format!("Invalid certificate file: {}", ca.display()).into()) + Error::Tls(format!("Invalid certificate {}", ca).into()) })?; } diff --git a/sqlx-core/src/postgres/connection/tls.rs b/sqlx-core/src/postgres/connection/tls.rs index 283cc1b1..0c780f40 100644 --- a/sqlx-core/src/postgres/connection/tls.rs +++ b/sqlx-core/src/postgres/connection/tls.rs @@ -70,7 +70,7 @@ async fn upgrade(stream: &mut PgStream, options: &PgConnectOptions) -> Result, pub(crate) database: Option, pub(crate) ssl_mode: PgSslMode, - pub(crate) ssl_root_cert: Option, + pub(crate) ssl_root_cert: Option, pub(crate) statement_cache_capacity: usize, pub(crate) application_name: Option, pub(crate) log_settings: LogSettings, @@ -130,7 +130,7 @@ impl PgConnectOptions { username: var("PGUSER").ok().unwrap_or_else(whoami::username), password: var("PGPASSWORD").ok(), database: var("PGDATABASE").ok(), - ssl_root_cert: var("PGSSLROOTCERT").ok().map(PathBuf::from), + ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), ssl_mode: var("PGSSLMODE") .ok() .and_then(|v| v.parse().ok()) @@ -267,7 +267,23 @@ impl PgConnectOptions { /// .ssl_root_cert("./ca-certificate.crt"); /// ``` pub fn ssl_root_cert(mut self, cert: impl AsRef) -> Self { - self.ssl_root_cert = Some(cert.as_ref().to_path_buf()); + self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + self + } + + /// Sets PEM encoded trusted SSL Certificate Authorities (CA). + /// + /// # Example + /// + /// ```rust + /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// let options = PgConnectOptions::new() + /// // Providing a CA certificate with less than VerifyCa is pointless + /// .ssl_mode(PgSslMode::VerifyCa) + /// .ssl_root_cert_from_pem(vec![]); + /// ``` + pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec) -> Self { + self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate)); self }