Merge from emacs--devo--0
[gnus] / lisp / mml2015.el
index f23a343..95d1192 100644 (file)
@@ -1,7 +1,7 @@
 ;;; mml2015.el --- MIME Security with Pretty Good Privacy (PGP)
 
 ;; Copyright (C) 2000, 2001, 2002, 2003, 2004,
-;;   2005, 2006 Free Software Foundation, Inc.
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
 ;; Keywords: PGP MIME MML
@@ -10,7 +10,7 @@
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published
-;; by the Free Software Foundation; either version 2, or (at your
+;; by the Free Software Foundation; either version 3, or (at your
 ;; option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful, but
 
 ;;; Code:
 
+;; For Emacs < 22.2.
+(eval-and-compile
+  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
+
 (eval-when-compile (require 'cl))
 (require 'mm-decode)
 (require 'mm-util)
 
 (defvar mc-pgp-always-sign)
 
+(declare-function epg-check-configuration "ext:epg-config"
+                  (config &optional minimum-version))
+(declare-function epg-configuration "ext:epg-config" ())
+
 (defvar mml2015-use (or
                     (condition-case nil
                         (progn
@@ -141,6 +149,28 @@ Whether the passphrase is cached at all is controlled by
   :group 'mime-security
   :type 'boolean)
 
+;; Extract plaintext from cleartext signature.  IMO, this kind of task
+;; should be done by GnuPG rather than Elisp, but older PGP backends
+;; (such as Mailcrypt, PGG, and gpg.el) discard the output from GnuPG.
+(defun mml2015-extract-cleartext-signature ()
+  (goto-char (point-min))
+  (forward-line)
+  ;; We need to be careful not to strip beyond the armor headers.
+  ;; Previously, an attacker could replace the text inside our
+  ;; markup with trailing garbage by injecting whitespace into the
+  ;; message.
+  (while (looking-at "Hash:")          ; The only header allowed in cleartext
+    (forward-line))                    ; signatures according to RFC2440.
+  (when (looking-at "[\t ]*$")
+    (forward-line))
+  (delete-region (point-min) (point))
+  (if (re-search-forward "^-----BEGIN PGP SIGNATURE-----" nil t)
+      (delete-region (match-beginning 0) (point-max)))
+  (goto-char (point-min))
+  (while (re-search-forward "^- " nil t)
+    (replace-match "" t t)
+    (forward-line 1)))
+
 ;;; mailcrypt wrapper
 
 (eval-and-compile
@@ -151,9 +181,8 @@ Whether the passphrase is cached at all is controlled by
   (autoload 'mc-cleanup-recipient-headers "mc-toplev")
   (autoload 'mc-sign-generic "mc-toplev"))
 
-(eval-when-compile
-  (defvar mc-default-scheme)
-  (defvar mc-schemes))
+(defvar mc-default-scheme)
+(defvar mc-schemes)
 
 (defvar mml2015-decrypt-function 'mailcrypt-decrypt)
 (defvar mml2015-verify-function 'mailcrypt-verify)
@@ -327,7 +356,8 @@ Whether the passphrase is cached at all is controlled by
        (mm-set-handle-multipart-parameter
         mm-security-handle 'gnus-info "OK")
       (mm-set-handle-multipart-parameter
-       mm-security-handle 'gnus-info "Failed"))))
+       mm-security-handle 'gnus-info "Failed")))
+  (mml2015-extract-cleartext-signature))
 
 (defun mml2015-mailcrypt-sign (cont)
   (mc-sign-generic (message-options-get 'message-sender)
@@ -374,6 +404,10 @@ Whether the passphrase is cached at all is controlled by
     (insert (format "--%s--\n" boundary))
     (goto-char (point-max))))
 
+;; We require mm-decode, which requires mm-bodies, which autoloads
+;; message-options-get (!).
+(declare-function message-options-set "message" (symbol value))
+
 (defun mml2015-mailcrypt-encrypt (cont &optional sign)
   (let ((mc-pgp-always-sign
         (or mc-pgp-always-sign
@@ -524,9 +558,8 @@ Whether the passphrase is cached at all is controlled by
       (with-temp-buffer
        (setq message (current-buffer))
        (insert part)
-       ;; Convert <LF> to <CR><LF> in verify mode.  Sign and
-       ;; clearsign use --textmode. The conversion is not necessary.
-       ;; In clearverify, the conversion is not necessary either.
+       ;; Convert <LF> to <CR><LF> in signed text.  If --textmode is
+       ;; specified when signing, the conversion is not necessary.
        (goto-char (point-min))
        (end-of-line)
        (while (not (eobp))
@@ -594,7 +627,8 @@ Whether the passphrase is cached at all is controlled by
        (with-current-buffer mml2015-result-buffer
         (mml2015-gpg-extract-signature-details)))
     (mm-set-handle-multipart-parameter
-     mm-security-handle 'gnus-info "Failed")))
+     mm-security-handle 'gnus-info "Failed"))
+  (mml2015-extract-cleartext-signature))
 
 (defun mml2015-gpg-sign (cont)
   (let ((boundary (mml-compute-boundary cont))
@@ -684,10 +718,9 @@ Whether the passphrase is cached at all is controlled by
 
 ;;; pgg wrapper
 
-(eval-when-compile
-  (defvar pgg-default-user-id)
-  (defvar pgg-errors-buffer)
-  (defvar pgg-output-buffer))
+(defvar pgg-default-user-id)
+(defvar pgg-errors-buffer)
+(defvar pgg-output-buffer)
 
 (eval-and-compile
   (autoload 'pgg-decrypt-region "pgg")
@@ -783,9 +816,8 @@ Whether the passphrase is cached at all is controlled by
          handle)
       (with-temp-buffer
        (insert part)
-       ;; Convert <LF> to <CR><LF> in verify mode.  Sign and
-       ;; clearsign use --textmode. The conversion is not necessary.
-       ;; In clearverify, the conversion is not necessary either.
+       ;; Convert <LF> to <CR><LF> in signed text.  If --textmode is
+       ;; specified when signing, the conversion is not necessary.
        (goto-char (point-min))
        (end-of-line)
        (while (not (eobp))
@@ -834,7 +866,7 @@ Whether the passphrase is cached at all is controlled by
     (if (condition-case err
            (prog1
                (mm-with-unibyte-buffer
-                 (insert (encode-coding-string text coding-system))
+                 (insert (mm-encode-coding-string text coding-system))
                  (pgg-verify-region (point-min) (point-max) nil t))
              (goto-char (point-min))
              (while (search-forward "\r\n" nil t)
@@ -858,7 +890,8 @@ Whether the passphrase is cached at all is controlled by
         (with-current-buffer pgg-errors-buffer
           (mml2015-gpg-extract-signature-details)))
       (mm-set-handle-multipart-parameter
-       mm-security-handle 'gnus-info "Failed"))))
+       mm-security-handle 'gnus-info "Failed")))
+  (mml2015-extract-cleartext-signature))
 
 (defun mml2015-pgg-sign (cont)
   (let ((pgg-errors-buffer mml2015-result-buffer)
@@ -922,13 +955,12 @@ Whether the passphrase is cached at all is controlled by
 
 ;;; epg wrapper
 
-(eval-and-compile
-  (autoload 'epg-make-context "epg"))
+(defvar epg-user-id-alist)
+(defvar epg-digest-algorithm-alist)
+(defvar inhibit-redisplay)
 
-(eval-when-compile
-  (defvar epg-user-id-alist)
-  (defvar epg-digest-algorithm-alist)
-  (defvar inhibit-redisplay)
+(eval-and-compile
+  (autoload 'epg-make-context "epg")
   (autoload 'epg-context-set-armor "epg")
   (autoload 'epg-context-set-textmode "epg")
   (autoload 'epg-context-set-signers "epg")
@@ -942,15 +974,14 @@ Whether the passphrase is cached at all is controlled by
   (autoload 'epg-encrypt-string "epg")
   (autoload 'epg-passphrase-callback-function "epg")
   (autoload 'epg-context-set-passphrase-callback "epg")
+  (autoload 'epg-key-sub-key-list "epg")
+  (autoload 'epg-sub-key-capability "epg")
+  (autoload 'epg-sub-key-validity "epg")
   (autoload 'epg-configuration "epg-config")
   (autoload 'epg-expand-group "epg-config")
   (autoload 'epa-select-keys "epa"))
 
-(eval-when-compile
-  (defvar password-cache-expiry)
-  (autoload 'password-read "password")
-  (autoload 'password-cache-add "password")
-  (autoload 'password-cache-remove "password"))
+(defvar password-cache-expiry)
 
 (defvar mml2015-epg-secret-key-id-list nil)
 
@@ -975,6 +1006,18 @@ Whether the passphrase is cached at all is controlled by
              (cons key-id mml2015-epg-secret-key-id-list))
        (copy-sequence passphrase)))))
 
+(defun mml2015-epg-find-usable-key (keys usage)
+  (catch 'found
+    (while keys
+      (let ((pointer (epg-key-sub-key-list (car keys))))
+       (while pointer
+         (if (and (memq usage (epg-sub-key-capability (car pointer)))
+                  (not (memq (epg-sub-key-validity (car pointer))
+                             '(revoked expired))))
+             (throw 'found (car keys)))
+         (setq pointer (cdr pointer))))
+      (setq keys (cdr keys)))))
+
 (defun mml2015-epg-decrypt (handle ctl)
   (catch 'error
     (let ((inhibit-redisplay t)
@@ -1083,9 +1126,11 @@ Whether the passphrase is cached at all is controlled by
        (mm-set-handle-multipart-parameter
         mm-security-handle 'gnus-info "Corrupted")
        (throw 'error handle))
-      (setq context (epg-make-context))
+      (setq part (mm-replace-in-string part "\n" "\r\n" t)
+           signature (mm-get-part signature)
+           context (epg-make-context))
       (condition-case error
-         (setq plain (epg-verify-string context (mm-get-part signature) part))
+         (setq plain (epg-verify-string context signature part))
        (error
         (mm-set-handle-multipart-parameter
          mm-security-handle 'gnus-info "Failed")
@@ -1103,8 +1148,8 @@ Whether the passphrase is cached at all is controlled by
 (defun mml2015-epg-clear-verify ()
   (let ((inhibit-redisplay t)
        (context (epg-make-context))
-       (signature (encode-coding-string (buffer-string)
-                                        buffer-file-coding-system))
+       (signature (mm-encode-coding-string (buffer-string)
+                                           coding-system-for-write))
        plain)
     (condition-case error
        (setq plain (epg-verify-string context signature))
@@ -1117,29 +1162,43 @@ Whether the passphrase is cached at all is controlled by
         (mm-set-handle-multipart-parameter
          mm-security-handle 'gnus-details (mml2015-format-error error)))))
     (if plain
-       (mm-set-handle-multipart-parameter
-        mm-security-handle 'gnus-info
-        (epg-verify-result-to-string
-         (epg-context-result-for context 'verify))))))
+       (progn
+         (mm-set-handle-multipart-parameter
+          mm-security-handle 'gnus-info
+          (epg-verify-result-to-string
+           (epg-context-result-for context 'verify)))
+         (delete-region (point-min) (point-max))
+         (insert (mm-decode-coding-string plain coding-system-for-read)))
+      (mml2015-extract-cleartext-signature))))
 
 (defun mml2015-epg-sign (cont)
   (let* ((inhibit-redisplay t)
-       (context (epg-make-context))
-       (boundary (mml-compute-boundary cont))
-       (signers
-        (or (message-options-get 'mml2015-epg-signers)
-            (message-options-set
-             'mml2015-epg-signers
-             (if mml2015-verbose
-                 (epa-select-keys context "\
+        (context (epg-make-context))
+        (boundary (mml-compute-boundary cont))
+        signer-key
+        (signers
+         (or (message-options-get 'mml2015-epg-signers)
+             (message-options-set
+              'mml2015-epg-signers
+              (if mml2015-verbose
+                  (epa-select-keys context "\
 Select keys for signing.
 If no one is selected, default secret key is used.  "
-                                  mml2015-signers t)
-               (if mml2015-signers
-                   (mapcar (lambda (name)
-                             (car (epg-list-keys context name t)))
-                           mml2015-signers))))))
-       signature micalg)
+                                   mml2015-signers t)
+                (if mml2015-signers
+                    (mapcar
+                     (lambda (signer)
+                       (setq signer-key (mml2015-epg-find-usable-key
+                                         (epg-list-keys context signer t)
+                                         'sign))
+                       (unless (or signer-key
+                                   (y-or-n-p
+                                    (format "No secret key for %s; skip it? "
+                                            signer)))
+                         (error "No secret key for %s" signer))
+                       signer-key)
+                     mml2015-signers))))))
+        signature micalg)
     (epg-context-set-armor context t)
     (epg-context-set-textmode context t)
     (epg-context-set-signers context signers)
@@ -1163,7 +1222,7 @@ If no one is selected, default secret key is used.  "
     (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
                    boundary))
     (if micalg
-       (insert (format "\tmicalg=%s; "
+       (insert (format "\tmicalg=pgp-%s; "
                        (downcase
                         (cdr (assq micalg
                                    epg-digest-algorithm-alist))))))
@@ -1183,19 +1242,24 @@ If no one is selected, default secret key is used.  "
        (config (epg-configuration))
        (recipients (message-options-get 'mml2015-epg-recipients))
        cipher signers
-       (boundary (mml-compute-boundary cont)))
+       (boundary (mml-compute-boundary cont))
+       recipient-key signer-key)
     (unless recipients
       (setq recipients
            (apply #'nconc
                   (mapcar
                    (lambda (recipient)
                      (or (epg-expand-group config recipient)
-                         (list recipient)))
+                         (list (concat "<" recipient ">"))))
                    (split-string
                     (or (message-options-get 'message-recipients)
                         (message-options-set 'message-recipients
                                              (read-string "Recipients: ")))
                     "[ \f\t\n\r\v,]+"))))
+      (when mml2015-encrypt-to-self
+       (unless mml2015-signers
+         (error "mml2015-signers not set"))
+       (setq recipients (nconc recipients mml2015-signers)))
       (if mml2015-verbose
          (setq recipients
                (epa-select-keys context "\
@@ -1203,17 +1267,20 @@ Select recipients for encryption.
 If no one is selected, symmetric encryption will be performed.  "
                                 recipients))
        (setq recipients
-             (delq nil (mapcar (lambda (name)
-                                 (car (epg-list-keys context name)))
-                               recipients))))
-      (if mml2015-encrypt-to-self
-         (if mml2015-signers
-             (setq recipients
-                   (nconc recipients
-                          (mapcar (lambda (name)
-                                    (car (epg-list-keys context name)))
-                                  mml2015-signers)))
-           (error "mml2015-signers not set")))
+             (mapcar
+              (lambda (recipient)
+                (setq recipient-key (mml2015-epg-find-usable-key
+                                     (epg-list-keys context recipient)
+                                     'encrypt))
+                (unless (or recipient-key
+                            (y-or-n-p
+                             (format "No public key for %s; skip it? "
+                                     recipient)))
+                  (error "No public key for %s" recipient))
+                recipient-key)
+              recipients))
+       (unless recipients
+         (error "No recipient specified")))
       (message-options-set 'mml2015-epg-recipients recipients))
     (when sign
       (setq signers
@@ -1226,9 +1293,19 @@ Select keys for signing.
 If no one is selected, default secret key is used.  "
                                      mml2015-signers t)
                   (if mml2015-signers
-                      (mapcar (lambda (name)
-                                (car (epg-list-keys context name t)))
-                              mml2015-signers))))))
+                      (mapcar
+                       (lambda (signer)
+                         (setq signer-key (mml2015-epg-find-usable-key
+                                           (epg-list-keys context signer t)
+                                           'sign))
+                         (unless (or signer-key
+                                     (y-or-n-p
+                                      (format
+                                       "No secret key for %s; skip it? "
+                                       signer)))
+                           (error "No secret key for %s" signer))
+                         signer-key)
+                       mml2015-signers))))))
       (epg-context-set-signers context signers))
     (epg-context-set-armor context t)
     (epg-context-set-textmode context t)
@@ -1264,6 +1341,9 @@ If no one is selected, default secret key is used.  "
 
 ;;; General wrapper
 
+(autoload 'gnus-buffer-live-p "gnus-util")
+(autoload 'gnus-get-buffer-create "gnus")
+
 (defun mml2015-clean-buffer ()
   (if (gnus-buffer-live-p mml2015-result-buffer)
       (with-current-buffer mml2015-result-buffer