mirror of
https://github.com/chatmail/relay.git
synced 2026-08-21 16:00:56 +00:00
fix(dkim): Accept TXT records with escaped quotes (#61)
This commit is contained in:
@@ -14,6 +14,18 @@ use viadkim::verifier::LookupTxt;
|
||||
// "top 1000 relays" is much more than enough, the limit is mostly to prevent DoS attacks.
|
||||
const LRU_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(1000).expect("1000 != 0");
|
||||
|
||||
/// Normalizes a TXT record RDATA by removing irrelevant characters.
|
||||
///
|
||||
/// Some DKIM key records use e.g. LF + WSP line breaks.
|
||||
/// This is technically not correct, and `viadkim` fails to parse such records,
|
||||
/// but in practice this is accepted by many implementations.
|
||||
///
|
||||
/// Additionally, removes escaped quotes, as such records as:
|
||||
/// `"...UL9" "\" \"7vGm..."` proved to still be accepted by e.g. dkimpy or OpenDKIM.
|
||||
fn normalize_rdata(txt_data: &str) -> String {
|
||||
txt_data.replace([' ', '\t', '\n', '\r', '"'], "")
|
||||
}
|
||||
|
||||
/// DNS resolver for DKIM TXT records, that caches RDATA in memory.
|
||||
#[derive(Clone)]
|
||||
struct CachedResolver {
|
||||
@@ -38,15 +50,6 @@ impl CachedResolver {
|
||||
})
|
||||
}
|
||||
|
||||
/// Normalizes a TXT record RDATA by removing whitespace characters.
|
||||
///
|
||||
/// Some DKIM key records use e.g. LF + WSP line breaks.
|
||||
/// This is technically not correct, and `viadkim` fails to parse such records,
|
||||
/// but in practice this is accepted by many implementations.
|
||||
fn normalize_rdata(txt_data: &str) -> String {
|
||||
txt_data.replace([' ', '\t', '\n', '\r'], "")
|
||||
}
|
||||
|
||||
/// Invalidates the cached RDATA for a given selector and domain.
|
||||
///
|
||||
/// Fails silently.
|
||||
@@ -98,7 +101,10 @@ impl LookupTxt for CachedResolver {
|
||||
.map(|txt| {
|
||||
let rdata_raw = txt.txt_data().concat();
|
||||
let rdata = String::from_utf8_lossy(&rdata_raw);
|
||||
Self::normalize_rdata(&rdata).into_bytes()
|
||||
log::trace!("TXT (concat): {:?}", rdata);
|
||||
let normalized = normalize_rdata(&rdata);
|
||||
log::trace!("TXT (normalized): {:?}", normalized);
|
||||
normalized.into_bytes()
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
@@ -124,7 +130,8 @@ impl LookupTxt for MockResolver {
|
||||
|
||||
fn lookup_txt(&self, _domain: &str) -> Self::Query<'_> {
|
||||
Box::pin(async move {
|
||||
let txts: Self::Answer = Box::new(std::iter::once(Ok(self.0.clone().into_bytes())));
|
||||
let txts: Self::Answer =
|
||||
Box::new(std::iter::once(Ok(normalize_rdata(&self.0).into_bytes())));
|
||||
Ok(txts)
|
||||
})
|
||||
}
|
||||
@@ -256,13 +263,29 @@ impl DkimVerifier {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_dkim_verifier() {
|
||||
let verifier = DkimVerifier::mock(
|
||||
r#"v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5krC4Xi5Wkr6eMlla38LCFmV645E3FLAgsRl2YJ0SrZ4N2Vw1/yH0mefvtk7HYE7ytV7RQl/er2CkSsaHLJSYLmPCBw5CO6PSsBSXuh6DBqdylh/1t9vVQ9p38fTwn9gU1QvplcpRQL9eepRra1k24VMIaVy2ZZcu3LI9zkPsR7o7TyNaeMhsL8ouWInWc1NSid+p0SgliQuwHIejZhlTPE60JLbJE0OR9I4wmq3377H6z/QrO8XeabCgtmTuzE/hTRyIyNS40jql/99pjlhIcjM2U+P2B0FjwYt7BwLHsgANr74ctlnKY+SdH25rNwVpPmkotaULG5SJCByKBkfCwIDAQAB;s=email;t=s"#.to_string()
|
||||
);
|
||||
let raw_mail = include_bytes!("../test_data/dkim-abjadiyah.eml");
|
||||
verifier.verify(raw_mail, "abjadiyah.xyz").await.unwrap();
|
||||
#[case::simple_simple_canonicalization(
|
||||
r#"v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5krC4Xi5Wkr6eMlla38LCFmV645E3FLAgsRl2YJ0SrZ4N2Vw1/yH0mefvtk7HYE7ytV7RQl/er2CkSsaHLJSYLmPCBw5CO6PSsBSXuh6DBqdylh/1t9vVQ9p38fTwn9gU1QvplcpRQL9eepRra1k24VMIaVy2ZZcu3LI9zkPsR7o7TyNaeMhsL8ouWInWc1NSid+p0SgliQuwHIejZhlTPE60JLbJE0OR9I4wmq3377H6z/QrO8XeabCgtmTuzE/hTRyIyNS40jql/99pjlhIcjM2U+P2B0FjwYt7BwLHsgANr74ctlnKY+SdH25rNwVpPmkotaULG5SJCByKBkfCwIDAQAB;s=email;t=s"#,
|
||||
include_bytes!("../test_data/dkim-abjadiyah.eml")
|
||||
)]
|
||||
#[case::txt_escaped_quotes(
|
||||
r#"v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1giTh8KDkEchWhrAB6hGnb+V87kTezkt5I3SP7BGNg8wpv0yAuj/SUmnsttYmcEU+zmNAPqxePmCNvmjLYi/c3YyWEBwHcLyZE9OlS9W4enPdsoCuEN3DayzN4JCV3MsXMedCORvLFXmIARDXDLJUSJeqCeQoudXa9GmF1CrCmx70YyTtV0xOIxEzo7z0DkUL9" "7vGmNJCv6EMpi9wccMKKu8NSmOv+DBw1MLIJqChSZMCs8CYZ5i0KT/+Lijtn6B7wyOcAuQsVL+zr7DWYrFdrePe0wGuivfJ3SvUEfUo1SIykl0nvm0iLGhjNmNa1e/tUw4ULXhQ12Qw685+sq7wIDAQAB;s=email;t=s"#,
|
||||
include_bytes!("../test_data/dkim-privitty.eml")
|
||||
)]
|
||||
async fn test_dkim_verifier(#[case] txt: &str, #[case] message: &[u8]) {
|
||||
let verifier = DkimVerifier::mock(txt.to_string());
|
||||
verifier.verify(message, "abjadiyah.xyz").await.unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::escaped_quotes(
|
||||
r#"v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1giTh8KDkEchWhrAB6hGnb+V87kTezkt5I3SP7BGNg8wpv0yAuj/SUmnsttYmcEU+zmNAPqxePmCNvmjLYi/c3YyWEBwHcLyZE9OlS9W4enPdsoCuEN3DayzN4JCV3MsXMedCORvLFXmIARDXDLJUSJeqCeQoudXa9GmF1CrCmx70YyTtV0xOIxEzo7z0DkUL9" "7vGmNJCv6EMpi9wccMKKu8NSmOv+DBw1MLIJqChSZMCs8CYZ5i0KT/+Lijtn6B7wyOcAuQsVL+zr7DWYrFdrePe0wGuivfJ3SvUEfUo1SIykl0nvm0iLGhjNmNa1e/tUw4ULXhQ12Qw685+sq7wIDAQAB;s=email;t=s"#,
|
||||
r#"v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1giTh8KDkEchWhrAB6hGnb+V87kTezkt5I3SP7BGNg8wpv0yAuj/SUmnsttYmcEU+zmNAPqxePmCNvmjLYi/c3YyWEBwHcLyZE9OlS9W4enPdsoCuEN3DayzN4JCV3MsXMedCORvLFXmIARDXDLJUSJeqCeQoudXa9GmF1CrCmx70YyTtV0xOIxEzo7z0DkUL97vGmNJCv6EMpi9wccMKKu8NSmOv+DBw1MLIJqChSZMCs8CYZ5i0KT/+Lijtn6B7wyOcAuQsVL+zr7DWYrFdrePe0wGuivfJ3SvUEfUo1SIykl0nvm0iLGhjNmNa1e/tUw4ULXhQ12Qw685+sq7wIDAQAB;s=email;t=s"#
|
||||
)]
|
||||
fn test_normalize_rdata(#[case] input: &str, #[case] expected: &str) {
|
||||
assert_eq!(normalize_rdata(input), expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user