emacs-diffs
[Top][All Lists]
Advanced

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

scratch/package-security 733e674 1/2: Support package checksum verificat


From: Stefan Kangas
Subject: scratch/package-security 733e674 1/2: Support package checksum verification
Date: Sat, 21 Nov 2020 18:43:14 -0500 (EST)

branch: scratch/package-security
commit 733e674af4f66ba7e9f0614b931c44484acce2b9
Author: Stefan Kangas <stefankangas@gmail.com>
Commit: Stefan Kangas <stefan@marxist.se>

    Support package checksum verification
    
    Package checksum verification is the first step towards protecting
    users of package.el against replay attacks.  Signing individual
    packages still allows a hostile actor to distribute an out-of-date
    package containing a known security defect.  To avoid that, we need to
    distribute signed package metadata (the ELPA "archive-contents" file)
    and checksums for the individual packages together.  (Bug#19479)
    
    A subsequent patch will add support for last-update and expiration
    timestamps in "archive-contents", without which the protection against
    replay attacks will be largely ineffective.
    
    Taken together, this feature will make signing individual packages
    obsolete.  We will instead rely on signing the metadata, package
    checksums and timestamps.  Note that individual package signatures
    should still be distributed for a long time still to support old
    versions of Emacs.
    
    For more on replay attacks, see:
    
https://www2.cs.arizona.edu/stork/packagemanagersecurity/attacks-on-package-managers.html
    
    * lisp/emacs-lisp/package.el (package-verify-checksums): New
    defcustom.
    (package-desc, package--ac-desc)
    (package--add-to-archive-contents, package-install-from-archive): New
    fields 'size' and 'checksums'.
    (package-desc-filename): New function.
    
    (package-error): New error type.
    (bad-signature): Inherit from error type 'package-error'.
    (bad-checksum, bad-size): New error types.
    (package-insecure-hash-algorithms): New constant.
    (package--verify-package-checksum)
    (package--verify-package-size): New function to verify that the
    checksum and size of a package corresponds to the checksum and size
    data in the "archive-contents" file on the package archive.
    (package--show-verify-checksum-error): New function to show
    details of an error on checksum verification.
    
    * lisp/emacs-lisp/package-x.el (package-upload-buffer-internal):
    Update to use above new fields 'size' and 'checksums'.
    
    * test/lisp/emacs-lisp/package-tests.el (package-test-refresh-contents)
    (package-test-install-single-from-archive)
    (package-test-list-filter-by-archive)
    (package-test-list-filter-by-status): Update tests.
    (with-install-using-checksum): New macro.
    (package-test-install-wrong-size-single)
    (package-test-install-wrong-size-tar): New tests for size checking.
    (package-test-install-with-checksum/single-valid)
    (package-test-install-with-checksum/single-invalid)
    (package-test-install-with-checksum/tar-valid)
    (package-test-install-with-checksum/tar-invalid): New tests for
    installing packages with checksums.
    (package-test-verification-text)
    (package-tests-valid-md5-checksum)
    (package-tests-valid-sha256-checksum)
    (package-tests-valid-sha512-checksum): New variables.
    (package-tests--run-verify-checksums-test): New macro.
    (package-test-verify-package-checksums-nil/ignore-invalid)
    (package-test-verify-package-checksums-allow-missing)
    (package-test-verify-package-checksums-allow-missing/missing)
    (package-test-verify-package-checksums-allow-missing/ignore-unsupported)
    (package-test-verify-package-checksums-t)
    (package-test-verify-package-checksums-t/invalid-fails)
    (package-test-verify-package-checksums-t/missing-fails)
    (package-test-verify-package-checksums-all)
    (package-test-verify-package-checksums-all/invalid-fails)
    (package-test-verify-package-checksums-all/missing-fails)
    (package-test-verify-package-checksums-all/no-supported-hash-fails)
    (package-test-verify-package-checksums-all/ignore-unsupported)
    (package-test-verify-package-size): New tests for the checksum
    support.
    
    * test/lisp/emacs-lisp/package-resources/archive-contents:
    * test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el:
    * test/lisp/emacs-lisp/package-resources/checksum-valid-123.el:
    * test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar:
    * test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar:
    New test data files.
    
    * doc/emacs/package.texi (Package Installation): Document package
    checksum checking.
    * etc/NEWS: Announce it.
---
 doc/emacs/package.texi                             |  45 +++--
 etc/NEWS                                           |  15 ++
 lisp/emacs-lisp/package-x.el                       |   4 +-
 lisp/emacs-lisp/package.el                         | 166 +++++++++++++++--
 .../emacs-lisp/package-resources/archive-contents  |  36 +++-
 .../package-resources/checksum-invalid-1.0.el      |  17 ++
 .../package-resources/checksum-invalid-tar-0.1.tar | Bin 0 -> 10240 bytes
 .../package-resources/checksum-valid-123.el        |  17 ++
 .../package-resources/checksum-valid-tar-0.99.tar  | Bin 0 -> 10240 bytes
 .../package-resources/wrong-size-single-1.0.el     |   1 +
 .../package-resources/wrong-size-tar-1.0.tar       | Bin 0 -> 10240 bytes
 test/lisp/emacs-lisp/package-tests.el              | 203 ++++++++++++++++++++-
 12 files changed, 470 insertions(+), 34 deletions(-)

diff --git a/doc/emacs/package.texi b/doc/emacs/package.texi
index 4981dd5..9a58356 100644
--- a/doc/emacs/package.texi
+++ b/doc/emacs/package.texi
@@ -338,20 +338,41 @@ name of the package archive directory.  You can alter 
this list if you
 wish to use third party package archives---but do so at your own risk,
 and use only third parties that you think you can trust!
 
-@anchor{Package Signing}
+@anchor{Package Checksums}
 @cindex package security
+@cindex package checksums
+  To improve security, maintainers of package archive can add two
+important measures: checksums and signatures of metadata.  When used
+together, they can increase your trust that a downloaded package has
+not been tampered with, and is not out of date.  Valid checksums and
+signatures are not a cast-iron guarantee that a package is not
+malicious, so you should still exercise caution.  Only install
+packages from package archives that you trust.
+
+  When installing a package from an archive providing package
+checksums, the package system will automatically verify that they
+match the downloaded package.  By default, Emacs will refuse to
+install a package with an invalid checksum, but still allow installing
+a package if checksums are missing.  To disable installing packages
+from archives without checksums, you can set user the user option
+@code{package-verify-checksums} to @code{t}.  This improves security,
+but requires that all package archives you use distribute checksums.
+
+@anchor{Package Signing}
 @cindex package signing
   The maintainers of package archives can increase the trust that you
-can have in their packages by @dfn{signing} them.  They generate a
-private/public pair of cryptographic keys, and use the private key to
-create a @dfn{signature file} for each package.  With the public key, you
-can use the signature files to verify the package creator and make sure
-the package has not been tampered with.  Signature verification uses
-@uref{https://www.gnupg.org/, the GnuPG package} via the EasyPG
-interface (@pxref{Top,, EasyPG, epa, Emacs EasyPG Assistant Manual}).
-A valid signature is not a cast-iron
-guarantee that a package is not malicious, so you should still
-exercise caution.  Package archives should provide instructions
+can have in their packages and package listings by @dfn{signing} them.
+They generate a private/public pair of cryptographic keys, and use the
+private key to create a @dfn{signature file} for the package listing
+itself or each individual package.  With the public key, you can use
+the signature files to verify the files have not been tampered with.
+Signature verification uses @uref{https://www.gnupg.org/, the GnuPG
+package} via the EasyPG interface (@pxref{Top,, EasyPG, epa, Emacs
+EasyPG Assistant Manual}).
+
+The public key for the GNU package archive is distributed with Emacs,
+in the @file{etc/package-keyring.gpg}.  Emacs uses it automatically.
+Other package archives should provide instructions
 on how you can obtain their public key.  One way is to download the
 key from a server such as @url{https://pgp.mit.edu/}.
 Use @kbd{M-x package-import-keyring} to import the key into Emacs.
@@ -361,8 +382,6 @@ subdirectory of @code{package-user-dir}, which causes Emacs 
to invoke
 GnuPG with the option @samp{--homedir} when verifying signatures.
 If @code{package-gnupghome-dir} is @code{nil}, GnuPG's option
 @samp{--homedir} is omitted.
-The public key for the GNU package archive is distributed with Emacs,
-in the @file{etc/package-keyring.gpg}.  Emacs uses it automatically.
 
 @vindex package-check-signature
 @vindex package-unsigned-archives
diff --git a/etc/NEWS b/etc/NEWS
index 9361cff..da18848 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -867,6 +867,21 @@ See the new user options 'package-name-column-width',
 'package-version-column-width', 'package-status-column-width', and
 'package-archive-column-width'.
 
+*** Support for package checksums.
+This improves the security of the Emacs package system.  If the
+package archives you use implements package checksums, you will
+automatically benefit from this by default.
+
+The user option 'package-verify-checksums' controls how and when the
+package system will use checksums.  The default is 'allow-missing',
+which will check them when they are available yet allow installation
+if they are missing.
+
+For improved security, you might want to set this to 't' or
+'all'.  Make sure that the package archives you use support checksums
+before setting these values, or you will be unable to install
+packages.
+
 ** gdb-mi
 
 +++
diff --git a/lisp/emacs-lisp/package-x.el b/lisp/emacs-lisp/package-x.el
index c01b6ef..964cf07 100644
--- a/lisp/emacs-lisp/package-x.el
+++ b/lisp/emacs-lisp/package-x.el
@@ -219,7 +219,9 @@ if it exists."
          (let ((contents (or (package--archive-contents-from-url archive-url)
                              (package--archive-contents-from-file)))
                (new-desc (package-make-ac-desc
-                           split-version requires desc file-type extras)))
+                           split-version requires desc file-type extras
+                           ;; FIXME: Use better values than nil nil.
+                           nil nil)))
            (if (> (car contents) package-archive-version)
                (error "Unrecognized archive version %d" (car contents)))
            (let ((elt (assq pkg-name (cdr contents))))
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 9c37ce4..308f9eb 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -335,6 +335,31 @@ default directory."
   :risky t
   :version "26.1")
 
+(defcustom package-verify-checksums 'allow-missing
+  "Non-nil means to verify the checksum of a package before installing it.
+
+This can be one of:
+- t                Require a valid checksum; refuse to install
+                   package if the checksum is missing or invalid.
+                   Verify only one checksum.
+- `all'            Same as t, but verify all available (and supported)
+                   checksums.
+- `allow-missing'  Same as t if a checksum exists, but install a
+                   package even if there is no checksum.
+- nil              Ignore checksums.
+
+The package checksums are automatically fetched from package
+archives with the package data on `package-refresh-contents'.
+
+Note that setting this to nil is intended for debugging, and
+should normally not be used since it will decrease security."
+  :type '(choice (const nil :tag "Never")
+                 (const allow-missing :tag "Allow missing")
+                 (const t :tag "Require valid checksum")
+                 (const t :tag "Require valid checksum, and check all"))
+  :risky t
+  :version "28.1")
+
 (defcustom package-check-signature 'allow-unsigned
   "Non-nil means to check package signatures when installing.
 More specifically the value can be:
@@ -418,6 +443,14 @@ synchronously."
   :version "28.1")
 
 
+;;; Errors
+
+(define-error 'package-error "Unknown package error")
+(define-error 'bad-size "Package size mismatch" 'package-error)
+(define-error 'bad-signature "Failed to verify signature" 'package-error)
+(define-error 'bad-checksum "Failed to verify checksum" 'package-error)
+
+
 ;;; `package-desc' object definition
 ;; This is the struct used internally to represent packages.
 ;; Functions that deal with packages should generally take this object
@@ -449,6 +482,8 @@ synchronously."
                                  requirements)))
                  (kind (plist-get rest-plist :kind))
                  (archive (plist-get rest-plist :archive))
