From 03db7b55faf77464b2a6fca942c63f5441c9e7e0 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Tue, 26 Jul 2022 21:04:24 +0200 Subject: [PATCH] cleaned up TLS code, added certificate and key checks to `warpgate check` --- Cargo.lock | 6 +- justfile | 3 + warpgate-admin/Cargo.toml | 1 - warpgate-common/Cargo.toml | 30 +++-- warpgate-common/src/config.rs | 1 - warpgate-common/src/lib.rs | 2 + warpgate-common/src/protocols/mod.rs | 6 +- warpgate-common/src/recordings/mod.rs | 6 +- warpgate-common/src/recordings/terminal.rs | 3 +- warpgate-common/src/tls/cert.rs | 111 ++++++++++++++++++ warpgate-common/src/tls/error.rs | 15 +++ warpgate-common/src/tls/mod.rs | 5 + warpgate-common/src/types/aliases.rs | 4 + .../{types.rs => types/listen_endpoint.rs} | 65 ---------- warpgate-common/src/types/mod.rs | 7 ++ warpgate-common/src/types/secret.rs | 64 ++++++++++ warpgate-protocol-http/src/lib.rs | 22 ++-- warpgate-protocol-mysql/Cargo.toml | 2 - warpgate-protocol-mysql/src/error.rs | 4 +- warpgate-protocol-mysql/src/lib.rs | 32 +++-- warpgate-protocol-mysql/src/tls/mod.rs | 2 +- .../src/tls/rustls_helpers.rs | 71 +---------- .../src/tls/rustls_root_certs.rs | 4 +- warpgate/src/commands/check.rs | 29 ++++- 24 files changed, 313 insertions(+), 182 deletions(-) create mode 100644 warpgate-common/src/tls/cert.rs create mode 100644 warpgate-common/src/tls/error.rs create mode 100644 warpgate-common/src/tls/mod.rs create mode 100644 warpgate-common/src/types/aliases.rs rename warpgate-common/src/{types.rs => types/listen_endpoint.rs} (50%) create mode 100644 warpgate-common/src/types/mod.rs create mode 100644 warpgate-common/src/types/secret.rs diff --git a/Cargo.lock b/Cargo.lock index b89cca2..8ab0867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4391,7 +4391,6 @@ dependencies = [ "warpgate-common", "warpgate-db-entities", "warpgate-protocol-ssh", - "warpgate-web", ] [[package]] @@ -4414,6 +4413,8 @@ dependencies = [ "rand", "rand_chacha", "rand_core", + "rustls", + "rustls-pemfile", "sea-orm", "serde", "serde_json", @@ -4427,6 +4428,7 @@ dependencies = [ "uuid", "warpgate-db-entities", "warpgate-db-migrations", + "webpki", ] [[package]] @@ -4501,7 +4503,6 @@ dependencies = [ "anyhow", "async-trait", "bytes", - "delegate", "mysql_common", "once_cell", "password-hash 0.2.3", @@ -4515,7 +4516,6 @@ dependencies = [ "tokio-rustls", "tracing", "uuid", - "warpgate-admin", "warpgate-common", "warpgate-database-protocols", "warpgate-db-entities", diff --git a/justfile b/justfile index 45ee034..8438f05 100644 --- a/justfile +++ b/justfile @@ -31,3 +31,6 @@ openapi: cd warpgate-web && yarn openapi:client:admin && yarn openapi:client:gateway cleanup: (fix "--allow-dirty") (clippy "--fix" "--allow-dirty") fmt svelte-check lint + +udeps: + cargo udeps --all-targets diff --git a/warpgate-admin/Cargo.toml b/warpgate-admin/Cargo.toml index b62c23a..d134faa 100644 --- a/warpgate-admin/Cargo.toml +++ b/warpgate-admin/Cargo.toml @@ -40,5 +40,4 @@ uuid = { version = "1.0", features = ["v4", "serde"] } warpgate-common = { version = "*", path = "../warpgate-common" } warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" } warpgate-protocol-ssh = { version = "*", path = "../warpgate-protocol-ssh" } -warpgate-web = { version = "*", path = "../warpgate-web" } regex = "1.6" diff --git a/warpgate-common/Cargo.toml b/warpgate-common/Cargo.toml index 57620bc..44cc0fc 100644 --- a/warpgate-common/Cargo.toml +++ b/warpgate-common/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" argon2 = "0.4" async-trait = "0.1" bytes = "1.2" -chrono = {version = "0.4", features = ["serde"]} +chrono = { version = "0.4", features = ["serde"] } data-encoding = "2.3" humantime-serde = "1.1" lazy_static = "1.4" @@ -17,20 +17,32 @@ once_cell = "1.10" packet = "0.1" password-hash = "0.4" poem = "^1.3.30" -poem-openapi = {version = "2.0.4", features = ["swagger-ui", "chrono", "uuid", "static-files"]} +poem-openapi = { version = "2.0.4", features = [ + "swagger-ui", + "chrono", + "uuid", + "static-files", +] } rand = "0.8" rand_chacha = "0.3" -rand_core = {version = "0.6", features = ["std"]} -sea-orm = {version = "^0.9", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros"], default-features = false} +rand_core = { version = "0.6", features = ["std"] } +sea-orm = { version = "^0.9", features = [ + "sqlx-sqlite", + "runtime-tokio-native-tls", + "macros", +], default-features = false } serde = "1.0" serde_json = "1.0" thiserror = "1.0" -tokio = {version = "1.20", features = ["tracing"]} -totp-rs = {version = "2.0", features = ["otpauth"]} +tokio = { version = "1.20", features = ["tracing"] } +totp-rs = { version = "2.0", features = ["otpauth"] } tracing = "0.1" tracing-core = "0.1" tracing-subscriber = "0.3" url = "2.2" -uuid = {version = "1.0", features = ["v4", "serde"]} -warpgate-db-entities = {version = "*", path = "../warpgate-db-entities"} -warpgate-db-migrations = {version = "*", path = "../warpgate-db-migrations"} +uuid = { version = "1.0", features = ["v4", "serde"] } +warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" } +warpgate-db-migrations = { version = "*", path = "../warpgate-db-migrations" } +rustls = { version = "0.20", features = ["dangerous_configuration"] } +rustls-pemfile = "1.0" +webpki = "0.22" diff --git a/warpgate-common/src/config.rs b/warpgate-common/src/config.rs index 05ba4fc..345f118 100644 --- a/warpgate-common/src/config.rs +++ b/warpgate-common/src/config.rs @@ -230,7 +230,6 @@ fn _default_ssh_keys_path() -> String { "./data/keys".to_owned() } - #[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq, Copy)] pub enum SshHostKeyVerificationMode { #[serde(rename = "prompt")] diff --git a/warpgate-common/src/lib.rs b/warpgate-common/src/lib.rs index a6a76e2..82b1f60 100644 --- a/warpgate-common/src/lib.rs +++ b/warpgate-common/src/lib.rs @@ -13,6 +13,7 @@ mod protocols; pub mod recordings; mod services; mod state; +mod tls; mod try_macro; mod types; @@ -23,5 +24,6 @@ pub use error::WarpgateError; pub use protocols::*; pub use services::*; pub use state::{SessionState, SessionStateInit, State}; +pub use tls::*; pub use try_macro::*; pub use types::*; diff --git a/warpgate-common/src/protocols/mod.rs b/warpgate-common/src/protocols/mod.rs index cea12ef..8c91b48 100644 --- a/warpgate-common/src/protocols/mod.rs +++ b/warpgate-common/src/protocols/mod.rs @@ -13,11 +13,11 @@ pub enum TargetTestError { Unreachable, #[error("authentication failed")] AuthenticationError, - #[error("connection error")] + #[error("connection error: {0}")] ConnectionError(String), - #[error("misconfigured")] + #[error("misconfigured: {0}")] Misconfigured(String), - #[error("I/O")] + #[error("I/O: {0}")] Io(#[from] std::io::Error), } diff --git a/warpgate-common/src/recordings/mod.rs b/warpgate-common/src/recordings/mod.rs index 8b4cb37..fe0b173 100644 --- a/warpgate-common/src/recordings/mod.rs +++ b/warpgate-common/src/recordings/mod.rs @@ -19,13 +19,13 @@ use writer::RecordingWriter; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("I/O")] + #[error("I/O: {0}")] Io(#[from] std::io::Error), - #[error("Database")] + #[error("Database: {0}")] Database(#[from] sea_orm::DbErr), - #[error("Failed to serialize a recording item")] + #[error("Failed to serialize a recording item: {0}")] Serialization(#[from] serde_json::Error), #[error("Writer is closed")] diff --git a/warpgate-common/src/recordings/terminal.rs b/warpgate-common/src/recordings/terminal.rs index 3d36413..f7f0a6b 100644 --- a/warpgate-common/src/recordings/terminal.rs +++ b/warpgate-common/src/recordings/terminal.rs @@ -4,8 +4,7 @@ use tokio::time::Instant; use warpgate_db_entities::Recording::RecordingKind; use super::writer::RecordingWriter; -use super::Recorder; -use super::{Result, Error}; +use super::{Error, Recorder, Result}; #[derive(Serialize)] #[serde(untagged)] diff --git a/warpgate-common/src/tls/cert.rs b/warpgate-common/src/tls/cert.rs new file mode 100644 index 0000000..d5106fc --- /dev/null +++ b/warpgate-common/src/tls/cert.rs @@ -0,0 +1,111 @@ +use std::path::Path; +use std::sync::Arc; + +use poem::listener::RustlsCertificate; +use rustls::sign::{CertifiedKey, SigningKey}; +use rustls::{Certificate, PrivateKey}; +use tokio::fs::File; +use tokio::io::AsyncReadExt; + +use crate::RustlsSetupError; + +pub struct TlsCertificateBundle { + bytes: Vec, + certificates: Vec, +} + +pub struct TlsPrivateKey { + bytes: Vec, + key: Arc, +} + +pub struct TlsCertificateAndPrivateKey { + pub certificate: TlsCertificateBundle, + pub private_key: TlsPrivateKey, +} + +impl TlsCertificateBundle { + pub async fn from_file>(path: P) -> Result { + let mut file = File::open(path).await?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).await?; + Self::from_bytes(bytes) + } + + pub fn from_bytes(bytes: Vec) -> Result { + let certificates = rustls_pemfile::certs(&mut &bytes[..]).map(|mut certs| { + certs + .drain(..) + .map(Certificate) + .collect::>() + })?; + if certificates.is_empty() { + return Err(RustlsSetupError::NoCertificates) + } + Ok(Self { + bytes, + certificates, + }) + } +} + +impl TlsPrivateKey { + pub async fn from_file>(path: P) -> Result { + let mut file = File::open(path).await?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).await?; + Self::from_bytes(bytes) + } + + pub fn from_bytes(bytes: Vec) -> Result { + let mut key = rustls_pemfile::pkcs8_private_keys(&mut bytes.as_slice())? + .drain(..) + .next() + .map(PrivateKey); + + if key.is_none() { + key = rustls_pemfile::rsa_private_keys(&mut bytes.as_slice())? + .drain(..) + .next() + .map(PrivateKey); + } + + let key = key.ok_or(RustlsSetupError::NoKeys)?; + let key = rustls::sign::any_supported_type(&key)?; + + Ok(Self { bytes, key }) + } +} + +impl Into> for TlsCertificateBundle { + fn into(self) -> Vec { + self.bytes + } +} + +impl Into> for TlsPrivateKey { + fn into(self) -> Vec { + self.bytes + } +} + +impl Into for TlsCertificateAndPrivateKey { + fn into(self) -> RustlsCertificate { + RustlsCertificate::new() + .cert(self.certificate) + .key(self.private_key) + } +} + +impl Into for TlsCertificateAndPrivateKey { + fn into(self) -> CertifiedKey { + let cert = self.certificate; + let key = self.private_key; + CertifiedKey { + cert: cert.certificates, + key: key.key, + ocsp: None, + sct_list: None, + } + } +} diff --git a/warpgate-common/src/tls/error.rs b/warpgate-common/src/tls/error.rs new file mode 100644 index 0000000..dacdf8b --- /dev/null +++ b/warpgate-common/src/tls/error.rs @@ -0,0 +1,15 @@ +#[derive(thiserror::Error, Debug)] +pub enum RustlsSetupError { + #[error("rustls: {0}")] + Rustls(#[from] rustls::Error), + #[error("sign: {0}")] + Sign(#[from] rustls::sign::SignError), + #[error("no certificates found in certificate file")] + NoCertificates, + #[error("no private keys found in key file")] + NoKeys, + #[error("I/O: {0}")] + Io(#[from] std::io::Error), + #[error("PKI: {0}")] + Pki(#[from] webpki::Error), +} diff --git a/warpgate-common/src/tls/mod.rs b/warpgate-common/src/tls/mod.rs new file mode 100644 index 0000000..c38fd62 --- /dev/null +++ b/warpgate-common/src/tls/mod.rs @@ -0,0 +1,5 @@ +mod cert; +mod error; + +pub use cert::*; +pub use error::*; diff --git a/warpgate-common/src/types/aliases.rs b/warpgate-common/src/types/aliases.rs new file mode 100644 index 0000000..515c950 --- /dev/null +++ b/warpgate-common/src/types/aliases.rs @@ -0,0 +1,4 @@ +use uuid::Uuid; + +pub type SessionId = Uuid; +pub type ProtocolName = &'static str; diff --git a/warpgate-common/src/types.rs b/warpgate-common/src/types/listen_endpoint.rs similarity index 50% rename from warpgate-common/src/types.rs rename to warpgate-common/src/types/listen_endpoint.rs index 0482429..d8670be 100644 --- a/warpgate-common/src/types.rs +++ b/warpgate-common/src/types/listen_endpoint.rs @@ -2,72 +2,7 @@ use std::fmt::Debug; use std::net::{SocketAddr, ToSocketAddrs}; use std::ops::Deref; -use bytes::Bytes; -use data_encoding::HEXLOWER; -use rand::Rng; use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::helpers::rng::get_crypto_rng; - -pub type SessionId = Uuid; -pub type ProtocolName = &'static str; - -#[derive(PartialEq, Eq, Clone)] -pub struct Secret(T); - -impl Secret { - pub fn random() -> Self { - Secret::new(HEXLOWER.encode(&Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>()))) - } -} - -impl Secret { - pub const fn new(v: T) -> Self { - Self(v) - } - - pub fn expose_secret(&self) -> &T { - &self.0 - } -} - -impl From for Secret { - fn from(v: T) -> Self { - Self::new(v) - } -} - -impl<'de, T> Deserialize<'de> for Secret -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let v = Deserialize::deserialize::(deserializer)?; - Ok(Self::new(v)) - } -} - -impl Serialize for Secret -where - T: Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.serialize(serializer) - } -} - -impl Debug for Secret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "") - } -} #[derive(Clone)] pub struct ListenEndpoint(pub SocketAddr); diff --git a/warpgate-common/src/types/mod.rs b/warpgate-common/src/types/mod.rs new file mode 100644 index 0000000..59d4afe --- /dev/null +++ b/warpgate-common/src/types/mod.rs @@ -0,0 +1,7 @@ +mod aliases; +mod listen_endpoint; +mod secret; + +pub use aliases::*; +pub use listen_endpoint::*; +pub use secret::*; diff --git a/warpgate-common/src/types/secret.rs b/warpgate-common/src/types/secret.rs new file mode 100644 index 0000000..0d83348 --- /dev/null +++ b/warpgate-common/src/types/secret.rs @@ -0,0 +1,64 @@ +use std::fmt::Debug; + +use bytes::Bytes; +use data_encoding::HEXLOWER; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +use crate::helpers::rng::get_crypto_rng; + +#[derive(PartialEq, Eq, Clone)] +pub struct Secret(T); + +impl Secret { + pub fn random() -> Self { + Secret::new(HEXLOWER.encode(&Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>()))) + } +} + +impl Secret { + pub const fn new(v: T) -> Self { + Self(v) + } + + pub fn expose_secret(&self) -> &T { + &self.0 + } +} + +impl From for Secret { + fn from(v: T) -> Self { + Self::new(v) + } +} + +impl<'de, T> Deserialize<'de> for Secret +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = Deserialize::deserialize::(deserializer)?; + Ok(Self::new(v)) + } +} + +impl Serialize for Secret +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +impl Debug for Secret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "") + } +} diff --git a/warpgate-protocol-http/src/lib.rs b/warpgate-protocol-http/src/lib.rs index 71d1ef0..13ee6e5 100644 --- a/warpgate-protocol-http/src/lib.rs +++ b/warpgate-protocol-http/src/lib.rs @@ -19,7 +19,7 @@ use common::page_admin_auth; pub use common::PROTOCOL_NAME; use logging::{log_request_result, span_for_request}; use poem::endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint}; -use poem::listener::{Listener, RustlsCertificate, RustlsConfig, TcpListener}; +use poem::listener::{Listener, RustlsConfig, TcpListener}; use poem::middleware::SetHeader; use poem::session::{CookieConfig, MemoryStorage, ServerSession}; use poem::web::Data; @@ -28,7 +28,7 @@ use poem_openapi::OpenApiService; use tokio::sync::Mutex; use tracing::*; use warpgate_admin::admin_api_app; -use warpgate_common::{ProtocolServer, Services, Target, TargetTestError}; +use warpgate_common::{ProtocolServer, Services, Target, TargetTestError, TlsCertificateAndPrivateKey, TlsCertificateBundle, TlsPrivateKey}; use warpgate_web::Assets; use crate::common::{endpoint_admin_auth, endpoint_auth, page_auth, COOKIE_MAX_AGE}; @@ -136,29 +136,29 @@ impl ProtocolServer for HTTPProtocolServer { } }); - let (certificate, key) = { + let certificate_and_key = { let config = self.services.config.lock().await; let certificate_path = config .paths_relative_to .join(&config.store.http.certificate); let key_path = config.paths_relative_to.join(&config.store.http.key); - ( - std::fs::read(&certificate_path).with_context(|| { + TlsCertificateAndPrivateKey { + certificate: TlsCertificateBundle::from_file(&certificate_path).await.with_context(|| { + format!("reading TLS private key from '{}'", key_path.display()) + })?, + private_key: TlsPrivateKey::from_file(&key_path).await.with_context(|| { format!( - "reading SSL certificate from '{}'", + "reading TLS certificate from '{}'", certificate_path.display() ) })?, - std::fs::read(&key_path).with_context(|| { - format!("reading SSL private key from '{}'", key_path.display()) - })?, - ) + } }; info!(?address, "Listening"); Server::new(TcpListener::bind(address).rustls( - RustlsConfig::new().fallback(RustlsCertificate::new().cert(certificate).key(key)), + RustlsConfig::new().fallback(certificate_and_key.into()), )) .run(app) .await?; diff --git a/warpgate-protocol-mysql/Cargo.toml b/warpgate-protocol-mysql/Cargo.toml index 86576cb..cd43be5 100644 --- a/warpgate-protocol-mysql/Cargo.toml +++ b/warpgate-protocol-mysql/Cargo.toml @@ -5,7 +5,6 @@ name = "warpgate-protocol-mysql" version = "0.3.0" [dependencies] -warpgate-admin = { version = "*", path = "../warpgate-admin" } warpgate-common = { version = "*", path = "../warpgate-common" } warpgate-db-entities = { version = "*", path = "../warpgate-db-entities" } warpgate-database-protocols = { version = "*", path = "../warpgate-database-protocols" } @@ -19,7 +18,6 @@ mysql_common = "0.29" rand = "0.8" sha1 = "0.10.1" password-hash = { version = "0.2", features = ["std"] } -delegate = "0.6" rustls = { version = "0.20", features = ["dangerous_configuration"] } rustls-pemfile = "1.0" tokio-rustls = "0.23" diff --git a/warpgate-protocol-mysql/src/error.rs b/warpgate-protocol-mysql/src/error.rs index 3a96f57..1736366 100644 --- a/warpgate-protocol-mysql/src/error.rs +++ b/warpgate-protocol-mysql/src/error.rs @@ -1,10 +1,10 @@ use std::error::Error; -use warpgate_common::WarpgateError; +use warpgate_common::{RustlsSetupError, WarpgateError}; use warpgate_database_protocols::error::Error as SqlxError; use crate::stream::MySqlStreamError; -use crate::tls::{MaybeTlsStreamError, RustlsSetupError}; +use crate::tls::MaybeTlsStreamError; #[derive(thiserror::Error, Debug)] pub enum MySqlError { diff --git a/warpgate-protocol-mysql/src/lib.rs b/warpgate-protocol-mysql/src/lib.rs index d41a97f..f484f7a 100644 --- a/warpgate-protocol-mysql/src/lib.rs +++ b/warpgate-protocol-mysql/src/lib.rs @@ -8,17 +8,22 @@ mod stream; mod tls; use std::fmt::Debug; use std::net::SocketAddr; +use std::sync::Arc; use anyhow::{Context, Result}; use async_trait::async_trait; +use rustls::server::NoClientAuth; use rustls::ServerConfig; use tokio::net::TcpListener; use tracing::*; -use warpgate_common::{ProtocolServer, Services, SessionStateInit, Target, TargetTestError}; +use warpgate_common::{ + ProtocolServer, Services, SessionStateInit, Target, TargetTestError, + TlsCertificateAndPrivateKey, TlsCertificateBundle, TlsPrivateKey, +}; use crate::session::MySqlSession; use crate::session_handle::MySqlSessionHandle; -use crate::tls::FromCertificateAndKey; +use crate::tls::ResolveServerCert; pub struct MySQLProtocolServer { services: Services, @@ -35,27 +40,34 @@ impl MySQLProtocolServer { #[async_trait] impl ProtocolServer for MySQLProtocolServer { async fn run(self, address: SocketAddr) -> Result<()> { - let (certificate, key) = { + let certificate_and_key = { let config = self.services.config.lock().await; let certificate_path = config .paths_relative_to .join(&config.store.mysql.certificate); let key_path = config.paths_relative_to.join(&config.store.mysql.key); - ( - std::fs::read(&certificate_path).with_context(|| { + TlsCertificateAndPrivateKey { + certificate: TlsCertificateBundle::from_file(&certificate_path) + .await + .with_context(|| { + format!("reading SSL private key from '{}'", key_path.display()) + })?, + private_key: TlsPrivateKey::from_file(&key_path).await.with_context(|| { format!( "reading SSL certificate from '{}'", certificate_path.display() ) })?, - std::fs::read(&key_path).with_context(|| { - format!("reading SSL private key from '{}'", key_path.display()) - })?, - ) + } }; - let tls_config = ServerConfig::try_from_certificate_and_key(certificate, key)?; + let tls_config = ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(NoClientAuth::new()) + .with_cert_resolver(Arc::new(ResolveServerCert(Arc::new( + certificate_and_key.into(), + )))); info!(?address, "Listening"); let listener = TcpListener::bind(address).await?; diff --git a/warpgate-protocol-mysql/src/tls/mod.rs b/warpgate-protocol-mysql/src/tls/mod.rs index ecd2b13..011c630 100644 --- a/warpgate-protocol-mysql/src/tls/mod.rs +++ b/warpgate-protocol-mysql/src/tls/mod.rs @@ -3,5 +3,5 @@ mod rustls_helpers; mod rustls_root_certs; pub use maybe_tls_stream::{MaybeTlsStream, MaybeTlsStreamError, UpgradableStream}; -pub use rustls_helpers::{configure_tls_connector, FromCertificateAndKey, RustlsSetupError}; +pub use rustls_helpers::{configure_tls_connector, ResolveServerCert}; pub use rustls_root_certs::ROOT_CERT_STORE; diff --git a/warpgate-protocol-mysql/src/tls/rustls_helpers.rs b/warpgate-protocol-mysql/src/tls/rustls_helpers.rs index e78afe7..6fa5d45 100644 --- a/warpgate-protocol-mysql/src/tls/rustls_helpers.rs +++ b/warpgate-protocol-mysql/src/tls/rustls_helpers.rs @@ -3,75 +3,14 @@ use std::sync::Arc; use std::time::SystemTime; use rustls::client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier}; -use rustls::server::{ClientHello, NoClientAuth, ResolvesServerCert}; +use rustls::server::{ClientHello, ResolvesServerCert}; use rustls::sign::CertifiedKey; -use rustls::{Certificate, ClientConfig, Error as TlsError, PrivateKey, ServerConfig, ServerName}; +use rustls::{ClientConfig, Error as TlsError, ServerName}; +use warpgate_common::RustlsSetupError; use super::ROOT_CERT_STORE; -#[derive(thiserror::Error, Debug)] -pub enum RustlsSetupError { - #[error("rustls")] - Rustls(#[from] rustls::Error), - #[error("sign")] - Sign(#[from] rustls::sign::SignError), - #[error("no private keys in key file")] - NoKeys, - #[error("I/O")] - Io(#[from] std::io::Error), - #[error("PKI")] - Pki(#[from] webpki::Error), -} - -pub trait FromCertificateAndKey -where - Self: Sized, -{ - fn try_from_certificate_and_key(cert: Vec, key: Vec) -> Result; -} - -impl FromCertificateAndKey for rustls::ServerConfig { - fn try_from_certificate_and_key( - cert: Vec, - key_bytes: Vec, - ) -> Result { - let certificates = rustls_pemfile::certs(&mut &cert[..]).map(|mut certs| { - certs - .drain(..) - .map(Certificate) - .collect::>() - })?; - - let mut key = rustls_pemfile::pkcs8_private_keys(&mut key_bytes.as_slice())? - .drain(..) - .next() - .map(PrivateKey); - - if key.is_none() { - key = rustls_pemfile::rsa_private_keys(&mut key_bytes.as_slice())? - .drain(..) - .next() - .map(PrivateKey); - } - - let key = key.ok_or(RustlsSetupError::NoKeys)?; - let key = rustls::sign::any_supported_type(&key)?; - - let cert_key = Arc::new(CertifiedKey { - cert: certificates, - key, - ocsp: None, - sct_list: None, - }); - - Ok(ServerConfig::builder() - .with_safe_defaults() - .with_client_cert_verifier(NoClientAuth::new()) - .with_cert_resolver(Arc::new(ResolveServerCert(cert_key)))) - } -} - -struct ResolveServerCert(Arc); +pub struct ResolveServerCert(pub Arc); impl ResolvesServerCert for ResolveServerCert { fn resolve(&self, _: ClientHello) -> Option> { @@ -117,7 +56,7 @@ pub async fn configure_tls_connector( Ok(config) } -struct DummyTlsVerifier; +pub struct DummyTlsVerifier; impl ServerCertVerifier for DummyTlsVerifier { fn verify_server_cert( diff --git a/warpgate-protocol-mysql/src/tls/rustls_root_certs.rs b/warpgate-protocol-mysql/src/tls/rustls_root_certs.rs index 075e939..165626a 100644 --- a/warpgate-protocol-mysql/src/tls/rustls_root_certs.rs +++ b/warpgate-protocol-mysql/src/tls/rustls_root_certs.rs @@ -7,7 +7,9 @@ pub static ROOT_CERT_STORE: Lazy = Lazy::new(|| { for cert in rustls_native_certs::load_native_certs().expect("could not load root TLS certificates") { - roots.add(&rustls::Certificate(cert.0)).expect("could not add root TLS certificate"); + roots + .add(&rustls::Certificate(cert.0)) + .expect("could not add root TLS certificate"); } roots }); diff --git a/warpgate/src/commands/check.rs b/warpgate/src/commands/check.rs index a494aa7..e8d6165 100644 --- a/warpgate/src/commands/check.rs +++ b/warpgate/src/commands/check.rs @@ -1,10 +1,35 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use tracing::*; +use warpgate_common::{TlsCertificateBundle, TlsPrivateKey}; use crate::config::load_config; pub(crate) async fn command(cli: &crate::Cli) -> Result<()> { - load_config(&cli.config, true)?; + let config = load_config(&cli.config, true)?; + if config.store.http.enable { + TlsCertificateBundle::from_file( + config + .paths_relative_to + .join(&config.store.http.certificate), + ) + .await + .with_context(|| format!("Checking HTTPS certificate"))?; + TlsPrivateKey::from_file(config.paths_relative_to.join(&config.store.http.key)) + .await + .with_context(|| format!("Checking HTTPS key"))?; + } + if config.store.mysql.enable { + TlsCertificateBundle::from_file( + config + .paths_relative_to + .join(&config.store.mysql.certificate), + ) + .await + .with_context(|| format!("Checking MySQL certificate"))?; + TlsPrivateKey::from_file(config.paths_relative_to.join(&config.store.mysql.key)) + .await + .with_context(|| format!("Checking MySQL key"))?; + } info!("No problems found"); Ok(()) }