2013-04-24 17:48:36 -04:00
|
|
|
;;; GNU Guix --- Functional package management for GNU
|
2016-09-06 03:23:43 -04:00
|
|
|
;;; Copyright © 2010, 2011, 2013, 2014, 2016 Ludovic Courtès <ludo@gnu.org>
|
2013-06-10 03:46:13 -04:00
|
|
|
;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
|
2013-04-24 17:48:36 -04:00
|
|
|
;;;
|
|
|
|
;;; This file is part of GNU Guix.
|
|
|
|
;;;
|
|
|
|
;;; GNU Guix is free software; you can redistribute it and/or modify it
|
|
|
|
;;; under the terms of the GNU General Public License as published by
|
|
|
|
;;; the Free Software Foundation; either version 3 of the License, or (at
|
|
|
|
;;; your option) any later version.
|
|
|
|
;;;
|
|
|
|
;;; GNU Guix is distributed in the hope that it will be useful, but
|
|
|
|
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;;; GNU General Public License for more details.
|
|
|
|
;;;
|
|
|
|
;;; You should have received a copy of the GNU General Public License
|
|
|
|
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
(define-module (guix gnupg)
|
|
|
|
#:use-module (ice-9 popen)
|
|
|
|
#:use-module (ice-9 match)
|
|
|
|
#:use-module (ice-9 regex)
|
|
|
|
#:use-module (ice-9 rdelim)
|
2013-06-10 03:46:13 -04:00
|
|
|
#:use-module (ice-9 i18n)
|
2013-04-24 17:48:36 -04:00
|
|
|
#:use-module (srfi srfi-1)
|
2013-06-10 03:46:13 -04:00
|
|
|
#:use-module (guix ui)
|
2013-05-11 06:44:19 -04:00
|
|
|
#:export (%gpg-command
|
|
|
|
%openpgp-key-server
|
|
|
|
gnupg-verify
|
2013-04-24 17:48:36 -04:00
|
|
|
gnupg-verify*
|
|
|
|
gnupg-status-good-signature?
|
|
|
|
gnupg-status-missing-key?))
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
;;;
|
|
|
|
;;; GnuPG interface.
|
|
|
|
;;;
|
|
|
|
;;; Code:
|
|
|
|
|
2013-05-11 06:44:19 -04:00
|
|
|
(define %gpg-command
|
|
|
|
;; The GnuPG 2.x command-line program name.
|
2016-09-06 03:23:43 -04:00
|
|
|
(make-parameter (or (getenv "GUIX_GPG_COMMAND") "gpg")))
|
2013-05-11 06:44:19 -04:00
|
|
|
|
|
|
|
(define %openpgp-key-server
|
|
|
|
;; The default key server. Note that keys.gnupg.net appears to be
|
|
|
|
;; unreliable.
|
|
|
|
(make-parameter "pgp.mit.edu"))
|
2013-04-24 17:48:36 -04:00
|
|
|
|
|
|
|
(define (gnupg-verify sig file)
|
|
|
|
"Verify signature SIG for FILE. Return a status s-exp if GnuPG failed."
|
|
|
|
|
|
|
|
(define (status-line->sexp line)
|
|
|
|
;; See file `doc/DETAILS' in GnuPG.
|
|
|
|
(define sigid-rx
|
|
|
|
(make-regexp
|
2014-09-24 13:08:03 -04:00
|
|
|
"^\\[GNUPG:\\] SIG_ID ([A-Za-z0-9+/]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+)"))
|
2013-04-24 17:48:36 -04:00
|
|
|
(define goodsig-rx
|
|
|
|
(make-regexp "^\\[GNUPG:\\] GOODSIG ([[:xdigit:]]+) (.+)$"))
|
|
|
|
(define validsig-rx
|
|
|
|
(make-regexp
|
|
|
|
"^\\[GNUPG:\\] VALIDSIG ([[:xdigit:]]+) ([[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}) ([[:digit:]]+) .*$"))
|
|
|
|
(define expkeysig-rx ; good signature, but expired key
|
|
|
|
(make-regexp "^\\[GNUPG:\\] EXPKEYSIG ([[:xdigit:]]+) (.*)$"))
|
|
|
|
(define errsig-rx
|
|
|
|
(make-regexp
|
|
|
|
"^\\[GNUPG:\\] ERRSIG ([[:xdigit:]]+) ([^ ]+) ([^ ]+) ([^ ]+) ([[:digit:]]+) ([[:digit:]]+)"))
|
|
|
|
|
|
|
|
(cond ((regexp-exec sigid-rx line)
|
|
|
|
=>
|
|
|
|
(lambda (match)
|
|
|
|
`(signature-id ,(match:substring match 1) ; sig id
|
|
|
|
,(match:substring match 2) ; date
|
|
|
|
,(string->number ; timestamp
|
|
|
|
(match:substring match 3)))))
|
|
|
|
((regexp-exec goodsig-rx line)
|
|
|
|
=>
|
|
|
|
(lambda (match)
|
|
|
|
`(good-signature ,(match:substring match 1) ; key id
|
|
|
|
,(match:substring match 2)))) ; user name
|
|
|
|
((regexp-exec validsig-rx line)
|
|
|
|
=>
|
|
|
|
(lambda (match)
|
|
|
|
`(valid-signature ,(match:substring match 1) ; fingerprint
|
|
|
|
,(match:substring match 2) ; sig creation date
|
|
|
|
,(string->number ; timestamp
|
|
|
|
(match:substring match 3)))))
|
|
|
|
((regexp-exec expkeysig-rx line)
|
|
|
|
=>
|
|
|
|
(lambda (match)
|
|
|
|
`(expired-key-signature ,(match:substring match 1) ; fingerprint
|
|
|
|
,(match:substring match 2)))) ; user name
|
|
|
|
((regexp-exec errsig-rx line)
|
|
|
|
=>
|
|
|
|
(lambda (match)
|
|
|
|
`(signature-error ,(match:substring match 1) ; key id or fingerprint
|
|
|
|
,(match:substring match 2) ; pubkey algo
|
|
|
|
,(match:substring match 3) ; hash algo
|
|
|
|
,(match:substring match 4) ; sig class
|
|
|
|
,(string->number ; timestamp
|
|
|
|
(match:substring match 5))
|
|
|
|
,(let ((rc
|
|
|
|
(string->number ; return code
|
|
|
|
(match:substring match 6))))
|
|
|
|
(case rc
|
|
|
|
((9) 'missing-key)
|
|
|
|
((4) 'unknown-algorithm)
|
|
|
|
(else rc))))))
|
|
|
|
(else
|
|
|
|
`(unparsed-line ,line))))
|
|
|
|
|
|
|
|
(define (parse-status input)
|
|
|
|
(let loop ((line (read-line input))
|
|
|
|
(result '()))
|
|
|
|
(if (eof-object? line)
|
|
|
|
(reverse result)
|
|
|
|
(loop (read-line input)
|
|
|
|
(cons (status-line->sexp line) result)))))
|
|
|
|
|
2013-05-11 06:44:19 -04:00
|
|
|
(let* ((pipe (open-pipe* OPEN_READ (%gpg-command) "--status-fd=1"
|
2013-04-24 17:48:36 -04:00
|
|
|
"--verify" sig file))
|
|
|
|
(status (parse-status pipe)))
|
|
|
|
;; Ignore PIPE's exit status since STATUS above should contain all the
|
|
|
|
;; info we need.
|
|
|
|
(close-pipe pipe)
|
|
|
|
status))
|
|
|
|
|
|
|
|
(define (gnupg-status-good-signature? status)
|
|
|
|
"If STATUS, as returned by `gnupg-verify', denotes a good signature, return
|
|
|
|
a key-id/user pair; return #f otherwise."
|
|
|
|
(any (lambda (sexp)
|
|
|
|
(match sexp
|
|
|
|
(((or 'good-signature 'expired-key-signature) key-id user)
|
|
|
|
(cons key-id user))
|
|
|
|
(_ #f)))
|
|
|
|
status))
|
|
|
|
|
|
|
|
(define (gnupg-status-missing-key? status)
|
|
|
|
"If STATUS denotes a missing-key error, then return the key-id of the
|
|
|
|
missing key."
|
|
|
|
(any (lambda (sexp)
|
|
|
|
(match sexp
|
|
|
|
(('signature-error key-id _ ...)
|
|
|
|
key-id)
|
|
|
|
(_ #f)))
|
|
|
|
status))
|
|
|
|
|
|
|
|
(define (gnupg-receive-keys key-id server)
|
2013-05-11 06:44:19 -04:00
|
|
|
(system* (%gpg-command) "--keyserver" server "--recv-keys" key-id))
|
2013-04-24 17:48:36 -04:00
|
|
|
|
2013-06-10 03:46:13 -04:00
|
|
|
(define* (gnupg-verify* sig file
|
|
|
|
#:key (key-download 'interactive)
|
|
|
|
(server (%openpgp-key-server)))
|
2013-04-24 17:48:36 -04:00
|
|
|
"Like `gnupg-verify', but try downloading the public key if it's missing.
|
2013-06-10 03:46:13 -04:00
|
|
|
Return #t if the signature was good, #f otherwise. KEY-DOWNLOAD specifies a
|
|
|
|
download policy for missing OpenPGP keys; allowed values: 'always', 'never',
|
|
|
|
and 'interactive' (default)."
|
2013-04-24 17:48:36 -04:00
|
|
|
(let ((status (gnupg-verify sig file)))
|
|
|
|
(or (gnupg-status-good-signature? status)
|
|
|
|
(let ((missing (gnupg-status-missing-key? status)))
|
2013-06-10 03:46:13 -04:00
|
|
|
(define (download-and-try-again)
|
|
|
|
;; Download the missing key and try again.
|
|
|
|
(begin
|
|
|
|
(gnupg-receive-keys missing server)
|
|
|
|
(gnupg-status-good-signature? (gnupg-verify sig file))))
|
|
|
|
|
|
|
|
(define (receive?)
|
|
|
|
(let ((answer
|
ui: Rename '_' to 'G_'.
This avoids collisions with '_' when the latter is used as a 'match'
pattern for instance. See
<https://lists.gnu.org/archive/html/guix-devel/2017-04/msg00464.html>.
* guix/ui.scm: Rename '_' to 'G_'.
* po/guix/Makevars (XGETTEXT_OPTIONS): Adjust accordingly.
* build-aux/compile-all.scm (warnings): Remove 'format'.
* gnu/packages.scm,
gnu/services.scm,
gnu/services/shepherd.scm,
gnu/system.scm,
gnu/system/shadow.scm,
guix/gnupg.scm,
guix/http-client.scm,
guix/import/cpan.scm,
guix/import/elpa.scm,
guix/import/pypi.scm,
guix/nar.scm,
guix/scripts.scm,
guix/scripts/archive.scm,
guix/scripts/authenticate.scm,
guix/scripts/build.scm,
guix/scripts/challenge.scm,
guix/scripts/container.scm,
guix/scripts/container/exec.scm,
guix/scripts/copy.scm,
guix/scripts/download.scm,
guix/scripts/edit.scm,
guix/scripts/environment.scm,
guix/scripts/gc.scm,
guix/scripts/graph.scm,
guix/scripts/hash.scm,
guix/scripts/import.scm,
guix/scripts/import/cpan.scm,
guix/scripts/import/cran.scm,
guix/scripts/import/crate.scm,
guix/scripts/import/elpa.scm,
guix/scripts/import/gem.scm,
guix/scripts/import/gnu.scm,
guix/scripts/import/hackage.scm,
guix/scripts/import/nix.scm,
guix/scripts/import/pypi.scm,
guix/scripts/import/stackage.scm,
guix/scripts/lint.scm,
guix/scripts/offload.scm,
guix/scripts/pack.scm,
guix/scripts/package.scm,
guix/scripts/perform-download.scm,
guix/scripts/publish.scm,
guix/scripts/pull.scm,
guix/scripts/refresh.scm,
guix/scripts/size.scm,
guix/scripts/substitute.scm,
guix/scripts/system.scm,
guix/ssh.scm,
guix/upstream.scm: Use 'G_' instead of '_'. Most of this change was
obtained by running: "sed -i -e's/(_ "/(G_ "/g' `find -name \*.scm`".
2017-05-03 09:57:02 -04:00
|
|
|
(begin (format #t (G_ "~a~a~%")
|
2013-06-10 03:46:13 -04:00
|
|
|
"Would you like to download this key "
|
|
|
|
"and add it to your keyring?")
|
|
|
|
(read-line))))
|
|
|
|
(string-match (locale-yes-regexp) answer)))
|
|
|
|
|
2013-04-24 17:48:36 -04:00
|
|
|
(and missing
|
2013-06-10 03:46:13 -04:00
|
|
|
(case key-download
|
|
|
|
((never) #f)
|
|
|
|
((always)
|
|
|
|
(download-and-try-again))
|
|
|
|
(else
|
|
|
|
(and (receive?)
|
|
|
|
(download-and-try-again)))))))))
|
2013-04-24 17:48:36 -04:00
|
|
|
|
|
|
|
;;; gnupg.scm ends here
|