Compare commits
43 Commits
docs-crede
...
better-tls
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
716d7baac2 | ||
|
|
659b0b50b1 | ||
|
|
f332439000 | ||
|
|
335cdea3f9 | ||
|
|
d3d8e24824 | ||
|
|
c4df9730aa | ||
|
|
bfed19e6ad | ||
|
|
629967ac98 | ||
|
|
06e381ec9c | ||
|
|
d9ce9a6e47 | ||
|
|
e892b55b6b | ||
|
|
7642b2130e | ||
|
|
5a3f189e50 | ||
|
|
31b8f297ec | ||
|
|
3a6ab6f398 | ||
|
|
7b8fc5a678 | ||
|
|
2b36935b1f | ||
|
|
d31490a2a9 | ||
|
|
b7482f0232 | ||
|
|
abc8cdf789 | ||
|
|
81b233def4 | ||
|
|
e644a6c2d3 | ||
|
|
0385ca3b19 | ||
|
|
d114f9caf3 | ||
|
|
785307b091 | ||
|
|
f16cbeec51 | ||
|
|
5cbe9ba283 | ||
|
|
2f4e36ac61 | ||
|
|
610b72e93b | ||
|
|
69b7c5500a | ||
|
|
63c5fcccfc | ||
|
|
b583aff36c | ||
|
|
512c5e3ce8 | ||
|
|
771d212198 | ||
|
|
83ba93944d | ||
|
|
de3ab006e2 | ||
|
|
9504b7f45c | ||
|
|
c91b356a96 | ||
|
|
118c1ad47f | ||
|
|
8bf4d3a9c1 | ||
|
|
1fcff673ba | ||
|
|
8c70c0cfb4 | ||
|
|
63d8d30088 |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,3 +1,39 @@
|
||||
<a name="v0.11.16"></a>
|
||||
### v0.11.16 (2025-05-12)
|
||||
|
||||
#### Features
|
||||
|
||||
* Always implement `Clone` for `AsyncFileTransport` ([#1075])
|
||||
|
||||
#### Changes
|
||||
|
||||
* `Tls`, `CertificateStore`, `TlsParameters`, `TlsParametersBuilder`, `Certificate` and `Identity`
|
||||
are now marked as deprecated when no TLS backend is enabled. They will be properly feature gated
|
||||
in lettre v0.12 ([#1084])
|
||||
|
||||
#### Misc
|
||||
|
||||
* Gate `web-time` behind `cfg(target_arch = "wasm32")]` ([#1086])
|
||||
* Add missing `#[doc(cfg(...))]` attributes ([#1086])
|
||||
* Upgrade `webpki-roots` to v1 ([#1088])
|
||||
* Cleanup internal `TlsParameters` and `(Async)NetworkStream` structures ([#1082])
|
||||
* Feature gate internal `TransportBuilder::tls` to avoid recursive call site warnings ([#1083])
|
||||
* Fix workaround for embedding cargo script in rustdoc output ([#1077])
|
||||
* Fix `clippy::io_other_error` warnings ([#1078])
|
||||
* Upgrade semver compatible dependencies ([#1076], [#1079], [#1080])
|
||||
|
||||
[#1075]: https://github.com/lettre/lettre/pull/1075
|
||||
[#1076]: https://github.com/lettre/lettre/pull/1076
|
||||
[#1077]: https://github.com/lettre/lettre/pull/1077
|
||||
[#1078]: https://github.com/lettre/lettre/pull/1078
|
||||
[#1079]: https://github.com/lettre/lettre/pull/1079
|
||||
[#1080]: https://github.com/lettre/lettre/pull/1080
|
||||
[#1082]: https://github.com/lettre/lettre/pull/1082
|
||||
[#1083]: https://github.com/lettre/lettre/pull/1083
|
||||
[#1084]: https://github.com/lettre/lettre/pull/1084
|
||||
[#1086]: https://github.com/lettre/lettre/pull/1086
|
||||
[#1088]: https://github.com/lettre/lettre/pull/1088
|
||||
|
||||
<a name="v0.11.15"></a>
|
||||
### v0.11.15 (2025-03-10)
|
||||
|
||||
|
||||
470
Cargo.lock
generated
470
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "lettre"
|
||||
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
|
||||
version = "0.11.15"
|
||||
version = "0.11.16"
|
||||
description = "Email client"
|
||||
readme = "README.md"
|
||||
homepage = "https://lettre.rs"
|
||||
@@ -49,8 +49,9 @@ percent-encoding = { version = "2.3", optional = true }
|
||||
## tls
|
||||
native-tls = { version = "0.2.9", optional = true } # feature
|
||||
rustls = { version = "0.23.18", default-features = false, features = ["logging", "std", "tls12"], optional = true }
|
||||
rustls-platform-verifier = { version = "0.6.0", optional = true }
|
||||
rustls-native-certs = { version = "0.8", optional = true }
|
||||
webpki-roots = { version = "0.26", optional = true }
|
||||
webpki-roots = { version = "1.0.0", optional = true }
|
||||
boring = { version = "4", optional = true }
|
||||
|
||||
# async
|
||||
@@ -73,6 +74,7 @@ sha2 = { version = "0.10", features = ["oid"], optional = true }
|
||||
rsa = { version = "0.9", optional = true }
|
||||
ed25519-dalek = { version = "2", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
## web-time for wasm support
|
||||
web-time = { version = "1.1.0", optional = true }
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -28,8 +28,8 @@
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://deps.rs/crate/lettre/0.11.15">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.15/status.svg"
|
||||
<a href="https://deps.rs/crate/lettre/0.11.16">
|
||||
<img src="https://deps.rs/crate/lettre/0.11.16/status.svg"
|
||||
alt="dependency status" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -73,9 +73,9 @@ use lettre::{Message, SmtpTransport, Transport};
|
||||
|
||||
fn main() {
|
||||
let email = Message::builder()
|
||||
.from("NoBody <nobody@domain.tld>".parse().unwrap())
|
||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||
.from(Mailbox::new("NoBody".to_owned(), "nobody@domain.tld".parse().unwrap()))
|
||||
.reply_to(Mailbox::new("Yuin".to_owned(), "yuin@domain.tld".parse().unwrap()))
|
||||
.to(Mailbox::new("Hei".to_owned(), "hei@domain.tld".parse().unwrap()))
|
||||
.subject("Happy new year")
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(String::from("Be happy!"))
|
||||
|
||||
@@ -163,6 +163,7 @@ impl Envelope {
|
||||
}
|
||||
|
||||
#[cfg(feature = "builder")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "builder")))]
|
||||
impl TryFrom<&Headers> for Envelope {
|
||||
type Error = Error;
|
||||
|
||||
|
||||
27
src/lib.rs
27
src/lib.rs
@@ -93,17 +93,20 @@
|
||||
//! When the `rustls` feature is enabled, one of the following verification backends
|
||||
//! MUST also be enabled.
|
||||
//!
|
||||
//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`])
|
||||
//! * **rustls-platform-verifier**: verify TLS certificate using the OS's native certificate store (see [`rustls-platform-verifier`])
|
||||
//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`]) - when in doubt use `rustls-platform-verifier`
|
||||
//! * **webpki-roots**: verify TLS certificates against Mozilla's root certificates (see [`webpki-roots`])
|
||||
//!
|
||||
//! For the `rustls-native-certs` backend to work correctly, the following packages
|
||||
//! will need to be installed in order for the build stage and the compiled program
|
||||
//! to run properly.
|
||||
//! The following packages will need to be installed in order for the build
|
||||
//! stage and the compiled program to run properly.
|
||||
//!
|
||||
//! | Distro | Build-time packages | Runtime packages |
|
||||
//! | ------------ | -------------------------- | ---------------------------- |
|
||||
//! | Debian | none | `ca-certificates` |
|
||||
//! | Alpine Linux | none | `ca-certificates` |
|
||||
//! | Verification backend | Distro | Build-time packages | Runtime packages |
|
||||
//! | --------------------- | ------------ | -------------------------- | ---------------------------- |
|
||||
//! | `rustls-platform-verifier` | Debian | none | `ca-certificates` |
|
||||
//! | `rustls-platform-verifier` | Alpine Linux | none | `ca-certificates` |
|
||||
//! | `rustls-native-certs` | Debian | none | `ca-certificates` |
|
||||
//! | `rustls-native-certs` | Alpine Linux | none | `ca-certificates` |
|
||||
//! | `webpki-roots` | any | none | none |
|
||||
//!
|
||||
//! ### Sendmail transport
|
||||
//!
|
||||
@@ -151,6 +154,7 @@
|
||||
//! [AWS-LC]: https://github.com/aws/aws-lc
|
||||
//! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs
|
||||
//! [`ring`]: https://crates.io/crates/ring
|
||||
//! [`rustls-platform-verifier`]: https://crates.io/crates/rustls-platform-verifier
|
||||
//! [`rustls-native-certs`]: https://crates.io/crates/rustls-native-certs
|
||||
//! [`webpki-roots`]: https://crates.io/crates/webpki-roots
|
||||
//! [Tokio 1.x]: https://docs.rs/tokio/1
|
||||
@@ -158,7 +162,7 @@
|
||||
//! [mime 0.3]: https://docs.rs/mime/0.3
|
||||
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.15")]
|
||||
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.16")]
|
||||
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
|
||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -208,12 +212,13 @@ mod compiletime_checks {
|
||||
|
||||
#[cfg(all(
|
||||
feature = "rustls",
|
||||
not(feature = "rustls-platform-verifier"),
|
||||
not(feature = "rustls-native-certs"),
|
||||
not(feature = "webpki-roots")
|
||||
))]
|
||||
compile_error!(
|
||||
"feature `rustls` also requires either the `rustls-native-certs` or the `webpki-roots` feature to
|
||||
be enabled"
|
||||
"feature `rustls` also requires either the `rustls-platform-verifier`, the `rustls-native-certs`
|
||||
or the `webpki-roots` feature to be enabled"
|
||||
);
|
||||
|
||||
#[cfg(all(feature = "native-tls", feature = "boring-tls"))]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg(all(feature = "web", target_arch = "wasm32"))]
|
||||
pub(crate) fn now() -> SystemTime {
|
||||
fn to_std_systemtime(time: web_time::SystemTime) -> std::time::SystemTime {
|
||||
let duration = time
|
||||
@@ -18,7 +18,7 @@ pub(crate) fn now() -> SystemTime {
|
||||
to_std_systemtime(web_time::SystemTime::now())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "web"))]
|
||||
#[cfg(not(all(feature = "web", target_arch = "wasm32")))]
|
||||
pub(crate) fn now() -> SystemTime {
|
||||
// FIXME: change to #[expect(clippy::disallowed_methods, reason = "the `web` feature is disabled")]
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
|
||||
@@ -34,6 +34,7 @@ impl Error {
|
||||
|
||||
/// Returns true if the error is an envelope serialization or deserialization error
|
||||
#[cfg(feature = "file-transport-envelope")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport-envelope")))]
|
||||
pub fn is_envelope(&self) -> bool {
|
||||
matches!(self.inner.kind, Kind::Envelope)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ pub struct FileTransport {
|
||||
}
|
||||
|
||||
/// Asynchronously writes the content and the envelope information to a file
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
|
||||
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
|
||||
@@ -199,6 +199,7 @@ impl FileTransport {
|
||||
/// Writes the email content in eml format and the envelope
|
||||
/// in json format.
|
||||
#[cfg(feature = "file-transport-envelope")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport-envelope")))]
|
||||
pub fn with_envelope<P: AsRef<Path>>(path: P) -> FileTransport {
|
||||
FileTransport {
|
||||
path: PathBuf::from(path.as_ref()),
|
||||
@@ -211,6 +212,7 @@ impl FileTransport {
|
||||
///
|
||||
/// Reads the envelope and the raw message content.
|
||||
#[cfg(feature = "file-transport-envelope")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport-envelope")))]
|
||||
pub fn read(&self, email_id: &str) -> Result<(Envelope, Vec<u8>), Error> {
|
||||
use std::fs;
|
||||
|
||||
@@ -249,6 +251,7 @@ where
|
||||
/// Writes the email content in eml format and the envelope
|
||||
/// in json format.
|
||||
#[cfg(feature = "file-transport-envelope")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport-envelope")))]
|
||||
pub fn with_envelope<P: AsRef<Path>>(path: P) -> Self {
|
||||
Self {
|
||||
inner: FileTransport::with_envelope(path),
|
||||
@@ -260,6 +263,7 @@ where
|
||||
///
|
||||
/// Reads the envelope and the raw message content.
|
||||
#[cfg(feature = "file-transport-envelope")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport-envelope")))]
|
||||
pub async fn read(&self, email_id: &str) -> Result<(Envelope, Vec<u8>), Error> {
|
||||
let eml_file = self.inner.path.join(format!("{email_id}.eml"));
|
||||
let eml = E::fs_read(&eml_file).await.map_err(error::io)?;
|
||||
@@ -272,6 +276,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
|
||||
impl<E: Executor> Clone for AsyncFileTransport<E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker_: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transport for FileTransport {
|
||||
type Ok = Id;
|
||||
type Error = Error;
|
||||
|
||||
@@ -234,7 +234,7 @@ where
|
||||
/// a proper URL encoder, like the following cargo script:
|
||||
///
|
||||
/// ```rust
|
||||
/// # let _ = r#"
|
||||
/// # const TOML: &str = r#"
|
||||
/// #!/usr/bin/env cargo
|
||||
///
|
||||
/// //! ```cargo
|
||||
|
||||
@@ -19,9 +19,6 @@ pub struct Credentials {
|
||||
|
||||
impl Credentials {
|
||||
/// Create a `Credentials` struct from username and password
|
||||
///
|
||||
/// When using [`Mechanism::Xoauth2`], `password` is the raw
|
||||
/// bearer access token.
|
||||
pub fn new(username: String, password: String) -> Credentials {
|
||||
Credentials {
|
||||
authentication_identity: username,
|
||||
|
||||
@@ -54,6 +54,7 @@ impl AsyncSmtpConnection {
|
||||
///
|
||||
/// Sends EHLO and parses server information
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
pub async fn connect_with_transport(
|
||||
stream: Box<dyn AsyncTokioStream>,
|
||||
hello_name: &ClientId,
|
||||
@@ -94,6 +95,7 @@ impl AsyncSmtpConnection {
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
pub async fn connect_tokio1<T: tokio1_crate::net::ToSocketAddrs>(
|
||||
server: T,
|
||||
timeout: Option<Duration>,
|
||||
@@ -112,6 +114,7 @@ impl AsyncSmtpConnection {
|
||||
///
|
||||
/// Sends EHLO and parses server information
|
||||
#[cfg(feature = "async-std1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))]
|
||||
pub async fn connect_asyncstd1<T: async_std::net::ToSocketAddrs>(
|
||||
server: T,
|
||||
timeout: Option<Duration>,
|
||||
@@ -376,6 +379,10 @@ impl AsyncSmtpConnection {
|
||||
|
||||
/// The X509 certificate of the server (DER encoded)
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
self.stream.get_ref().peer_certificate()
|
||||
}
|
||||
@@ -392,12 +399,14 @@ impl AsyncSmtpConnection {
|
||||
/// as the TLSA records match the leaf or issuer certificates.
|
||||
/// It cannot be called on non Boring TLS streams.
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn tls_verify_result(&self) -> Result<(), Error> {
|
||||
self.stream.get_ref().tls_verify_result()
|
||||
}
|
||||
|
||||
/// All the X509 certificates of the chain (DER encoded)
|
||||
#[cfg(any(feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "boring-tls"))))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
self.stream.get_ref().certificate_chain()
|
||||
}
|
||||
|
||||
@@ -9,13 +9,11 @@ use std::{
|
||||
#[cfg(feature = "async-std1")]
|
||||
use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs};
|
||||
use futures_io::{
|
||||
AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Error as IoError, ErrorKind,
|
||||
AsyncRead as FuturesAsyncRead, AsyncWrite as FuturesAsyncWrite, Error as IoError,
|
||||
Result as IoResult,
|
||||
};
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
|
||||
#[cfg(any(feature = "tokio1-rustls", feature = "async-std1-rustls"))]
|
||||
use rustls::pki_types::ServerName;
|
||||
use futures_rustls::client::TlsStream as AsyncStd1RustlsStream;
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
use tokio1_boring::SslStream as Tokio1SslStream;
|
||||
#[cfg(feature = "tokio1")]
|
||||
@@ -28,7 +26,7 @@ use tokio1_crate::net::{
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
use tokio1_native_tls_crate::TlsStream as Tokio1TlsStream;
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
|
||||
use tokio1_rustls::client::TlsStream as Tokio1RustlsStream;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "tokio1-native-tls",
|
||||
@@ -79,7 +77,7 @@ enum InnerAsyncNetworkStream {
|
||||
Tokio1NativeTls(Tokio1TlsStream<Box<dyn AsyncTokioStream>>),
|
||||
/// Encrypted Tokio 1.x TCP stream
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
Tokio1RustlsTls(Tokio1RustlsTlsStream<Box<dyn AsyncTokioStream>>),
|
||||
Tokio1Rustls(Tokio1RustlsStream<Box<dyn AsyncTokioStream>>),
|
||||
/// Encrypted Tokio 1.x TCP stream
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
Tokio1BoringTls(Tokio1SslStream<Box<dyn AsyncTokioStream>>),
|
||||
@@ -88,7 +86,7 @@ enum InnerAsyncNetworkStream {
|
||||
AsyncStd1Tcp(AsyncStd1TcpStream),
|
||||
/// Encrypted Tokio 1.x TCP stream
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>),
|
||||
AsyncStd1Rustls(AsyncStd1RustlsStream<AsyncStd1TcpStream>),
|
||||
/// Can't be built
|
||||
None,
|
||||
}
|
||||
@@ -113,17 +111,16 @@ impl AsyncNetworkStream {
|
||||
s.get_ref().get_ref().get_ref().peer_addr()
|
||||
}
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(s) => s.get_ref().0.peer_addr(),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => s.peer_addr(),
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(s) => s.get_ref().0.peer_addr(),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
Err(IoError::other(
|
||||
"InnerAsyncNetworkStream::None must never be built",
|
||||
))
|
||||
}
|
||||
@@ -131,11 +128,13 @@ impl AsyncNetworkStream {
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
pub fn use_existing_tokio1(stream: Box<dyn AsyncTokioStream>) -> AsyncNetworkStream {
|
||||
AsyncNetworkStream::new(InnerAsyncNetworkStream::Tokio1Tcp(stream))
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
pub async fn connect_tokio1<T: Tokio1ToSocketAddrs>(
|
||||
server: T,
|
||||
timeout: Option<Duration>,
|
||||
@@ -202,6 +201,7 @@ impl AsyncNetworkStream {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async-std1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async-std1")))]
|
||||
pub async fn connect_asyncstd1<T: AsyncStd1ToSocketAddrs>(
|
||||
server: T,
|
||||
timeout: Option<Duration>,
|
||||
@@ -317,11 +317,9 @@ impl AsyncNetworkStream {
|
||||
tcp_stream: Box<dyn AsyncTokioStream>,
|
||||
tls_parameters: TlsParameters,
|
||||
) -> Result<InnerAsyncNetworkStream, Error> {
|
||||
let domain = tls_parameters.domain().to_owned();
|
||||
|
||||
match tls_parameters.connector {
|
||||
match tls_parameters.inner {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerTlsParameters::NativeTls(connector) => {
|
||||
InnerTlsParameters::NativeTls(inner) => {
|
||||
#[cfg(not(feature = "tokio1-native-tls"))]
|
||||
panic!("built without the tokio1-native-tls feature");
|
||||
|
||||
@@ -329,16 +327,16 @@ impl AsyncNetworkStream {
|
||||
return {
|
||||
use tokio1_native_tls_crate::TlsConnector;
|
||||
|
||||
let connector = TlsConnector::from(connector);
|
||||
let connector = TlsConnector::from(inner.connector);
|
||||
let stream = connector
|
||||
.connect(&domain, tcp_stream)
|
||||
.connect(&inner.server_name, tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::Tokio1NativeTls(stream))
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerTlsParameters::RustlsTls(config) => {
|
||||
InnerTlsParameters::Rustls(inner) => {
|
||||
#[cfg(not(feature = "tokio1-rustls"))]
|
||||
panic!("built without the tokio1-rustls feature");
|
||||
|
||||
@@ -346,28 +344,25 @@ impl AsyncNetworkStream {
|
||||
return {
|
||||
use tokio1_rustls::TlsConnector;
|
||||
|
||||
let domain = ServerName::try_from(domain.as_str())
|
||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
||||
|
||||
let connector = TlsConnector::from(config);
|
||||
let connector = TlsConnector::from(inner.connector);
|
||||
let stream = connector
|
||||
.connect(domain.to_owned(), tcp_stream)
|
||||
.connect(inner.server_name.inner(), tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::Tokio1RustlsTls(stream))
|
||||
Ok(InnerAsyncNetworkStream::Tokio1Rustls(stream))
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerTlsParameters::BoringTls(connector) => {
|
||||
InnerTlsParameters::BoringTls(inner) => {
|
||||
#[cfg(not(feature = "tokio1-boring-tls"))]
|
||||
panic!("built without the tokio1-boring-tls feature");
|
||||
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
return {
|
||||
let mut config = connector.configure().map_err(error::connection)?;
|
||||
config.set_verify_hostname(tls_parameters.accept_invalid_hostnames);
|
||||
let mut config = inner.connector.configure().map_err(error::connection)?;
|
||||
config.set_verify_hostname(inner.extra_info.accept_invalid_hostnames);
|
||||
|
||||
let stream = tokio1_boring::connect(config, &domain, tcp_stream)
|
||||
let stream = tokio1_boring::connect(config, &inner.server_name, tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::Tokio1BoringTls(stream))
|
||||
@@ -380,17 +375,15 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
async fn upgrade_asyncstd1_tls(
|
||||
tcp_stream: AsyncStd1TcpStream,
|
||||
mut tls_parameters: TlsParameters,
|
||||
tls_parameters: TlsParameters,
|
||||
) -> Result<InnerAsyncNetworkStream, Error> {
|
||||
let domain = mem::take(&mut tls_parameters.domain);
|
||||
|
||||
match tls_parameters.connector {
|
||||
match tls_parameters.inner {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerTlsParameters::NativeTls(connector) => {
|
||||
InnerTlsParameters::NativeTls(_) => {
|
||||
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
||||
}
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerTlsParameters::RustlsTls(config) => {
|
||||
InnerTlsParameters::Rustls(inner) => {
|
||||
#[cfg(not(feature = "async-std1-rustls"))]
|
||||
panic!("built without the async-std1-rustls feature");
|
||||
|
||||
@@ -398,19 +391,16 @@ impl AsyncNetworkStream {
|
||||
return {
|
||||
use futures_rustls::TlsConnector;
|
||||
|
||||
let domain = ServerName::try_from(domain.as_str())
|
||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
||||
|
||||
let connector = TlsConnector::from(config);
|
||||
let connector = TlsConnector::from(inner.connector);
|
||||
let stream = connector
|
||||
.connect(domain.to_owned(), tcp_stream)
|
||||
.connect(inner.server_name.inner(), tcp_stream)
|
||||
.await
|
||||
.map_err(error::connection)?;
|
||||
Ok(InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream))
|
||||
Ok(InnerAsyncNetworkStream::AsyncStd1Rustls(stream))
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerTlsParameters::BoringTls(connector) => {
|
||||
InnerTlsParameters::BoringTls(_inner) => {
|
||||
panic!("boring-tls isn't supported with async-std yet.");
|
||||
}
|
||||
}
|
||||
@@ -423,18 +413,19 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(_) => true,
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(_) => true,
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(_) => true,
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(_) => true,
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true,
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(_) => true,
|
||||
InnerAsyncNetworkStream::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn tls_verify_result(&self) -> Result<(), Error> {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
@@ -444,7 +435,7 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(_) => panic!("Unsupported"),
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => {
|
||||
stream.ssl().verify_result().map_err(error::tls)
|
||||
@@ -454,10 +445,11 @@ impl AsyncNetworkStream {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => panic!("Unsupported"),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(_) => panic!("Unsupported"),
|
||||
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
match &self.inner {
|
||||
#[cfg(feature = "tokio1")]
|
||||
@@ -467,7 +459,7 @@ impl AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(stream) => Ok(stream
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
@@ -488,7 +480,7 @@ impl AsyncNetworkStream {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
@@ -515,7 +507,7 @@ impl AsyncNetworkStream {
|
||||
.to_der()
|
||||
.map_err(error::tls)?),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(stream) => Ok(stream
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
@@ -535,7 +527,7 @@ impl AsyncNetworkStream {
|
||||
Err(error::client("Connection is not encrypted"))
|
||||
}
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(stream) => Ok(stream
|
||||
.get_ref()
|
||||
.1
|
||||
.peer_certificates()
|
||||
@@ -575,7 +567,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => {
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(s) => {
|
||||
let mut b = Tokio1ReadBuf::new(buf);
|
||||
match Pin::new(s).poll_read(cx, &mut b) {
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||
@@ -595,7 +587,7 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_read(cx, buf),
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_read(cx, buf),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(s) => Pin::new(s).poll_read(cx, buf),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(0))
|
||||
@@ -617,13 +609,13 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_write(cx, buf),
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(s) => Pin::new(s).poll_write(cx, buf),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(0))
|
||||
@@ -638,13 +630,13 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_flush(cx),
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(s) => Pin::new(s).poll_flush(cx),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(()))
|
||||
@@ -659,13 +651,13 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
||||
#[cfg(feature = "tokio1-native-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1NativeTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "tokio1-rustls")]
|
||||
InnerAsyncNetworkStream::Tokio1RustlsTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
InnerAsyncNetworkStream::Tokio1Rustls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "tokio1-boring-tls")]
|
||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||
#[cfg(feature = "async-std1")]
|
||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_close(cx),
|
||||
#[cfg(feature = "async-std1-rustls")]
|
||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_close(cx),
|
||||
InnerAsyncNetworkStream::AsyncStd1Rustls(s) => Pin::new(s).poll_close(cx),
|
||||
InnerAsyncNetworkStream::None => {
|
||||
debug_assert!(false, "InnerAsyncNetworkStream::None must never be built");
|
||||
Poll::Ready(Ok(()))
|
||||
|
||||
@@ -300,6 +300,10 @@ impl SmtpConnection {
|
||||
|
||||
/// The X509 certificate of the server (DER encoded)
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
self.stream.get_ref().peer_certificate()
|
||||
}
|
||||
@@ -316,12 +320,14 @@ impl SmtpConnection {
|
||||
/// as the TLSA records match the leaf or issuer certificates.
|
||||
/// It cannot be called on non Boring TLS streams.
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn tls_verify_result(&self) -> Result<(), Error> {
|
||||
self.stream.get_ref().tls_verify_result()
|
||||
}
|
||||
|
||||
/// All the X509 certificates of the chain (DER encoded)
|
||||
#[cfg(any(feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "boring-tls"))))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
self.stream.get_ref().certificate_chain()
|
||||
}
|
||||
|
||||
@@ -34,12 +34,14 @@ pub use self::async_net::AsyncNetworkStream;
|
||||
pub use self::async_net::AsyncTokioStream;
|
||||
use self::net::NetworkStream;
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
pub(super) use self::tls::InnerTlsParameters;
|
||||
pub(super) use self::tls::current::InnerTlsParameters;
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
pub use self::tls::TlsVersion;
|
||||
pub use self::tls::current::TlsVersion;
|
||||
pub use self::{
|
||||
connection::SmtpConnection,
|
||||
tls::{Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder},
|
||||
tls::current::{
|
||||
Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
|
||||
@@ -12,7 +12,7 @@ use boring::ssl::SslStream;
|
||||
#[cfg(feature = "native-tls")]
|
||||
use native_tls::TlsStream;
|
||||
#[cfg(feature = "rustls")]
|
||||
use rustls::{pki_types::ServerName, ClientConnection, StreamOwned};
|
||||
use rustls::{ClientConnection, StreamOwned};
|
||||
use socket2::{Domain, Protocol, Type};
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
@@ -37,7 +37,7 @@ enum InnerNetworkStream {
|
||||
NativeTls(TlsStream<TcpStream>),
|
||||
/// Encrypted TCP stream
|
||||
#[cfg(feature = "rustls")]
|
||||
RustlsTls(StreamOwned<ClientConnection, TcpStream>),
|
||||
Rustls(StreamOwned<ClientConnection, TcpStream>),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
BoringTls(SslStream<TcpStream>),
|
||||
/// Can't be built
|
||||
@@ -60,7 +60,7 @@ impl NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::Rustls(s) => s.get_ref().peer_addr(),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(s) => s.get_ref().peer_addr(),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -80,7 +80,7 @@ impl NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(s) => s.get_ref().shutdown(how),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::Rustls(s) => s.get_ref().shutdown(how),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(s) => s.get_ref().shutdown(how),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -172,30 +172,33 @@ impl NetworkStream {
|
||||
tcp_stream: TcpStream,
|
||||
tls_parameters: &TlsParameters,
|
||||
) -> Result<InnerNetworkStream, Error> {
|
||||
Ok(match &tls_parameters.connector {
|
||||
Ok(match &tls_parameters.inner {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerTlsParameters::NativeTls(connector) => {
|
||||
let stream = connector
|
||||
.connect(tls_parameters.domain(), tcp_stream)
|
||||
InnerTlsParameters::NativeTls(inner) => {
|
||||
let stream = inner
|
||||
.connector
|
||||
.connect(&inner.server_name, tcp_stream)
|
||||
.map_err(error::connection)?;
|
||||
InnerNetworkStream::NativeTls(stream)
|
||||
}
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerTlsParameters::RustlsTls(connector) => {
|
||||
let domain = ServerName::try_from(tls_parameters.domain())
|
||||
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
|
||||
let connection = ClientConnection::new(Arc::clone(connector), domain.to_owned())
|
||||
.map_err(error::connection)?;
|
||||
InnerTlsParameters::Rustls(inner) => {
|
||||
let connection = ClientConnection::new(
|
||||
Arc::clone(&inner.connector),
|
||||
inner.server_name.inner_ref().clone(),
|
||||
)
|
||||
.map_err(error::connection)?;
|
||||
let stream = StreamOwned::new(connection, tcp_stream);
|
||||
InnerNetworkStream::RustlsTls(stream)
|
||||
InnerNetworkStream::Rustls(stream)
|
||||
}
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerTlsParameters::BoringTls(connector) => {
|
||||
let stream = connector
|
||||
InnerTlsParameters::BoringTls(inner) => {
|
||||
let stream = inner
|
||||
.connector
|
||||
.configure()
|
||||
.map_err(error::connection)?
|
||||
.verify_hostname(tls_parameters.accept_invalid_hostnames)
|
||||
.connect(tls_parameters.domain(), tcp_stream)
|
||||
.verify_hostname(inner.extra_info.accept_invalid_hostnames)
|
||||
.connect(&inner.server_name, tcp_stream)
|
||||
.map_err(error::connection)?;
|
||||
InnerNetworkStream::BoringTls(stream)
|
||||
}
|
||||
@@ -208,7 +211,7 @@ impl NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(_) => true,
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(_) => true,
|
||||
InnerNetworkStream::Rustls(_) => true,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(_) => true,
|
||||
InnerNetworkStream::None => {
|
||||
@@ -219,13 +222,14 @@ impl NetworkStream {
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn tls_verify_result(&self) -> Result<(), Error> {
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(_) => panic!("Unsupported"),
|
||||
InnerNetworkStream::Rustls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(stream) => {
|
||||
stream.ssl().verify_result().map_err(error::tls)
|
||||
@@ -235,13 +239,14 @@ impl NetworkStream {
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "boring-tls"))))]
|
||||
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(_) => panic!("Unsupported"),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(stream) => Ok(stream
|
||||
InnerNetworkStream::Rustls(stream) => Ok(stream
|
||||
.conn
|
||||
.peer_certificates()
|
||||
.unwrap()
|
||||
@@ -261,6 +266,10 @@ impl NetworkStream {
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||
match &self.inner {
|
||||
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||
@@ -272,7 +281,7 @@ impl NetworkStream {
|
||||
.to_der()
|
||||
.map_err(error::tls)?),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(stream) => Ok(stream
|
||||
InnerNetworkStream::Rustls(stream) => Ok(stream
|
||||
.conn
|
||||
.peer_certificates()
|
||||
.unwrap()
|
||||
@@ -296,7 +305,7 @@ impl NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
InnerNetworkStream::Rustls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_read_timeout(duration),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -314,7 +323,7 @@ impl NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
InnerNetworkStream::Rustls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(stream) => stream.get_ref().set_write_timeout(duration),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -332,7 +341,7 @@ impl Read for NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(s) => s.read(buf),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(s) => s.read(buf),
|
||||
InnerNetworkStream::Rustls(s) => s.read(buf),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(s) => s.read(buf),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -350,7 +359,7 @@ impl Write for NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(s) => s.write(buf),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(s) => s.write(buf),
|
||||
InnerNetworkStream::Rustls(s) => s.write(buf),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(s) => s.write(buf),
|
||||
InnerNetworkStream::None => {
|
||||
@@ -366,7 +375,7 @@ impl Write for NetworkStream {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerNetworkStream::NativeTls(s) => s.flush(),
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerNetworkStream::RustlsTls(s) => s.flush(),
|
||||
InnerNetworkStream::Rustls(s) => s.flush(),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerNetworkStream::BoringTls(s) => s.flush(),
|
||||
InnerNetworkStream::None => {
|
||||
|
||||
111
src/transport/smtp/client/tls/boring_tls.rs
Normal file
111
src/transport/smtp/client/tls/boring_tls.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
use boring::{
|
||||
ssl::{SslConnector, SslMethod, SslVerifyMode, SslVersion},
|
||||
x509::store::X509StoreBuilder,
|
||||
};
|
||||
|
||||
use crate::transport::smtp::error::{self, Error};
|
||||
|
||||
pub(super) fn build_connector(
|
||||
builder: super::TlsParametersBuilder<super::BoringTls>,
|
||||
) -> Result<(Box<str>, SslConnector), Error> {
|
||||
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
|
||||
|
||||
if builder.accept_invalid_certs {
|
||||
tls_builder.set_verify(SslVerifyMode::NONE);
|
||||
} else {
|
||||
match builder.cert_store {
|
||||
CertificateStore::System => {}
|
||||
CertificateStore::None => {
|
||||
// Replace the default store with an empty store.
|
||||
tls_builder.set_cert_store(X509StoreBuilder::new().map_err(error::tls)?.build());
|
||||
}
|
||||
}
|
||||
|
||||
let cert_store = tls_builder.cert_store_mut();
|
||||
|
||||
for cert in builder.root_certs {
|
||||
cert_store.add_cert(cert.0).map_err(error::tls)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identity) = builder.identity {
|
||||
tls_builder
|
||||
.set_certificate(identity.chain.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
tls_builder
|
||||
.set_private_key(identity.key.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
}
|
||||
|
||||
let min_tls_version = match builder.min_tls_version {
|
||||
MinTlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||
MinTlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||
MinTlsVersion::Tlsv12 => SslVersion::TLS1_2,
|
||||
MinTlsVersion::Tlsv13 => SslVersion::TLS1_3,
|
||||
};
|
||||
|
||||
tls_builder
|
||||
.set_min_proto_version(Some(min_tls_version))
|
||||
.map_err(error::tls)?;
|
||||
Ok((builder.server_name.into_boxed_str(), tls_builder.build()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum CertificateStore {
|
||||
#[default]
|
||||
System,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Certificate(pub(super) boring::x509::X509);
|
||||
|
||||
impl Certificate {
|
||||
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self(boring::x509::X509::from_pem(pem).map_err(error::tls)?))
|
||||
}
|
||||
|
||||
pub(super) fn from_der(der: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self(boring::x509::X509::from_der(der).map_err(error::tls)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Certificate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Identity {
|
||||
pub(super) chain: boring::x509::X509,
|
||||
pub(super) key: boring::pkey::PKey<boring::pkey::Private>,
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||
let chain = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
||||
Ok(Self { chain, key })
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Identity").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum MinTlsVersion {
|
||||
Tlsv10,
|
||||
Tlsv11,
|
||||
#[default]
|
||||
Tlsv12,
|
||||
Tlsv13,
|
||||
}
|
||||
@@ -1,26 +1,9 @@
|
||||
use std::fmt::{self, Debug};
|
||||
#[cfg(feature = "rustls")]
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
use boring::{
|
||||
pkey::PKey,
|
||||
ssl::{SslConnector, SslVersion},
|
||||
x509::store::X509StoreBuilder,
|
||||
};
|
||||
#[cfg(feature = "native-tls")]
|
||||
use native_tls::{Protocol, TlsConnector};
|
||||
#[cfg(feature = "rustls")]
|
||||
use rustls::{
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
||||
pki_types::{self, pem::PemObject, CertificateDer, PrivateKeyDer, ServerName, UnixTime},
|
||||
server::ParsedCertificate,
|
||||
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
||||
};
|
||||
|
||||
use super::TlsBackend;
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
use crate::transport::smtp::{error, Error};
|
||||
use crate::transport::smtp::error;
|
||||
use crate::transport::smtp::Error;
|
||||
|
||||
/// TLS protocol versions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -65,6 +48,16 @@ pub enum TlsVersion {
|
||||
/// connecting to a local server.
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `Tls` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub enum Tls {
|
||||
/// Insecure (plaintext) connection only.
|
||||
///
|
||||
@@ -138,14 +131,25 @@ impl Debug for Tls {
|
||||
/// Source for the base set of root certificates to trust.
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `CertificateStore` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub enum CertificateStore {
|
||||
/// Use the default for the TLS backend.
|
||||
///
|
||||
/// For native-tls, this will use the system certificate store on Windows, the keychain on
|
||||
/// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
|
||||
///
|
||||
/// For rustls, this will also use the system store if the `rustls-native-certs` feature is
|
||||
/// enabled, or will fall back to `webpki-roots`.
|
||||
/// For rustls, this will use the system certificate verifier if the `rustls-platform-verifier`
|
||||
/// feature is enabled. If the `rustls-native-certs` feature is enabled, system certificate
|
||||
/// store will be used. Otherwise, it will fall back to `webpki-roots`.
|
||||
///
|
||||
/// The boring-tls backend uses the same logic as OpenSSL on all platforms.
|
||||
#[default]
|
||||
@@ -161,16 +165,32 @@ pub enum CertificateStore {
|
||||
|
||||
/// Parameters to use for secure clients
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `TlsParameters` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub struct TlsParameters {
|
||||
pub(crate) connector: InnerTlsParameters,
|
||||
/// The domain name which is expected in the TLS certificate from the server
|
||||
pub(super) domain: String,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
pub(super) accept_invalid_hostnames: bool,
|
||||
pub(in crate::transport::smtp) inner: InnerTlsParameters,
|
||||
}
|
||||
|
||||
/// Builder for `TlsParameters`
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `TlsParametersBuilder` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub struct TlsParametersBuilder {
|
||||
domain: String,
|
||||
cert_store: CertificateStore,
|
||||
@@ -221,6 +241,8 @@ impl TlsParametersBuilder {
|
||||
|
||||
/// Controls whether certificates with an invalid hostname are accepted
|
||||
///
|
||||
/// This option is silently disabled when using `rustls-platform-verifier`.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
///
|
||||
/// # Warning
|
||||
@@ -244,6 +266,10 @@ impl TlsParametersBuilder {
|
||||
///
|
||||
/// Defaults to [`Tlsv12`][TlsVersion::Tlsv12].
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn set_min_tls_version(mut self, min_tls_version: TlsVersion) -> Self {
|
||||
self.min_tls_version = min_tls_version;
|
||||
self
|
||||
@@ -290,30 +316,20 @@ impl TlsParametersBuilder {
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
pub fn build_native(self) -> Result<TlsParameters, Error> {
|
||||
let mut tls_builder = TlsConnector::builder();
|
||||
|
||||
match self.cert_store {
|
||||
CertificateStore::Default => {}
|
||||
CertificateStore::None => {
|
||||
tls_builder.disable_built_in_roots(true);
|
||||
}
|
||||
let cert_store = match self.cert_store {
|
||||
CertificateStore::Default => super::native_tls::CertificateStore::System,
|
||||
CertificateStore::None => super::native_tls::CertificateStore::None,
|
||||
#[allow(unreachable_patterns)]
|
||||
other => {
|
||||
return Err(error::tls(format!(
|
||||
"{other:?} is not supported in native tls"
|
||||
)))
|
||||
}
|
||||
}
|
||||
for cert in self.root_certs {
|
||||
tls_builder.add_root_certificate(cert.native_tls);
|
||||
}
|
||||
tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
|
||||
tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
|
||||
|
||||
};
|
||||
let min_tls_version = match self.min_tls_version {
|
||||
TlsVersion::Tlsv10 => Protocol::Tlsv10,
|
||||
TlsVersion::Tlsv11 => Protocol::Tlsv11,
|
||||
TlsVersion::Tlsv12 => Protocol::Tlsv12,
|
||||
TlsVersion::Tlsv10 => super::native_tls::MinTlsVersion::Tlsv10,
|
||||
TlsVersion::Tlsv11 => super::native_tls::MinTlsVersion::Tlsv11,
|
||||
TlsVersion::Tlsv12 => super::native_tls::MinTlsVersion::Tlsv12,
|
||||
TlsVersion::Tlsv13 => {
|
||||
return Err(error::tls(
|
||||
"min tls version Tlsv13 not supported in native tls",
|
||||
@@ -321,182 +337,114 @@ impl TlsParametersBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||
let mut builder = super::TlsParametersBuilder::<super::NativeTls>::new(self.domain)
|
||||
.certificate_store(cert_store)
|
||||
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||
.min_tls_version(min_tls_version);
|
||||
for cert in self.root_certs {
|
||||
builder = builder.add_root_certificate(cert.native_tls);
|
||||
}
|
||||
if let Some(identity) = self.identity {
|
||||
tls_builder.identity(identity.native_tls);
|
||||
builder = builder.identify_with(identity.native_tls);
|
||||
}
|
||||
|
||||
let connector = tls_builder.build().map_err(error::tls)?;
|
||||
Ok(TlsParameters {
|
||||
connector: InnerTlsParameters::NativeTls(connector),
|
||||
domain: self.domain,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||
})
|
||||
builder
|
||||
.build()
|
||||
.map(super::NativeTls::__build_current_tls_parameters)
|
||||
}
|
||||
|
||||
/// Creates a new `TlsParameters` using boring-tls with the provided configuration
|
||||
///
|
||||
/// Warning: this uses the certificate store passed via `certificate_store`
|
||||
/// instead of the one configured in [`TlsParametersBuilder::certificate_store`].
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn build_boring(self) -> Result<TlsParameters, Error> {
|
||||
use boring::ssl::{SslMethod, SslVerifyMode};
|
||||
|
||||
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
|
||||
|
||||
if self.accept_invalid_certs {
|
||||
tls_builder.set_verify(SslVerifyMode::NONE);
|
||||
} else {
|
||||
match self.cert_store {
|
||||
CertificateStore::Default => {}
|
||||
CertificateStore::None => {
|
||||
// Replace the default store with an empty store.
|
||||
tls_builder
|
||||
.set_cert_store(X509StoreBuilder::new().map_err(error::tls)?.build());
|
||||
}
|
||||
#[allow(unreachable_patterns)]
|
||||
other => {
|
||||
return Err(error::tls(format!(
|
||||
"{other:?} is not supported in boring tls"
|
||||
)))
|
||||
}
|
||||
let cert_store = match self.cert_store {
|
||||
CertificateStore::Default => super::boring_tls::CertificateStore::System,
|
||||
CertificateStore::None => super::boring_tls::CertificateStore::None,
|
||||
#[allow(unreachable_patterns)]
|
||||
other => {
|
||||
return Err(error::tls(format!(
|
||||
"{other:?} is not supported in native tls"
|
||||
)))
|
||||
}
|
||||
|
||||
let cert_store = tls_builder.cert_store_mut();
|
||||
|
||||
for cert in self.root_certs {
|
||||
cert_store.add_cert(cert.boring_tls).map_err(error::tls)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(identity) = self.identity {
|
||||
tls_builder
|
||||
.set_certificate(identity.boring_tls.0.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
tls_builder
|
||||
.set_private_key(identity.boring_tls.1.as_ref())
|
||||
.map_err(error::tls)?;
|
||||
}
|
||||
|
||||
};
|
||||
let min_tls_version = match self.min_tls_version {
|
||||
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||
TlsVersion::Tlsv12 => SslVersion::TLS1_2,
|
||||
TlsVersion::Tlsv13 => SslVersion::TLS1_3,
|
||||
TlsVersion::Tlsv10 => super::boring_tls::MinTlsVersion::Tlsv10,
|
||||
TlsVersion::Tlsv11 => super::boring_tls::MinTlsVersion::Tlsv11,
|
||||
TlsVersion::Tlsv12 => super::boring_tls::MinTlsVersion::Tlsv12,
|
||||
TlsVersion::Tlsv13 => super::boring_tls::MinTlsVersion::Tlsv13,
|
||||
};
|
||||
|
||||
tls_builder
|
||||
.set_min_proto_version(Some(min_tls_version))
|
||||
.map_err(error::tls)?;
|
||||
let connector = tls_builder.build();
|
||||
Ok(TlsParameters {
|
||||
connector: InnerTlsParameters::BoringTls(connector),
|
||||
domain: self.domain,
|
||||
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||
})
|
||||
let mut builder = super::TlsParametersBuilder::<super::BoringTls>::new(self.domain)
|
||||
.certificate_store(cert_store)
|
||||
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||
.min_tls_version(min_tls_version);
|
||||
for cert in self.root_certs {
|
||||
builder = builder.add_root_certificate(cert.boring_tls);
|
||||
}
|
||||
if let Some(identity) = self.identity {
|
||||
builder = builder.identify_with(identity.boring_tls);
|
||||
}
|
||||
|
||||
builder
|
||||
.build()
|
||||
.map(super::BoringTls::__build_current_tls_parameters)
|
||||
}
|
||||
|
||||
/// Creates a new `TlsParameters` using rustls with the provided configuration
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
||||
let just_version3 = &[&rustls::version::TLS13];
|
||||
let supported_versions = match self.min_tls_version {
|
||||
let cert_store = match self.cert_store {
|
||||
CertificateStore::Default => super::rustls::CertificateStore::default(),
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
CertificateStore::WebpkiRoots => super::rustls::CertificateStore::WebpkiRoots,
|
||||
CertificateStore::None => super::rustls::CertificateStore::None,
|
||||
};
|
||||
let min_tls_version = match self.min_tls_version {
|
||||
TlsVersion::Tlsv10 => {
|
||||
return Err(error::tls("min tls version Tlsv10 not supported in rustls"))
|
||||
}
|
||||
TlsVersion::Tlsv11 => {
|
||||
return Err(error::tls("min tls version Tlsv11 not supported in rustls"))
|
||||
}
|
||||
TlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
|
||||
TlsVersion::Tlsv13 => just_version3,
|
||||
TlsVersion::Tlsv12 => super::rustls::MinTlsVersion::Tlsv12,
|
||||
TlsVersion::Tlsv13 => super::rustls::MinTlsVersion::Tlsv13,
|
||||
};
|
||||
|
||||
let crypto_provider = crate::rustls_crypto::crypto_provider();
|
||||
let tls = ClientConfig::builder_with_provider(Arc::clone(&crypto_provider))
|
||||
.with_protocol_versions(supported_versions)
|
||||
.map_err(error::tls)?;
|
||||
|
||||
// Build TLS config
|
||||
let mut root_cert_store = RootCertStore::empty();
|
||||
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
fn load_native_roots(store: &mut RootCertStore) {
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||
rustls_native_certs::load_native_certs();
|
||||
let errors_len = errors.len();
|
||||
|
||||
let (added, ignored) = store.add_parsable_certificates(certs);
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!(
|
||||
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
||||
);
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
let _ = (errors_len, added, ignored);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rustls", feature = "webpki-roots"))]
|
||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
||||
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
}
|
||||
|
||||
match self.cert_store {
|
||||
CertificateStore::Default => {
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
load_native_roots(&mut root_cert_store);
|
||||
#[cfg(all(not(feature = "rustls-native-certs"), feature = "webpki-roots"))]
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
#[cfg(all(feature = "rustls", feature = "webpki-roots"))]
|
||||
CertificateStore::WebpkiRoots => {
|
||||
load_webpki_roots(&mut root_cert_store);
|
||||
}
|
||||
CertificateStore::None => {}
|
||||
}
|
||||
let mut builder = super::TlsParametersBuilder::<super::Rustls>::new(self.domain)
|
||||
.certificate_store(cert_store)
|
||||
.dangerous_accept_invalid_certs(self.accept_invalid_certs)
|
||||
.dangerous_accept_invalid_hostnames(self.accept_invalid_hostnames)
|
||||
.min_tls_version(min_tls_version);
|
||||
for cert in self.root_certs {
|
||||
for rustls_cert in cert.rustls {
|
||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
||||
for cert in cert.rustls {
|
||||
builder = builder.add_root_certificate(cert);
|
||||
}
|
||||
}
|
||||
if let Some(identity) = self.identity {
|
||||
builder = builder.identify_with(identity.rustls_tls);
|
||||
}
|
||||
|
||||
let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames {
|
||||
let verifier = InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: self.accept_invalid_hostnames,
|
||||
ignore_invalid_certs: self.accept_invalid_certs,
|
||||
roots: root_cert_store,
|
||||
crypto_provider,
|
||||
};
|
||||
tls.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
} else {
|
||||
tls.with_root_certificates(root_cert_store)
|
||||
};
|
||||
|
||||
let tls = if let Some(identity) = self.identity {
|
||||
let (client_certificates, private_key) = identity.rustls_tls;
|
||||
tls.with_client_auth_cert(client_certificates, private_key)
|
||||
.map_err(error::tls)?
|
||||
} else {
|
||||
tls.with_no_client_auth()
|
||||
};
|
||||
|
||||
Ok(TlsParameters {
|
||||
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
||||
domain: self.domain,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||
})
|
||||
builder
|
||||
.build()
|
||||
.map(super::Rustls::__build_current_tls_parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum InnerTlsParameters {
|
||||
pub(in crate::transport::smtp) enum InnerTlsParameters {
|
||||
#[cfg(feature = "native-tls")]
|
||||
NativeTls(TlsConnector),
|
||||
NativeTls(super::TlsParameters<super::NativeTls>),
|
||||
#[cfg(feature = "rustls")]
|
||||
RustlsTls(Arc<ClientConfig>),
|
||||
Rustls(super::TlsParameters<super::Rustls>),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
BoringTls(SslConnector),
|
||||
BoringTls(super::TlsParameters<super::BoringTls>),
|
||||
}
|
||||
|
||||
impl TlsParameters {
|
||||
@@ -508,7 +456,7 @@ impl TlsParameters {
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub fn new(domain: String) -> Result<Self, Error> {
|
||||
TlsParametersBuilder::new(domain).build()
|
||||
Self::new_with::<super::DefaultTlsBackend>(domain)
|
||||
}
|
||||
|
||||
/// Creates a new `TlsParameters` builder
|
||||
@@ -520,82 +468,86 @@ impl TlsParameters {
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
pub fn new_native(domain: String) -> Result<Self, Error> {
|
||||
TlsParametersBuilder::new(domain).build_native()
|
||||
Self::new_with::<super::NativeTls>(domain)
|
||||
}
|
||||
|
||||
/// Creates a new `TlsParameters` using rustls
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub fn new_rustls(domain: String) -> Result<Self, Error> {
|
||||
TlsParametersBuilder::new(domain).build_rustls()
|
||||
Self::new_with::<super::Rustls>(domain)
|
||||
}
|
||||
|
||||
/// Creates a new `TlsParameters` using boring
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub fn new_boring(domain: String) -> Result<Self, Error> {
|
||||
TlsParametersBuilder::new(domain).build_boring()
|
||||
Self::new_with::<super::BoringTls>(domain)
|
||||
}
|
||||
|
||||
fn new_with<B: TlsBackend>(domain: String) -> Result<Self, Error> {
|
||||
super::TlsParametersBuilder::<B>::new(domain)
|
||||
.build()
|
||||
.map(B::__build_current_tls_parameters)
|
||||
}
|
||||
|
||||
pub fn domain(&self) -> &str {
|
||||
&self.domain
|
||||
match self.inner {
|
||||
#[cfg(feature = "native-tls")]
|
||||
InnerTlsParameters::NativeTls(ref inner) => &inner.server_name,
|
||||
#[cfg(feature = "rustls")]
|
||||
InnerTlsParameters::Rustls(ref inner) => inner.server_name.as_ref(),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
InnerTlsParameters::BoringTls(ref inner) => &inner.server_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `Certificate` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub struct Certificate {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls::Certificate,
|
||||
native_tls: super::native_tls::Certificate,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls: Vec<CertificateDer<'static>>,
|
||||
rustls: Vec<super::rustls::Certificate>,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: boring::x509::X509,
|
||||
boring_tls: super::boring_tls::Certificate,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
impl Certificate {
|
||||
/// Create a `Certificate` from a DER encoded certificate
|
||||
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
|
||||
#[cfg(feature = "native-tls")]
|
||||
let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
let boring_tls_cert = boring::x509::X509::from_der(&der).map_err(error::tls)?;
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls_cert,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls: vec![der.into()],
|
||||
native_tls: super::native_tls::Certificate::from_der(&der)?,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: boring_tls_cert,
|
||||
boring_tls: super::boring_tls::Certificate::from_der(&der)?,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls: vec![super::rustls::Certificate::from_der(der)],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a `Certificate` from a PEM encoded certificate
|
||||
pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||
#[cfg(feature = "native-tls")]
|
||||
let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
let boring_tls_cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
let rustls_cert = {
|
||||
CertificateDer::pem_slice_iter(pem)
|
||||
.collect::<Result<Vec<_>, pki_types::pem::Error>>()
|
||||
.map_err(|_| error::tls("invalid certificates"))?
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls_cert,
|
||||
native_tls: super::native_tls::Certificate::from_pem(pem)?,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls: rustls_cert,
|
||||
rustls: super::rustls::Certificate::from_pem_bundle(pem)?,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: boring_tls_cert,
|
||||
boring_tls: super::boring_tls::Certificate::from_pem(pem)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -607,14 +559,25 @@ impl Debug for Certificate {
|
||||
}
|
||||
|
||||
/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[cfg_attr(
|
||||
not(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")),
|
||||
deprecated(
|
||||
note = "starting from lettre v0.12 `Identity` won't be available when none of the TLS backends are enabled"
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls")))
|
||||
)]
|
||||
pub struct Identity {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: native_tls::Identity,
|
||||
native_tls: super::native_tls::Identity,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls_tls: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
|
||||
rustls_tls: super::rustls::Identity,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: (boring::x509::X509, PKey<boring::pkey::Private>),
|
||||
boring_tls: super::boring_tls::Identity,
|
||||
}
|
||||
|
||||
impl Debug for Identity {
|
||||
@@ -623,132 +586,16 @@ impl Debug for Identity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Identity {
|
||||
fn clone(&self) -> Self {
|
||||
Identity {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: self.native_tls.clone(),
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls_tls: (self.rustls_tls.0.clone(), self.rustls_tls.1.clone_key()),
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: (self.boring_tls.0.clone(), self.boring_tls.1.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
impl Identity {
|
||||
pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
#[cfg(feature = "native-tls")]
|
||||
native_tls: Identity::from_pem_native_tls(pem, key)?,
|
||||
native_tls: super::native_tls::Identity::from_pem(pem, key)?,
|
||||
#[cfg(feature = "rustls")]
|
||||
rustls_tls: Identity::from_pem_rustls_tls(pem, key)?,
|
||||
rustls_tls: super::rustls::Identity::from_pem(pem, key)?,
|
||||
#[cfg(feature = "boring-tls")]
|
||||
boring_tls: Identity::from_pem_boring_tls(pem, key)?,
|
||||
boring_tls: super::boring_tls::Identity::from_pem(pem, key)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
fn from_pem_native_tls(pem: &[u8], key: &[u8]) -> Result<native_tls::Identity, Error> {
|
||||
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
fn from_pem_rustls_tls(
|
||||
pem: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
|
||||
let key = match PrivateKeyDer::from_pem_slice(key) {
|
||||
Ok(key) => key,
|
||||
Err(pki_types::pem::Error::NoItemsFound) => {
|
||||
return Err(error::tls("no private key found"))
|
||||
}
|
||||
Err(err) => return Err(error::tls(err)),
|
||||
};
|
||||
|
||||
Ok((vec![pem.to_owned().into()], key))
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
fn from_pem_boring_tls(
|
||||
pem: &[u8],
|
||||
key: &[u8],
|
||||
) -> Result<(boring::x509::X509, PKey<boring::pkey::Private>), Error> {
|
||||
let cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
||||
Ok((cert, key))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[derive(Debug)]
|
||||
struct InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: bool,
|
||||
ignore_invalid_certs: bool,
|
||||
roots: RootCertStore,
|
||||
crypto_provider: Arc<CryptoProvider>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer<'_>,
|
||||
intermediates: &[CertificateDer<'_>],
|
||||
server_name: &ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
now: UnixTime,
|
||||
) -> Result<ServerCertVerified, TlsError> {
|
||||
let cert = ParsedCertificate::try_from(end_entity)?;
|
||||
|
||||
if !self.ignore_invalid_certs {
|
||||
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
||||
&cert,
|
||||
&self.roots,
|
||||
intermediates,
|
||||
now,
|
||||
self.crypto_provider.signature_verification_algorithms.all,
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.ignore_invalid_hostnames {
|
||||
rustls::client::verify_server_name(&cert, server_name)?;
|
||||
}
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
||||
verify_tls12_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.crypto_provider.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, TlsError> {
|
||||
verify_tls13_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.crypto_provider.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
self.crypto_provider
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
}
|
||||
}
|
||||
253
src/transport/smtp/client/tls/mod.rs
Normal file
253
src/transport/smtp/client/tls/mod.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use crate::transport::smtp::Error;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
pub(super) mod boring_tls;
|
||||
pub(super) mod current;
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
pub(super) mod native_tls;
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
pub(super) mod rustls;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(private_bounds)]
|
||||
pub(in crate::transport::smtp) struct TlsParameters<B: TlsBackend> {
|
||||
pub(in crate::transport::smtp) server_name: B::ServerName,
|
||||
pub(in crate::transport::smtp) connector: B::Connector,
|
||||
pub(in crate::transport::smtp) extra_info: B::ExtraInfo,
|
||||
}
|
||||
|
||||
impl<B: TlsBackend> Clone for TlsParameters<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
server_name: self.server_name.clone(),
|
||||
connector: self.connector.clone(),
|
||||
extra_info: self.extra_info.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TlsParametersBuilder<B: TlsBackend> {
|
||||
server_name: String,
|
||||
cert_store: B::CertificateStore,
|
||||
root_certs: Vec<B::Certificate>,
|
||||
identity: Option<B::Identity>,
|
||||
accept_invalid_certs: bool,
|
||||
accept_invalid_hostnames: bool,
|
||||
min_tls_version: B::MinTlsVersion,
|
||||
}
|
||||
|
||||
impl<B: TlsBackend> TlsParametersBuilder<B> {
|
||||
fn new(server_name: String) -> Self {
|
||||
Self {
|
||||
server_name,
|
||||
cert_store: Default::default(),
|
||||
root_certs: Vec::new(),
|
||||
identity: None,
|
||||
accept_invalid_certs: false,
|
||||
accept_invalid_hostnames: false,
|
||||
min_tls_version: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn certificate_store(mut self, cert_store: B::CertificateStore) -> Self {
|
||||
self.cert_store = cert_store;
|
||||
self
|
||||
}
|
||||
|
||||
fn add_root_certificate(mut self, cert: B::Certificate) -> Self {
|
||||
self.root_certs.push(cert);
|
||||
self
|
||||
}
|
||||
|
||||
fn identify_with(mut self, identity: B::Identity) -> Self {
|
||||
self.identity = Some(identity);
|
||||
self
|
||||
}
|
||||
|
||||
fn min_tls_version(mut self, min_tls_version: B::MinTlsVersion) -> Self {
|
||||
self.min_tls_version = min_tls_version;
|
||||
self
|
||||
}
|
||||
|
||||
fn dangerous_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
|
||||
self.accept_invalid_certs = accept_invalid_certs;
|
||||
self
|
||||
}
|
||||
|
||||
fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
||||
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
||||
self
|
||||
}
|
||||
|
||||
fn build(self) -> Result<TlsParameters<B>, Error> {
|
||||
B::__build_connector(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
trait TlsBackend: private::SealedTlsBackend {
|
||||
type CertificateStore: Default;
|
||||
type Certificate;
|
||||
type Identity;
|
||||
type MinTlsVersion: Default;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn __build_connector(builder: TlsParametersBuilder<Self>)
|
||||
-> Result<TlsParameters<Self>, Error>;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters;
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
type DefaultTlsBackend = NativeTls;
|
||||
|
||||
#[cfg(all(feature = "rustls", not(feature = "native-tls")))]
|
||||
type DefaultTlsBackend = Rustls;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "boring-tls",
|
||||
not(feature = "native-tls"),
|
||||
not(feature = "rustls")
|
||||
))]
|
||||
type DefaultTlsBackend = BoringTls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(in crate::transport::smtp) struct NativeTls;
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
||||
impl TlsBackend for NativeTls {
|
||||
type CertificateStore = self::native_tls::CertificateStore;
|
||||
type Certificate = self::native_tls::Certificate;
|
||||
type Identity = self::native_tls::Identity;
|
||||
type MinTlsVersion = self::native_tls::MinTlsVersion;
|
||||
|
||||
fn __build_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<TlsParameters<Self>, Error> {
|
||||
self::native_tls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||
server_name,
|
||||
connector,
|
||||
extra_info: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||
self::current::TlsParameters {
|
||||
inner: self::current::InnerTlsParameters::NativeTls(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(in crate::transport::smtp) struct Rustls;
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
|
||||
impl TlsBackend for Rustls {
|
||||
type CertificateStore = self::rustls::CertificateStore;
|
||||
type Certificate = self::rustls::Certificate;
|
||||
type Identity = self::rustls::Identity;
|
||||
type MinTlsVersion = self::rustls::MinTlsVersion;
|
||||
|
||||
fn __build_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<TlsParameters<Self>, Error> {
|
||||
self::rustls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||
server_name,
|
||||
connector,
|
||||
extra_info: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||
self::current::TlsParameters {
|
||||
inner: self::current::InnerTlsParameters::Rustls(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(in crate::transport::smtp) struct BoringTls;
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||
impl TlsBackend for BoringTls {
|
||||
type CertificateStore = self::boring_tls::CertificateStore;
|
||||
type Certificate = self::boring_tls::Certificate;
|
||||
type Identity = self::boring_tls::Identity;
|
||||
type MinTlsVersion = self::boring_tls::MinTlsVersion;
|
||||
|
||||
fn __build_connector(
|
||||
builder: TlsParametersBuilder<Self>,
|
||||
) -> Result<TlsParameters<Self>, Error> {
|
||||
let accept_invalid_hostnames = builder.accept_invalid_hostnames;
|
||||
self::boring_tls::build_connector(builder).map(|(server_name, connector)| TlsParameters {
|
||||
server_name,
|
||||
connector,
|
||||
extra_info: BoringTlsExtraInfo {
|
||||
accept_invalid_hostnames,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn __build_current_tls_parameters(inner: TlsParameters<Self>) -> self::current::TlsParameters {
|
||||
self::current::TlsParameters {
|
||||
inner: self::current::InnerTlsParameters::BoringTls(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::transport::smtp) struct BoringTlsExtraInfo {
|
||||
pub(super) accept_invalid_hostnames: bool,
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub(in crate::transport::smtp) trait SealedTlsBackend:
|
||||
Sized
|
||||
{
|
||||
type ServerName: Clone + AsRef<str>;
|
||||
type Connector: Clone;
|
||||
type ExtraInfo: Clone;
|
||||
}
|
||||
|
||||
#[cfg(feature = "native-tls")]
|
||||
impl SealedTlsBackend for super::NativeTls {
|
||||
type ServerName = Box<str>;
|
||||
type Connector = native_tls::TlsConnector;
|
||||
type ExtraInfo = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls")]
|
||||
impl SealedTlsBackend for super::Rustls {
|
||||
type ServerName = super::rustls::ServerName;
|
||||
type Connector = std::sync::Arc<rustls::client::ClientConfig>;
|
||||
type ExtraInfo = ();
|
||||
}
|
||||
|
||||
#[cfg(feature = "boring-tls")]
|
||||
impl SealedTlsBackend for super::BoringTls {
|
||||
type ServerName = Box<str>;
|
||||
type Connector = boring::ssl::SslConnector;
|
||||
type ExtraInfo = super::BoringTlsExtraInfo;
|
||||
}
|
||||
}
|
||||
95
src/transport/smtp/client/tls/native_tls.rs
Normal file
95
src/transport/smtp/client/tls/native_tls.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
use native_tls::TlsConnector;
|
||||
|
||||
use crate::transport::smtp::error::{self, Error};
|
||||
|
||||
pub(super) fn build_connector(
|
||||
builder: super::TlsParametersBuilder<super::NativeTls>,
|
||||
) -> Result<(Box<str>, TlsConnector), Error> {
|
||||
let mut tls_builder = TlsConnector::builder();
|
||||
|
||||
match builder.cert_store {
|
||||
CertificateStore::System => {}
|
||||
CertificateStore::None => {
|
||||
tls_builder.disable_built_in_roots(true);
|
||||
}
|
||||
}
|
||||
for cert in builder.root_certs {
|
||||
tls_builder.add_root_certificate(cert.0);
|
||||
}
|
||||
tls_builder.danger_accept_invalid_hostnames(builder.accept_invalid_hostnames);
|
||||
tls_builder.danger_accept_invalid_certs(builder.accept_invalid_certs);
|
||||
|
||||
let min_tls_version = match builder.min_tls_version {
|
||||
MinTlsVersion::Tlsv10 => native_tls::Protocol::Tlsv10,
|
||||
MinTlsVersion::Tlsv11 => native_tls::Protocol::Tlsv11,
|
||||
MinTlsVersion::Tlsv12 => native_tls::Protocol::Tlsv12,
|
||||
};
|
||||
|
||||
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||
if let Some(identity) = builder.identity {
|
||||
tls_builder.identity(identity.0);
|
||||
}
|
||||
|
||||
let connector = tls_builder.build().map_err(error::tls)?;
|
||||
Ok((builder.server_name.into_boxed_str(), connector))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum CertificateStore {
|
||||
#[default]
|
||||
System,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Certificate(pub(super) native_tls::Certificate);
|
||||
|
||||
impl Certificate {
|
||||
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self(
|
||||
native_tls::Certificate::from_pem(pem).map_err(error::tls)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn from_der(der: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self(
|
||||
native_tls::Certificate::from_der(der).map_err(error::tls)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Certificate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Identity(pub(super) native_tls::Identity);
|
||||
|
||||
impl Identity {
|
||||
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self(
|
||||
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Identity").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum MinTlsVersion {
|
||||
Tlsv10,
|
||||
Tlsv11,
|
||||
#[default]
|
||||
Tlsv12,
|
||||
}
|
||||
329
src/transport/smtp/client/tls/rustls.rs
Normal file
329
src/transport/smtp/client/tls/rustls.rs
Normal file
@@ -0,0 +1,329 @@
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use rustls::{
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
||||
pki_types::{self, UnixTime},
|
||||
server::ParsedCertificate,
|
||||
ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||
};
|
||||
|
||||
use crate::transport::smtp::error::{self, Error};
|
||||
|
||||
pub(super) fn build_connector(
|
||||
builder: super::TlsParametersBuilder<super::Rustls>,
|
||||
) -> Result<(ServerName, Arc<ClientConfig>), Error> {
|
||||
let just_version3 = &[&rustls::version::TLS13];
|
||||
let supported_versions = match builder.min_tls_version {
|
||||
MinTlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
|
||||
MinTlsVersion::Tlsv13 => just_version3,
|
||||
};
|
||||
|
||||
let crypto_provider = crate::rustls_crypto::crypto_provider();
|
||||
let tls = ClientConfig::builder_with_provider(Arc::clone(&crypto_provider))
|
||||
.with_protocol_versions(supported_versions)
|
||||
.map_err(error::tls)?;
|
||||
|
||||
// Build TLS config
|
||||
let mut root_cert_store = RootCertStore::empty();
|
||||
#[cfg(feature = "rustls-platform-verifier")]
|
||||
let mut extra_roots = Vec::new();
|
||||
|
||||
match builder.cert_store {
|
||||
#[cfg(feature = "rustls-platform-verifier")]
|
||||
CertificateStore::PlatformVerifier => {
|
||||
extra_roots = builder
|
||||
.root_certs
|
||||
.iter()
|
||||
.map(|cert| cert.0.clone())
|
||||
.collect();
|
||||
}
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
CertificateStore::NativeCerts => {
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||
rustls_native_certs::load_native_certs();
|
||||
let errors_len = errors.len();
|
||||
|
||||
let (added, ignored) = root_cert_store.add_parsable_certificates(certs);
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::debug!(
|
||||
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
||||
);
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
let _ = (errors_len, added, ignored);
|
||||
}
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
CertificateStore::WebpkiRoots => {
|
||||
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
}
|
||||
CertificateStore::None => {}
|
||||
}
|
||||
for cert in builder.root_certs {
|
||||
root_cert_store.add(cert.0).map_err(error::tls)?;
|
||||
}
|
||||
|
||||
let tls = match (
|
||||
builder.cert_store,
|
||||
builder.accept_invalid_certs,
|
||||
builder.accept_invalid_hostnames,
|
||||
) {
|
||||
#[cfg(feature = "rustls-platform-verifier")]
|
||||
(CertificateStore::PlatformVerifier, false, _) => {
|
||||
tls.dangerous().with_custom_certificate_verifier(Arc::new(
|
||||
rustls_platform_verifier::Verifier::new_with_extra_roots(
|
||||
extra_roots,
|
||||
crypto_provider,
|
||||
)
|
||||
.map_err(error::tls)?,
|
||||
))
|
||||
}
|
||||
(_, true, _) | (_, _, true) => {
|
||||
let verifier = InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: builder.accept_invalid_hostnames,
|
||||
ignore_invalid_certs: builder.accept_invalid_certs,
|
||||
roots: root_cert_store,
|
||||
crypto_provider,
|
||||
};
|
||||
tls.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||
}
|
||||
_ => tls.with_root_certificates(root_cert_store),
|
||||
};
|
||||
|
||||
let tls = if let Some(identity) = builder.identity {
|
||||
tls.with_client_auth_cert(identity.chain, identity.key)
|
||||
.map_err(error::tls)?
|
||||
} else {
|
||||
tls.with_no_client_auth()
|
||||
};
|
||||
let server_name = ServerName::try_from(builder.server_name)?;
|
||||
Ok((server_name, Arc::new(tls)))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(in crate::transport::smtp) struct ServerName {
|
||||
val: pki_types::ServerName<'static>,
|
||||
str_val: Box<str>,
|
||||
}
|
||||
|
||||
impl ServerName {
|
||||
#[allow(dead_code)]
|
||||
pub(in crate::transport::smtp) fn inner(self) -> pki_types::ServerName<'static> {
|
||||
self.val
|
||||
}
|
||||
|
||||
pub(in crate::transport::smtp) fn inner_ref(&self) -> &pki_types::ServerName<'static> {
|
||||
&self.val
|
||||
}
|
||||
|
||||
fn try_from(value: String) -> Result<Self, crate::transport::smtp::Error> {
|
||||
let val: pki_types::ServerName<'_> = value
|
||||
.as_str()
|
||||
.try_into()
|
||||
.map_err(crate::transport::smtp::error::tls)?;
|
||||
Ok(Self {
|
||||
val: val.to_owned(),
|
||||
str_val: value.into_boxed_str(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ServerName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.str_val
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[allow(dead_code, missing_copy_implementations)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum CertificateStore {
|
||||
#[cfg(feature = "rustls-platform-verifier")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-platform-verifier")))]
|
||||
#[cfg_attr(feature = "rustls-platform-verifier", default)]
|
||||
PlatformVerifier,
|
||||
#[cfg(feature = "rustls-native-certs")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
|
||||
#[cfg_attr(
|
||||
all(
|
||||
not(feature = "rustls-platform-verifier"),
|
||||
feature = "rustls-native-certs",
|
||||
),
|
||||
default
|
||||
)]
|
||||
NativeCerts,
|
||||
#[cfg(feature = "webpki-roots")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
|
||||
#[cfg_attr(
|
||||
all(
|
||||
not(feature = "rustls-platform-verifier"),
|
||||
not(feature = "rustls-native-certs"),
|
||||
feature = "webpki-roots",
|
||||
),
|
||||
default
|
||||
)]
|
||||
WebpkiRoots,
|
||||
#[cfg_attr(
|
||||
all(
|
||||
not(feature = "webpki-roots"),
|
||||
not(feature = "rustls-platform-verifier"),
|
||||
not(feature = "rustls-native-certs")
|
||||
),
|
||||
default
|
||||
)]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Certificate(pub(super) pki_types::CertificateDer<'static>);
|
||||
|
||||
impl Certificate {
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn from_pem(pem: &[u8]) -> Result<Self, Error> {
|
||||
use rustls::pki_types::pem::PemObject as _;
|
||||
|
||||
Ok(Self(
|
||||
pki_types::CertificateDer::from_pem_slice(pem)
|
||||
.map_err(|_| error::tls("invalid certificate"))?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn from_pem_bundle(pem: &[u8]) -> Result<Vec<Self>, Error> {
|
||||
use rustls::pki_types::pem::PemObject as _;
|
||||
|
||||
pki_types::CertificateDer::pem_slice_iter(pem)
|
||||
.map(|cert| Ok(Self(cert?)))
|
||||
.collect::<Result<Vec<_>, pki_types::pem::Error>>()
|
||||
.map_err(|_| error::tls("invalid certificate"))
|
||||
}
|
||||
|
||||
pub(super) fn from_der(der: Vec<u8>) -> Self {
|
||||
Self(der.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Certificate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Certificate").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Identity {
|
||||
pub(super) chain: Vec<pki_types::CertificateDer<'static>>,
|
||||
pub(super) key: pki_types::PrivateKeyDer<'static>,
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
pub(super) fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||
use rustls::pki_types::pem::PemObject as _;
|
||||
|
||||
let key = match pki_types::PrivateKeyDer::from_pem_slice(key) {
|
||||
Ok(key) => key,
|
||||
Err(pki_types::pem::Error::NoItemsFound) => {
|
||||
return Err(error::tls("no private key found"))
|
||||
}
|
||||
Err(err) => return Err(error::tls(err)),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
chain: vec![pem.to_owned().into()],
|
||||
key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Identity {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
chain: self.chain.clone(),
|
||||
key: self.key.clone_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Identity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Identity").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub(super) enum MinTlsVersion {
|
||||
#[default]
|
||||
Tlsv12,
|
||||
Tlsv13,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InvalidCertsVerifier {
|
||||
ignore_invalid_hostnames: bool,
|
||||
ignore_invalid_certs: bool,
|
||||
roots: RootCertStore,
|
||||
crypto_provider: Arc<CryptoProvider>,
|
||||
}
|
||||
|
||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &pki_types::CertificateDer<'_>,
|
||||
intermediates: &[pki_types::CertificateDer<'_>],
|
||||
server_name: &pki_types::ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
now: UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
let cert = ParsedCertificate::try_from(end_entity)?;
|
||||
|
||||
if !self.ignore_invalid_certs {
|
||||
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
||||
&cert,
|
||||
&self.roots,
|
||||
intermediates,
|
||||
now,
|
||||
self.crypto_provider.signature_verification_algorithms.all,
|
||||
)?;
|
||||
}
|
||||
|
||||
if !self.ignore_invalid_hostnames {
|
||||
rustls::client::verify_server_name(&cert, server_name)?;
|
||||
}
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &pki_types::CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
verify_tls12_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.crypto_provider.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &pki_types::CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
verify_tls13_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&self.crypto_provider.signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
self.crypto_provider
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use super::{
|
||||
|
||||
pub(crate) trait TransportBuilder {
|
||||
fn new<T: Into<String>>(server: T) -> Self;
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
fn tls(self, tls: super::Tls) -> Self;
|
||||
fn port(self, port: u16) -> Self;
|
||||
fn credentials(self, credentials: Credentials) -> Self;
|
||||
@@ -24,6 +25,7 @@ impl TransportBuilder for SmtpTransportBuilder {
|
||||
Self::new(server)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
fn tls(self, tls: super::Tls) -> Self {
|
||||
self.tls(tls)
|
||||
}
|
||||
@@ -47,6 +49,7 @@ impl TransportBuilder for AsyncSmtpTransportBuilder {
|
||||
Self::new(server)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "native-tls", feature = "rustls", feature = "boring-tls"))]
|
||||
fn tls(self, tls: super::Tls) -> Self {
|
||||
self.tls(tls)
|
||||
}
|
||||
|
||||
@@ -149,11 +149,7 @@ impl<E: Executor> Pool<E> {
|
||||
pub(crate) async fn shutdown(&self) {
|
||||
let connections = { self.connections.lock().await.take() };
|
||||
if let Some(connections) = connections {
|
||||
stream::iter(connections)
|
||||
.for_each_concurrent(8, |conn| async move {
|
||||
conn.unpark().abort().await;
|
||||
})
|
||||
.await;
|
||||
abort_concurrent(connections.into_iter().map(ParkedConnection::unpark)).await;
|
||||
}
|
||||
|
||||
if let Some(handle) = self.handle.get() {
|
||||
|
||||
@@ -177,7 +177,7 @@ impl SmtpTransport {
|
||||
/// a proper URL encoder, like the following cargo script:
|
||||
///
|
||||
/// ```rust
|
||||
/// # let _ = r#"
|
||||
/// # const TOML: &str = r#"
|
||||
/// #!/usr/bin/env cargo
|
||||
///
|
||||
/// //! ```cargo
|
||||
|
||||
@@ -43,13 +43,11 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
sync::{Arc, Mutex as StdMutex},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
use async_trait::async_trait;
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
use futures_util::lock::Mutex as FuturesMutex;
|
||||
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
use crate::AsyncTransport;
|
||||
@@ -72,7 +70,7 @@ impl StdError for Error {}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StubTransport {
|
||||
response: Result<(), Error>,
|
||||
message_log: Arc<StdMutex<Vec<(Envelope, String)>>>,
|
||||
message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
|
||||
}
|
||||
|
||||
/// This transport logs messages and always returns the given response
|
||||
@@ -81,7 +79,7 @@ pub struct StubTransport {
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
|
||||
pub struct AsyncStubTransport {
|
||||
response: Result<(), Error>,
|
||||
message_log: Arc<FuturesMutex<Vec<(Envelope, String)>>>,
|
||||
message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
|
||||
}
|
||||
|
||||
impl StubTransport {
|
||||
@@ -89,7 +87,7 @@ impl StubTransport {
|
||||
pub fn new(response: Result<(), Error>) -> Self {
|
||||
Self {
|
||||
response,
|
||||
message_log: Arc::new(StdMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +95,7 @@ impl StubTransport {
|
||||
pub fn new_ok() -> Self {
|
||||
Self {
|
||||
response: Ok(()),
|
||||
message_log: Arc::new(StdMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +103,7 @@ impl StubTransport {
|
||||
pub fn new_error() -> Self {
|
||||
Self {
|
||||
response: Err(Error),
|
||||
message_log: Arc::new(StdMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +122,7 @@ impl AsyncStubTransport {
|
||||
pub fn new(response: Result<(), Error>) -> Self {
|
||||
Self {
|
||||
response,
|
||||
message_log: Arc::new(FuturesMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +130,7 @@ impl AsyncStubTransport {
|
||||
pub fn new_ok() -> Self {
|
||||
Self {
|
||||
response: Ok(()),
|
||||
message_log: Arc::new(FuturesMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,14 +138,14 @@ impl AsyncStubTransport {
|
||||
pub fn new_error() -> Self {
|
||||
Self {
|
||||
response: Err(Error),
|
||||
message_log: Arc::new(FuturesMutex::new(vec![])),
|
||||
message_log: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all logged messages sent using [`AsyncTransport::send_raw`]
|
||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||
pub async fn messages(&self) -> Vec<(Envelope, String)> {
|
||||
self.message_log.lock().await.clone()
|
||||
self.message_log.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +171,7 @@ impl AsyncTransport for AsyncStubTransport {
|
||||
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
self.message_log
|
||||
.lock()
|
||||
.await
|
||||
.unwrap()
|
||||
.push((envelope.clone(), String::from_utf8_lossy(email).into()));
|
||||
self.response
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user