DKIM2 verification
This commit is contained in:
28
crates/common/src/cache/principals.rs
vendored
28
crates/common/src/cache/principals.rs
vendored
@@ -15,7 +15,7 @@ use crate::{
|
||||
DomainCache, EmailAddress, EmailAddressRef, EmailCache, MailingListCache, PermissionsGroup,
|
||||
RECOVERY_ADMIN_ID, RoleCache, TenantCache, permissions::BuildPermissions,
|
||||
},
|
||||
config::smtp::auth::DkimSigner,
|
||||
config::smtp::auth::DkimSigners,
|
||||
expr::if_block::BootstrapExprExt,
|
||||
network::mta::AddressResolver,
|
||||
storage::{
|
||||
@@ -839,7 +839,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dkim_signers(&self, domain: &str) -> trc::Result<Option<Arc<[DkimSigner]>>> {
|
||||
pub async fn dkim_signers(&self, domain: &str) -> trc::Result<Option<Arc<DkimSigners>>> {
|
||||
let Some(domain) = self.domain(domain).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -868,26 +868,24 @@ impl Server {
|
||||
.equal(Property::DomainId, domain.id),
|
||||
)
|
||||
.await?;
|
||||
let mut signatures = Vec::with_capacity(ids.len());
|
||||
let domain_name = &domain.names[0];
|
||||
let mut signers = DkimSigners {
|
||||
dkim1: Vec::with_capacity(ids.len()),
|
||||
dkim2: None,
|
||||
};
|
||||
for id in ids {
|
||||
if let Some(signature) = self.registry().object::<DkimSignature>(id).await?
|
||||
&& matches!(signature.stage(), DkimRotationStage::Active)
|
||||
&& let Err(err) = signers.insert(domain_name.to_string(), signature).await
|
||||
{
|
||||
match DkimSigner::new(domain.names[0].to_string(), signature).await {
|
||||
Ok(signer) => signatures.push(signer),
|
||||
Err(err) => {
|
||||
trc::error!(
|
||||
err.ctx(trc::Key::Id, id.id()).caused_by(trc::location!())
|
||||
);
|
||||
}
|
||||
}
|
||||
trc::error!(err.ctx(trc::Key::Id, id.id()).caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
|
||||
if !signatures.is_empty() {
|
||||
let signatures: Arc<[DkimSigner]> = signatures.into();
|
||||
let _ = guard.insert(signatures.clone());
|
||||
Ok(Some(signatures))
|
||||
if !signers.dkim1.is_empty() || signers.dkim2.is_some() {
|
||||
let signers = Arc::new(signers);
|
||||
let _ = guard.insert(signers.clone());
|
||||
Ok(Some(signers))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
mailstore::spamfilter::SpamClassifier,
|
||||
server::tls::parse_certificates,
|
||||
smtp::{
|
||||
auth::DkimSigner,
|
||||
auth::DkimSigners,
|
||||
resolver::{Policy, Tlsa},
|
||||
},
|
||||
},
|
||||
@@ -164,7 +164,7 @@ impl Caches {
|
||||
),
|
||||
dkim_signers: Cache::new(
|
||||
cache.dkim_signatures,
|
||||
(std::mem::size_of::<DkimSigner>() + 255) as u64,
|
||||
(std::mem::size_of::<DkimSigners>() + 255) as u64,
|
||||
),
|
||||
dns_txt: CacheWithTtl::new(cache.dns_txt, (std::mem::size_of::<Txt>() + 255) as u64),
|
||||
dns_mx: CacheWithTtl::new(cache.dns_mx, ((std::mem::size_of::<MX>() + 255) * 2) as u64),
|
||||
|
||||
@@ -11,15 +11,16 @@ use crate::expr::{
|
||||
use mail_auth::{
|
||||
common::crypto::{Ed25519Key, HashAlgorithm, RsaKey, Sha256, SigningKey},
|
||||
dkim::{Canonicalization, Done},
|
||||
dkim2::{Dkim2Signer, Done as Dkim2Done, Flag},
|
||||
};
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use registry::{
|
||||
schema::{
|
||||
enums::{self, ExpressionConstant},
|
||||
enums::{self, Dkim2Flag, ExpressionConstant},
|
||||
prelude::ObjectType,
|
||||
structs::{Dkim1Signature, DkimSignature, SenderAuth},
|
||||
},
|
||||
types::ObjectImpl,
|
||||
types::{ObjectImpl, map::Map},
|
||||
};
|
||||
use rustls_pki_types::{PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, pem::PemObject};
|
||||
use store::registry::bootstrap::Bootstrap;
|
||||
@@ -71,14 +72,15 @@ pub enum VerifyStrategy {
|
||||
Disable,
|
||||
}
|
||||
|
||||
pub enum DkimSigner {
|
||||
pub enum Dkim1Signer {
|
||||
RsaSha256(mail_auth::dkim::DkimSigner<RsaKey<Sha256>, Done>),
|
||||
Ed25519Sha256(mail_auth::dkim::DkimSigner<Ed25519Key, Done>),
|
||||
}
|
||||
|
||||
pub enum ArcSealer {
|
||||
RsaSha256(mail_auth::arc::ArcSealer<RsaKey<Sha256>, Done>),
|
||||
Ed25519Sha256(mail_auth::arc::ArcSealer<Ed25519Key, Done>),
|
||||
#[derive(Default)]
|
||||
pub struct DkimSigners {
|
||||
pub dkim1: Vec<Dkim1Signer>,
|
||||
pub dkim2: Option<Dkim2Signer<Dkim2Done>>,
|
||||
}
|
||||
|
||||
impl MailAuthConfig {
|
||||
@@ -123,8 +125,8 @@ impl MailAuthConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl DkimSigner {
|
||||
pub async fn new(domain: String, signature: DkimSignature) -> trc::Result<Self> {
|
||||
impl DkimSigners {
|
||||
pub async fn insert(&mut self, domain: String, signature: DkimSignature) -> trc::Result<()> {
|
||||
let mut errors = vec![];
|
||||
if !signature.validate(&mut errors) {
|
||||
return Err(trc::DkimEvent::BuildError
|
||||
@@ -156,9 +158,10 @@ impl DkimSigner {
|
||||
.details("Failed to build ED25519 key")
|
||||
})?;
|
||||
|
||||
Ok(DkimSigner::Ed25519Sha256(build_dkim1_signer(
|
||||
domain, signature, key,
|
||||
)))
|
||||
self.dkim1
|
||||
.push(Dkim1Signer::Ed25519Sha256(build_dkim1_signer(
|
||||
domain, signature, key,
|
||||
)));
|
||||
}
|
||||
DkimSignature::Dkim1RsaSha256(signature) => {
|
||||
let private_key = signature
|
||||
@@ -168,17 +171,70 @@ impl DkimSigner {
|
||||
.map_err(|err| trc::DkimEvent::BuildError.reason(err))?;
|
||||
let key = rsa_key_parse(private_key.as_bytes())?;
|
||||
|
||||
Ok(DkimSigner::RsaSha256(build_dkim1_signer(
|
||||
self.dkim1.push(Dkim1Signer::RsaSha256(build_dkim1_signer(
|
||||
domain, signature, key,
|
||||
)))
|
||||
)));
|
||||
}
|
||||
DkimSignature::Dkim2Ed25519Sha256(_) | DkimSignature::Dkim2RsaSha256(_) => {
|
||||
todo!()
|
||||
DkimSignature::Dkim2Ed25519Sha256(signature) => {
|
||||
let private_key = signature
|
||||
.private_key
|
||||
.secret()
|
||||
.await
|
||||
.map_err(|err| trc::DkimEvent::BuildError.reason(err))?;
|
||||
let private_key = simple_pem_parse(&private_key).ok_or_else(|| {
|
||||
trc::DkimEvent::BuildError
|
||||
.reason("Failed to parse ED25519 private key PEM")
|
||||
.details("Invalid PEM format")
|
||||
})?;
|
||||
let key =
|
||||
Ed25519Key::from_pkcs8_maybe_unchecked_der(&private_key).map_err(|err| {
|
||||
trc::DkimEvent::BuildError
|
||||
.reason(err)
|
||||
.details("Failed to build ED25519 key")
|
||||
})?;
|
||||
|
||||
self.dkim2 = Some(match self.dkim2.take() {
|
||||
None => Dkim2Signer::from_key(key)
|
||||
.domain(domain)
|
||||
.selector(signature.selector)
|
||||
.flags(map_dkim2_flags(signature.flags)),
|
||||
Some(signer) => signer
|
||||
.additional_key(key, signature.selector)
|
||||
.flags(map_dkim2_flags(signature.flags)),
|
||||
});
|
||||
}
|
||||
DkimSignature::Dkim2RsaSha256(signature) => {
|
||||
let private_key = signature
|
||||
.private_key
|
||||
.secret()
|
||||
.await
|
||||
.map_err(|err| trc::DkimEvent::BuildError.reason(err))?;
|
||||
let key = rsa_key_parse(private_key.as_bytes())?;
|
||||
|
||||
self.dkim2 = Some(match self.dkim2.take() {
|
||||
None => Dkim2Signer::from_key(key)
|
||||
.domain(domain)
|
||||
.selector(signature.selector)
|
||||
.flags(map_dkim2_flags(signature.flags)),
|
||||
Some(signer) => signer
|
||||
.additional_key(key, signature.selector)
|
||||
.flags(map_dkim2_flags(signature.flags)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_dkim2_flags(flags: Map<enums::Dkim2Flag>) -> impl Iterator<Item = Flag> {
|
||||
flags.into_inner().into_iter().map(|flag| match flag {
|
||||
Dkim2Flag::Donotmodify => Flag::DoNotModify,
|
||||
Dkim2Flag::Donotexplode => Flag::DoNotExplode,
|
||||
Dkim2Flag::Feedback => Flag::Feedback,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rsa_key_parse(private_key: &[u8]) -> trc::Result<RsaKey<Sha256>> {
|
||||
PrivatePkcs1KeyDer::from_pem_slice(private_key)
|
||||
.map(PrivateKeyDer::Pkcs1)
|
||||
@@ -309,8 +365,16 @@ impl VerifyStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for DkimSigner {
|
||||
impl CacheItemWeight for Dkim1Signer {
|
||||
fn weight(&self) -> u64 {
|
||||
std::mem::size_of::<Self>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for DkimSigners {
|
||||
fn weight(&self) -> u64 {
|
||||
(std::mem::size_of::<Self>()
|
||||
+ self.dkim1.len() * std::mem::size_of::<Dkim1Signer>()
|
||||
+ std::mem::size_of::<Dkim2Signer<Dkim2Done>>()) as u64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
scripts::Scripting,
|
||||
spamfilter::{IpResolver, SpamClassifier, SpamFilterConfig},
|
||||
},
|
||||
smtp::auth::DkimSigner,
|
||||
smtp::auth::DkimSigners,
|
||||
},
|
||||
ipc::TrainTaskController,
|
||||
network::security::BlockedIps,
|
||||
@@ -193,7 +193,7 @@ pub struct Caches {
|
||||
pub tenants: Cache<u32, Arc<TenantCache>>,
|
||||
pub lists: Cache<u32, Arc<MailingListCache>>,
|
||||
|
||||
pub dkim_signers: Cache<u32, Arc<[DkimSigner]>>,
|
||||
pub dkim_signers: Cache<u32, Arc<DkimSigners>>,
|
||||
|
||||
pub dns_txt: CacheWithTtl<Box<str>, Txt>,
|
||||
pub dns_mx: CacheWithTtl<Box<str>, RecordSet<MX>>,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
config::{
|
||||
mailstore::spamfilter::SpamClassifier,
|
||||
smtp::{
|
||||
auth::DkimSigner,
|
||||
auth::DkimSigners,
|
||||
queue::{
|
||||
ConnectionStrategy, DEFAULT_QUEUE_NAME, MxConfig, QueueExpiry, QueueName,
|
||||
QueueStrategy, RequireOptional, RoutingStrategy, TlsStrategy, VirtualQueue,
|
||||
@@ -188,7 +188,7 @@ impl Server {
|
||||
&self,
|
||||
domain: &str,
|
||||
session_id: u64,
|
||||
) -> trc::Result<Option<Arc<[DkimSigner]>>> {
|
||||
) -> trc::Result<Option<Arc<DkimSigners>>> {
|
||||
if let Some(signers) = self.dkim_signers(domain).await? {
|
||||
Ok(Some(signers))
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user