1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
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.
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.
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/>.
19 (define-module (test-git-authenticate)
21 #:use-module (guix git)
22 #:use-module (guix git-authenticate)
23 #:use-module (guix openpgp)
24 #:use-module (guix tests git)
25 #:use-module (guix tests gnupg)
26 #:use-module (guix build utils)
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-34)
29 #:use-module (srfi srfi-64)
30 #:use-module (rnrs bytevectors)
31 #:use-module (rnrs io ports))
33 ;; Test the (guix git-authenticate) tools.
35 (define (gpg+git-available?)
36 (and (which (git-command))
37 (which (gpg-command)) (which (gpgconf-command))))
40 (test-begin "git-authenticate")
42 (unless (which (git-command)) (test-skip 1))
43 (test-assert "unsigned commits"
44 (with-temporary-git-repository directory
46 (commit "first commit")
48 (commit "second commit"))
49 (with-repository directory repository
50 (let ((commit1 (find-commit repository "first"))
51 (commit2 (find-commit repository "second")))
52 (guard (c ((unsigned-commit-error? c)
53 (oid=? (git-authentication-error-commit c)
54 (commit-id commit1))))
55 (authenticate-commits repository (list commit1 commit2)
56 #:keyring-reference "master")
59 (unless (gpg+git-available?) (test-skip 1))
60 (test-assert "signed commits, SHA1 signature"
61 (with-fresh-gnupg-setup (list %ed25519-public-key-file
62 %ed25519-secret-key-file)
63 ;; Force use of SHA1 for signatures.
64 (call-with-output-file (string-append (getenv "GNUPGHOME") "/gpg.conf")
66 (display "digest-algo sha1" port)))
68 (with-temporary-git-repository directory
70 (add "signer.key" ,(call-with-input-file %ed25519-public-key-file
72 (add ".guix-authorizations"
74 `(authorizations (version 0)
75 ((,(key-fingerprint %ed25519-public-key-file)
77 (commit "first commit"
78 (signer ,(key-fingerprint %ed25519-public-key-file))))
79 (with-repository directory repository
80 (let ((commit (find-commit repository "first")))
81 (guard (c ((unsigned-commit-error? c)
82 (oid=? (git-authentication-error-commit c)
84 (authenticate-commits repository (list commit)
85 #:keyring-reference "master")
88 (unless (gpg+git-available?) (test-skip 1))
89 (test-assert "signed commits, default authorizations"
90 (with-fresh-gnupg-setup (list %ed25519-public-key-file
91 %ed25519-secret-key-file)
92 (with-temporary-git-repository directory
93 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
95 (commit "zeroth commit")
97 (commit "first commit"
98 (signer ,(key-fingerprint %ed25519-public-key-file)))
100 (commit "second commit"
101 (signer ,(key-fingerprint %ed25519-public-key-file))))
102 (with-repository directory repository
103 (let ((commit1 (find-commit repository "first"))
104 (commit2 (find-commit repository "second")))
105 (authenticate-commits repository (list commit1 commit2)
106 #:default-authorizations
107 (list (openpgp-public-key-fingerprint
109 %ed25519-public-key-file)))
110 #:keyring-reference "master"))))))
112 (unless (gpg+git-available?) (test-skip 1))
113 (test-assert "signed commits, .guix-authorizations"
114 (with-fresh-gnupg-setup (list %ed25519-public-key-file
115 %ed25519-secret-key-file)
116 (with-temporary-git-repository directory
117 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
119 (add ".guix-authorizations"
121 `(authorizations (version 0)
123 %ed25519-public-key-file)
124 (name "Charlie"))))))
125 (commit "zeroth commit")
127 (commit "first commit"
128 (signer ,(key-fingerprint %ed25519-public-key-file)))
129 (add ".guix-authorizations"
130 ,(object->string `(authorizations (version 0) ()))) ;empty
131 (commit "second commit"
132 (signer ,(key-fingerprint %ed25519-public-key-file)))
134 (commit "third commit"
135 (signer ,(key-fingerprint %ed25519-public-key-file))))
136 (with-repository directory repository
137 (let ((commit1 (find-commit repository "first"))
138 (commit2 (find-commit repository "second"))
139 (commit3 (find-commit repository "third")))
140 ;; COMMIT1 and COMMIT2 are fine.
141 (and (authenticate-commits repository (list commit1 commit2)
142 #:keyring-reference "master")
144 ;; COMMIT3 is signed by an unauthorized key according to its
145 ;; parent's '.guix-authorizations' file.
146 (guard (c ((unauthorized-commit-error? c)
147 (and (oid=? (git-authentication-error-commit c)
150 (openpgp-public-key-fingerprint
151 (unauthorized-commit-error-signing-key c))
152 (openpgp-public-key-fingerprint
154 %ed25519-public-key-file))))))
155 (authenticate-commits repository
156 (list commit1 commit2 commit3)
157 #:keyring-reference "master")
160 (unless (gpg+git-available?) (test-skip 1))
161 (test-assert "signed commits, .guix-authorizations, unauthorized merge"
162 (with-fresh-gnupg-setup (list %ed25519-public-key-file
163 %ed25519-secret-key-file
164 %ed25519bis-public-key-file
165 %ed25519bis-secret-key-file)
166 (with-temporary-git-repository directory
168 ,(call-with-input-file %ed25519-public-key-file
171 ,(call-with-input-file %ed25519bis-public-key-file
173 (add ".guix-authorizations"
175 `(authorizations (version 0)
177 %ed25519-public-key-file)
179 (commit "zeroth commit")
181 (commit "first commit"
182 (signer ,(key-fingerprint %ed25519-public-key-file)))
185 (add "devel/1.txt" "1")
186 (commit "first devel commit"
187 (signer ,(key-fingerprint %ed25519bis-public-key-file)))
190 (commit "second commit"
191 (signer ,(key-fingerprint %ed25519-public-key-file)))
192 (merge "devel" "merge"
193 (signer ,(key-fingerprint %ed25519-public-key-file))))
194 (with-repository directory repository
195 (let ((master1 (find-commit repository "first commit"))
196 (master2 (find-commit repository "second commit"))
197 (devel1 (find-commit repository "first devel commit"))
198 (merge (find-commit repository "merge")))
199 (define (correct? c commit)
200 (and (oid=? (git-authentication-error-commit c)
203 (openpgp-public-key-fingerprint
204 (unauthorized-commit-error-signing-key c))
205 (openpgp-public-key-fingerprint
206 (read-openpgp-packet %ed25519bis-public-key-file)))))
208 (and (authenticate-commits repository (list master1 master2)
209 #:keyring-reference "master")
211 ;; DEVEL1 is signed by an unauthorized key according to its
212 ;; parent's '.guix-authorizations' file.
213 (guard (c ((unauthorized-commit-error? c)
214 (correct? c devel1)))
215 (authenticate-commits repository
216 (list master1 devel1)
217 #:keyring-reference "master")
220 ;; MERGE is authorized but one of its ancestors is not.
221 (guard (c ((unauthorized-commit-error? c)
222 (correct? c devel1)))
223 (authenticate-commits repository
224 (list master1 master2
226 #:keyring-reference "master")
229 (unless (gpg+git-available?) (test-skip 1))
230 (test-assert "signed commits, .guix-authorizations, authorized merge"
231 (with-fresh-gnupg-setup (list %ed25519-public-key-file
232 %ed25519-secret-key-file
233 %ed25519bis-public-key-file
234 %ed25519bis-secret-key-file)
235 (with-temporary-git-repository directory
237 ,(call-with-input-file %ed25519-public-key-file
240 ,(call-with-input-file %ed25519bis-public-key-file
242 (add ".guix-authorizations"
244 `(authorizations (version 0)
246 %ed25519-public-key-file)
248 (commit "zeroth commit")
250 (commit "first commit"
251 (signer ,(key-fingerprint %ed25519-public-key-file)))
254 (add ".guix-authorizations"
255 ,(object->string ;add the second signer
256 `(authorizations (version 0)
258 %ed25519-public-key-file)
261 %ed25519bis-public-key-file))))))
262 (commit "first devel commit"
263 (signer ,(key-fingerprint %ed25519-public-key-file)))
264 (add "devel/2.txt" "2")
265 (commit "second devel commit"
266 (signer ,(key-fingerprint %ed25519bis-public-key-file)))
269 (commit "second commit"
270 (signer ,(key-fingerprint %ed25519-public-key-file)))
271 (merge "devel" "merge"
272 (signer ,(key-fingerprint %ed25519-public-key-file)))
273 ;; After the merge, the second signer is authorized.
275 (commit "third commit"
276 (signer ,(key-fingerprint %ed25519bis-public-key-file))))
277 (with-repository directory repository
278 (let ((master1 (find-commit repository "first commit"))
279 (master2 (find-commit repository "second commit"))
280 (devel1 (find-commit repository "first devel commit"))
281 (devel2 (find-commit repository "second devel commit"))
282 (merge (find-commit repository "merge"))
283 (master3 (find-commit repository "third commit")))
284 (authenticate-commits repository
285 (list master1 master2 devel1 devel2
287 #:keyring-reference "master"))))))
289 (unless (gpg+git-available?) (test-skip 1))
290 (test-assert "signed commits, .guix-authorizations removed"
291 (with-fresh-gnupg-setup (list %ed25519-public-key-file
292 %ed25519-secret-key-file)
293 (with-temporary-git-repository directory
294 `((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
296 (add ".guix-authorizations"
298 `(authorizations (version 0)
300 %ed25519-public-key-file)
301 (name "Charlie"))))))
302 (commit "zeroth commit")
304 (commit "first commit"
305 (signer ,(key-fingerprint %ed25519-public-key-file)))
306 (remove ".guix-authorizations")
307 (commit "second commit"
308 (signer ,(key-fingerprint %ed25519-public-key-file)))
310 (commit "third commit"
311 (signer ,(key-fingerprint %ed25519-public-key-file))))
312 (with-repository directory repository
313 (let ((commit1 (find-commit repository "first"))
314 (commit2 (find-commit repository "second"))
315 (commit3 (find-commit repository "third")))
316 ;; COMMIT1 and COMMIT2 are fine.
317 (and (authenticate-commits repository (list commit1 commit2)
318 #:keyring-reference "master")
320 ;; COMMIT3 is rejected because COMMIT2 removes
321 ;; '.guix-authorizations'.
322 (guard (c ((unauthorized-commit-error? c)
323 (oid=? (git-authentication-error-commit c)
324 (commit-id commit2))))
325 (authenticate-commits repository
326 (list commit1 commit2 commit3)
327 #:keyring-reference "master")
330 (test-end "git-authenticate")