projects/guix·blob perlssl · refs

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