archive: Add '--generate-key'.
authorLudovic Courtès <ludo@gnu.org>
Mon, 30 Dec 2013 21:46:21 +0000 (22:46 +0100)
committerLudovic Courtès <ludo@gnu.org>
Mon, 30 Dec 2013 21:57:37 +0000 (22:57 +0100)
* guix/pk-crypto.scm (error-source, error-string): New procedures.
* guix/pki.scm (%private-key-file): New variable.
* guix/scripts/archive.scm (show-help): Document '--generate-key'.
  (%options): Add "generate-key".
  (generate-key-pair): New procedure.
  (guix-archive): Call 'generate-key' when OPTS contains a
  'generate-key' pair.
* doc/guix.texi (Setting Up the Daemon): Suggest generating a key pair.
  (Invoking guix archive): Document '--generate-key'.

doc/guix.texi
guix/pk-crypto.scm
guix/pki.scm
guix/scripts/archive.scm

index afa7654..ec52934 100644 (file)
@@ -237,6 +237,14 @@ case, shared memory support is unavailable in the chroot environment.
 The workaround is to make sure that @file{/dev/shm} is directly a
 @code{tmpfs} mount point.}.
 
+Finally, you may want to generate a key pair to allow the daemon to
+export signed archives of files from the store (@pxref{Invoking guix
+archive}):
+
+@example
+# guix archive --generate-key
+@end example
+
 Guix may also be used in a single-user setup, with @command{guix-daemon}
 running as an unprivileged user.  However, to maximize non-interference
 of build processes, the daemon still needs to perform certain operations
@@ -948,6 +956,20 @@ resulting archive to the standard output.
 Read an archive from the standard input, and import the files listed
 therein into the store.  Abort if the archive has an invalid digital
 signature.
+
+@item --generate-key[=@var{parameters}]
+Generate a new key pair for the daemons.  This is a prerequisite before
+archives can be exported with @code{--export}.  Note that this operation
+usually takes time, because it needs to gather enough entropy to
+generate the key pair.
+
+The generated key pair is typically stored under @file{/etc/guix}, in
+@file{signing-key.pub} (public key) and @file{signing-key.sec} (private
+key, which must be kept secret.)  When @var{parameters} is omitted, it
+is a 4096-bit RSA key.  Alternately, @var{parameters} can specify
+@code{genkey} parameters suitable for Libgcrypt (@pxref{General
+public-key related Functions, @code{gcry_pk_genkey},, gcrypt, The
+Libgcrypt Reference Manual}).
 @end table
 
 To export store files as an archive to the standard output, run:
index d5b3eeb..50f7094 100644 (file)
@@ -25,6 +25,8 @@
   #:use-module (rnrs bytevectors)
   #:use-module (ice-9 match)
   #:export (canonical-sexp?
+            error-source
+            error-string
             string->canonical-sexp
             canonical-sexp->string
             number->canonical-sexp
       (set-pointer-finalizer! ptr finalize-canonical-sexp!))
     sexp))
 
