DANE: Verify DNSSEC is supported by the resolver before attempting to validate TLSA records
This commit is contained in:
@@ -194,66 +194,6 @@ pub fn rsa_key_parse(private_key: &[u8]) -> trc::Result<RsaKey<Sha256>> {
|
||||
})
|
||||
}
|
||||
|
||||
/*impl ArcSealer {
|
||||
pub fn new(selector: String, domain: String, signature: DkimSignature) -> trc::Result<Self> {
|
||||
let mut errors = vec![];
|
||||
if !signature.validate(&mut errors) {
|
||||
return Err(trc::DkimEvent::BuildError
|
||||
.reason("DKIM signature validation failed")
|
||||
.details(
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|v| trc::Value::from(v.to_string()))
|
||||
.collect::<Vec<_>>(),
|
||||
));
|
||||
}
|
||||
|
||||
match signature {
|
||||
DkimSignature::Dkim1Ed25519Sha256(signature) => {
|
||||
let private_key = simple_pem_parse(&signature.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")
|
||||
})?;
|
||||
|
||||
Ok(ArcSealer::Ed25519Sha256(build_dkim1_sealer(
|
||||
domain, selector, signature, key,
|
||||
)))
|
||||
}
|
||||
DkimSignature::Dkim1RsaSha256(signature) => {
|
||||
let key = PrivatePkcs1KeyDer::from_pem_slice(signature.private_key.as_bytes())
|
||||
.map(PrivateKeyDer::Pkcs1)
|
||||
.or_else(|_| {
|
||||
PrivatePkcs8KeyDer::from_pem_slice(signature.private_key.as_bytes())
|
||||
.map(PrivateKeyDer::Pkcs8)
|
||||
})
|
||||
.map_err(|err| {
|
||||
trc::DkimEvent::BuildError
|
||||
.reason(err)
|
||||
.details("Failed to build RSA key")
|
||||
})
|
||||
.and_then(|key| {
|
||||
RsaKey::<Sha256>::from_key_der(key).map_err(|err| {
|
||||
trc::DkimEvent::BuildError
|
||||
.reason(err)
|
||||
.details("Failed to build RSA key")
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(ArcSealer::RsaSha256(build_dkim1_sealer(
|
||||
domain, selector, signature, key,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn simple_pem_parse(contents: &str) -> Option<Vec<u8>> {
|
||||
let mut contents = contents.as_bytes().iter().copied();
|
||||
let mut base64 = vec![];
|
||||
@@ -338,59 +278,6 @@ fn build_dkim1_signer<T: SigningKey>(
|
||||
signer
|
||||
}
|
||||
|
||||
/*fn build_dkim1_sealer<T: SigningKey<Hasher = Sha256>>(
|
||||
domain: String,
|
||||
selector: String,
|
||||
mut signature: Dkim1Signature,
|
||||
key: T,
|
||||
) -> mail_auth::arc::ArcSealer<T, Done> {
|
||||
if !signature
|
||||
.headers
|
||||
.iter()
|
||||
.any(|h| h.eq_ignore_ascii_case("DKIM-Signature"))
|
||||
{
|
||||
signature
|
||||
.headers
|
||||
.push_unchecked("DKIM-Signature".to_string());
|
||||
}
|
||||
|
||||
let mut sealer = mail_auth::arc::ArcSealer::from_key(key)
|
||||
.domain(domain)
|
||||
.selector(selector)
|
||||
.headers(signature.headers);
|
||||
|
||||
match signature.canonicalization {
|
||||
enums::DkimCanonicalization::RelaxedRelaxed => {
|
||||
sealer = sealer
|
||||
.body_canonicalization(Canonicalization::Relaxed)
|
||||
.header_canonicalization(Canonicalization::Relaxed);
|
||||
}
|
||||
enums::DkimCanonicalization::SimpleSimple => {
|
||||
sealer = sealer
|
||||
.body_canonicalization(Canonicalization::Simple)
|
||||
.header_canonicalization(Canonicalization::Simple);
|
||||
}
|
||||
enums::DkimCanonicalization::RelaxedSimple => {
|
||||
sealer = sealer
|
||||
.body_canonicalization(Canonicalization::Simple)
|
||||
.header_canonicalization(Canonicalization::Relaxed);
|
||||
}
|
||||
enums::DkimCanonicalization::SimpleRelaxed => {
|
||||
sealer = sealer
|
||||
.body_canonicalization(Canonicalization::Relaxed)
|
||||
.header_canonicalization(Canonicalization::Simple);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(expire) = signature.expire {
|
||||
sealer = sealer.expiration(expire.into_inner().as_secs());
|
||||
}
|
||||
|
||||
sealer
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
impl<'x> TryFrom<expr::Variable<'x>> for VerifyStrategy {
|
||||
type Error = ();
|
||||
|
||||
|
||||
@@ -14,8 +14,11 @@ use self::{
|
||||
auth::MailAuthConfig, queue::QueueConfig, report::ReportConfig, resolver::Resolvers,
|
||||
session::SessionConfig,
|
||||
};
|
||||
use crate::expr::Expression;
|
||||
use registry::{schema::structs::Rate, types::id::ObjectId};
|
||||
use crate::{config::smtp::queue::RequireOptional, expr::Expression};
|
||||
use registry::{
|
||||
schema::{properties::ObjectType, structs::Rate},
|
||||
types::id::ObjectId,
|
||||
};
|
||||
use store::registry::bootstrap::Bootstrap;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -49,12 +52,31 @@ pub const THROTTLE_HELO_DOMAIN: u16 = 1 << 9;
|
||||
|
||||
impl SmtpConfig {
|
||||
pub async fn parse(bp: &mut Bootstrap) -> Self {
|
||||
Self {
|
||||
let config = Self {
|
||||
session: SessionConfig::parse(bp).await,
|
||||
queue: QueueConfig::parse(bp).await,
|
||||
resolvers: Resolvers::parse(bp).await,
|
||||
mail_auth: MailAuthConfig::parse(bp).await,
|
||||
report: ReportConfig::parse(bp).await,
|
||||
};
|
||||
|
||||
if !config.resolvers.dnssec_available
|
||||
&& config
|
||||
.queue
|
||||
.tls_strategy
|
||||
.values()
|
||||
.any(|t| !matches!(t.dane, RequireOptional::Disable))
|
||||
{
|
||||
bp.build_warning(
|
||||
ObjectType::DnsResolver.singleton(),
|
||||
concat!(
|
||||
"The configured DNS resolver cannot validate DNSSEC. ",
|
||||
"DANE has been disabled to avoid deferring mail. ",
|
||||
"Configure a DNSSEC-validating resolver to enable DANE."
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,13 @@ use mail_auth::{
|
||||
ResolverConfig, ResolverOpts,
|
||||
},
|
||||
net::runtime::TokioRuntimeProvider,
|
||||
proto::rr::{Name, RecordType},
|
||||
system_conf::read_system_conf,
|
||||
},
|
||||
};
|
||||
use registry::schema::{
|
||||
enums::{DnsResolverProtocol, MtaRequiredOrOptional, PolicyEnforcement},
|
||||
enums::{DnsResolverProtocol, PolicyEnforcement},
|
||||
prelude::ObjectType,
|
||||
structs::{DnsResolver, MtaSts, MtaTlsStrategy, SystemSettings},
|
||||
structs::{DnsResolver, MtaSts, SystemSettings},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -223,40 +222,24 @@ impl Resolvers {
|
||||
.expect("Failed to build DNSSEC resolver"),
|
||||
};
|
||||
|
||||
let uses_dane = bp
|
||||
.list_infallible::<MtaTlsStrategy>()
|
||||
.await
|
||||
.iter()
|
||||
.any(|obj| obj.object.dane != MtaRequiredOrOptional::Disable);
|
||||
|
||||
let dnssec_available = if uses_dane && !cfg!(any(test, feature = "test_mode")) {
|
||||
let available = dnssec_capable(&dnssec.resolver).await;
|
||||
if !available {
|
||||
bp.build_warning(
|
||||
ObjectType::DnsResolver.singleton(),
|
||||
concat!(
|
||||
"The configured DNS resolver cannot validate DNSSEC. ",
|
||||
"DANE has been disabled to avoid deferring mail. ",
|
||||
"Configure a DNSSEC-validating resolver to enable DANE."
|
||||
),
|
||||
);
|
||||
}
|
||||
available
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
Resolvers {
|
||||
dns: MessageAuthenticator::new(resolver_config, opts).unwrap(),
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
dnssec_available: dnssec_capable(&dnssec.resolver).await,
|
||||
#[cfg(feature = "test_mode")]
|
||||
dnssec_available: true,
|
||||
dnssec,
|
||||
dnssec_available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "test_mode"))]
|
||||
async fn dnssec_capable(resolver: &TokioResolver) -> bool {
|
||||
resolver
|
||||
.lookup(Name::root(), RecordType::DNSKEY)
|
||||
.lookup(
|
||||
hickory_proto::rr::Name::root(),
|
||||
hickory_proto::rr::RecordType::DNSKEY,
|
||||
)
|
||||
.await
|
||||
.is_ok_and(|lookup| {
|
||||
lookup
|
||||
|
||||
Reference in New Issue
Block a user