emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: ELPA security


From: Ted Zlatanov
Subject: Re: ELPA security
Date: Sun, 16 Jun 2013 07:18:56 -0400
User-agent: Gnus/5.130008 (Ma Gnus v0.8) Emacs/24.3.50 (darwin)

On Tue, 08 Jan 2013 15:59:33 -0500 Stefan Monnier <address@hidden> wrote: 

>>> Actually, I see a problem with this scheme, now that we also keep around
>>> older versions of the packages.  So maybe it's better to keep the
>>> signatures in a separate file, next to the signed file (e.g. have foo.tar
>>> and foo.tar.gpgsig).
>> Then maybe the file listed in the package vector should be the *.gpgsig
>> one, since otherwise it becomes easy to bypass the check by filtering
>> out any traces of the signature file.

SM> Right, we'd need to indicate somewhere that the sig should be
SM> present, indeed.

SM> A simple way to do that is to tell package.el directly, e.g. via
SM> `package-archives' or just by declaring that all ELPA archives should
SM> always have such signatures (they're pretty easy to add, so I'd expect
SM> marmalade and melpa to adjust pretty quickly).

Please see the attached patch.  The code is not ready for testing, it's
just for review before I implement things further.

Changes:

* add `package-signed-archives', a list of logical archive names with
  default '("gnu").  Add `package-archive-signed-p' to check it.

* change `package--with-work-buffer' to take an archive entry instead of
  just the location.  When an archive is `package-archive-signed-p',
  create a signing buffer and load the archive filename with ".gpgsig"
  appended.  Then call `package--verify-signature' on the package buffer
  and the signing buffer.  If it fails, do `y-or-n-p', and if the user
  rejects, error out.

* `package--verify-signature' is mocked to t right now, but will check
  the maintainer signature.

* `package-download-single' and `package-download-tar' now pass the
  archive entry, not just the location, to `package--with-work-buffer'

* rename `package-archive-base' to `package-archive-for'

* installable packages say "signed" or "unsigned" before the archive name

If you're OK with the code changes I'll get them working and start
implementing `package--verify-signature'.

Ted

=== modified file 'lisp/emacs-lisp/package.el'
--- lisp/emacs-lisp/package.el  2013-06-15 15:36:11 +0000
+++ lisp/emacs-lisp/package.el  2013-06-16 11:05:16 +0000
@@ -229,6 +229,15 @@
   :group 'package
   :version "24.1")
 
+(defcustom package-signed-archives '("gnu")
+  "An list of archives whose contents are signed.
+
+Signed archives trigger verification of each package's contents."
+  :type '(list string :tag "Archive name")
+  :risky t
+  :group 'package
+  :version "24.4")
+
 (defcustom package-pinned-packages nil
   "An alist of packages that are pinned to a specific archive
 
@@ -699,20 +708,39 @@
         nil nil nil 'excl))
       (package--make-autoloads-and-compile name pkg-dir))))
 
-(defmacro package--with-work-buffer (location file &rest body)
-  "Run BODY in a buffer containing the contents of FILE at LOCATION.
-LOCATION is the base location of a package archive, and should be
-one of the URLs (or file names) specified in `package-archives'.
+(defun package-archive-signed-p (archive)
+  "Returns whether ARCHIVE is signed.
+ARCHIVE is a package archive in the form (NAME . LOCATION) and should
+be specified in `package-archives'."
+  (member (car archive) package-signed-archives))
+
+(defmacro package--with-work-buffer (archive file &rest body)
+  "Run BODY in a buffer containing the contents of FILE at ARCHIVE.
+ARCHIVE is a package archive in the form (NAME . LOCATION) and should
+be specified in `package-archives'.
 FILE is the name of a file relative to that base location.
 
-This macro retrieves FILE from LOCATION into a temporary buffer,
-and evaluates BODY while that buffer is current.  This work
-buffer is killed afterwards.  Return the last value in BODY."
-  `(let* ((http (string-match "\\`https?:" ,location))
+This macro retrieves FILE from ARCHIVE into a temporary buffer,
+checks its signature if the ARCHIVE is defined to be signed by
+`package-signed-archives', and evaluates BODY while that buffer
+is current.  This work buffer is killed afterwards.  Return the
+last value in BODY."
+  `(let* ((archive-name (car ,archive))
+          (location (cdr ,archive))
+          (sign-file (concat ,file ".gpgsig"))
+          (http (string-match "\\`https?:" location))
+          (sign (when (package-archive-signed-p archive)
+                  (concat location sign-file)))
          (buffer
           (if http
-              (url-retrieve-synchronously (concat ,location ,file))
-            (generate-new-buffer "*package work buffer*"))))
+              (url-retrieve-synchronously url)
+            (generate-new-buffer "*package work buffer*")))
+          (sign-buffer (when sign
+                         (if http
+                             ;; Retrieve the signature file too.
+                             (url-retrieve-synchronously
+                              (concat location sign-file))
+                           (generate-new-buffer "*package sign buffer*")))))
      (prog1
         (with-current-buffer buffer
           (if http
@@ -720,12 +748,32 @@
                      (re-search-forward "^$" nil 'move)
                      (forward-char)
                      (delete-region (point-min) (point)))
-            (unless (file-name-absolute-p ,location)
+            (unless (file-name-absolute-p location)
               (error "Archive location %s is not an absolute file name"
-                     ,location))
-            (insert-file-contents (expand-file-name ,file ,location)))
+                     location))
+            (insert-file-contents (expand-file-name ,file location)))
+           (when sign-buffer
+             (with-current-buffer sign-buffer
+               (if http
+                   (progn (package-handle-response)
+                          (re-search-forward "^$" nil 'move)
+                          (forward-char)
+                          (delete-region (point-min) (point)))
+                 ;; No need to check the location again like we did above.
+                 (insert-file-contents (expand-file-name sign-file location)))
+               (unless (package--verify-signature archive sign-buffer buffer)
+                 (let ((q (format "Can't verify .gpgsig for %s"
+                                  (concat location ,file))))
+                   (unless (y-or-n-p (concat q "; continue? (y/n)"))
+                     (error q))))))
           ,@body)
