-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
208 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
[package] | ||
name = "mail-auth" | ||
description = "DKIM, ARC, SPF and DMARC library for Rust" | ||
version = "0.3.10" | ||
version = "0.3.11" | ||
edition = "2021" | ||
authors = [ "Stalwart Labs <[email protected]>"] | ||
license = "Apache-2.0 OR MIT" | ||
|
@@ -18,6 +18,7 @@ doctest = false | |
[features] | ||
default = ["ring", "rustls-pemfile"] | ||
rust-crypto = ["ed25519-dalek", "rsa", "sha1", "sha2"] | ||
generate = ["rsa", "rand"] | ||
test = [] | ||
|
||
[dependencies] | ||
|
@@ -30,14 +31,15 @@ mail-builder = { version = "0.3", features = ["ludicrous_mode"] } | |
parking_lot = "0.12.0" | ||
quick-xml = "0.31" | ||
ring = { version = "0.17", optional = true } | ||
rsa = { version = "0.7", optional = true } | ||
rsa = { version = "0.9.6", optional = true } | ||
rustls-pemfile = { version = "2", optional = true } | ||
serde = { version = "1.0", features = ["derive"] } | ||
serde_json = "1.0" | ||
sha1 = { version = "0.10", features = ["oid"], optional = true } | ||
sha2 = { version = "0.10.6", features = ["oid"], optional = true } | ||
hickory-resolver = { version = "0.24", features = ["dns-over-rustls", "dnssec-ring"] } | ||
zip = "0.6.3" | ||
rand = { version = "0.8.5", optional = true } | ||
|
||
[dev-dependencies] | ||
tokio = { version = "1.16", features = ["net", "io-util", "time", "rt-multi-thread", "macros"] } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/* | ||
* Copyright (c) 2020-2023, Stalwart Labs Ltd. | ||
* | ||
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | ||
* option. This file may not be copied, modified, or distributed | ||
* except according to those terms. | ||
*/ | ||
|
||
use mail_builder::encoders::base64::base64_encode; | ||
use rsa::{ | ||
pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey}, | ||
RsaPrivateKey, RsaPublicKey, | ||
}; | ||
|
||
use crate::{common::crypto::Ed25519Key, Error}; | ||
|
||
pub struct DkimKeyPair { | ||
private_key: Vec<u8>, | ||
public_key: Vec<u8>, | ||
} | ||
|
||
impl DkimKeyPair { | ||
/// Generates a new RSA key pair encoded in PKCS#1 DER format with the given number of bits | ||
pub fn generate_rsa(bits: usize) -> crate::Result<Self> { | ||
//TODO: Use `ring` once it supports RSA key generation | ||
let priv_key = RsaPrivateKey::new(&mut rand::thread_rng(), bits) | ||
.map_err(|err| Error::CryptoError(err.to_string()))?; | ||
let pub_key = RsaPublicKey::from(&priv_key); | ||
|
||
Ok(DkimKeyPair { | ||
private_key: priv_key | ||
.to_pkcs1_der() | ||
.map_err(|err| Error::CryptoError(err.to_string()))? | ||
.as_bytes() | ||
.to_vec(), | ||
public_key: pub_key | ||
.to_pkcs1_der() | ||
.map_err(|err| Error::CryptoError(err.to_string()))? | ||
.as_bytes() | ||
.to_vec(), | ||
}) | ||
} | ||
|
||
/// Generates a new Ed25519 key pair encoded in PKCS#8 DER format | ||
pub fn generate_ed25519() -> crate::Result<Self> { | ||
let pkcs8_der = | ||
Ed25519Key::generate_pkcs8().map_err(|err| Error::CryptoError(err.to_string()))?; | ||
Check failure on line 49 in src/dkim/generate.rs GitHub Actions / clippyno function or associated item named `generate_pkcs8` found for struct `common::crypto::rust_crypto::Ed25519Key` in the current scope
|
||
let key = Ed25519Key::from_pkcs8_der(&pkcs8_der).unwrap(); | ||
Check failure on line 50 in src/dkim/generate.rs GitHub Actions / clippyno function or associated item named `from_pkcs8_der` found for struct `common::crypto::rust_crypto::Ed25519Key` in the current scope
|
||
|
||
Ok(DkimKeyPair { | ||
private_key: pkcs8_der, | ||
public_key: key.public_key(), | ||
}) | ||
} | ||
|
||
pub fn public_key(&self) -> &[u8] { | ||
&self.public_key | ||
} | ||
|
||
pub fn private_key(&self) -> &[u8] { | ||
&self.private_key | ||
} | ||
|
||
pub fn into_inner(self) -> (Vec<u8>, Vec<u8>) { | ||
(self.private_key, self.public_key) | ||
} | ||
|
||
pub fn encoded_public_key(&self) -> String { | ||
String::from_utf8(base64_encode(&self.public_key).unwrap_or_default()).unwrap_or_default() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::dkim::sign::test::verify; | ||
use std::time::{Duration, Instant}; | ||
|
||
use crate::{ | ||
common::{ | ||
crypto::{Ed25519Key, RsaKey, Sha256}, | ||
parse::TxtRecordParser, | ||
verify::DomainKey, | ||
}, | ||
dkim::{generate::DkimKeyPair, DkimSigner, DomainKeyReport}, | ||
Resolver, | ||
}; | ||
|
||
#[tokio::test] | ||
async fn dkim_generate_verify() { | ||
let rsa_pkcs = DkimKeyPair::generate_rsa(2048).unwrap(); | ||
let ed_pkcs = DkimKeyPair::generate_ed25519().unwrap(); | ||
|
||
let rsa_public = format!("v=DKIM1; t=s; p={}", rsa_pkcs.encoded_public_key()); | ||
let ed_public = format!("v=DKIM1; k=ed25519; p={}", ed_pkcs.encoded_public_key()); | ||
|
||
let pk_ed = Ed25519Key::from_pkcs8_der(&ed_pkcs.private_key).unwrap(); | ||
let pk_rsa = RsaKey::<Sha256>::from_der(&rsa_pkcs.private_key).unwrap(); | ||
|
||
// Create resolver | ||
let resolver = Resolver::new_system_conf().unwrap(); | ||
#[cfg(any(test, feature = "test"))] | ||
{ | ||
resolver.txt_add( | ||
"default._domainkey.example.com.".to_string(), | ||
DomainKey::parse(rsa_public.as_bytes()).unwrap(), | ||
Instant::now() + Duration::new(3600, 0), | ||
); | ||
resolver.txt_add( | ||
"ed._domainkey.example.com.".to_string(), | ||
DomainKey::parse(ed_public.as_bytes()).unwrap(), | ||
Instant::now() + Duration::new(3600, 0), | ||
); | ||
resolver.txt_add( | ||
"_report._domainkey.example.com.".to_string(), | ||
DomainKeyReport::parse("ra=dkim-failures; rp=100; rr=x".as_bytes()).unwrap(), | ||
Instant::now() + Duration::new(3600, 0), | ||
); | ||
} | ||
|
||
let message = concat!( | ||
"From: [email protected]\r\n", | ||
"To: [email protected]\r\n", | ||
"Subject: TPS Report\r\n", | ||
"\r\n", | ||
"I'm going to need those TPS reports ASAP. ", | ||
"So, if you could do that, that'd be great.\r\n" | ||
); | ||
|
||
dbg!("Test generated RSA key"); | ||
verify( | ||
&resolver, | ||
DkimSigner::from_key(pk_rsa) | ||
.domain("example.com") | ||
.selector("default") | ||
.headers(["From", "To", "Subject"]) | ||
.agent_user_identifier("\"John Doe\" <[email protected]>") | ||
.sign(message.as_bytes()) | ||
.unwrap(), | ||
message, | ||
Ok(()), | ||
) | ||
.await; | ||
|
||
dbg!("Test ED25519 generated key"); | ||
verify( | ||
&resolver, | ||
DkimSigner::from_key(pk_ed) | ||
.domain("example.com") | ||
.selector("ed") | ||
.headers(["From", "To", "Subject"]) | ||
.sign(message.as_bytes()) | ||
.unwrap(), | ||
message, | ||
Ok(()), | ||
) | ||
.await; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters