Repository: lettre/lettre (language: rust)

Issue:
SSL routines:ssl3_get_server_certificate:certificate verify failed:s3_clnt.c:1264
**Describe the bug**
This is not a bug, this is a lettre usage issue.  I am learning lettre crate to create a standalone mail client.
How can I disable cerifitcate verification from lettre side ? 

Also once tested OK , how can I [specify local .pem file](https://stackoverflow.com/questions/17084886/ssl-error-routinesssl3-get-server-certificatecertificate-verify-failed)  ?

**Sep 23 11:33:12.509 ERROR r2d2: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed:s3_clnt.c:1264: (self signed certificate in certificate chain)**

**To Reproduce**
compile smtp_starttls.rs below.

**Expected behavior**

Send out test email using [smtp_starttls.rs ](https://github.com/lettre/lettre/blob/master/examples/smtp_starttls.rs)  via smtp.my.com that use TLS.

> // main.rs
> use tracing_subscriber;
> use lettre::{transport::smtp::authentication::Credentials, Message, SmtpTransport, Transport};
>
> fn main() {
>     tracing_subscriber::fmt::init();
> 
>     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())
>         .subject("Happy new year")
>         .body("Be happy!")
>         .unwrap();
> 
>     let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
> 
>     // Open a remote connection to gmail using STARTTLS
>     let mailer = SmtpTransport::starttls_relay("smtp.my.com")
>         .unwrap()
>         .credentials(creds)
>         .build();
> 
>     // Send the email
>     match mailer.send(&email) {
>         Ok(_) => println!("Email sent successfully!"),
>         Err(e) => panic!("Could not send email: {:?}", e),
>     }
> }


**Environment (please complete the following information):**
- Lettre version
lettre = "0.10.0-alpha.2"
- OS
CentOS Linux release 7.7.1908 (Core)
**Additional context**
Add any other context about the problem here.



Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index d0da2e0f7..1dd16e848 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -103,6 +103,10 @@ required-features = ["smtp-transport", "native-tls"]
 name = "smtp_starttls"
 required-features = ["smtp-transport", "native-tls"]
 
+[[example]]
+name = "smtp_selfsigned"
+required-features = ["smtp-transport", "native-tls"]
+
 [[example]]
 name = "tokio02_smtp_tls"
 required-features = ["smtp-transport", "tokio02", "tokio02-native-tls"]
diff --git a/examples/smtp_selfsigned.rs b/examples/smtp_selfsigned.rs
new file mode 100644
index 000000000..8cf19272e
--- /dev/null
+++ b/examples/smtp_selfsigned.rs
@@ -0,0 +1,41 @@
+use std::fs;
+
+use lettre::{
+    transport::smtp::authentication::Credentials,
+    transport::smtp::client::{Certificate, Tls, TlsParameters},
+    Message, SmtpTransport, Transport,
+};
+
+fn main() {
+    tracing_subscriber::fmt::init();
+
+    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())
+        .subject("Happy new year")
+        .body("Be happy!")
+        .unwrap();
+
+    // Use a custom certificate stored on disk to securely verify the server's certificate
+    let pem_cert = fs::read("certificate.pem").unwrap();
+    let cert = Certificate::from_pem(&pem_cert).unwrap();
+    let mut tls = TlsParameters::builder("smtp.server.com".to_string());
+    tls.add_root_certificate(cert);
+    let tls = tls.build().unwrap();
+
+    let creds = Credentials::new("smtp_username".to_string(), "smtp_password".to_string());
+
+    // Open a remote connection to the smtp server
+    let mailer = SmtpTransport::builder_dangerous("smtp.server.com")
+        .port(465)
+        .tls(Tls::Wrapper(tls))
+        .credentials(creds)
+        .build();
+
+    // Send the email
+    match mailer.send(&email) {
+        Ok(_) => println!("Email sent successfully!"),
+        Err(e) => panic!("Could not send email: {:?}", e),
+    }
+}
diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs
index 54c403f44..61c493fb2 100644
--- a/src/transport/smtp/client/mod.rs
+++ b/src/transport/smtp/client/mod.rs
@@ -34,7 +34,7 @@ pub(super) use self::tls::InnerTlsParameters;
 pub use self::{
     connection::SmtpConnection,
     mock::MockStream,
-    tls::{Tls, TlsParameters, TlsParametersBuilder},
+    tls::{Certificate, Tls, TlsParameters, TlsParametersBuilder},
 };
 
 #[cfg(feature = "tokio02")]
diff --git a/src/transport/smtp/client/tls.rs b/src/transport/smtp/client/tls.rs
index 8e9008734..0b61b66c5 100644
--- a/src/transport/smtp/client/tls.rs
+++ b/src/transport/smtp/client/tls.rs
@@ -1,18 +1,15 @@
 #[cfg(feature = "rustls-tls")]
 use std::sync::Arc;
 
-#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
-use crate::transport::smtp::error::Error;
-
 #[cfg(feature = "native-tls")]
 use native_tls::{Protocol, TlsConnector};
 #[cfg(feature = "rustls-tls")]
-use rustls::{
-    Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError,
-};
+use rustls::{ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError};
 #[cfg(feature = "rustls-tls")]
 use webpki::DNSNameRef;
 
