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