58b2a03ff944ee646e1ea2ee63d8abd7f0f2140d
[gnupg.git] / tests / openpgp / tofu.scm
1 #!/usr/bin/env gpgscm
2
3 ;; Copyright (C) 2016 g10 Code GmbH
4 ;;
5 ;; This file is part of GnuPG.
6 ;;
7 ;; GnuPG is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 3 of the License, or
10 ;; (at your option) any later version.
11 ;;
12 ;; GnuPG is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;; GNU General Public License for more details.
16 ;;
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; if not, see <http://www.gnu.org/licenses/>.
19
20 (load (in-srcdir "tests" "openpgp" "defs.scm"))
21 (load (with-path "time.scm"))
22 (setup-environment)
23
24 (define GPGTIME 1480943782)
25
26 ;; Generate a --faked-system-time parameter for a particular offset.
27 (define (faketime delta)
28   (string-append "--faked-system-time=" (number->string (+ GPGTIME delta))))
29
30 ;; Redefine GPG without --always-trust and a fixed time.
31 (define GPG `(,(tool 'gpg) --no-permission-warning ,(faketime 0)))
32
33 (catch (skip "Tofu not supported")
34        (call-check `(,@GPG --trust-model=tofu --list-config)))
35
36 (let ((trust-model (gpg-config 'gpg "trust-model")))
37   (trust-model::update "tofu"))
38
39 (define KEYS '("1C005AF3" "BE04EB2B" "B662E42F"))
40
41 ;; Import the test keys.
42 (for-each (lambda (keyid)
43             (call-check `(,@GPG --import
44                                 ,(in-srcdir "tests" "openpgp" "tofu" "conflicting"
45                                             (string-append keyid ".gpg"))))
46             (catch (fail "Missing key" keyid)
47                    (call-check `(,@GPG --list-keys ,keyid))))
48           KEYS)
49
50 ;; Get tofu policy for KEYID.  Any remaining arguments are simply
51 ;; passed to GPG.
52 ;;
53 ;; This function only supports keys with a single user id.
54 (define (getpolicy keyid . args)
55   (let ((policy
56          (list-ref (assoc "tfs" (gpg-with-colons
57                                  `(--with-tofu-info
58                                    ,@args
59                                    --list-keys ,keyid))) 5)))
60     (unless (member policy '("auto" "good" "unknown" "bad" "ask"))
61             (fail "Bad policy:" policy))
62     policy))
63
64 ;; Check that KEYID's tofu policy matches EXPECTED-POLICY.  Any
65 ;; remaining arguments are simply passed to GPG.
66 ;;
67 ;; This function only supports keys with a single user id.
68 (define (checkpolicy keyid expected-policy . args)
69   (let ((policy (apply getpolicy `(,keyid ,@args))))
70     (unless (string=? policy expected-policy)
71             (fail keyid ": Expected policy to be" expected-policy
72                    "but got" policy))))
73
74 ;; Get the trust level for KEYID.  Any remaining arguments are simply
75 ;; passed to GPG.
76 ;;
77 ;; This function only supports keys with a single user id.
78 (define (gettrust keyid . args)
79   (let ((trust
80          (list-ref (assoc "pub" (gpg-with-colons
81                                  `(,@args
82                                    --list-keys ,keyid))) 1)))
83     (unless (and (= 1 (string-length trust))
84                  (member (string-ref trust 0) (string->list "oidreqnmfuws-")))
85             (fail "Bad trust value:" trust))
86     trust))
87
88 ;; Check that KEYID's trust level matches EXPECTED-TRUST.  Any
89 ;; remaining arguments are simply passed to GPG.
90 ;;
91 ;; This function only supports keys with a single user id.
92 (define (checktrust keyid expected-trust . args)
93   (let ((trust (apply gettrust `(,keyid ,@args))))
94     (unless (string=? trust expected-trust)
95             (fail keyid ": Expected trust to be" expected-trust
96                    "but got" trust))))
97
98 ;; Set key KEYID's policy to POLICY.  Any remaining arguments are
99 ;; passed as options to gpg.
100 (define (setpolicy keyid policy . args)
101   (call-check `(,@GPG ,@args
102                       --tofu-policy ,policy ,keyid)))
103
104 (info "Checking tofu policies and trust...")
105
106 ;; Carefully remove the TOFU db.
107 (catch '() (unlink (path-join GNUPGHOME "tofu.db")))
108
109 ;; Verify a message.  There should be no conflict and the trust
110 ;; policy should be set to auto.
111 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-1.txt")))
112
113 (checkpolicy "1C005AF3" "auto")
114 ;; Check default trust.
115 (checktrust "1C005AF3" "m")
116
117 ;; Trust should be derived lazily.  Thus, if the policy is set to
118 ;; auto and we change --tofu-default-policy, then the trust should
119 ;; change as well.  Try it.
120 (checktrust "1C005AF3" "f" '--tofu-default-policy=good)
121 (checktrust "1C005AF3" "-" '--tofu-default-policy=unknown)
122 (checktrust "1C005AF3" "n" '--tofu-default-policy=bad)
123 (checktrust "1C005AF3" "q" '--tofu-default-policy=ask)
124
125 ;; Change the policy to something other than auto and make sure the
126 ;; policy and the trust are correct.
127 (for-each-p
128  "Setting a fixed policy..."
129  (lambda (policy)
130    (let ((expected-trust
131           (cond
132            ((string=? "good" policy) "f")
133            ((string=? "unknown" policy) "-")
134            (else "n"))))
135      (setpolicy "1C005AF3" policy)
136
137      ;; Since we have a fixed policy, the trust level shouldn't
138      ;; change if we change the default policy.
139      (for-each-p
140       ""
141       (lambda (default-policy)
142         (checkpolicy "1C005AF3" policy
143                      '--tofu-default-policy default-policy)
144         (checktrust "1C005AF3" expected-trust
145                     '--tofu-default-policy default-policy))
146       '("auto" "good" "unknown" "bad" "ask"))))
147  '("good" "unknown" "bad"))
148
149 ;; At the end, 1C005AF3's policy should be bad.
150 (checkpolicy "1C005AF3" "bad")
151
152 ;; 1C005AF3 and BE04EB2B conflict.  A policy setting of "auto"
153 ;; (BE04EB2B's state) will result in an effective policy of ask.  But,
154 ;; a policy setting of "bad" will result in an effective policy of
155 ;; bad.
156 (setpolicy "BE04EB2B" "auto")
157 (checkpolicy "BE04EB2B" "ask")
158 (checkpolicy "1C005AF3" "bad")
159
160 ;; 1C005AF3, B662E42F, and BE04EB2B conflict.  We change BE04EB2B's
161 ;; policy to auto and leave 1C005AF3's policy at bad.  This conflict
162 ;; should cause BE04EB2B's effective policy to be ask (since it is
163 ;; auto), but not affect 1C005AF3's policy.
164 (setpolicy "BE04EB2B" "auto")
165 (checkpolicy "BE04EB2B" "ask")
166 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "B662E42F-1.txt")))
167 (checkpolicy "BE04EB2B" "ask")
168 (checkpolicy "1C005AF3" "bad")
169 (checkpolicy "B662E42F" "ask")
170
171 ;; Check that the stats are emitted correctly.
172
173 (display "Checking TOFU stats...\n")
174
175 (define (check-counts keyid expected-sigs expected-sig-days
176                       expected-encs expected-enc-days . args)
177   (let*
178       ((tfs (assoc "tfs"
179                    (gpg-with-colons
180                     `(--with-tofu-info ,@args --list-keys ,keyid))))
181        (sigs (string->number (list-ref tfs 3)))
182        (sig-days (string->number (list-ref tfs 11)))
183        (encs (string->number (list-ref tfs 4)))
184        (enc-days (string->number (list-ref tfs 12)))
185        )
186     ; (display keyid) (display ": ") (display tfs) (display "\n")
187     (unless (= sigs expected-sigs)
188             (fail keyid ": # signatures (" sigs ") does not match expected"
189                    "# signatures (" expected-sigs ").\n"))
190     (unless (= sig-days expected-sig-days)
191             (fail keyid ": # signature days (" sig-days ")"
192                   "does not match expected"
193                   "# signature days (" expected-sig-days ").\n"))
194     (unless (= encs expected-encs)
195             (fail keyid ": # encryptions (" encs ") does not match expected"
196                    "# encryptions (" expected-encs ").\n"))
197     (unless (= enc-days expected-enc-days)
198             (fail keyid ": # encryption days (" encs ")"
199                   "does not match expected"
200                   "# encryption days (" expected-enc-days ").\n"))
201     ))
202
203 ;; Carefully remove the TOFU db.
204 (catch '() (unlink (path-join GNUPGHOME "tofu.db")))
205
206 (check-counts "1C005AF3" 0 0 0 0)
207 (check-counts "BE04EB2B" 0 0 0 0)
208 (check-counts "B662E42F" 0 0 0 0)
209
210 ;; Verify a message.  The signature count should increase by 1.
211 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-1.txt")))
212
213 (check-counts "1C005AF3" 1 1 0 0)
214
215 ;; Verify the same message.  The signature count should remain the
216 ;; same.
217 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-1.txt")))
218 (check-counts "1C005AF3" 1 1 0 0)
219
220 ;; Verify another message.
221 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-2.txt")))
222 (check-counts "1C005AF3" 2 1 0 0)
223
224 ;; Verify another message.
225 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-3.txt")))
226 (check-counts "1C005AF3" 3 1 0 0)
227
228 ;; Verify a message from a different sender.  The signature count
229 ;; should increase by 1 for that key.
230 (call-check `(,@GPG --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "BE04EB2B-1.txt")))
231 (check-counts "1C005AF3" 3 1 0 0)
232 (check-counts "BE04EB2B" 1 1 0 0)
233 (check-counts "B662E42F" 0 0 0 0)
234
235 ;; Verify another message on a new day.  (Recall: we are interested in
236 ;; when the message was first verified, not when the signer claimed
237 ;; that it was signed.)
238 (call-check `(,@GPG ,(faketime (days->seconds 2))
239                     --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-4.txt")))
240 (check-counts "1C005AF3" 4 2 0 0)
241 (check-counts "BE04EB2B" 1 1 0 0)
242 (check-counts "B662E42F" 0 0 0 0)
243
244 ;; And another.
245 (call-check `(,@GPG ,(faketime (days->seconds 2))
246                     --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "1C005AF3-5.txt")))
247 (check-counts "1C005AF3" 5 2 0 0)
248 (check-counts "BE04EB2B" 1 1 0 0)
249 (check-counts "B662E42F" 0 0 0 0)
250
251 ;; Another, but for a different key.
252 (call-check `(,@GPG ,(faketime (days->seconds 2))
253                     --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "BE04EB2B-2.txt")))
254 (check-counts "1C005AF3" 5 2 0 0)
255 (check-counts "BE04EB2B" 2 2 0 0)
256 (check-counts "B662E42F" 0 0 0 0)
257
258 ;; And add a third day.
259 (call-check `(,@GPG ,(faketime (days->seconds 4))
260                     --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "BE04EB2B-3.txt")))
261 (check-counts "1C005AF3" 5 2 0 0)
262 (check-counts "BE04EB2B" 3 3 0 0)
263 (check-counts "B662E42F" 0 0 0 0)
264
265 (call-check `(,@GPG ,(faketime (days->seconds 4))
266                     --verify ,(in-srcdir "tests" "openpgp" "tofu" "conflicting" "BE04EB2B-4.txt")))
267 (check-counts "1C005AF3" 5 2 0 0)
268 (check-counts "BE04EB2B" 4 3 0 0)
269 (check-counts "B662E42F" 0 0 0 0)
270
271 ;; Check that we detect the following attack:
272 ;;
273 ;; Alice and Bob each have a key and cross sign them.  Bob then adds a
274 ;; new user id, "Alice".  TOFU should now detect a conflict, because
275 ;; Alice only signed Bob's "Bob" user id.
276
277 (display "Checking cross sigs...\n")
278 (define GPG `(,(tool 'gpg) --no-permission-warning
279               --faked-system-time=1476304861))
280
281 ;; Carefully remove the TOFU db.
282 (catch '() (unlink (path-join GNUPGHOME "tofu.db")))
283
284 (define DIR "tofu/cross-sigs")
285 ;; The test keys.
286 (define KEYA "1938C3A0E4674B6C217AC0B987DB2814EC38277E")
287 (define KEYB "DC463A16E42F03240D76E8BA8B48C6BD871C2247")
288 (define KEYIDA (substring KEYA (- (string-length KEYA) 8)))
289 (define KEYIDB (substring KEYB (- (string-length KEYB) 8)))
290
291 (define (verify-messages)
292   (for-each
293    (lambda (key)
294      (for-each
295       (lambda (i)
296         (let ((fn (in-srcdir "tests" "openpgp" DIR (string-append key "-" i ".txt"))))
297           (call-check `(,@GPG --verify ,fn))))
298       (list "1" "2")))
299    (list KEYIDA KEYIDB)))
300
301 ;; Import the public keys.
302 (display "    > Two keys. ")
303 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDA "-1.gpg"))))
304 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-1.gpg"))))
305 ;; Make sure the tofu engine registers the keys.
306 (verify-messages)
307 (display "<\n")
308
309 ;; Since there is no conflict, the policy should be auto.
310 (checkpolicy KEYA "auto")
311 (checkpolicy KEYB "auto")
312
313 ;; Import the cross sigs.
314 (display "    > Adding cross signatures. ")
315 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDA "-2.gpg"))))
316 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-2.gpg"))))
317 (verify-messages)
318 (display "<\n")
319
320 ;; There is still no conflict, so the policy shouldn't have changed.
321 (checkpolicy KEYA "auto")
322 (checkpolicy KEYB "auto")
323
324 ;; Import the conflicting user id.
325 (display "    > Adding conflicting user id. ")
326 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-3.gpg"))))
327 (verify-messages)
328 (display "<\n")
329
330 (checkpolicy KEYA "ask")
331 (checkpolicy KEYB "ask")
332
333 ;; Import Alice's signature on the conflicting user id.  Since there
334 ;; is now a cross signature, we should revert to the default policy.
335 (display "    > Adding cross signature on user id. ")
336 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-4.gpg"))))
337 (verify-messages)
338 (display "<\n")
339
340 (checkpolicy KEYA "auto")
341 (checkpolicy KEYB "auto")
342
343 ;; Remove the keys.
344 (call-check `(,@GPG --delete-key ,KEYA))
345 (call-check `(,@GPG --delete-key ,KEYB))
346
347
348 ;; Check that we detect the following attack:
349 ;;
350 ;; Alice has an ultimately trusted key and she signs Bob's key.  Then
351 ;; Bob adds a new user id, "Alice".  TOFU should now detect a
352 ;; conflict, because Alice only signed Bob's "Bob" user id.
353 ;;
354 ;;
355 ;; The Alice key:
356 ;;   pub   rsa2048 2016-10-11 [SC]
357 ;;         1938C3A0E4674B6C217AC0B987DB2814EC38277E
358 ;;   uid           [ultimate] Spy Cow <spy@cow.com>
359 ;;   sub   rsa2048 2016-10-11 [E]
360 ;;
361 ;; The Bob key:
362 ;;
363 ;;   pub   rsa2048 2016-10-11 [SC]
364 ;;         DC463A16E42F03240D76E8BA8B48C6BD871C2247
365 ;;   uid           [  full  ] Spy R. Cow <spy@cow.com>
366 ;;   uid           [  full  ] Spy R. Cow <spy@cow.de>
367 ;;   sub   rsa2048 2016-10-11 [E]
368
369 (display "Checking UTK sigs...\n")
370 (define GPG `(,(tool 'gpg) --no-permission-warning
371               --faked-system-time=1476304861))
372
373 ;; Carefully remove the TOFU db.
374 (catch '() (unlink (path-join GNUPGHOME "tofu.db")))
375
376 (define DIR "tofu/cross-sigs")
377 ;; The test keys.
378 (define KEYA "1938C3A0E4674B6C217AC0B987DB2814EC38277E")
379 (define KEYB "DC463A16E42F03240D76E8BA8B48C6BD871C2247")
380 (define KEYIDA (substring KEYA (- (string-length KEYA) 8)))
381 (define KEYIDB (substring KEYB (- (string-length KEYB) 8)))
382
383 (define (verify-messages)
384   (for-each
385    (lambda (key)
386      (for-each
387       (lambda (i)
388         (let ((fn (in-srcdir "tests" "openpgp" DIR (string-append key "-" i ".txt"))))
389           (call-check `(,@GPG --verify ,fn))))
390       (list "1" "2")))
391    (list KEYIDA KEYIDB)))
392
393 ;; Import the public keys.
394 (display "    > Two keys. ")
395 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDA "-1.gpg"))))
396 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-1.gpg"))))
397 (display "<\n")
398
399 (checkpolicy KEYA "auto")
400 (checkpolicy KEYB "auto")
401
402 ;; Import the cross sigs.
403 (display "    > Adding cross signatures. ")
404 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDA "-2.gpg"))))
405 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-2.gpg"))))
406 (display "<\n")
407
408 (checkpolicy KEYA "auto")
409 (checkpolicy KEYB "auto")
410
411 ;; Make KEYA ultimately trusted.
412 (display (string-append "    > Marking " KEYA " as ultimately trusted. "))
413 (pipe:do
414  (pipe:echo (string-append KEYA ":6:\n"))
415  (pipe:gpg `(--import-ownertrust)))
416 (display "<\n")
417
418 ;; An ultimately trusted key's policy is good.
419 (checkpolicy KEYA "good")
420 ;; A key signed by a UTK for which there is no policy gets the default
421 ;; policy of good.
422 (checkpolicy KEYB "good")
423
424 ;; Import the conflicting user id.
425 (display "    > Adding conflicting user id. ")
426 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-3.gpg"))))
427 (verify-messages)
428 (display "<\n")
429
430 (checkpolicy KEYA "good")
431 (checkpolicy KEYB "ask")
432
433 ;; Import Alice's signature on the conflicting user id.
434 (display "    > Adding cross signature on user id. ")
435 (call-check `(,@GPG --import ,(in-srcdir "tests" "openpgp" DIR (string-append KEYIDB "-4.gpg"))))
436 (verify-messages)
437 (display "<\n")
438
439 (checkpolicy KEYA "good")
440 (checkpolicy KEYB "good")
441
442 ;; Remove the keys.
443 (call-check `(,@GPG --delete-key ,KEYA))
444 (call-check `(,@GPG --delete-key ,KEYB))