Commit | Line | Data |
---|---|---|
0fdd3bea | 1 | ;;; GNU Guix --- Functional package management for GNU |
9cfa3225 | 2 | ;;; Copyright © 2010, 2011, 2013, 2014, 2016, 2018, 2019 Ludovic Courtès <ludo@gnu.org> |
392b5d8c | 3 | ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org> |
aa78c596 | 4 | ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr> |
4c91332c | 5 | ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
0fdd3bea LC |
6 | ;;; |
7 | ;;; This file is part of GNU Guix. | |
8 | ;;; | |
9 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | |
10 | ;;; under the terms of the GNU General Public License as published by | |
11 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
12 | ;;; your option) any later version. | |
13 | ;;; | |
14 | ;;; GNU Guix is distributed in the hope that it will be useful, but | |
15 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | ;;; GNU General Public License for more details. | |
18 | ;;; | |
19 | ;;; You should have received a copy of the GNU General Public License | |
20 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
21 | ||
22 | (define-module (guix gnupg) | |
23 | #:use-module (ice-9 popen) | |
24 | #:use-module (ice-9 match) | |
25 | #:use-module (ice-9 regex) | |
26 | #:use-module (ice-9 rdelim) | |
392b5d8c | 27 | #:use-module (ice-9 i18n) |
0fdd3bea | 28 | #:use-module (srfi srfi-1) |
b9e1fddf LC |
29 | #:use-module (guix i18n) |
30 | #:use-module ((guix utils) #:select (config-directory)) | |
31 | #:use-module ((guix build utils) #:select (mkdir-p)) | |
0ba91c94 LC |
32 | #:export (%gpg-command |
33 | %openpgp-key-server | |
b9e1fddf | 34 | current-keyring |
0ba91c94 | 35 | gnupg-verify |
0fdd3bea LC |
36 | gnupg-verify* |
37 | gnupg-status-good-signature? | |
38 | gnupg-status-missing-key?)) | |
39 | ||
40 | ;;; Commentary: | |
41 | ;;; | |
42 | ;;; GnuPG interface. | |
43 | ;;; | |
44 | ;;; Code: | |
45 | ||
0ba91c94 LC |
46 | (define %gpg-command |
47 | ;; The GnuPG 2.x command-line program name. | |
0c90ed55 | 48 | (make-parameter (or (getenv "GUIX_GPG_COMMAND") "gpg"))) |
0ba91c94 | 49 | |
b9e1fddf LC |
50 | (define %gpgv-command |
51 | ;; The 'gpgv' program. | |
52 | (make-parameter (or (getenv "GUIX_GPGV_COMMAND") "gpgv"))) | |
53 | ||
54 | (define current-keyring | |
55 | ;; The default keyring of "trusted keys". | |
56 | (make-parameter (string-append (config-directory #:ensure? #f) | |
57 | "/gpg/trustedkeys.kbx"))) | |
58 | ||
0ba91c94 | 59 | (define %openpgp-key-server |
4c91332c MC |
60 | ;; The default key server. It defaults to #f, which causes GnuPG to use the |
61 | ;; one it is configured with. | |
62 | (make-parameter #f)) | |
0fdd3bea | 63 | |
d8169d05 LC |
64 | ;; Regexps for status lines. See file `doc/DETAILS' in GnuPG. |
65 | ||
66 | (define sigid-rx | |
67 | (make-regexp | |
68 | "^\\[GNUPG:\\] SIG_ID ([A-Za-z0-9+/]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+)")) | |
69 | (define goodsig-rx | |
70 | (make-regexp "^\\[GNUPG:\\] GOODSIG ([[:xdigit:]]+) (.+)$")) | |
71 | (define validsig-rx | |
72 | (make-regexp | |
73 | "^\\[GNUPG:\\] VALIDSIG ([[:xdigit:]]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+) .*$")) | |
74 | (define expkeysig-rx ; good signature, but expired key | |
75 | (make-regexp "^\\[GNUPG:\\] EXPKEYSIG ([[:xdigit:]]+) (.*)$")) | |
aa78c596 TGR |
76 | (define revkeysig-rx ; good signature, but revoked key |
77 | (make-regexp "^\\[GNUPG:\\] REVKEYSIG ([[:xdigit:]]+) (.*)$")) | |
d8169d05 LC |
78 | (define errsig-rx |
79 | ;; Note: The fingeprint part (the last element of the line) appeared in | |
80 | ;; GnuPG 2.2.7 according to 'doc/DETAILS', and it may be missing. | |
81 | (make-regexp | |
82 | "^\\[GNUPG:\\] ERRSIG ([[:xdigit:]]+) ([^ ]+) ([^ ]+) ([^ ]+) ([[:digit:]]+) ([[:digit:]]+)(.*)")) | |
83 | ||
84 | ||
b9e1fddf LC |
85 | (define* (gnupg-verify sig file |
86 | #:optional (keyring (current-keyring))) | |
87 | "Verify signature SIG for FILE against the keys in KEYRING. All the keys in | |
88 | KEYRING as assumed to be \"trusted\", whether or not they expired or were | |
89 | revoked. Return a status s-exp if GnuPG failed." | |
0fdd3bea | 90 | |
217b4a15 LC |
91 | (define (maybe-fingerprint str) |
92 | (match (string-trim-both str) | |
93 | ((or "-" "") #f) | |
94 | (fpr fpr))) | |
95 | ||
0fdd3bea | 96 | (define (status-line->sexp line) |
0fdd3bea LC |
97 | (cond ((regexp-exec sigid-rx line) |
98 | => | |
99 | (lambda (match) | |
100 | `(signature-id ,(match:substring match 1) ; sig id | |
101 | ,(match:substring match 2) ; date | |
102 | ,(string->number ; timestamp | |
103 | (match:substring match 3))))) | |
104 | ((regexp-exec goodsig-rx line) | |
105 | => | |
106 | (lambda (match) | |
107 | `(good-signature ,(match:substring match 1) ; key id | |
108 | ,(match:substring match 2)))) ; user name | |
109 | ((regexp-exec validsig-rx line) | |
110 | => | |
111 | (lambda (match) | |
112 | `(valid-signature ,(match:substring match 1) ; fingerprint | |
113 | ,(match:substring match 2) ; sig creation date | |
114 | ,(string->number ; timestamp | |
115 | (match:substring match 3))))) | |
116 | ((regexp-exec expkeysig-rx line) | |
117 | => | |
118 | (lambda (match) | |
119 | `(expired-key-signature ,(match:substring match 1) ; fingerprint | |
120 | ,(match:substring match 2)))) ; user name | |
aa78c596 TGR |
121 | ((regexp-exec revkeysig-rx line) |
122 | => | |
123 | (lambda (match) | |
124 | `(revoked-key-signature ,(match:substring match 1) ; fingerprint | |
125 | ,(match:substring match 2)))) ; user name | |
0fdd3bea LC |
126 | ((regexp-exec errsig-rx line) |
127 | => | |
128 | (lambda (match) | |
217b4a15 | 129 | `(signature-error ,(match:substring match 1) ; key id |
0fdd3bea LC |
130 | ,(match:substring match 2) ; pubkey algo |
131 | ,(match:substring match 3) ; hash algo | |
132 | ,(match:substring match 4) ; sig class | |
133 | ,(string->number ; timestamp | |
134 | (match:substring match 5)) | |
135 | ,(let ((rc | |
136 | (string->number ; return code | |
137 | (match:substring match 6)))) | |
138 | (case rc | |
139 | ((9) 'missing-key) | |
140 | ((4) 'unknown-algorithm) | |
217b4a15 LC |
141 | (else rc))) |
142 | ,(maybe-fingerprint ; fingerprint or #f | |
143 | (match:substring match 7))))) | |
0fdd3bea LC |
144 | (else |
145 | `(unparsed-line ,line)))) | |
146 | ||
147 | (define (parse-status input) | |
148 | (let loop ((line (read-line input)) | |
149 | (result '())) | |
150 | (if (eof-object? line) | |
151 | (reverse result) | |
152 | (loop (read-line input) | |
153 | (cons (status-line->sexp line) result))))) | |
154 | ||
b9e1fddf LC |
155 | (let* ((pipe (open-pipe* OPEN_READ (%gpgv-command) "--status-fd=1" |
156 | "--keyring" keyring sig file)) | |
0fdd3bea LC |
157 | (status (parse-status pipe))) |
158 | ;; Ignore PIPE's exit status since STATUS above should contain all the | |
159 | ;; info we need. | |
160 | (close-pipe pipe) | |
161 | status)) | |
162 | ||
163 | (define (gnupg-status-good-signature? status) | |
164 | "If STATUS, as returned by `gnupg-verify', denotes a good signature, return | |
9cfa3225 LC |
165 | a fingerprint/user pair; return #f otherwise." |
166 | (match (assq 'valid-signature status) | |
167 | (('valid-signature fingerprint date timestamp) | |
168 | (match (or (assq 'good-signature status) | |
aa78c596 TGR |
169 | (assq 'expired-key-signature status) |
170 | (assq 'revoked-key-signature status)) | |
9cfa3225 LC |
171 | ((_ key-id user) (cons fingerprint user)) |
172 | (_ #f))) | |
173 | (_ | |
174 | #f))) | |
0fdd3bea LC |
175 | |
176 | (define (gnupg-status-missing-key? status) | |
217b4a15 LC |
177 | "If STATUS denotes a missing-key error, then return the fingerprint of the |
178 | missing key or its key id if the fingerprint is unavailable." | |
0fdd3bea LC |
179 | (any (lambda (sexp) |
180 | (match sexp | |
217b4a15 LC |
181 | (('signature-error key-id _ ... 'missing-key fingerprint) |
182 | (or fingerprint key-id)) | |
0fdd3bea LC |
183 | (_ #f))) |
184 | status)) | |
185 | ||
4c91332c MC |
186 | (define* (gnupg-receive-keys fingerprint/key-id |
187 | #:key server (keyring (current-keyring))) | |
188 | "Download FINGERPRINT/KEY-ID from SERVER if specified, otherwise from | |
189 | GnuPG's default/configured one. The key is added to KEYRING." | |
b9e1fddf LC |
190 | (unless (file-exists? keyring) |
191 | (mkdir-p (dirname keyring)) | |
4c91332c | 192 | (call-with-output-file keyring (const #t))) ;create an empty keybox |
b9e1fddf | 193 | |
4c91332c MC |
194 | (zero? (apply system* |
195 | `(,(%gpg-command) | |
196 | ,@(if server | |
197 | (list "--keyserver" server) | |
198 | '()) | |
199 | "--no-default-keyring" "--keyring" ,keyring | |
200 | "--recv-keys" ,fingerprint/key-id)))) | |
0fdd3bea | 201 | |
392b5d8c | 202 | (define* (gnupg-verify* sig file |
b9e1fddf LC |
203 | #:key |
204 | (key-download 'interactive) | |
4c91332c | 205 | server |
b9e1fddf | 206 | (keyring (current-keyring))) |
0fdd3bea | 207 | "Like `gnupg-verify', but try downloading the public key if it's missing. |
f94f9d67 LC |
208 | Return two values: 'valid-signature and a fingerprint/name pair upon success, |
209 | 'missing-key and a fingerprint if the key could not be found, and | |
210 | 'invalid-signature with a fingerprint if the signature is invalid. | |
211 | ||
212 | KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed | |
213 | values: 'always', 'never', and 'interactive' (default). Return a | |
214 | fingerprint/user name pair on success and #f otherwise." | |
0fdd3bea | 215 | (let ((status (gnupg-verify sig file))) |
f94f9d67 LC |
216 | (match (gnupg-status-good-signature? status) |
217 | ((fingerprint . user) | |
218 | (values 'valid-signature (cons fingerprint user))) | |
219 | (#f | |
220 | (let ((missing (gnupg-status-missing-key? status))) | |
221 | (define (download-and-try-again) | |
222 | ;; Download the missing key and try again. | |
4c91332c | 223 | (if (gnupg-receive-keys missing #:server server #:keyring keyring) |
f94f9d67 LC |
224 | (match (gnupg-status-good-signature? |
225 | (gnupg-verify sig file keyring)) | |
226 | (#f | |
227 | (values 'invalid-signature missing)) | |
228 | ((fingerprint . user) | |
229 | (values 'valid-signature | |
230 | (cons fingerprint user)))) | |
231 | (values 'missing-key missing))) | |
232 | ||
233 | (define (receive?) | |
234 | (let ((answer | |
235 | (begin | |
236 | (format #t (G_ "Would you like to add this key \ | |
b9e1fddf | 237 | to keyring '~a'?~%") |
f94f9d67 LC |
238 | keyring) |
239 | (read-line)))) | |
240 | (string-match (locale-yes-regexp) answer))) | |
241 | ||
242 | (case key-download | |
243 | ((never) | |
244 | (values 'missing-key missing)) | |
245 | ((always) | |
246 | (download-and-try-again)) | |
247 | (else | |
248 | (if (receive?) | |
249 | (download-and-try-again) | |
250 | (values 'missing-key missing))))))))) | |
0fdd3bea LC |
251 | |
252 | ;;; gnupg.scm ends here |