Subject: [bug#32674] [PATCH 1/1] gnupg: Use 'gpgv' and keybox files; adjust 'guix refresh' accordingly.

From: Ludovic Courtès <[email-masked]>
Date: Sun, 9 Sep 2018 22:46:41 +0200

* guix/gnupg.scm (%gpgv-command, current-keyring): New variables
(gnupg-verify): Add optional 'keyring' parameter.  Use 'gpgv' instead of
'gpg' and pass it '--keyring'.
(gnupg-receive-keys): Add optional 'keyring' parameter and honor it.
(gnupg-verify*): Add #:keyring and honor it.
* guix/scripts/refresh.scm (%options, show-help): Add '--keyring'.
(guix-refresh): Parameterize CURRENT-KEYRING.
* doc/guix.texi (Invoking guix refresh): Document '--keyring' and the
keybox format.
---
 doc/guix.texi            | 30 +++++++++++++++++++++
 guix/gnupg.scm           | 58 +++++++++++++++++++++++++++++-----------
 guix/scripts/refresh.scm | 13 +++++++--
 3 files changed, 83 insertions(+), 18 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 19a497c74..6eccf9e05 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -7268,6 +7268,36 @@ The following options can be used to customize GnuPG operation:
 Use @var{command} as the GnuPG 2.x command.  @var{command} is searched
 for in @code{$PATH}.
 
+@item --keyring=@var{file}
+Use @var{file} as the keyring for upstream keys.  @var{file} must be in the
+@dfn{keybox format}.  Keybox files usually have a name ending in @file{.kbx}
+and the GNU@tie{}Privacy Guard (GPG) can manipulate these files
+(@pxref{kbxutil, @command{kbxutil},, gnupg, Using the GNU Privacy Guard}, for
+information on a tool to manipulate keybox files).
+
+When this option is omitted, @command{guix refresh} uses
+@file{~/.config/guix/upstream/trustedkeys.kbx} as the keyring for upstream
+signing keys.  OpenPGP signatures are checked against keys from this keyring;
+missing keys are downloaded to this keyring as well (see
+@option{--key-download} below.)
+
+You can export keys from your default GPG keyring into a keybox file using
+commands like this one:
+
+@example
+gpg --export rms@@gnu.org | kbxutil --import-openpgp >> mykeyring.kbx
+@end example
+
+Likewise, you can fetch keys to a specific keybox file like this:
+
+@example
+gpg --no-default-keyring --keyring mykeyring.kbx \
+  --recv-keys @value{OPENPGP-SIGNING-KEY-ID}
+@end example
+
+@ref{GPG Configuration Options, @option{--keyring},, gnupg, Using the GNU
+Privacy Guard}, for more information on GPG's @option{--keyring} option.
+
 @item --key-download=@var{policy}
 Handle missing OpenPGP keys according to @var{policy}, which may be one
 of:
diff --git a/guix/gnupg.scm b/guix/gnupg.scm
index ac0ed5ab2..b30ce461b 100644
--- a/guix/gnupg.scm
+++ b/guix/gnupg.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2010, 2011, 2013, 2014, 2016 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2010, 2011, 2013, 2014, 2016, 2018 Ludovic Courtès <[email-masked]>
 ;;; Copyright © 2013 Nikita Karetnikov <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
@@ -24,9 +24,12 @@
   #:use-module (ice-9 rdelim)
   #:use-module (ice-9 i18n)
   #:use-module (srfi srfi-1)
-  #:use-module (guix ui)
+  #:use-module (guix i18n)
+  #:use-module ((guix utils) #:select (config-directory))
+  #:use-module ((guix build utils) #:select (mkdir-p))
   #:export (%gpg-command
             %openpgp-key-server
+            current-keyring
             gnupg-verify
             gnupg-verify*
             gnupg-status-good-signature?
@@ -42,13 +45,25 @@
   ;; The GnuPG 2.x command-line program name.
   (make-parameter (or (getenv "GUIX_GPG_COMMAND") "gpg")))
 
