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