+use crate::transport::smtp::error::Error;
+
 /// Accepted protocols by default.
 /// This removes TLS 1.0 and 1.1 compared to tls-native defaults.
 // This is also rustls' default behavior
@@ -46,9 +43,10 @@ pub struct TlsParameters {
 }
 
 /// Builder for `TlsParameters`
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct TlsParametersBuilder {
     domain: String,
+    root_certs: Vec<Certificate>,
     accept_invalid_hostnames: bool,
     accept_invalid_certs: bool,
 }
@@ -58,11 +56,20 @@ impl TlsParametersBuilder {
     pub fn new(domain: String) -> Self {
         Self {
             domain,
+            root_certs: Vec::new(),
             accept_invalid_hostnames: false,
             accept_invalid_certs: false,
         }
     }
 
+    /// Add a custom root certificate
+    ///
+    /// Can be used to safely connect to a server using a self signed certificate, for example.
+    pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut Self {
+        self.root_certs.push(cert);
+        self
+    }
+
     /// Controls whether certificates with an invalid hostname are accepted
     ///
     /// Defaults to `false`.
@@ -130,8 +137,13 @@ impl TlsParametersBuilder {
     #[cfg(feature = "native-tls")]
     pub fn build_native(self) -> Result<TlsParameters, Error> {
         let mut tls_builder = TlsConnector::builder();
+
+        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);
+
         tls_builder.min_protocol_version(Some(DEFAULT_TLS_MIN_PROTOCOL));
         let connector = tls_builder.build()?;
         Ok(TlsParameters {
@@ -146,10 +158,19 @@ impl TlsParametersBuilder {
         use webpki_roots::TLS_SERVER_ROOTS;
 
         let mut tls = ClientConfig::new();
+
+        for cert in self.root_certs {
+            for rustls_cert in cert.rustls {
+                tls.root_store
+                    .add(&rustls_cert)
+                    .map_err(|_| Error::InvalidCertificate)?;
+            }
+        }
         if self.accept_invalid_certs {
             tls.dangerous()
                 .set_certificate_verifier(Arc::new(InvalidCertsVerifier {}));
         }
+
         tls.root_store.add_server_trust_anchors(&TLS_SERVER_ROOTS);
         Ok(TlsParameters {
             connector: InnerTlsParameters::RustlsTls(tls),
@@ -195,6 +216,55 @@ impl TlsParameters {
     }
 }
 
+/// A client certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
+#[derive(Clone)]
+#[allow(missing_copy_implementations)]
+pub struct Certificate {
+    #[cfg(feature = "native-tls")]
+    native_tls: native_tls::Certificate,
+    #[cfg(feature = "rustls-tls")]
+    rustls: Vec<rustls::Certificate>,
+}
+
+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::InvalidCertificate)?;
+
+        Ok(Self {
+            #[cfg(feature = "native-tls")]
+            native_tls: native_tls_cert,
+            #[cfg(feature = "rustls-tls")]
+            rustls: vec![rustls::Certificate(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::InvalidCertificate)?;
+
+        #[cfg(feature = "rustls-tls")]
+        let rustls_cert = {
+            use rustls::internal::pemfile;
+            use std::io::Cursor;
+
+            let mut pem = Cursor::new(pem);
+            pemfile::certs(&mut pem).map_err(|_| Error::InvalidCertificate)?
+        };
+
+        Ok(Self {
+            #[cfg(feature = "native-tls")]
+            native_tls: native_tls_cert,
+            #[cfg(feature = "rustls-tls")]
+            rustls: rustls_cert,
+        })
+    }
+}
+
 #[cfg(feature = "rustls-tls")]
 struct InvalidCertsVerifier;
 
@@ -203,7 +273,7 @@ impl ServerCertVerifier for InvalidCertsVerifier {
     fn verify_server_cert(
         &self,
         _roots: &RootCertStore,
-        _presented_certs: &[Certificate],
+        _presented_certs: &[rustls::Certificate],
         _dns_name: DNSNameRef<'_>,
         _ocsp_response: &[u8],
     ) -> Result<ServerCertVerified, TLSError> {
diff --git a/src/transport/smtp/error.rs b/src/transport/smtp/error.rs
index edb311c2a..fa6bd4aa1 100644
--- a/src/transport/smtp/error.rs
+++ b/src/transport/smtp/error.rs
@@ -41,6 +41,8 @@ pub enum Error {
     /// Invalid hostname
     #[cfg(feature = "rustls-tls")]
     InvalidDNSName(webpki::InvalidDNSNameError),
+    #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
+    InvalidCertificate,
     #[cfg(feature = "r2d2")]
     Pool(r2d2::Error),
 }
@@ -69,6 +71,8 @@ impl Display for Error {
             Parsing(ref err) => fmt.write_str(err.description()),
             #[cfg(feature = "rustls-tls")]
             InvalidDNSName(ref err) => err.fmt(fmt),
+            #[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
+            InvalidCertificate => fmt.write_str("invalid certificate"),
             #[cfg(feature = "r2d2")]
             Pool(ref err) => err.fmt(fmt),
         }


Test patch: