1#!/usr/bin/env bash
2
3# TODO:
4# - decryption/verification with signer key not available
5# - verification of signatures from expired/revoked keys
6
7test_description='PGP/MIME signature verification and decryption'
8. $(dirname "$0")/test-lib.sh || exit 1
9. $NOTMUCH_SRCDIR/test/test-lib-emacs.sh || exit 1
10
11##################################################
12
13test_require_emacs
14add_gnupg_home
15
16test_begin_subtest "emacs delivery of signed message"
17test_expect_success \
18'emacs_fcc_message \
19    "test signed message 001" \
20    "This is a test signed message." \
21    "(mml-secure-message-sign)"'
22
23test_begin_subtest "signed part content-type indexing"
24output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize)
25test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)"
26
27test_begin_subtest "signature verification"
28output=$(notmuch show --format=json --verify subject:"test signed message 001" \
29    | notmuch_json_show_sanitize \
30    | sed -e 's|"created": [1234567890]*|"created": 946728000|g')
31expected='[[[{"id": "XXXXX",
32 "match": true,
33 "excluded": false,
34 "filename": ["YYYYY"],
35 "timestamp": 946728000,
36 "date_relative": "2000-01-01",
37 "tags": ["inbox","signed"],
38 "crypto": {"signed": {"status": [{ "status": "good", "created": 946728000, "email": "'"$SELF_EMAIL"'", "fingerprint": "'$FINGERPRINT'", "userid": "'"$SELF_USERID"'"}]}},
39 "headers": {"Subject": "test signed message 001",
40 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
41 "To": "test_suite@notmuchmail.org",
42 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
43 "body": [{"id": 1,
44 "sigstatus": [{"status": "good",
45 "fingerprint": "'$FINGERPRINT'",
46 "created": 946728000,
47 "email": "'"$SELF_EMAIL"'",
48 "userid": "'"$SELF_USERID"'"}],
49 "content-type": "multipart/signed",
50 "content": [{"id": 2,
51 "content-type": "text/plain",
52 "content": "This is a test signed message.\n"},
53 {"id": 3,
54 "content-type": "application/pgp-signature",
55 "content-length": "NONZERO"}]}]},
56 []]]]'
57test_expect_equal_json \
58    "$output" \
59    "$expected"
60
61test_begin_subtest "detection of modified signed contents"
62emacs_fcc_message \
63    "bad signed message 001" \
64    "Incriminating stuff. This is a test signed message." \
65    "(mml-secure-message-sign)"
66
67file=$(notmuch search --output=files subject:"bad signed message 001")
68
69sed -i 's/Incriminating stuff. //' ${file}
70
71output=$(notmuch show --format=json --verify subject:"bad signed message 001" \
72    | notmuch_json_show_sanitize \
73    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
74expected='[[[{"id": "XXXXX",
75 "match": true,
76 "excluded": false,
77 "filename": ["YYYYY"],
78 "timestamp": 946728000,
79 "date_relative": "2000-01-01",
80 "tags": ["inbox","signed"],
81 "crypto": {"signed": {"status": [{ "status": "bad", "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'"}]}},
82 "headers": {"Subject": "bad signed message 001",
83 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
84 "To": "test_suite@notmuchmail.org",
85 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
86 "body": [{"id": 1,
87 "sigstatus": [{"status": "bad",
88 "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'"}],
89 "content-type": "multipart/signed",
90 "content": [{"id": 2,
91 "content-type": "text/plain",
92 "content": "This is a test signed message.\n"},
93 {"id": 3,
94 "content-type": "application/pgp-signature",
95 "content-length": "NONZERO"}]}]},
96 []]]]'
97test_expect_equal_json \
98    "$output" \
99    "$expected"
100
101test_begin_subtest "corrupted pgp/mime signature"
102emacs_fcc_message \
103    "bad signed message 002" \
104    "Incriminating stuff. This is a test signed message." \
105    "(mml-secure-message-sign)"
106
107file=$(notmuch search --output=files subject:"bad signed message 002")
108
109awk '/-----BEGIN PGP SIGNATURE-----/{flag=1;print;next} \
110     /-----END PGP SIGNATURE-----/{flag=0;print;next} \
111     flag{gsub(/[A-Za-z]/,"0");print}!flag{print}' $file > $file.new
112
113rm $file
114mv $file.new $file
115
116output=$(notmuch show --format=json --verify subject:"bad signed message 002" \
117    | notmuch_json_show_sanitize \
118    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
119expected='[[[{"id": "XXXXX",
120 "crypto": {},
121 "match": true,
122 "excluded": false,
123 "filename": ["YYYYY"],
124 "timestamp": 946728000,
125 "date_relative": "2000-01-01",
126 "tags": ["inbox","signed"],
127 "headers": {"Subject": "bad signed message 002",
128 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
129 "To": "test_suite@notmuchmail.org",
130 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
131 "body": [{"id": 1,
132 "sigstatus": [],
133 "content-type": "multipart/signed",
134 "content": [{"id": 2,
135 "content-type": "text/plain",
136 "content": "Incriminating stuff. This is a test signed message.\n"},
137 {"id": 3,
138 "content-type": "application/pgp-signature",
139 "content-length": "NONZERO"}]}]},
140 []]]]'
141test_expect_equal_json \
142    "$output" \
143    "$expected"
144
145test_begin_subtest "signature verification without full user ID validity"
146# give the key no owner trust, removes validity on all user IDs of the
147# certificate in the absence of other trusted certifiers:
148gpg --quiet --batch --no-tty --export-ownertrust > "$GNUPGHOME/ownertrust.bak"
149echo "${FINGERPRINT}:3:" | gpg --quiet --batch --no-tty --import-ownertrust
150output=$(notmuch show --format=json --verify subject:"test signed message 001" \
151    | notmuch_json_show_sanitize \
152    | sed -e 's|"created": [1234567890]*|"created": 946728000|g')
153expected='[[[{"id": "XXXXX",
154 "match": true,
155 "excluded": false,
156 "filename": ["YYYYY"],
157 "timestamp": 946728000,
158 "date_relative": "2000-01-01",
159 "tags": ["inbox","signed"],
160 "crypto": {"signed": {"status": [{ "status": "good", "created": 946728000, "fingerprint": "'$FINGERPRINT'"}]}},
161 "headers": {"Subject": "test signed message 001",
162 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
163 "To": "test_suite@notmuchmail.org",
164 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
165 "body": [{"id": 1,
166 "sigstatus": [{"status": "good",
167 "fingerprint": "'$FINGERPRINT'",
168 "created": 946728000}],
169 "content-type": "multipart/signed",
170 "content": [{"id": 2,
171 "content-type": "text/plain",
172 "content": "This is a test signed message.\n"},
173 {"id": 3,
174 "content-type": "application/pgp-signature",
175 "content-length": "NONZERO"}]}]},
176 []]]]'
177test_expect_equal_json \
178    "$output" \
179    "$expected"
180gpg --quiet --batch --no-tty --import-ownertrust < "$GNUPGHOME/ownertrust.bak"
181
182test_begin_subtest "signature verification with signer key unavailable"
183# move the gnupghome temporarily out of the way
184mv "${GNUPGHOME}"{,.bak}
185output=$(notmuch show --format=json --verify subject:"test signed message 001" \
186    | notmuch_json_show_sanitize \
187    | sed -e 's|"created": [1234567890]*|"created": 946728000|g')
188expected='[[[{"id": "XXXXX",
189 "match": true,
190 "excluded": false,
191 "filename": ["YYYYY"],
192 "timestamp": 946728000,
193 "date_relative": "2000-01-01",
194 "tags": ["inbox","signed"],
195 "crypto": {"signed": {"status": [{"errors": {"key-missing": true}, "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'", "status": "error"}]}},
196 "headers": {"Subject": "test signed message 001",
197 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
198 "To": "test_suite@notmuchmail.org",
199 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
200 "body": [{"id": 1,
201 "sigstatus": [{"status": "error",
202 "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'",
203 "errors": {"key-missing": true}}],
204 "content-type": "multipart/signed",
205 "content": [{"id": 2,
206 "content-type": "text/plain",
207 "content": "This is a test signed message.\n"},
208 {"id": 3,
209 "content-type": "application/pgp-signature",
210 "content-length": "NONZERO"}]}]},
211 []]]]'
212test_expect_equal_json \
213    "$output" \
214    "$expected"
215mv "${GNUPGHOME}"{.bak,}
216
217test_begin_subtest "emacs delivery of encrypted message with attachment"
218# create a test encrypted message with attachment
219cat <<EOF >TESTATTACHMENT
220This is a test file.
221EOF
222test_expect_success \
223'emacs_fcc_message \
224    "test encrypted message 001" \
225    "This is a test encrypted message.\n" \
226    "(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
227
228test_begin_subtest "encrypted part content-type indexing"
229output=$(notmuch search mimetype:multipart/encrypted and mimetype:application/pgp-encrypted and mimetype:application/octet-stream | notmuch_search_sanitize)
230test_expect_equal "$output" "thread:XXX   2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
231
232test_begin_subtest "decryption, --format=text"
233output=$(notmuch show --format=text --decrypt=true subject:"test encrypted message 001" \
234    | notmuch_show_sanitize_all \
235    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
236expected='message{ id:XXXXX depth:0 match:1 excluded:0 filename:XXXXX
237header{
238Notmuch Test Suite <test_suite@notmuchmail.org> (2000-01-01) (encrypted inbox)
239Subject: test encrypted message 001
240From: Notmuch Test Suite <test_suite@notmuchmail.org>
241To: test_suite@notmuchmail.org
242Date: Sat, 01 Jan 2000 12:00:00 +0000
243header}
244body{
245part{ ID: 1, Content-type: multipart/encrypted
246part{ ID: 2, Content-type: application/pgp-encrypted
247Non-text part: application/pgp-encrypted
248part}
249part{ ID: 3, Content-type: multipart/mixed
250part{ ID: 4, Content-type: text/plain
251This is a test encrypted message.
252part}
253attachment{ ID: 5, Filename: TESTATTACHMENT, Content-type: application/octet-stream
254Non-text part: application/octet-stream
255attachment}
256part}
257part}
258body}
259message}'
260test_expect_equal \
261    "$output" \
262    "$expected"
263
264test_begin_subtest "decryption, --format=json"
265output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
266    | notmuch_json_show_sanitize \
267    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
268expected='[[[{"id": "XXXXX",
269 "match": true,
270 "excluded": false,
271 "filename": ["YYYYY"],
272 "timestamp": 946728000,
273 "date_relative": "2000-01-01",
274 "tags": ["encrypted","inbox"],
275 "crypto": {"decrypted": {"status": "full"}},
276 "headers": {"Subject": "test encrypted message 001",
277 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
278 "To": "test_suite@notmuchmail.org",
279 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
280 "body": [{"id": 1,
281 "encstatus": [{"status": "good"}],
282 "content-type": "multipart/encrypted",
283 "content": [{"id": 2,
284 "content-type": "application/pgp-encrypted",
285 "content-length": "NONZERO"},
286 {"id": 3,
287 "content-type": "multipart/mixed",
288 "content": [{"id": 4,
289 "content-type": "text/plain",
290 "content": "This is a test encrypted message.\n"},
291 {"id": 5,
292 "content-type": "application/octet-stream",
293 "content-disposition": "attachment",
294 "content-length": "NONZERO",
295 "content-transfer-encoding": "base64",
296 "filename": "TESTATTACHMENT"}]}]}]},
297 []]]]'
298test_expect_equal_json \
299    "$output" \
300    "$expected"
301
302test_begin_subtest "decryption, --format=json, --part=4"
303output=$(notmuch show --format=json --part=4 --decrypt=true subject:"test encrypted message 001" \
304    | notmuch_json_show_sanitize \
305    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
306expected='{"id": 4,
307 "content-type": "text/plain",
308 "content": "This is a test encrypted message.\n"}'
309test_expect_equal_json \
310    "$output" \
311    "$expected"
312
313test_begin_subtest "decrypt attachment (--part=5 --format=raw)"
314notmuch show \
315    --format=raw \
316    --part=5 \
317    --decrypt=true \
318    subject:"test encrypted message 001" >OUTPUT
319test_expect_equal_file TESTATTACHMENT OUTPUT
320
321test_begin_subtest "decryption failure with missing key"
322mv "${GNUPGHOME}"{,.bak}
323output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 001" \
324    | notmuch_json_show_sanitize \
325    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
326expected='[[[{"id": "XXXXX",
327 "crypto": {},
328 "match": true,
329 "excluded": false,
330 "filename": ["YYYYY"],
331 "timestamp": 946728000,
332 "date_relative": "2000-01-01",
333 "tags": ["encrypted","inbox"],
334 "headers": {"Subject": "test encrypted message 001",
335 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
336 "To": "test_suite@notmuchmail.org",
337 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
338 "body": [{"id": 1,
339 "encstatus": [{"status": "bad"}],
340 "content-type": "multipart/encrypted",
341 "content": [{"id": 2,
342 "content-type": "application/pgp-encrypted",
343 "content-length": "NONZERO"},
344 {"id": 3,
345 "content-type": "application/octet-stream",
346 "content-length": "NONZERO"}]}]},
347 []]]]'
348test_expect_equal_json \
349    "$output" \
350    "$expected"
351mv "${GNUPGHOME}"{.bak,}
352
353test_begin_subtest "emacs delivery of encrypted + signed message"
354test_expect_success \
355'emacs_fcc_message \
356    "test encrypted message 002" \
357    "This is another test encrypted message.\n" \
358    "(mml-secure-message-sign-encrypt)"'
359
360test_begin_subtest "decryption + signature verification"
361output=$(notmuch show --format=json --decrypt=true subject:"test encrypted message 002" \
362    | notmuch_json_show_sanitize \
363    | sed -e 's|"created": [1234567890]*|"created": 946728000|g')
364expected='[[[{"id": "XXXXX",
365 "match": true,
366 "excluded": false,
367 "filename": ["YYYYY"],
368 "timestamp": 946728000,
369 "date_relative": "2000-01-01",
370 "tags": ["encrypted","inbox"],
371 "crypto": {"signed": {"status": [{ "status": "good", "created": 946728000, "fingerprint": "'$FINGERPRINT'", "email": "'"$SELF_EMAIL"'", "userid": "'"$SELF_USERID"'"}],
372                       "encrypted": true },
373            "decrypted": {"status": "full"}},
374 "headers": {"Subject": "test encrypted message 002",
375 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
376 "To": "test_suite@notmuchmail.org",
377 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
378 "body": [{"id": 1,
379 "encstatus": [{"status": "good"}],
380 "sigstatus": [{"status": "good",
381 "fingerprint": "'$FINGERPRINT'",
382 "created": 946728000,
383 "email": "'"$SELF_EMAIL"'",
384 "userid": "'"$SELF_USERID"'"}],
385 "content-type": "multipart/encrypted",
386 "content": [{"id": 2,
387 "content-type": "application/pgp-encrypted",
388 "content-length": "NONZERO"},
389 {"id": 3,
390 "content-type": "text/plain",
391 "content": "This is another test encrypted message.\n"}]}]},
392 []]]]'
393test_expect_equal_json \
394    "$output" \
395    "$expected"
396
397test_begin_subtest "reply to encrypted message"
398output=$(notmuch reply --decrypt=true subject:"test encrypted message 002" \
399    | notmuch_drop_mail_headers In-Reply-To References)
400expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
401Subject: Re: test encrypted message 002
402To: test_suite@notmuchmail.org
403
404On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:
405> This is another test encrypted message.'
406test_expect_equal \
407    "$output" \
408    "$expected"
409
410test_begin_subtest "Reply within emacs to an encrypted message"
411test_emacs "(let ((message-hidden-headers '())
412      (notmuch-crypto-process-mime 't))
413  (notmuch-show \"subject:test.encrypted.message.002\")
414  (notmuch-show-reply)
415  (test-output))"
416grep -v -e '^In-Reply-To:' -e '^References:' -e '^Fcc:' < OUTPUT > OUTPUT.clean
417cat <<EOF >EXPECTED
418From: Notmuch Test Suite <test_suite@notmuchmail.org>
419To: test_suite@notmuchmail.org
420Subject: Re: test encrypted message 002
421--text follows this line--
422<#secure method=pgpmime mode=signencrypt>
423Notmuch Test Suite <test_suite@notmuchmail.org> writes:
424
425> This is another test encrypted message.
426EOF
427test_expect_equal_file EXPECTED OUTPUT.clean
428
429test_begin_subtest "signature verification with revoked key"
430# generate revocation certificate and load it to revoke key
431echo "y
4321
433Notmuch Test Suite key revocation (automated) $(date '+%F_%T%z')
434
435y
436
437" \
438    | gpg --no-tty --quiet --command-fd 0 --armor --gen-revoke "0x${FINGERPRINT}!" 2>/dev/null \
439    | gpg --no-tty --quiet --import
440output=$(notmuch show --format=json --verify subject:"test signed message 001" \
441    | notmuch_json_show_sanitize \
442    | sed -e 's|"created": [1234567890]*|"created": 946728000|')
443expected='[[[{"id": "XXXXX",
444 "match": true,
445 "excluded": false,
446 "filename": ["YYYYY"],
447 "timestamp": 946728000,
448 "date_relative": "2000-01-01",
449 "tags": ["inbox","signed"],
450 "crypto": {"signed": {"status": [{"errors": {"key-revoked": true}, "keyid": "'$(echo $FINGERPRINT | cut -c 25-)'", "status": "error"}]}},
451 "headers": {"Subject": "test signed message 001",
452 "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
453 "To": "test_suite@notmuchmail.org",
454 "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
455 "body": [{"id": 1,
456 "sigstatus": [{"status": "error",
457 "keyid": "6D92612D94E46381",
458 "errors": {"key-revoked": true}}],
459 "content-type": "multipart/signed",
460 "content": [{"id": 2,
461 "content-type": "text/plain",
462 "content": "This is a test signed message.\n"},
463 {"id": 3,
464 "content-type": "application/pgp-signature",
465 "content-length": "NONZERO"}]}]},
466 []]]]'
467test_expect_equal_json \
468    "$output" \
469    "$expected"
470
471test_done
472