diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 97290b8f..29e1f3c4 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -45,10 +45,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup | Install NASM (Windows) + uses: ilammy/setup-nasm@v1 + if: matrix.os == 'windows-latest' + - name: Setup | Install toolchain run: | - rustup toolchain install stable --profile minimal - rustup toolchain install nightly --profile minimal + rustup update --no-self-update stable + rustup update --no-self-update nightly + rustup set profile minimal - name: Setup | Install cargo-fuzz run: | @@ -101,8 +106,9 @@ jobs: - name: Setup | Install toolchain run: | # 1.66 is the Minimum Supported Rust Version (MSRV) for imap-flow. - rustup toolchain install 1.66 --profile minimal - rustup toolchain install nightly --profile minimal + rustup update --no-self-update 1.66 + rustup update --no-self-update nightly + rustup set profile minimal - name: Setup | Cache dependencies uses: Swatinem/rust-cache@v2.5.1 diff --git a/Cargo.toml b/Cargo.toml index 867c61af..18bdd1e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ bounded-static = "0.5.0" bytes = "1.5.0" imap-codec = { version = "2.0.0", features = ["quirk_crlf_relaxed", "bounded-static"] } imap-types = { version = "2.0.0" } -rustls = { version = "0.21.11", optional = true } +rustls = { version = "0.23.1", optional = true } thiserror = "1.0.49" tokio = { version = "1.32.0", optional = true, features = ["io-util", "macros", "net"] } -tokio-rustls = { version = "0.24.1", optional = true } +tokio-rustls = { version = "0.26.0", optional = true } tracing = "0.1.40" [dev-dependencies] diff --git a/deny.toml b/deny.toml index bf276379..3b00fa49 100644 --- a/deny.toml +++ b/deny.toml @@ -7,7 +7,7 @@ allow-git = [ ] [licenses] -allow = [ "Apache-2.0", "MIT", "Unicode-DFS-2016", "ISC", "OpenSSL" ] +allow = [ "Apache-2.0", "BSD-3-Clause", "MIT", "Unicode-DFS-2016", "ISC", "OpenSSL" ] [[licenses.clarify]] name = "ring" diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index 2c592bc4..24c2454e 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -11,13 +11,13 @@ colored = "2.0.4" imap-codec = { version = "2.0.0", features = ["bounded-static", "quirk_crlf_relaxed", "ext_id"] } imap-flow = { path = ".." } imap-types = { version = "2.0.0", features = ["bounded-static", "ext_id"] } -rustls = "0.21.7" +once_cell = "1.19.0" +rustls-native-certs = "0.7.0" rustls-pemfile = "2.0.0-alpha.1" serde = { version = "1.0.171", features = ["derive"] } thiserror = "1.0.49" tokio = { version = "1.28", features = ["full"] } -tokio-rustls = "0.24.1" +tokio-rustls = "0.26.0" toml = "0.8.2" tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -webpki-roots = "0.25.2" diff --git a/proxy/src/proxy.rs b/proxy/src/proxy.rs index 28f5e134..78edfd57 100644 --- a/proxy/src/proxy.rs +++ b/proxy/src/proxy.rs @@ -13,10 +13,13 @@ use imap_types::{ extensions::idle::IdleDone, response::{Code, Status}, }; -use rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}; +use once_cell::sync::Lazy; use thiserror::Error; use tokio::net::{TcpListener, TcpStream}; -use tokio_rustls::{TlsAcceptor, TlsConnector}; +use tokio_rustls::{ + rustls::{pki_types::ServerName, ClientConfig, RootCertStore, ServerConfig}, + TlsAcceptor, TlsConnector, +}; use tracing::{error, info, trace}; use crate::{ @@ -24,6 +27,16 @@ use crate::{ util::{self, ControlFlow, IdentityError}, }; +static ROOT_CERT_STORE: Lazy = Lazy::new(|| { + let mut root_store = RootCertStore::empty(); + + for cert in rustls_native_certs::load_native_certs().unwrap() { + root_store.add(cert).unwrap(); + } + + root_store +}); + const LITERAL_ACCEPT_TEXT: &str = "proxy: Literal accepted by proxy"; const LITERAL_REJECT_TEXT: &str = "proxy: Literal rejected by proxy"; const COMMAND_REJECTED_TEXT: &str = "proxy: Command rejected by server"; @@ -35,7 +48,7 @@ pub enum ProxyError { #[error(transparent)] Identity(#[from] IdentityError), #[error(transparent)] - Tls(#[from] rustls::Error), + Tls(#[from] tokio_rustls::rustls::Error), } pub trait State: Send + 'static {} @@ -84,8 +97,7 @@ impl Proxy { } }; - let mut config = rustls::ServerConfig::builder() - .with_safe_defaults() + let mut config = ServerConfig::builder() .with_no_client_auth() // Note: The name is misleading. We provide the full chain here. .with_single_cert(certificate_chain, leaf_key)?; @@ -134,25 +146,8 @@ impl Proxy { let proxy_to_server = match self.service.connect { Connect::Tls { ref host, .. } => { let config = { - let root_store = { - let mut root_store = RootCertStore::empty(); - - root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map( - |ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - }, - )); - - root_store - }; - let mut config = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) + .with_root_certificates(ROOT_CERT_STORE.clone()) .with_no_client_auth(); // See @@ -162,7 +157,7 @@ impl Proxy { }; let connector = TlsConnector::from(Arc::new(config)); - let dnsname = ServerName::try_from(host.as_str()).unwrap(); + let dnsname = ServerName::try_from(host.clone()).unwrap(); info!(?server_addr_port, "Starting TLS with server"); Stream::tls(connector.connect(dnsname, stream_to_server).await?.into()) diff --git a/proxy/src/util.rs b/proxy/src/util.rs index 23da7567..3f96caeb 100644 --- a/proxy/src/util.rs +++ b/proxy/src/util.rs @@ -8,8 +8,8 @@ use imap_types::{ Greeting, Status, StatusBody, Tagged, }, }; -use rustls::{Certificate, PrivateKey}; use thiserror::Error; +use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use tracing::warn; pub enum ControlFlow { @@ -144,9 +144,9 @@ pub enum IdentityError { UnexpectedKeyCount { path: String, found: usize }, } -pub fn load_certificate_chain_pem>( +pub fn load_certificate_chain_pem<'a, P: AsRef>( path: P, -) -> Result, IdentityError> { +) -> Result>, IdentityError> { let display_path = path.as_ref().display().to_string(); let mut reader = BufReader::new(File::open(path).map_err(|error| IdentityError::Io { @@ -156,7 +156,7 @@ pub fn load_certificate_chain_pem>( rustls_pemfile::certs(&mut reader) .map(|res| { - res.map(|der| Certificate(der.to_vec())) + res.map(|der| CertificateDer::from(der.to_vec())) .map_err(|source| IdentityError::Io { source, path: display_path.clone(), @@ -165,7 +165,7 @@ pub fn load_certificate_chain_pem>( .collect() } -pub fn load_leaf_key_pem>(path: P) -> Result { +pub fn load_leaf_key_pem<'a, P: AsRef>(path: P) -> Result, IdentityError> { let display_path = path.as_ref().display().to_string(); let mut reader = BufReader::new(File::open(&path).map_err(|source| IdentityError::Io { @@ -180,7 +180,9 @@ pub fn load_leaf_key_pem>(path: P) -> Result