+(define error-source
+  (let* ((ptr  (libgcrypt-func "gcry_strsource"))
+         (proc (pointer->procedure '* ptr (list int))))
+    (lambda (err)
+      "Return the error source (a string) for ERR, an error code as thrown
+along with 'gcry-error'."
+      (pointer->string (proc err)))))
+
+(define error-string
+  (let* ((ptr  (libgcrypt-func "gcry_strerror"))
+         (proc (pointer->procedure '* ptr (list int))))
+    (lambda (err)
+      "Return the error description (a string) for ERR, an error code as
+thrown along with 'gcry-error'."
+      (pointer->string (proc err)))))
+
 (define string->canonical-sexp
   (let* ((ptr  (libgcrypt-func "gcry_sexp_new"))
          (proc (pointer->procedure int ptr `(* * ,size_t ,int))))
index 1ed84e5..759cd04 100644 (file)
@@ -23,6 +23,7 @@
   #:use-module (ice-9 match)
   #:use-module (rnrs io ports)
   #:export (%public-key-file
+            %private-key-file
             current-acl
             public-keys->acl
             acl->public-keys
@@ -69,6 +70,9 @@ element in KEYS must be a canonical sexp with type 'public-key'."
 (define %public-key-file
   (string-append %config-directory "/signing-key.pub"))
 
+(define %private-key-file
+  (string-append %config-directory "/signing-key.sec"))
+
 (define (ensure-acl)
   "Make sure the ACL file exists, and create an initialized one if needed."
   (unless (file-exists? %acl-file)
index df538ed..a9e4155 100644 (file)
@@ -23,6 +23,8 @@
   #:use-module (guix packages)
   #:use-module (guix derivations)
   #:use-module (guix ui)
+  #:use-module (guix pki)
+  #:use-module (guix pk-crypto)
   #:use-module (ice-9 match)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-11)
@@ -52,6 +54,9 @@ Export/import one or more packages from/to the store.\n"))
   (display (_ "
       --import           import from the archive passed on stdin"))
   (newline)
+  (display (_ "
+      --generate-key[=PARAMETERS]
+                         generate a key pair with the given parameters"))
   (display (_ "
   -e, --expression=EXPR  build the package or derivation EXPR evaluates to"))
   (display (_ "
@@ -95,6 +100,17 @@ Export/import one or more packages from/to the store.\n"))
         (option '("import") #f #f
                 (lambda (opt name arg result)
                   (alist-cons 'import #t result)))
+        (option '("generate-key") #f #t
+                (lambda (opt name arg result)
+                  (catch 'gcry-error
+                    (lambda ()
+                      (let ((params
+                             (string->canonical-sexp
+                              (or arg "(genkey (rsa (nbits 4:4096)))"))))
+                        (alist-cons 'generate-key params result)))
+                    (lambda args
+                      (leave (_ "invalid key generation parameters: ~s~%")
+                             arg)))))
 
         (option '(#\S "source") #f #f
                 (lambda (opt name arg result)
@@ -204,7 +220,41 @@ resulting archive to the standard output port."
     (if (or (assoc-ref opts 'dry-run?)
             (build-derivations store drv))
         (export-paths store files (current-output-port))
-        (leave (_ "unable to export the given packages")))))
+        (leave (_ "unable to export the given packages~%")))))
+
+(define (generate-key-pair parameters)
+  "Generate a key pair with PARAMETERS, a canonical sexp, and store it in the
+right place."
+  (when (or (file-exists? %public-key-file)
+            (file-exists? %private-key-file))
+    (leave (_ "key pair exists under '~a'; remove it first~%")
+           (dirname %public-key-file)))
+
+  (format (current-error-port)
+          (_ "Please wait while gathering entropy to generate the key pair;
+this may take time...~%"))
+
+  (let* ((pair   (catch 'gcry-error
+                   (lambda ()
+                     (generate-key parameters))
+                   (lambda (key err)
+                     (leave (_ "key generation failed: ~a: ~a~%")
+                            (error-source err)
+                            (error-string err)))))
+         (public (find-sexp-token pair 'public-key))
+         (secret (find-sexp-token pair 'private-key)))
+    ;; Create the following files as #o400.
+    (umask #o266)
+
+    (with-atomic-file-output %public-key-file
+      (lambda (port)
+        (display (canonical-sexp->string public) port)))
+    (with-atomic-file-output %private-key-file
+      (lambda (port)
+        (display (canonical-sexp->string secret) port)))
+
+    ;; Make the public key readable by everyone.
+    (chmod %public-key-file #o444)))
 
 (define (guix-archive . args)
   (define (parse-options)
@@ -220,13 +270,17 @@ resulting archive to the standard output port."
     ;; Ask for absolute file names so that .drv file names passed from the
     ;; user to 'read-derivation' are absolute when it returns.
     (with-fluids ((%file-port-name-canonicalization 'absolute))
-      (let* ((opts  (parse-options))
-             (store (open-connection)))
-
-        (cond ((assoc-ref opts 'export)
-               (export-from-store store opts))
-              ((assoc-ref opts 'import)
-               (import-paths store (current-input-port)))
+      (let ((opts (parse-options)))
+        (cond ((assoc-ref opts 'generate-key)
+               =>
+               generate-key-pair)
               (else
-               (leave
-                (_ "either '--export' or '--import' must be specified"))))))))
+               (let ((store (open-connection)))
+                 (cond ((assoc-ref opts 'export)
+                        (export-from-store store opts))
+                       ((assoc-ref opts 'import)
+                        (import-paths store (current-input-port)))
+                       (else
+                        (leave
+                         (_ "either '--export' or '--import' \
+must be specified~%")))))))))))