+                 (checksums (plist-get rest-plist :checksums))
+                 (size (plist-get rest-plist :size))
                  (extras (let (alist)
                            (while rest-plist
                              (unless (memq (car rest-plist) '(:kind :archive))
@@ -486,6 +521,13 @@ Slots:
 
 `extras' Optional alist of additional keyword-value pairs.
 
+`size'  Size of the package in bytes.
+
+`checksums' Checksums for the package file.  Alist of ((ALGORITHM
+        . CHECKSUM)) where ALGORITHM is a symbol specifying a
+        `secure-hash' algorithm, and CHECKSUM is a string
+        containing the checksum.
+
 `signed' Flag to indicate that the package is signed by provider."
   name
   version
@@ -495,7 +537,9 @@ Slots:
   archive
   dir
   extras
-  signed)
+  signed
+  size
+  checksums)
 
 (defun package--from-builtin (bi-desc)
   "Create a `package-desc' object from BI-DESC.
@@ -558,6 +602,13 @@ Signal an error if the kind is none of the above."
     ('dir "")
     (kind (error "Unknown package kind: %s" kind))))
 
+(defun package-desc-filename (pkg-desc)
+  "Return file-name of package-desc object PKG-DESC.
+This is the concatenation of `package-desc-full-name' and
+`package-desc-suffix'."
+  (concat (package-desc-full-name pkg-desc)
+          (package-desc-suffix pkg-desc)))
+
 (defun package-desc--keywords (pkg-desc)
   "Return keywords of package-desc object PKG-DESC.
 These keywords come from the foo-pkg.el file, and in general
@@ -1334,7 +1385,88 @@ errors signaled by ERROR-FORM or by BODY).
                    url))
           (insert-file-contents-literally url)))))
 
