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