-       (kill-buffer buffer))))
+       (kill-buffer buffer)
+       (when sign-buffer
+         (kill-buffer sign-buffer)))))
+
+(defun package--verify-signature archive sign-buffer buffer
+  "Verify SIGN-BUFFER signs BUFFER correctly for ARCHIVE."
+  t)
 
 (defun package-handle-response ()
   "Handle the response from a `url-retrieve-synchronously' call.
@@ -742,16 +790,16 @@
 
 (defun package-download-single (name version desc requires)
   "Download and install a single-file package."
-  (let ((location (package-archive-base name))
+  (let ((archive (package-archive-for name))
        (file (concat (symbol-name name) "-" version ".el")))
-    (package--with-work-buffer location file
+    (package--with-work-buffer archive file
       (package-unpack-single name version desc requires))))
 
 (defun package-download-tar (name version)
   "Download and install a tar package."
-  (let ((location (package-archive-base name))
+  (let ((archive (package-archive-for name))
        (file (concat (symbol-name name) "-" version ".tar")))
-    (package--with-work-buffer location file
+    (package--with-work-buffer archive file
       (package-unpack name version))))
 
 (defvar package--initialized nil)
@@ -875,6 +923,7 @@
 (defun package--add-to-archive-contents (package archive)
   "Add the PACKAGE from the given ARCHIVE if necessary.
 PACKAGE should have the form (NAME . PACKAGE--AC-DESC).
+
 Also, add the originating archive to the `package-desc' structure."
   (let* ((name (car package))
          (version (package--ac-desc-version (cdr package)))
@@ -1094,10 +1143,10 @@
       (error "Package `%s' is a system package, not deleting"
             (package-desc-full-name pkg-desc)))))
 
-(defun package-archive-base (name)
+(defun package-archive-for (name)
   "Return the archive containing the package NAME."
   (let ((desc (cdr (assq (intern-soft name) package-archive-contents))))
-    (cdr (assoc (package-desc-archive desc) package-archives))))
+    (assoc (package-desc-archive desc) package-archives)))
 
 (defun package--download-one-archive (archive file)
   "Retrieve an archive file FILE from ARCHIVE, and cache it.
@@ -1106,7 +1155,7 @@
 \"archives/NAME/archive-contents\" in `package-user-dir'."
   (let* ((dir (expand-file-name (format "archives/%s" (car archive))
                                 package-user-dir)))
-    (package--with-work-buffer (cdr archive) file
+    (package--with-work-buffer archive file
       ;; Read the retrieved buffer to make sure it is valid (e.g. it
       ;; may fetch a URL redirect page).
       (when (listp (read buffer))
@@ -1229,7 +1278,11 @@
                                    'font-lock-face 'font-lock-builtin-face)
                       "  Alternate version available")
             (insert "Available"))
-          (insert " from " archive)
+          (insert " from " (if (package-archive-signed-p
+                                 (assoc archive package-archives))
+                                "signed"
+                              "unsigned")
+                   " " archive)
           (insert " -- ")
           (let ((button-text (if (display-graphic-p) "Install" "[Install]"))
                 (button-face (if (display-graphic-p)
@@ -1287,7 +1340,7 @@
        ;; For elpa packages, try downloading the commentary.  If that
        ;; fails, try an existing readme file in `package-user-dir'.
        (cond ((condition-case nil
-                  (package--with-work-buffer (package-archive-base package)
+                  (package--with-work-buffer (package-archive-for package)
                                              (concat package-name 
"-readme.txt")
                     (setq buffer-file-name
                           (expand-file-name readme package-user-dir))


reply via email to

[Prev in Thread] Current Thread [Next in Thread]