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