+(define %gpgv-command
+  ;; The 'gpgv' program.
+  (make-parameter (or (getenv "GUIX_GPGV_COMMAND") "gpgv")))
+
+(define current-keyring
+  ;; The default keyring of "trusted keys".
+  (make-parameter (string-append (config-directory #:ensure? #f)
+                                 "/gpg/trustedkeys.kbx")))
+
 (define %openpgp-key-server
   ;; The default key server.  Note that keys.gnupg.net appears to be
   ;; unreliable.
   (make-parameter "pgp.mit.edu"))
 
-(define (gnupg-verify sig file)
-  "Verify signature SIG for FILE.  Return a status s-exp if GnuPG failed."
+(define* (gnupg-verify sig file
+                       #:optional (keyring (current-keyring)))
+  "Verify signature SIG for FILE against the keys in KEYRING.  All the keys in
+KEYRING as assumed to be \"trusted\", whether or not they expired or were
+revoked.  Return a status s-exp if GnuPG failed."
 
   (define (status-line->sexp line)
     ;; See file `doc/DETAILS' in GnuPG.
@@ -117,8 +132,8 @@
           (loop (read-line input)
                 (cons (status-line->sexp line) result)))))
 
-  (let* ((pipe   (open-pipe* OPEN_READ (%gpg-command) "--status-fd=1"
-                             "--verify" sig file))
+  (let* ((pipe   (open-pipe* OPEN_READ (%gpgv-command) "--status-fd=1"
+                             "--keyring" keyring sig file))
          (status (parse-status pipe)))
     ;; Ignore PIPE's exit status since STATUS above should contain all the
     ;; info we need.
@@ -145,12 +160,21 @@ missing key."
            (_ #f)))
        status))
 
-(define (gnupg-receive-keys key-id server)
-  (system* (%gpg-command) "--keyserver" server "--recv-keys" key-id))
+(define* (gnupg-receive-keys key-id server
+                             #:optional (keyring (current-keyring)))
+  (unless (file-exists? keyring)
+    (mkdir-p (dirname keyring))
+    (call-with-output-file keyring (const #t)))   ;create an empty keybox
+
+  (system* (%gpg-command) "--keyserver" server
+           "--no-default-keyring" "--keyring" keyring
+           "--recv-keys" key-id))
 
 (define* (gnupg-verify* sig file
-                        #:key (key-download 'interactive)
-                              (server (%openpgp-key-server)))
+                        #:key
+                        (key-download 'interactive)
+                        (server (%openpgp-key-server))
+                        (keyring (current-keyring)))
   "Like `gnupg-verify', but try downloading the public key if it's missing.
 Return #t if the signature was good, #f otherwise.  KEY-DOWNLOAD specifies a
 download policy for missing OpenPGP keys; allowed values: 'always', 'never',
@@ -161,15 +185,17 @@ and 'interactive' (default)."
           (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))))
+              (gnupg-receive-keys missing server keyring)
+              (gnupg-status-good-signature? (gnupg-verify sig file
+                                                          keyring))))
 
           (define (receive?)
             (let ((answer
-                   (begin (format #t (G_ "~a~a~%")
-                                  "Would you like to download this key "
-                                  "and add it to your keyring?")
-                          (read-line))))
+                   (begin
+                     (format #t (G_ "Would you like to add this key \
+to keyring '~a'?~%")
+                             keyring)
+                     (read-line))))
               (string-match (locale-yes-regexp) answer)))
 
           (and missing
diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm
index bcc23bd39..58fc64db1 100644
--- a/guix/scripts/refresh.scm
+++ b/guix/scripts/refresh.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <[email-masked]>
 ;;; Copyright © 2013 Nikita Karetnikov <[email-masked]>
 ;;; Copyright © 2014 Eric Bavier <[email-masked]>
 ;;; Copyright © 2015 Alex Kost <[email-masked]>
@@ -89,6 +89,9 @@
                 (lambda (opt name arg result)
                   (alist-cons 'list-dependent? #t result)))
 
+        (option '("keyring") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'keyring arg result)))
         (option '("key-server") #t #f
                 (lambda (opt name arg result)
                   (alist-cons 'key-server arg result)))
@@ -138,6 +141,8 @@ specified with `--select'.\n"))
   -l, --list-dependent   list top-level dependent packages that would need to
                          be rebuilt as a result of upgrading PACKAGE..."))
   (newline)
+  (display (G_ "
+      --keyring=FILE     use FILE as the keyring of upstream OpenPGP keys"))
   (display (G_ "
       --key-server=HOST  use HOST as the OpenPGP key server"))
   (display (G_ "
@@ -437,7 +442,11 @@ update would trigger a complete rebuild."
                                 (%openpgp-key-server)))
                            (%gpg-command
                             (or (assoc-ref opts 'gpg-command)
-                                (%gpg-command))))
+                                (%gpg-command)))
+                           (current-keyring
+                            (or (assoc-ref opts 'keyring)
+                                (string-append (config-directory)
+                                               "/upstream/trustedkeys.kbx"))))
               (for-each
                (cut update-package store <> updaters
                     #:key-download key-download
-- 
2.18.0