projects/guix·blob bootstrap-team · refs

477 lines · 23.1 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020, 2022 Ludovic Courtès <[email protected]>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (test-git-authenticate)
20 #:use-module (git)
21 #:use-module (guix git)
22 #:use-module (guix git-authenticate)
23 #:use-module ((guix channels) #:select (openpgp-fingerprint))
24 #:use-module ((guix diagnostics)
25 #:select (formatted-message? formatted-message-arguments))
26 #:use-module (guix openpgp)
27 #:use-module ((guix tests) #:select (random-text))
28 #:use-module (guix tests git)
29 #:use-module (guix tests gnupg)
30 #:use-module (guix build utils)
31 #:use-module (srfi srfi-1)
32 #:use-module (srfi srfi-34)
33 #:use-module (srfi srfi-35)
34 #:use-module (srfi srfi-64)
35 #:use-module (rnrs bytevectors)
36 #:use-module (rnrs io ports))
37
38;; Test the (guix git-authenticate) tools.
39
40(define (gpg+git-available?)
41 (and (which (git-command))
42 (which (gpg-command)) (which (gpgconf-command))))
43
44
45(test-begin "git-authenticate")
46
47(test-assert "unsigned commits"
48 (with-temporary-git-repository directory
49 '((add "a.txt" "A")
50 (commit "first commit")
51 (add "b.txt" "B")
52 (commit "second commit"))
53 (with-repository directory repository
54 (let ((commit1 (find-commit repository "first"))
55 (commit2 (find-commit repository "second")))
56 (guard (c ((unsigned-commit-error? c)
57 (oid=? (git-authentication-error-commit c)
58 (commit-id commit1))))
59 (authenticate-commits repository (list commit1 commit2)
60 #:keyring-reference "master")
61 'failed)))))
62
63(unless (gpg+git-available?) (test-skip 1))
64(test-assert "signed commits, SHA1 signature"
65 (with-fresh-gnupg-setup (list %ed25519-public-key-file
66 %ed25519-secret-key-file)
67 ;; Force use of SHA1 for signatures.
68 (call-with-output-file (string-append (getenv "GNUPGHOME") "/gpg.conf")
69 (lambda (port)
70 (display "digest-algo sha1" port)))
71
72 (with-temporary-git-repository directory
73 `((add "a.txt" "A")
74 (add "signer.key" ,(call-with-input-file %ed25519-public-key-file
75 get-string-all))
76 (add ".guix-authorizations"
77 ,(object->string
78 `(authorizations (version 0)
79 ((,(key-fingerprint %ed25519-public-key-file)
80 (name "Charlie"))))))
81 (commit "first commit"
82 (signer ,(key-fingerprint %ed25519-public-key-file))))
83 (with-repository directory repository
84 (let ((commit (find-commit repository "first")))
85 (guard (c ((unsigned-commit-error? c)
86 (oid=? (git-authentication-error-commit c)
87 (commit-id commit))))
88 (authenticate-commits repository (list commit)
89 #:keyring-reference "master")
90 'failed))))))
91
92(unless (gpg+git-available?) (test-skip 1))
93(test-assert "signed commits, default authorizations"
94 (with-fresh-gnupg-setup (list %ed25519-public-key-file
95 %ed25519-secret-key-file)
96 (with-temporary-git-repository directory
97 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
98 get-string-all))
99 (commit "zeroth commit")
100 (add "a.txt" "A")
101 (commit "first commit"
102 (signer ,(key-fingerprint %ed25519-public-key-file)))
103 (add "b.txt" "B")
104 (commit "second commit"
105 (signer ,(key-fingerprint %ed25519-public-key-file))))
106 (with-repository directory repository
107 (let ((commit1 (find-commit repository "first"))
108 (commit2 (find-commit repository "second")))
109 (authenticate-commits repository (list commit1 commit2)
110 #:default-authorizations
111 (list (openpgp-public-key-fingerprint
112 (read-openpgp-packet
113 %ed25519-public-key-file)))
114 #:keyring-reference "master"))))))
115
116(unless (gpg+git-available?) (test-skip 1))
117(test-assert "signed commits, .guix-authorizations"
118 (with-fresh-gnupg-setup (list %ed25519-public-key-file
119 %ed25519-secret-key-file)
120 (with-temporary-git-repository directory
121 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
122 get-string-all))
123 (add ".guix-authorizations"
124 ,(object->string
125 `(authorizations (version 0)
126 ((,(key-fingerprint
127 %ed25519-public-key-file)
128 (name "Charlie"))))))
129 (commit "zeroth commit")
130 (add "a.txt" "A")
131 (commit "first commit"
132 (signer ,(key-fingerprint %ed25519-public-key-file)))
133 (add ".guix-authorizations"
134 ,(object->string `(authorizations (version 0) ()))) ;empty
135 (commit "second commit"
136 (signer ,(key-fingerprint %ed25519-public-key-file)))
137 (add "b.txt" "B")
138 (commit "third commit"
139 (signer ,(key-fingerprint %ed25519-public-key-file))))
140 (with-repository directory repository
141 (let ((commit1 (find-commit repository "first"))
142 (commit2 (find-commit repository "second"))
143 (commit3 (find-commit repository "third")))
144 ;; COMMIT1 and COMMIT2 are fine.
145 (and (authenticate-commits repository (list commit1 commit2)
146 #:keyring-reference "master")
147
148 ;; COMMIT3 is signed by an unauthorized key according to its
149 ;; parent's '.guix-authorizations' file.
150 (guard (c ((unauthorized-commit-error? c)
151 (and (oid=? (git-authentication-error-commit c)
152 (commit-id commit3))
153 (bytevector=?
154 (openpgp-public-key-fingerprint
155 (unauthorized-commit-error-signing-key c))
156 (openpgp-public-key-fingerprint
157 (read-openpgp-packet
158 %ed25519-public-key-file))))))
159 (authenticate-commits repository
160 (list commit1 commit2 commit3)
161 #:keyring-reference "master")
162 'failed)))))))
163
164(unless (gpg+git-available?) (test-skip 1))
165(test-assert "signed commits, .guix-authorizations, unauthorized merge"
166 (with-fresh-gnupg-setup (list %ed25519-public-key-file
167 %ed25519-secret-key-file
168 %ed25519-2-public-key-file
169 %ed25519-2-secret-key-file)
170 (with-temporary-git-repository directory
171 `((add "signer1.key"
172 ,(call-with-input-file %ed25519-public-key-file
173 get-string-all))
174 (add "signer2.key"
175 ,(call-with-input-file %ed25519-2-public-key-file
176 get-string-all))
177 (add ".guix-authorizations"
178 ,(object->string
179 `(authorizations (version 0)
180 ((,(key-fingerprint
181 %ed25519-public-key-file)
182 (name "Alice"))))))
183 (commit "zeroth commit")
184 (add "a.txt" "A")
185 (commit "first commit"
186 (signer ,(key-fingerprint %ed25519-public-key-file)))
187 (branch "devel")
188 (checkout "devel")
189 (add "devel/1.txt" "1")
190 (commit "first devel commit"
191 (signer ,(key-fingerprint %ed25519-2-public-key-file)))
192 (checkout "master")
193 (add "b.txt" "B")
194 (commit "second commit"
195 (signer ,(key-fingerprint %ed25519-public-key-file)))
196 (merge "devel" "merge"
197 (signer ,(key-fingerprint %ed25519-public-key-file))))
198 (with-repository directory repository
199 (let ((master1 (find-commit repository "first commit"))
200 (master2 (find-commit repository "second commit"))
201 (devel1 (find-commit repository "first devel commit"))
202 (merge (find-commit repository "merge")))
203 (define (correct? c commit)
204 (and (oid=? (git-authentication-error-commit c)
205 (commit-id commit))
206 (bytevector=?
207 (openpgp-public-key-fingerprint
208 (unauthorized-commit-error-signing-key c))
209 (openpgp-public-key-fingerprint
210 (read-openpgp-packet %ed25519-2-public-key-file)))))
211
212 (and (authenticate-commits repository (list master1 master2)
213 #:keyring-reference "master")
214
215 ;; DEVEL1 is signed by an unauthorized key according to its
216 ;; parent's '.guix-authorizations' file.
217 (guard (c ((unauthorized-commit-error? c)
218 (correct? c devel1)))
219 (authenticate-commits repository
220 (list master1 devel1)
221 #:keyring-reference "master")
222 #f)
223
224 ;; MERGE is authorized but one of its ancestors is not.
225 (guard (c ((unauthorized-commit-error? c)
226 (correct? c devel1)))
227 (authenticate-commits repository
228 (list master1 master2
229 devel1 merge)
230 #:keyring-reference "master")
231 #f)))))))
232
233(unless (gpg+git-available?) (test-skip 1))
234(test-assert "signed commits, .guix-authorizations, authorized merge"
235 (with-fresh-gnupg-setup (list %ed25519-public-key-file
236 %ed25519-secret-key-file
237 %ed25519-2-public-key-file
238 %ed25519-2-secret-key-file)
239 (with-temporary-git-repository directory
240 `((add "signer1.key"
241 ,(call-with-input-file %ed25519-public-key-file
242 get-string-all))
243 (add "signer2.key"
244 ,(call-with-input-file %ed25519-2-public-key-file
245 get-string-all))
246 (add ".guix-authorizations"
247 ,(object->string
248 `(authorizations (version 0)
249 ((,(key-fingerprint
250 %ed25519-public-key-file)
251 (name "Alice"))))))
252 (commit "zeroth commit")
253 (add "a.txt" "A")
254 (commit "first commit"
255 (signer ,(key-fingerprint %ed25519-public-key-file)))
256 (branch "devel")
257 (checkout "devel")
258 (add ".guix-authorizations"
259 ,(object->string ;add the second signer
260 `(authorizations (version 0)
261 ((,(key-fingerprint
262 %ed25519-public-key-file)
263 (name "Alice"))
264 (,(key-fingerprint
265 %ed25519-2-public-key-file))))))
266 (commit "first devel commit"
267 (signer ,(key-fingerprint %ed25519-public-key-file)))
268 (add "devel/2.txt" "2")
269 (commit "second devel commit"
270 (signer ,(key-fingerprint %ed25519-2-public-key-file)))
271 (checkout "master")
272 (add "b.txt" "B")
273 (commit "second commit"
274 (signer ,(key-fingerprint %ed25519-public-key-file)))
275 (merge "devel" "merge"
276 (signer ,(key-fingerprint %ed25519-public-key-file)))
277 ;; After the merge, the second signer is authorized.
278 (add "c.txt" "C")
279 (commit "third commit"
280 (signer ,(key-fingerprint %ed25519-2-public-key-file))))
281 (with-repository directory repository
282 (let ((master1 (find-commit repository "first commit"))
283 (master2 (find-commit repository "second commit"))
284 (devel1 (find-commit repository "first devel commit"))
285 (devel2 (find-commit repository "second devel commit"))
286 (merge (find-commit repository "merge"))
287 (master3 (find-commit repository "third commit")))
288 (authenticate-commits repository
289 (list master1 master2 devel1 devel2
290 merge master3)
291 #:keyring-reference "master"))))))
292
293(unless (gpg+git-available?) (test-skip 1))
294(test-assert "signed commits, .guix-authorizations removed"
295 (with-fresh-gnupg-setup (list %ed25519-public-key-file
296 %ed25519-secret-key-file)
297 (with-temporary-git-repository directory
298 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
299 get-string-all))
300 (add ".guix-authorizations"
301 ,(object->string
302 `(authorizations (version 0)
303 ((,(key-fingerprint
304 %ed25519-public-key-file)
305 (name "Charlie"))))))
306 (commit "zeroth commit")
307 (add "a.txt" "A")
308 (commit "first commit"
309 (signer ,(key-fingerprint %ed25519-public-key-file)))
310 (remove ".guix-authorizations")
311 (commit "second commit"
312 (signer ,(key-fingerprint %ed25519-public-key-file)))
313 (add "b.txt" "B")
314 (commit "third commit"
315 (signer ,(key-fingerprint %ed25519-public-key-file))))
316 (with-repository directory repository
317 (let ((commit1 (find-commit repository "first"))
318 (commit2 (find-commit repository "second"))
319 (commit3 (find-commit repository "third")))
320 ;; COMMIT1 and COMMIT2 are fine.
321 (and (authenticate-commits repository (list commit1 commit2)
322 #:keyring-reference "master")
323
324 ;; COMMIT3 is rejected because COMMIT2 removes
325 ;; '.guix-authorizations'.
326 (guard (c ((unauthorized-commit-error? c)
327 (oid=? (git-authentication-error-commit c)
328 (commit-id commit2))))
329 (authenticate-commits repository
330 (list commit1 commit2 commit3)
331 #:keyring-reference "master")
332 'failed)))))))
333
334(unless (gpg+git-available?) (test-skip 1))
335(test-assert "introductory commit, valid signature"
336 (with-fresh-gnupg-setup (list %ed25519-public-key-file
337 %ed25519-secret-key-file)
338 (let ((fingerprint (key-fingerprint %ed25519-public-key-file)))
339 (with-temporary-git-repository directory
340 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
341 get-string-all))
342 (add ".guix-authorizations"
343 ,(object->string
344 `(authorizations (version 0)
345 ((,(key-fingerprint
346 %ed25519-public-key-file)
347 (name "Charlie"))))))
348 (commit "zeroth commit" (signer ,fingerprint))
349 (add "a.txt" "A")
350 (commit "first commit" (signer ,fingerprint)))
351 (with-repository directory repository
352 (let ((commit0 (find-commit repository "zero"))
353 (commit1 (find-commit repository "first")))
354 ;; COMMIT0 is signed with the right key, and COMMIT1 is fine.
355 (authenticate-repository repository
356 (commit-id commit0)
357 (openpgp-fingerprint fingerprint)
358 #:keyring-reference "master"
359 #:cache-key (random-text))))))))
360
361(unless (gpg+git-available?) (test-skip 1))
362(test-equal "introductory commit, missing signature"
363 'intro-lacks-signature
364 (with-fresh-gnupg-setup (list %ed25519-public-key-file
365 %ed25519-secret-key-file)
366 (let ((fingerprint (key-fingerprint %ed25519-public-key-file)))
367 (with-temporary-git-repository directory
368 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
369 get-string-all))
370 (add ".guix-authorizations"
371 ,(object->string
372 `(authorizations (version 0)
373 ((,(key-fingerprint
374 %ed25519-public-key-file)
375 (name "Charlie"))))))
376 (commit "zeroth commit") ;unsigned!
377 (add "a.txt" "A")
378 (commit "first commit" (signer ,fingerprint)))
379 (with-repository directory repository
380 (let ((commit0 (find-commit repository "zero")))
381 ;; COMMIT0 is not signed.
382 (guard (c ((formatted-message? c)
383 ;; Message like "commit ~a lacks a signature".
384 (and (equal? (formatted-message-arguments c)
385 (list (oid->string (commit-id commit0))))
386 'intro-lacks-signature)))
387 (authenticate-repository repository
388 (commit-id commit0)
389 (openpgp-fingerprint fingerprint)
390 #:keyring-reference "master"
391 #:cache-key (random-text)))))))))
392
393(unless (gpg+git-available?) (test-skip 1))
394(test-equal "introductory commit, wrong signature"
395 'wrong-intro-signing-key
396 (with-fresh-gnupg-setup (list %ed25519-public-key-file
397 %ed25519-secret-key-file
398 %ed25519-2-public-key-file
399 %ed25519-2-secret-key-file)
400 (let ((fingerprint (key-fingerprint %ed25519-public-key-file))
401 (wrong-fingerprint (key-fingerprint %ed25519-2-public-key-file)))
402 (with-temporary-git-repository directory
403 `((add "signer1.key" ,(call-with-input-file %ed25519-public-key-file
404 get-string-all))
405 (add "signer2.key" ,(call-with-input-file %ed25519-2-public-key-file
406 get-string-all))
407 (add ".guix-authorizations"
408 ,(object->string
409 `(authorizations (version 0)
410 ((,(key-fingerprint
411 %ed25519-public-key-file)
412 (name "Charlie"))))))
413 (commit "zeroth commit" (signer ,wrong-fingerprint))
414 (add "a.txt" "A")
415 (commit "first commit" (signer ,fingerprint)))
416 (with-repository directory repository
417 (let ((commit0 (find-commit repository "zero"))
418 (commit1 (find-commit repository "first")))
419 ;; COMMIT0 is signed with the wrong key--not the one passed as the
420 ;; SIGNER argument to 'authenticate-repository'.
421 (guard (c ((formatted-message? c)
422 ;; Message like "commit ~a signed by ~a instead of ~a".
423 (and (equal? (formatted-message-arguments c)
424 (list (oid->string (commit-id commit0))
425 wrong-fingerprint fingerprint))
426 'wrong-intro-signing-key)))
427 (authenticate-repository repository
428 (commit-id commit0)
429 (openpgp-fingerprint fingerprint)
430 #:keyring-reference "master"
431 #:cache-key (random-text)))))))))
432
433(unless (gpg+git-available?) (test-skip 1))
434(test-equal "authenticate-repository, target not a descendant of intro"
435 'target-commit-not-a-descendant-of-intro
436 (with-fresh-gnupg-setup (list %ed25519-public-key-file
437 %ed25519-secret-key-file)
438 (let ((fingerprint (key-fingerprint %ed25519-public-key-file)))
439 (with-temporary-git-repository directory
440 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
441 get-string-all))
442 (add ".guix-authorizations"
443 ,(object->string
444 `(authorizations (version 0)
445 ((,(key-fingerprint
446 %ed25519-public-key-file)
447 (name "Charlie"))))))
448 (commit "zeroth commit" (signer ,fingerprint))
449 (branch "pre-intro-branch")
450 (checkout "pre-intro-branch")
451 (add "b.txt" "B")
452 (commit "alternate commit" (signer ,fingerprint))
453 (checkout "master")
454 (add "a.txt" "A")
455 (commit "first commit" (signer ,fingerprint))
456 (add "c.txt" "C")
457 (commit "second commit" (signer ,fingerprint)))
458 (with-repository directory repository
459 (let ((commit1 (find-commit repository "first"))
460 (commit-alt
461 (commit-lookup repository
462 (reference-target
463 (branch-lookup repository
464 "pre-intro-branch")))))
465 (guard (c ((formatted-message? c)
466 (and (equal? (formatted-message-arguments c)
467 (list (oid->string (commit-id commit-alt))
468 (oid->string (commit-id commit1))))
469 'target-commit-not-a-descendant-of-intro)))
470 (authenticate-repository repository
471 (commit-id commit1)
472 (openpgp-fingerprint fingerprint)
473 #:end (commit-id commit-alt)
474 #:keyring-reference "master"
475 #:cache-key (random-text)))))))))
476
477(test-end "git-authenticate")