summary refs log tree commit diff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2013-12-30 23:18:52 +0100
committerLudovic Courtès <ludo@gnu.org>2013-12-30 23:18:52 +0100
commitf82cc5fdbe62d835d884f2be2289c95da478da25 (patch)
treef2daeed33b1e041f53ad0f9e3eb70d4b9466c2d5
parent554f26ece3c6e3fb04d8069e6be1095e622a97c5 (diff)
downloadguix-f82cc5fdbe62d835d884f2be2289c95da478da25.tar.gz
archive: Add '--authorize'.
* guix/scripts/archive.scm (authorize-key): New procedure.
  (guix-archive): Call it when OPTS contains 'authorize-key'.
* tests/guix-archive.sh: Add test with invalid public key.
* guix/pki.scm: Export '%acl-file'.
* doc/guix.texi (Invoking guix archive): Make it clear that '--import'
  works only with authorized keys.  Document '--authorize'.
-rw-r--r--doc/guix.texi20
-rw-r--r--guix/pki.scm1
-rw-r--r--guix/scripts/archive.scm28
-rw-r--r--tests/guix-archive.sh3
4 files changed, 50 insertions, 2 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index ec529346c7..9976024c06 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -942,7 +942,8 @@ Archives are stored in the ``Nix archive'' or ``Nar'' format, which is
 comparable in spirit to `tar'.  When exporting, the daemon digitally
 signs the contents of the archive, and that digital signature is
 appended.  When importing, the daemon verifies the signature and rejects
-the import in case of an invalid signature.
+the import in case of an invalid signature or if the signing key is not
+authorized.
 @c FIXME: Add xref to daemon doc about signatures.
 
 The main options are:
@@ -955,9 +956,11 @@ resulting archive to the standard output.
 @item --import
 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.
+signature, or if it is signed by a public key not among the authorized
+keys (see @code{--authorize} below.)
 
 @item --generate-key[=@var{parameters}]
+@cindex signing, archives
 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
@@ -970,6 +973,19 @@ 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}).
+
+@item --authorize
+@cindex authorizing, archives
+Authorize imports signed by the public key passed on standard input.
+The public key must be in ``s-expression advanced format''---i.e., the
+same format as the @file{signing-key.pub} file.
+
+The list of authorized keys is kept in the human-editable file
+@file{/etc/guix/acl}.  The file contains
+@url{http://people.csail.mit.edu/rivest/Sexp.txt, ``advanced-format
+s-expressions''} and is structured as an access-control list in the
+@url{http://theworld.com/~cme/spki.txt, Simple Public-Key Infrastructure
+(SPKI)}.
 @end table
 
 To export store files as an archive to the standard output, run:
diff --git a/guix/pki.scm b/guix/pki.scm
index 759cd040e9..dc8139fbc9 100644
--- a/guix/pki.scm
+++ b/guix/pki.scm
@@ -24,6 +24,7 @@
   #:use-module (rnrs io ports)
   #:export (%public-key-file
             %private-key-file
+            %acl-file
             current-acl
             public-keys->acl
             acl->public-keys
diff --git a/guix/scripts/archive.scm b/guix/scripts/archive.scm
index a9e4155393..66000435b4 100644
--- a/guix/scripts/archive.scm
+++ b/guix/scripts/archive.scm
@@ -32,6 +32,7 @@
   #:use-module (srfi srfi-37)
   #:use-module (guix scripts build)
   #:use-module (guix scripts package)
+  #:use-module (rnrs io ports)
   #:export (guix-archive))
 
 
@@ -111,6 +112,9 @@ Export/import one or more packages from/to the store.\n"))
                     (lambda args
                       (leave (_ "invalid key generation parameters: ~s~%")
                              arg)))))
+        (option '("authorize") #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'authorize #t result)))
 
         (option '(#\S "source") #f #f
                 (lambda (opt name arg result)
@@ -256,6 +260,28 @@ this may take time...~%"))
     ;; Make the public key readable by everyone.
     (chmod %public-key-file #o444)))
 
+(define (authorize-key)
+  "Authorize imports signed by the public key passed as an advanced sexp on
+the input port."
+  (define (read-key)
+    (catch 'gcry-error
+      (lambda ()
+        (string->canonical-sexp (get-string-all (current-input-port))))
+      (lambda (key err)
+        (leave (_ "failed to read public key: ~a: ~a~%")
+               (error-source err) (error-string err)))))
+
+  (let ((key (read-key))
+        (acl (current-acl)))
+    (unless (eq? 'public-key (canonical-sexp-nth-data key 0))
+      (leave (_ "s-expression does not denote a public key~%")))
+
+    ;; Add KEY to the ACL and write that.
+    (let ((acl (public-keys->acl (cons key (acl->public-keys acl)))))
+      (with-atomic-file-output %acl-file
+        (lambda (port)
+          (display (canonical-sexp->string acl) port))))))
+
 (define (guix-archive . args)
   (define (parse-options)
     ;; Return the alist of option values.
@@ -274,6 +300,8 @@ this may take time...~%"))
         (cond ((assoc-ref opts 'generate-key)
                =>
                generate-key-pair)
+              ((assoc-ref opts 'authorize)
+               (authorize-key))
               (else
                (let ((store (open-connection)))
                  (cond ((assoc-ref opts 'export)
diff --git a/tests/guix-archive.sh b/tests/guix-archive.sh
index ef04835469..3ac618ae33 100644
--- a/tests/guix-archive.sh
+++ b/tests/guix-archive.sh
@@ -43,3 +43,6 @@ guix archive --import < "$archive" 2>&1 | grep "import.*guile-bootstrap"
 
 if guix archive something-that-does-not-exist
 then false; else true; fi
+
+if echo foo | guix archive --authorize
+then false; else true; fi