-(define-error 'bad-signature "Failed to verify signature")
+(defun package--show-verify-checksum-error (pkg-desc details)
+  "Show error on failed checksum verification of PKG-DESC with DETAILS.
+Error is displayed in a new buffer named \"*Error*\"."
+  (with-output-to-temp-buffer "*Error*"
+    (with-current-buffer standard-output
+      (insert (format "Failed to verify checksum of package `%s':\n\n"
+                      (package-desc-name pkg-desc)))
+      (insert details))))
+
+(defconst package-insecure-hash-algorithms '(md5 sha1)
+  "List of hash algorithms that are not considered secure.")
+
+(defun package--verify-package-checksum (pkg-desc)
+  "Verify checksums of `package-desc' object PKG-DESC.
+This assumes that the we are in a buffer containing package.
+
+The value of `package-verify-checksums' decides what this
+function does:
+- t                Verify that there is at least one valid checksum.
+- `all'            Like t, but check all supported checksums.
+- `allow-missing'  Verify checksum if it exists, otherwise do
+                   nothing.
+- nil              Do nothing.
+
+Signal an error of type `bad-checksum' if the verification."
+  (cl-flet*
+      ((supported-hashes
+        (lambda ()
+          (or (seq-filter
+               (lambda (h)
+                 (and (memql (car h) (secure-hash-algorithms))
+                      (not (memql (car h) package-insecure-hash-algorithms))))
+               (package-desc-checksums pkg-desc))
+              ;; Failed; signal error.
+              (package--show-verify-checksum-error
+               pkg-desc
+               (concat
+                (if (package-desc-checksums pkg-desc)
+                    (concat
+                     "No supported checksums found\n\n"
+                     (format-message "Package archive had: %s\n"
+                                     (package-desc-checksums pkg-desc))
+                     (format-message "Emacs supports: %s\n"
+                                     (secure-hash-algorithms)))
+                  "Package archive had no checksums for this package\n")))
+              (signal 'bad-checksum "no supported checksums found"))))
+       (do-check
+        (lambda (&optional all)
+          (dolist (hash (seq-take (supported-hashes)
+                                  (if all most-positive-fixnum 1)))
+            (let* ((algorithm (car hash))
+                   (expected (cdr hash))
+                   (actual (secure-hash algorithm (current-buffer))))
+              (if (equal expected actual) t
+                ;; Failed; signal error.
+                (package--show-verify-checksum-error
+                 pkg-desc
+                 (concat
+                  (format-message "\nChecksum mismatch (%s)\n\n" algorithm)
+                  (format-message "Expected: %s\n" expected)
+                  (format-message "Result: %s\n" actual)))
+                (signal 'bad-checksum (list "checksum mismatch" expected 
actual))))))))
+    (pcase package-verify-checksums
+      ('nil nil)
+      ('allow-missing (when (package-desc-checksums pkg-desc) (do-check)))
+      ('t (do-check))
+      ('all (do-check 'all))
+      (_ (user-error "Value of `package-verify-checksums' is invalid: `%s'"
+                     package-verify-checksums)))))
+
+(defun package--verify-package-size (pkg-desc)
+  "Verify package size of `package-desc' object PKG-DESC.
+This assumes that the we are in a buffer containing package."
+  (when-let ((expected (package-desc-size pkg-desc))
+             (actual (string-bytes (buffer-string))))
+    (unless (equal expected actual)
+      (with-output-to-temp-buffer "*Error*"
+        (with-current-buffer standard-output
+          (insert (format "Mismatch in package size for `%s':\n"
+                          (package-desc-name pkg-desc)))
+          (insert (format "Expected %s bytes, but received %s" expected 
actual))))
+      (signal 'bad-size (list "size mismatch" expected actual)))))
 
 (defun package--check-signature-content (content string &optional sig-file)
   "Check signature CONTENT against STRING.
@@ -1461,14 +1593,19 @@ the table."
                 (version-list-< table-version version))
         (puthash name version package--compatibility-table)))))
 
-;; Package descriptor objects used inside the "archive-contents" file.
-;; Changing this defstruct implies changing the format of the
-;; "archive-contents" files.
 (cl-defstruct (package--ac-desc
-               (:constructor package-make-ac-desc (version reqs summary kind 
extras))
+               (:constructor
+                package-make-ac-desc (version reqs summary kind extras size 
checksums))
                (:copier nil)
                (:type vector))
-  version reqs summary kind extras)
+  "Package descriptor object used inside the \"archive-contents\" file.
+Changing this defstruct implies changing the format of the
+\"archive-contents\" files.
+
+This is mainly used in `package--add-to-archive-contents' to make
+the code that parses the \"archive-contents\" file more
+readable."
+  version reqs summary kind extras size checksums)
 
 (defun package--append-to-alist (pkg-desc alist)
   "Append an entry for PKG-DESC to the start of ALIST and return it.
@@ -1506,10 +1643,14 @@ Also, add the originating archive to the `package-desc' 
structure."
            :summary (package--ac-desc-summary (cdr package))
            :kind (package--ac-desc-kind (cdr package))
            :archive archive
+           ;; Older "archive-contents" files might not have the
+           ;; below elements.
            :extras (and (> (length (cdr package)) 4)
-                        ;; Older archive-contents files have only 4
-                        ;; elements here.
-                        (package--ac-desc-extras (cdr package)))))
+                        (package--ac-desc-extras (cdr package)))
+           :size (and (> (length (cdr package)) 5)
+                      (package--ac-desc-size (cdr package)))
+           :checksums (and (> (length (cdr package)) 6)
+                           (package--ac-desc-checksums (cdr package)))))
          (pinned-to-archive (assoc name package-pinned-packages)))
     ;; Skip entirely if pinned to another archive.
     (when (not (and pinned-to-archive
@@ -1979,9 +2120,10 @@ if all the in-between dependencies are also in 
PACKAGE-LIST."
   (when (eq (package-desc-kind pkg-desc) 'dir)
     (error "Can't install directory package from archive"))
   (let* ((location (package-archive-base pkg-desc))
-         (file (concat (package-desc-full-name pkg-desc)
-                       (package-desc-suffix pkg-desc))))
+         (file (package-desc-filename pkg-desc)))
     (package--with-response-buffer location :file file
+      (package--verify-package-size pkg-desc)
+      (package--verify-package-checksum pkg-desc)
       (if (or (not (package-check-signature))
               (member (package-desc-archive pkg-desc)
                       package-unsigned-archives))
diff --git a/test/lisp/emacs-lisp/package-resources/archive-contents 
b/test/lisp/emacs-lisp/package-resources/archive-contents
index e2f9230..724972d 100644
--- a/test/lisp/emacs-lisp/package-resources/archive-contents
+++ b/test/lisp/emacs-lisp/package-resources/archive-contents
@@ -14,4 +14,38 @@
  (multi-file .
              [(0 2 3)
               nil "Example of a multi-file tar package" tar
-              ((:url . "http://puddles.li";))]))
+              ((:url . "http://puddles.li";))])
+ (checksum-valid  .
+                  [(123)
+                    nil "A single-file package with a valid checksum." single
+                    nil
+                    343
+                    ((sha512 . 
"a889917427569cc6817db5db08a88390d44ec010acdf6810c2dfaba04b9a03f00315378c3f03d5f4d531833028ad61db54c4c56106662585da6a0dde602f5c0d"))])
+ (checksum-valid-tar .
+                     [(0 99)
+                     nil "A multi-file package with a valid checksum." tar
+                     nil
+                     10240
+                     ((sha512 . 
"2be7c37a16db32a2b08fc917ed5f4241814e2665bda1bd15328c2e5a842e45b81f6f31274697248ffaabf8010796685acb3342c5920af53ddd1e75d7fd764bd1"))])
+ (checksum-invalid .
+                   [(1 0)
+                    nil "A single-file package with an invalid checksum." 
single
+                    nil
+                    365
+                    ((sha512 . "not-a-valid-checksum"))])
+ (checksum-invalid-tar .
+                       [(0 1)
+                        nil "A multi-file package with an invalid checksum." 
tar
+                        nil
+                        10240
+                        ((sha512 . "not-a-valid-checksum"))])
+ (wrong-size-single .
+                    [(1 0)
+                     nil "A single-file package with an invalid size." single
+                     nil
+                     1])
+ (wrong-size-tar .
+                 [(1 0)
+                  nil "A multi-file package with an invalid size." tar
+                  nil
+                  1]))
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el 
b/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el
new file mode 100644
index 0000000..c3d4790
--- /dev/null
+++ b/test/lisp/emacs-lisp/package-resources/checksum-invalid-1.0.el
@@ -0,0 +1,17 @@
+;;; checksum-invalid.el --- A package with an invalid checksum in 
archive-contents
+
+;; Version: 1.0
+
+;;; Commentary:
+
+;; This package has an invalid checksum in archive-contents and is
+;; just used to verify that package.el refuses to install.
+
+;;; Code:
+
+(defun p-equal-to-np-p ()
+  (error "FIXME"))
+
+(provide 'checksum-invalid)
+
+;;; checksum-invalid.el ends here
diff --git 
a/test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar 
b/test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar
new file mode 100644
index 0000000..8adc7f5
Binary files /dev/null and 
b/test/lisp/emacs-lisp/package-resources/checksum-invalid-tar-0.1.tar differ
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el 
b/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el
new file mode 100644
index 0000000..8cfc387
--- /dev/null
+++ b/test/lisp/emacs-lisp/package-resources/checksum-valid-123.el
@@ -0,0 +1,17 @@
+;;; checksum-valid.el --- A package with an valid checksum in archive-contents
+
+;; Version: 123
+
+;;; Commentary:
+
+;; This package has an valid checksum in archive-contents and is
+;; used to verify that package.el installs it.
+
+;;; Code:
+
+(defun p-equal-to-np-p ()
+  (error "FIXME"))
+
+(provide 'checksum-valid)
+
+;;; checksum-valid.el ends here
diff --git a/test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar 
b/test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar
new file mode 100644
index 0000000..e468754
Binary files /dev/null and 
b/test/lisp/emacs-lisp/package-resources/checksum-valid-tar-0.99.tar differ
diff --git a/test/lisp/emacs-lisp/package-resources/wrong-size-single-1.0.el 
b/test/lisp/emacs-lisp/package-resources/wrong-size-single-1.0.el
new file mode 100644
index 0000000..a4e3daf
--- /dev/null
+++ b/test/lisp/emacs-lisp/package-resources/wrong-size-single-1.0.el
@@ -0,0 +1 @@
+;; This file just has the wrong size (i.e. not 1 as specified).
diff --git a/test/lisp/emacs-lisp/package-resources/wrong-size-tar-1.0.tar 
b/test/lisp/emacs-lisp/package-resources/wrong-size-tar-1.0.tar
new file mode 100644
index 0000000..61d47c6
Binary files /dev/null and 
b/test/lisp/emacs-lisp/package-resources/wrong-size-tar-1.0.tar differ
diff --git a/test/lisp/emacs-lisp/package-tests.el 
b/test/lisp/emacs-lisp/package-tests.el
index 2326754..a81506d 100644
--- a/test/lisp/emacs-lisp/package-tests.el
+++ b/test/lisp/emacs-lisp/package-tests.el
@@ -44,6 +44,9 @@
 
 (setq package-menu-async nil)
 
+;; Silence byte-compiler.
+(defvar epg-config--program-alist)
+
 (defvar package-test-user-dir nil
   "Directory to use for installing packages during testing.")
 
@@ -304,14 +307,33 @@ Must called from within a `tar-mode' buffer."
   (with-package-test ()
     (package-initialize)
     (package-refresh-contents)
-    (should (eq 4 (length package-archive-contents)))))
+    (should (eq 10 (length package-archive-contents)))))
 
 (ert-deftest package-test-install-single-from-archive ()
   "Install a single package from a package archive."
   (with-package-test ()
     (package-initialize)
     (package-refresh-contents)
-    (package-install 'simple-single)))
+    (package-install 'simple-single)
+    (should (package-installed-p 'simple-single))))
+
+(ert-deftest package-test-install-wrong-size-single ()
+  "Install a tar package with invalid size."
+  (should-error
+   (with-package-test ()
+     (package-initialize)
+     (package-refresh-contents)
+     (package-install 'wrong-size-single))
+   :type 'bad-size))
+
+(ert-deftest package-test-install-wrong-size-tar ()
+  "Install a tar package with invalid size."
+  (should-error
+   (with-package-test ()
+     (package-initialize)
+     (package-refresh-contents)
+     (package-install 'wrong-size-tar))
+   :type 'bad-size))
 
 (ert-deftest package-test-install-prioritized ()
   "Install a lower version from a higher-prioritized archive."
@@ -389,8 +411,8 @@ Must called from within a `tar-mode' buffer."
     ;;       the testing environment currently only has one.
     (package-menu-filter-by-archive "gnu")
     (goto-char (point-min))
-    (should (looking-at "^\\s-+multi-file"))
-    (should (= (count-lines (point-min) (point-max)) 4))
+    (should (looking-at "^\\s-+checksum-invalid"))
+    (should (= (count-lines (point-min) (point-max)) 10))
     (should-error (package-menu-filter-by-archive "non-existent archive"))))
 
 (ert-deftest package-test-list-filter-by-keyword ()
@@ -416,7 +438,7 @@ Must called from within a `tar-mode' buffer."
     (package-menu-filter-by-status "available")
     (goto-char (point-min))
     (should (re-search-forward "^\\s-+multi-file" nil t))
-    (should (= (count-lines (point-min) (point-max)) 4))
+    (should (= (count-lines (point-min) (point-max)) 10))
     ;; No installed packages in default environment.
     (should-error (package-menu-filter-by-status "installed"))))
 
@@ -671,6 +693,169 @@ Must called from within a `tar-mode' buffer."
                "Status: Installed in ['`‘]signed-good-1.0/['’]."
                nil t))))))
 
