Hi everyone,
I have noticed that some recent versions of Delta Chat started to
spoof more than just the subject header when sending OpenPGP-encrypted
messages. From what I saw, they mask at least the following three headers:
1. Date -- the outer header contains a fictitious date while the protected
header contains the real date.
2. To -- the outer header is set to `"hidden-recipients": ;` while the
protected header contains the full display name and email address of
the recipient.
3. From -- the outer header contains only the email address while the
protected header contains both the email address and the display name
of the sender.
To my eyes, these ways of masking protected headers look like they should
be valid. I have not yet gotten around to looking any deeper into how
protected headers are implemented in notmuch. However, I attach a patch
with additional tests for the three cases described above as a form of
executable bug report.
I would not be surprised if future versions of Delta Chat started to
spoof even more unprotected headers, like References, In-Reply-To,
or even Message-ID. If anyone fixes the issues described above, they
might want to check if they can make the implementation generic enough
to cover such cases as well.
Best
Frank
From 422f184a93f677242c01837ee4666ba6605b70b4 Mon Sep 17 00:00:00 2001
From: Frank Seifferth <frankseifferth@posteo.net>
Date: Thu, 18 Dec 2025 17:41:07 +0100
Subject: [PATCH] Add more test cases for protected headers
Some versions of Delta Chat recently started to send spoofed headers
that are overridden by the protected headers of OpenPGP-encrypted
messages. Currently, notmuch uses the spoofed headers rather than the
real ones, with the subject being the sole exception. This commit adds
test cases for the following three headers: Date, To, and From. The
example messages were modelled after what Delta Chat produces, which
(in my opinion) look like it should be valid.
---
test/T356-protected-headers.sh | 70 ++++++++++++++++++-
.../protected-headers/spoofed-date.eml | 28 ++++++++
.../protected-headers/spoofed-recipient.eml | 28 ++++++++
.../protected-headers/spoofed-sender.eml | 29 ++++++++
4 files changed, 154 insertions(+), 1 deletion(-)
create mode 100644 test/corpora/protected-headers/spoofed-date.eml
create mode 100644 test/corpora/protected-headers/spoofed-recipient.eml
create mode 100644 test/corpora/protected-headers/spoofed-sender.eml
diff --git a/test/T356-protected-headers.sh b/test/T356-protected-headers.sh
index 9f640331..712c099d 100755
--- a/test/T356-protected-headers.sh
+++ b/test/T356-protected-headers.sh
@@ -24,6 +24,36 @@ test_json_nodes <<<"$output" \
'crypto:[0][0][0]["crypto"]={"decrypted": {"status": "full", "header-mask": {"Subject": "Subject Unavailable"}}}' \
'subject:[0][0][0]["headers"]["Subject"]="This is a protected header"'
+test_begin_subtest "verify spoofed date is shown without decryption"
+output=$(notmuch show --format=json id:spoofed-date@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["Date"]="Sat, 01 Jan 2000 12:00:00 +0000"'
+
+test_begin_subtest "verify real date is shown with decryption"
+output=$(notmuch show --decrypt=true --format=json id:spoofed-date@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["Date"]="Wed, 16 Dec 2015 17:19:18 +0100"'
+
+test_begin_subtest "verify spoofed recipient is shown without decryption"
+output=$(notmuch show --format=json id:spoofed-recipient@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["To"]="undisclosed-recipients: ;"'
+
+test_begin_subtest "verify real recipient is shown with decryption"
+output=$(notmuch show --decrypt=true --format=json id:spoofed-recipient@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["To"]="Notmuch Test Suite <test_suite@notmuchmail.org>"'
+
+test_begin_subtest "verify spoofed sender is shown without decryption"
+output=$(notmuch show --format=json id:spoofed-recipient@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["From"]="test_suite@notmuchmail.org"'
+
+test_begin_subtest "verify real sender is shown with decryption"
+output=$(notmuch show --decrypt=true --format=json id:spoofed-recipient@crypto.notmuchmail.org)
+test_json_nodes <<<"$output" \
+ 'subject:[0][0][0]["headers"]["From"]="Notmuch Test Suite <test_suite@notmuchmail.org>"'
+
test_begin_subtest "when no external header is present, show masked subject as null"
output=$(notmuch show --decrypt=true --format=json id:subjectless-protected-header@crypto.notmuchmail.org)
test_json_nodes <<<"$output" \
@@ -87,7 +117,7 @@ test_begin_subtest "protected subject is not indexed by default"
output=$(notmuch search --output=messages 'subject:"This is a protected header"')
test_expect_equal "$output" ''
-test_begin_subtest "reindex message with protected header"
+test_begin_subtest "reindex message with protected subject"
test_expect_success 'notmuch reindex --decrypt=true id:protected-header@crypto.notmuchmail.org'
test_begin_subtest "protected subject is indexed when cleartext is indexed"
@@ -105,6 +135,41 @@ test_json_nodes <<<"$output" \
'subject:["original"]["headers"]["Subject"]="This is a protected header"' \
'reply-subject:["reply-headers"]["Subject"]="Re: Subject Unavailable"'
+test_begin_subtest "spoofed date is used when cleartext is not indexed"
+output=$(notmuch search --output=messages 'id:spoofed-date@crypto.notmuchmail.org and date:2000-01-01')
+test_expect_equal "$output" 'id:spoofed-date@crypto.notmuchmail.org'
+
+test_begin_subtest "real date is masked when cleartext is not indexed"
+output=$(notmuch search --output=messages 'id:spoofed-date@crypto.notmuchmail.org and date:2015-12-16')
+test_expect_equal "$output" ''
+
+test_begin_subtest "real recipient is masked when cleartext is not indexed"
+output=$(notmuch search --output=messages 'id:spoofed-recipient@crypto.notmuchmail.org and to:"Notmuch Test Suite"')
+test_expect_equal "$output" ''
+
+test_begin_subtest "real sender is masked when cleartext is not indexed"
+output=$(notmuch search --output=messages 'id:spoofed-sender@crypto.notmuchmail.org and from:"Notmuch Test Suite"')
+test_expect_equal "$output" ''
+
+test_begin_subtest "reindex messages with spoofed headers"
+test_expect_success 'notmuch reindex --decrypt=true id:spoofed-date@crypto.notmuchmail.org or id:spoofed-recipient@crypto.notmuchmail.org or id:spoofed-sender@crypto.notmuchmail.org'
+
+test_begin_subtest "spoofed date is ignored when cleartext is indexed"
+output=$(notmuch search --output=messages 'id:spoofed-date@crypto.notmuchmail.org and date:2000-01-01')
+test_expect_equal "$output" ''
+
+test_begin_subtest "real date is used when cleartext is indexed"
+output=$(notmuch search --output=messages 'id:spoofed-date@crypto.notmuchmail.org and date:2015-12-16')
+test_expect_equal "$output" 'id:spoofed-date@crypto.notmuchmail.org'
+
+test_begin_subtest "real recipient is used when cleartext is indexed"
+output=$(notmuch search --output=messages 'id:spoofed-recipient@crypto.notmuchmail.org and to:"Notmuch Test Suite"')
+test_expect_equal "$output" 'id:spoofed-recipient@crypto.notmuchmail.org'
+
+test_begin_subtest "real sender is used when cleartext is indexed"
+output=$(notmuch search --output=messages 'id:spoofed-sender@crypto.notmuchmail.org and from:"Notmuch Test Suite"')
+test_expect_equal "$output" 'id:spoofed-sender@crypto.notmuchmail.org'
+
test_begin_subtest "verify correct protected header when submessage exists"
output=$(notmuch show --decrypt=true --format=json id:encrypted-message-with-forwarded-attachment@crypto.notmuchmail.org)
test_json_nodes <<<"$output" \
@@ -134,6 +199,9 @@ test_expect_equal "$output" 'id:encrypted-signed-not-masked@crypto.notmuchmail.o
id:encrypted-signed@crypto.notmuchmail.org
id:nested-rfc822-message@crypto.notmuchmail.org
id:protected-header@crypto.notmuchmail.org
+id:spoofed-date@crypto.notmuchmail.org
+id:spoofed-recipient@crypto.notmuchmail.org
+id:spoofed-sender@crypto.notmuchmail.org
id:subjectless-protected-header@crypto.notmuchmail.org'
test_begin_subtest "when rendering protected headers, avoid rendering legacy-display part"
diff --git a/test/corpora/protected-headers/spoofed-date.eml b/test/corpora/protected-headers/spoofed-date.eml
new file mode 100644
index 00000000..b3e5bb5a
--- /dev/null
+++ b/test/corpora/protected-headers/spoofed-date.eml
@@ -0,0 +1,28 @@
+From: test_suite@notmuchmail.org
+To: test_suite@notmuchmail.org
+Subject: Subject Unavailable
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-ID: <spoofed-date@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+ protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hF4DHXHP849rSK8SAQdALxucRFN71JbLt2GfNFt2/YrBvrshI8tGn7IzlxdE+1sw
++Pu7X4CisgRLrNP5NrkCeXd3qtdBL1oCp7Q8CE1MzagVZb3tGE9tdGjHqOHSUIcg
+0rsBt1bR4Vdunh5kgzfYVV3JqdtgQlZXF/AUrkB8LatxZ6nA9V0t/DC63dh6gUsJ
+EnbjAZq9z/k5Tjgi52+gXarl62cJQp7YSFLU2GgHCOjUMLugNPpr9J3YRtN9u7Np
+fLenbcQynXRWY0MNp+vRzWoVPi2g4A4J+L+LRKmChX/Ni6gqEU1ksXnhkX9+dZXs
+J4T6ixs1IyJxfyQCmA1bbi9exkORLdE7ZyxgYC3kSWUzivjwz68PdHUqitE+
+=XRi1
+-----END PGP MESSAGE-----
+--=-=-=--
diff --git a/test/corpora/protected-headers/spoofed-recipient.eml b/test/corpora/protected-headers/spoofed-recipient.eml
new file mode 100644
index 00000000..f9d47c57
--- /dev/null
+++ b/test/corpora/protected-headers/spoofed-recipient.eml
@@ -0,0 +1,28 @@
+From: test_suite@notmuchmail.org
+To: undisclosed-recipients: ;
+Subject: Subject Unavailable
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-ID: <spoofed-recipient@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+ protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hF4DHXHP849rSK8SAQdA15+QCv0bg+6LKzXtfj4pPyrOwkzIhCPgLd3aeMnv3WMw
+KcOKlfdgmEtI92AZFCpDErTceKpcTzVz2m9vaR3ZLIntf4Db8P/KPVTLeJla9Wy0
+0r4BEuH4GwCq/rM889/funOJBIm/amlNEAbOicBR9QJzqAkvQ1i9SsrAuA/MFA7S
+/sH3RrNT0x00UHh6i2Ho5B+GgJFBpYyijZb6dGXcMt8jMrhM/D3hQvu2VLL72h6z
+cHjqEIxYrgCXcmMKGzXFpv0WLYXVbe/BU6tcTUqzcQtRXQAImv3WQXmTx0ZXHxe0
+y4DkxxrdLMS4ogKp6iPCtqIjGV6wrBRVlcFGTh+1gmhYiAviKixx03XfRCw7YZTj
+=vcs2
+-----END PGP MESSAGE-----
+--=-=-=--
diff --git a/test/corpora/protected-headers/spoofed-sender.eml b/test/corpora/protected-headers/spoofed-sender.eml
new file mode 100644
index 00000000..ed1d2595
--- /dev/null
+++ b/test/corpora/protected-headers/spoofed-sender.eml
@@ -0,0 +1,29 @@
+From: test_suite@notmuchmail.org
+To: undisclosed-recipients: ;
+Subject: Subject Unavailable
+Date: Sat, 01 Jan 2000 12:00:00 +0000
+Message-ID: <spoofed-sender@crypto.notmuchmail.org>
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="=-=-=";
+ protocol="application/pgp-encrypted"
+
+--=-=-=
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--=-=-=
+Content-Type: application/octet-stream
+
+-----BEGIN PGP MESSAGE-----
+
+hF4DHXHP849rSK8SAQdA21XV8GyULURScZxf9wBJs5j+5z3OvXJYTKVVURbUVVAw
+sJL+xsbZY24IkCDqX6YEY/iCZgnwJMXWkgtkiapQnLhGj9NOiBa3++iuzZVJ9/26
+0sAJAZCrLPqeCBtB3WveXXrY974CFYE6q1hp27bye5hY0mNDxudEEUpfMA0t6dFV
+BhzYCkdIbZSwYglr8JM4vaHKpeMT3RgiANm9IxG4vKieLS8rFzJ54Q6EPIpROLkd
+WDpoCtfXTHj9vvr+Vx2eLdURCsdcA4Kjj4Xdujz04p5yq3UcCx/5Ml2vwKZJTVzO
+41mTVzkx52Y4iDgOupjdvAy0UIkowj/gXzhcVOL/0VRG4MMelQ3raiuNiOP19es+
+id0ZmRgEoEB0TsrY
+=DZBa
+-----END PGP MESSAGE-----
+--=-=-=--
--
2.39.5
_______________________________________________
notmuch mailing list -- notmuch@notmuchmail.org
To unsubscribe send an email to notmuch-leave@notmuchmail.org