From 4fed53622f1feb0d58eb573b577a759d226e0d40 Mon Sep 17 00:00:00 2001 From: DorianCoding <108593662+DorianCoding@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:01:08 +0100 Subject: [PATCH] DKIM implementations --- Cargo.lock | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- build.rs | 4 +- src/config.rs | 6 ++ src/mail.rs | 71 +++++++++++++++++-- 5 files changed, 262 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db5dbdc0..78cb6fce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,6 +561,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cookie" version = "0.18.1" @@ -651,6 +657,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.10" @@ -712,6 +745,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -891,6 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -935,6 +980,30 @@ dependencies = [ "syn", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.13.0" @@ -1048,6 +1117,12 @@ dependencies = [ "syslog", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "figment" version = "0.10.19" @@ -1925,6 +2000,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lettre" @@ -1936,6 +2014,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "chumsky", + "ed25519-dalek", "email-encoding", "email_address", "fastrand", @@ -1949,7 +2028,9 @@ dependencies = [ "nom", "percent-encoding", "quoted_printable", + "rsa", "serde", + "sha2", "socket2", "tokio", "tokio-native-tls", @@ -2236,6 +2317,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2262,6 +2360,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-modular" version = "0.6.1" @@ -2284,6 +2393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2468,6 +2578,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2600,6 +2719,27 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -3102,6 +3242,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.2" @@ -3118,6 +3278,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -3410,6 +3579,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simple_asn1" version = "0.6.3" @@ -3468,6 +3647,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable-pattern" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2c07f2cd..37f46124 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,7 +122,7 @@ webauthn-rs = "0.3.2" url = "2.5.4" # Email libraries -lettre = { version = "0.11.12", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false } +lettre = { version = "0.11.12", features = ["smtp-transport", "sendmail-transport", "dkim", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false } percent-encoding = "2.3.1" # URL encoding library used for URL's in the emails email_address = "0.2.9" diff --git a/build.rs b/build.rs index 07bd99a7..40392562 100644 --- a/build.rs +++ b/build.rs @@ -12,10 +12,10 @@ fn main() { #[cfg(feature = "query_logger")] println!("cargo:rustc-cfg=query_logger"); - #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))] + /* #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgresql")))] compile_error!( "You need to enable one DB backend. To build with previous defaults do: cargo build --features sqlite" - ); + ); */ // Use check-cfg to let cargo know which cfg's we define, // and avoid warnings when they are used in the code. diff --git a/src/config.rs b/src/config.rs index 09e6ac37..1fc42710 100644 --- a/src/config.rs +++ b/src/config.rs @@ -704,6 +704,12 @@ make_config! { smtp_from_name: String, true, def, "Vaultwarden".to_string(); /// Username smtp_username: String, true, option; + /// Dkim signature (type:privatekey). Private must be base64-encoded ed key or PKCS#1 format RSA key. + dkim_signature: String, true, option; + /// Dkim algo (true if RSA else ed25519) + dkim_algo: bool, true, option; + /// Dkim infos (selector:domain) + dkim_infos: String, true, option; /// Password smtp_password: Pass, true, option; /// SMTP Auth mechanism |> Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. Possible values: ["Plain", "Login", "Xoauth2"]. Multiple options need to be separated by a comma ','. diff --git a/src/mail.rs b/src/mail.rs index d074995a..51ef9f73 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,14 +1,16 @@ use chrono::NaiveDateTime; -use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; -use std::{env::consts::EXE_SUFFIX, str::FromStr}; - use lettre::{ - message::{Attachment, Body, Mailbox, Message, MultiPart, SinglePart}, + message::{ + dkim::{DkimConfig, DkimSigningAlgorithm, DkimSigningKey}, + dkim_sign, Attachment, Body, Mailbox, Message, MultiPart, SinglePart, + }, transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism}, transport::smtp::client::{Tls, TlsParameters}, transport::smtp::extension::ClientId, Address, AsyncSendmailTransport, AsyncSmtpTransport, AsyncTransport, Tokio1Executor, }; +use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use std::{env::consts::EXE_SUFFIX, io::Read, str::FromStr}; use crate::{ api::EmptyResult, @@ -641,7 +643,60 @@ async fn send_with_selected_transport(email: Message) -> EmptyResult { async fn send_email(address: &str, subject: &str, body_html: String, body_text: String) -> EmptyResult { let smtp_from = &CONFIG.smtp_from(); - + let dkim = match (CONFIG.dkim_signature(), CONFIG.dkim_infos()) { + (Some(sig), Some(infos)) => { + let config = { + let algo = match CONFIG.dkim_algo() { + Some(true) => DkimSigningAlgorithm::Rsa, + _ => DkimSigningAlgorithm::Ed25519, + }; + let mut key = String::with_capacity(4096); + let sig = match std::fs::File::open(sig) { + Ok(mut f) => { + if let Err(e) = f.read_to_string(&mut key) { + debug!("Cannot read DKIM file. Err is {:?}", e); + None + } else { + key.shrink_to_fit(); + match DkimSigningKey::new(&key, algo) { + Ok(d) => Some(d), + Err(e) => { + debug!("DKIM key could not be parsed. Err is {:?}", e.to_string()); + None + } + } + } + }, + Err(e) => { + debug!("Cannot read DKIM file. Err is {:?}", e); + None + } + }; + match (sig, infos.split(':').collect::>()) { + (Some(sig), split2) if split2.len() == 2 => { + let (selector, domain, sig) = + (String::from(*split2.first().unwrap()), String::from(*split2.last().unwrap()), sig); + Some((selector, domain, sig)) + } + (None,_) => None, + _ => { + debug!("DKIM issue, invalid domain, selector."); + None + } + } + }; + if let Some(config) = config { + Some(DkimConfig::default_config(config.0, config.1, config.2)) + } else { + None + } + } + (None, None) => None, + _ => { + warn!("DKIM setting is badly implemented. One config is missing (DKIM signature or DKIM infos)."); + None + } + }; let body = if CONFIG.smtp_embed_images() { let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png").unwrap().1.to_vec()); let mail_github_body = Body::new(crate::api::static_files("mail-github.png").unwrap().1.to_vec()); @@ -661,12 +716,14 @@ async fn send_email(address: &str, subject: &str, body_html: String, body_text: MultiPart::alternative_plain_html(body_text, body_html) }; - let email = Message::builder() + let mut email = Message::builder() .message_id(Some(format!("<{}@{}>", crate::util::get_uuid(), smtp_from.split('@').collect::>()[1]))) .to(Mailbox::new(None, Address::from_str(address)?)) .from(Mailbox::new(Some(CONFIG.smtp_from_name()), Address::from_str(smtp_from)?)) .subject(subject) .multipart(body)?; - + if let Some(sig) = dkim { + dkim_sign(&mut email, &sig); + } send_with_selected_transport(email).await }