+
+;;; Tests for package checksum verification.
+
+(defmacro with-install-using-checksum (ok fail package)
+  "Test installing PACKAGE while setting `package-verify-checksums'."
+  (declare (indent 2))
+  `(progn
+     (dolist (opt ,ok)
+       (let ((package-verify-checksums opt))
+         (with-package-test ()
+           (package-initialize)
+           (package-refresh-contents)
+           (package-install ,package)
+           (package-installed-p ,package))))
+     (dolist (opt ,fail)
+       (let ((package-verify-checksums opt))
+         (should-error
+          (with-package-test ()
+            (package-initialize)
+            (package-refresh-contents)
+            (package-install ,package))
+          :type 'bad-checksum)))))
+
+(ert-deftest package-test-install-with-checksum/single-valid ()
+  "Install a single package with valid checksum."
+  (with-install-using-checksum '(nil allow-missing t all) '() 'checksum-valid))
+
+(ert-deftest package-test-install-with-checksum/single-invalid ()
+  "Install a tar package with invalid checksum."
+  (with-install-using-checksum '(nil) '(allow-missing t all) 
'checksum-invalid))
+
+(ert-deftest package-test-install-with-checksum/tar-valid ()
+  "Install a tar package with valid checksum."
+  (with-install-using-checksum '(nil allow-missing t all) '() 
'checksum-valid-tar))
+
+(ert-deftest package-test-install-with-checksum/tar-invalid ()
+  "Install a tar package with invalid checksum."
+  (with-install-using-checksum '(nil) '(allow-missing t all) 
'checksum-invalid-tar))
+
+(defconst package-test-verification-text
+  "Example text for testing checksum verification.")
+(defconst package-tests-valid-md5-checksum
+  ;; (secure-hash 'md5 package-test-verification-text)
+  "abe6375809e532f081b808b3aa052dfb")
+(defconst package-tests-valid-sha256-checksum
+  ;; (secure-hash 'sha256 package-test-verification-text)
+  "6875aa4523e45ddef627b4edf1296f1d7dd0c22ddd6a6584f0228215d25eefcd")
+(defconst package-tests-valid-sha512-checksum
+  ;; (secure-hash 'sha512 package-test-verification-text)
+  (concat "bdc631f9e675b1ea34570f0a4bb44568dc5cecac905eea737f5f451bc52fd0c6"
+          "81b0d8b3dc2a942b9950fbe9096ebdf517668245c9b5a7bbdea8487a8f9cdce6"))
+
+(defmacro package-tests--run-verify-checksums-test (verify-checksums checksums)
+  "Run a test for `package-verify-checksums'."
+  (declare (indent 1))
+  `(with-temp-buffer
+     (insert package-test-verification-text)
+     (let ((package-verify-checksums ,verify-checksums)
+           (pkg (package-desc-create :name 'foobar
+                              :version '(1 0)
+                              :summary "Just a package with checksum."
+                              :kind 'single
+                              :checksums ,checksums)))
+       (package--verify-package-checksum pkg))))
+
+(ert-deftest package-test-verify-package-checksums-nil/ignore-invalid ()
+  "Ignore all checksums even when invalid."
+  (package-tests--run-verify-checksums-test nil
+    '((sha512 . "invalid")
+      (invalid . "invalid"))))
+
+(ert-deftest package-test-verify-package-checksums-nil/ignore-empty ()
+  "Ignore all checksums even when empty."
+  (package-tests--run-verify-checksums-test nil
+    nil))
+
+(ert-deftest package-test-verify-package-checksums-allow-missing ()
+  "Verify checksums (allow-missing) -- verify if available."
+  (package-tests--run-verify-checksums-test 'allow-missing
+    `((sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test-verify-package-checksums-allow-missing/missing ()
+  "Verify checksums (allow-missing) -- allow missing."
+  (package-tests--run-verify-checksums-test 'allow-missing
+    nil))
+
+(ert-deftest 
package-test-verify-package-checksums-allow-missing/ignore-unsupported ()
+  "Verify checksums (t) -- ignore unsupported algorithm."
+  (package-tests--run-verify-checksums-test 'allow-missing
+    `((ignore . "not supported")
+      (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test-verify-package-checksums-t ()
+  "Verify checksums (t) -- succeed when valid."
+  (package-tests--run-verify-checksums-test t
+    `((sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test-verify-package-checksums-t/invalid-fails ()
+  "Verify checksums (t) -- fail on invalid."
+  (should-error
+   (package-tests--run-verify-checksums-test t
+     '((sha512 . "invalid")))
+   :type 'bad-checksum))
+
+(ert-deftest package-test-verify-package-checksums-t/missing-fails ()
+  "Verify checksums (t) -- fail on missing."
+  (should-error
+   (package-tests--run-verify-checksums-test t
+     nil)
+   :type 'bad-checksum))
+
+(ert-deftest package-test-verify-package-checksums-t/ignore-unsupported ()
+  "Verify checksums (t) -- ignore unsupported algorithm."
+  (package-tests--run-verify-checksums-test t
+    `((ignore . "not supported")
+      (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test-verify-package-checksums-all ()
+  "Verify checksums (all) -- succeed on valid."
+  (package-tests--run-verify-checksums-test 'all
+    `((md5    . ,package-tests-valid-md5-checksum)
+      (sha256 . ,package-tests-valid-sha256-checksum)
+      (sha512 . ,package-tests-valid-sha512-checksum))))
+
+(ert-deftest package-test-verify-package-checksums-all/invalid-fails ()
+  "Verify checksums (all) -- fail if one checksum is invalid."
+  (should-error
+   (package-tests--run-verify-checksums-test 'all
+     `((md5    . ,package-tests-valid-md5-checksum)
+       (sha256 . "invalid")
+       (sha512 . ,package-tests-valid-sha512-checksum)))
+   :type 'bad-checksum))
+
+(ert-deftest package-test-verify-package-checksums-all/missing-fails ()
+  "Verify checksums (all) -- fail on missing checksums."
+  (should-error
+   (package-tests--run-verify-checksums-test 'all
+     nil)
+   :type 'bad-checksum))
+
+(ert-deftest package-test-verify-package-checksums-all/no-supported-hash-fails 
()
+  "Verify checksums (all) -- fail if we have no supported hash."
+  (should-error
+   (package-tests--run-verify-checksums-test 'all
+     '((unsupported . "invalid")))
+   :type 'bad-checksum))
+
+(ert-deftest package-test-verify-package-checksums-all/ignore-unsupported ()
+  "Verify checksums (all) -- succed if one hash algorithm is unsupported.
+If the rest succeed, just ignore the unsupported one."
+  (package-tests--run-verify-checksums-test 'all
+    `((md5    . ,package-tests-valid-md5-checksum)
+      (sha256 . ,package-tests-valid-sha256-checksum)
+      (sha512 . ,package-tests-valid-sha512-checksum)
+      (ignore . "not supported"))))
+
+(ert-deftest package-test-verify-package-size ()
+  (with-temp-buffer
+    (let ((pkg-desc (package-desc-create :size 6)))
+      (insert "123456")
+      (package--verify-package-size pkg-desc)
+      (insert "7")
+      (should-error (package--verify-package-size pkg-desc)))))
 
 
 ;;; Tests for package-x features.
@@ -684,7 +869,9 @@ Must called from within a `tar-mode' buffer."
                               'single
                               '((:authors ("J. R. Hacker" . "jrh@example.com"))
                                 (:maintainer "J. R. Hacker" . 
"jrh@example.com")
-                                (:url . "http://doodles.au";))))
+                                (:url . "http://doodles.au";))
+                              nil
+                              nil))
   "Expected contents of the archive entry from the \"simple-single\" package.")
 
 (defvar package-x-test--single-archive-entry-1-4
@@ -693,7 +880,9 @@ Must called from within a `tar-mode' buffer."
                               "A single-file package with no dependencies"
                               'single
                               '((:authors ("J. R. Hacker" . "jrh@example.com"))
-                                (:maintainer "J. R. Hacker" . 
"jrh@example.com"))))
+                                (:maintainer "J. R. Hacker" . 
"jrh@example.com"))
+                              nil
+                              nil))
   "Expected contents of the archive entry from the updated \"simple-single\" 
package.")
 
 (ert-deftest package-x-test-upload-buffer ()



reply via email to

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