Refactor mml-smime.el, mml1991.el, mml2015.el
authorJens Lechtenboerger <jens.lechtenboerger@fsfe.org>
Sun, 13 Dec 2015 15:12:30 +0000 (16:12 +0100)
committerJens Lechtenboerger <jens.lechtenboerger@fsfe.org>
Sat, 2 Jan 2016 14:12:21 +0000 (15:12 +0100)
Cf. discussion on ding mailing list, messages on 2015-10-16 and
2015-11-07.  Common code from the three files mml-smime.el, mml1991.el,
and mml2015.el is moved to mml-sec.el.  Auxiliary functions are added to
gnus-util.el.

The code is supported by test cases with necessary test keys.

Documentation in message.texi is updated.

46 files changed:
lisp/gnus-util.el
lisp/mml-sec.el
lisp/mml-smime.el
lisp/mml1991.el
lisp/mml2015.el
lisp/tests/gnustest-gnus-util.el [new file with mode: 0644]
lisp/tests/gnustest-mml-sec.README [new file with mode: 0644]
lisp/tests/gnustest-mml-sec.el [new file with mode: 0644]
lisp/tests/mml-gpghome/.gpg-v21-migrated [new file with mode: 0644]
lisp/tests/mml-gpghome/gpg-agent.conf [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/02089CDDC6DFE93B8EA10D9E876F983E61FEC476.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/171B444DE92BEF997229000D9784118A94EEC1C9.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/19FFEBC04DF3E037E16F6A4474DCB7984406975D.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/1E36D27DF9DAB96302D35268DADC5CE73EF45A2A.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/293109315BE584AB2EFEFCFCAD64666221D8B36C.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/335689599E1C0F66D73ADCF51E03EE36C97D121F.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/40BF94E540E3726CB150A1ADF7C1B514444B3FA6.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/515D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/5A11B1935C46D0B227A73978DCA1293A85604F1D.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/62643CEBC7AEBE6817577A34399483700D76BD64.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/680D01F368916A0021C14E3453B27B3C5F900683.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/6DF2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/78C17E134E86E691297F7B719B2F2CDF41976234.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/7F714F4D9D9676638214991E96D45704E4FFC409.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/854752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/93FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/A3BA94EAE83509CC90DB1B77B54A51959D8DABEA.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/A73E9D01F0465B518E8E7D5AD529077AAC1603B4.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/AE6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/C072AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/C43E1A079B28DFAEBB39CBA01793BDE11EF4B490.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/C67DAD345455EAD6D51368008FC3A53B8D195B5A.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/CB5E00CE582C2645D2573FC16B2F14F85A7F47AA.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/CC68630A06B048F5A91136C162C7A3273E20DE6F.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/E7E73903E1BF93481DE0E7C9769D6C31E1863CFF.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/F0117468BE801ED4B81972E159A98FDD4814DCEC.key [new file with mode: 0644]
lisp/tests/mml-gpghome/private-keys-v1.d/F4C5EFD5779BE892CAFD5B721D68DED677C9B151.key [new file with mode: 0644]
lisp/tests/mml-gpghome/pubring.gpg [new file with mode: 0644]
lisp/tests/mml-gpghome/pubring.gpg~ [new file with mode: 0644]
lisp/tests/mml-gpghome/pubring.kbx [new file with mode: 0644]
lisp/tests/mml-gpghome/pubring.kbx~ [new file with mode: 0644]
lisp/tests/mml-gpghome/random_seed [new file with mode: 0644]
lisp/tests/mml-gpghome/secring.gpg [new file with mode: 0644]
lisp/tests/mml-gpghome/trustdb.gpg [new file with mode: 0644]
lisp/tests/mml-gpghome/trustlist.txt [new file with mode: 0644]
texi/message.texi

index ea5f315..31645fc 100644 (file)
@@ -1996,6 +1996,14 @@ to case differences."
   (defun gnus-timer--function (timer)
     (elt timer 5)))
 
+(defun gnus-test-list (list predicate)
+  "To each element of LIST apply PREDICATE.
+Return nil if LIST is no list or is empty or some test returns nil;
+otherwise, return t."
+  (when (and list (listp list))
+    (let ((result (mapcar predicate list)))
+      (not (memq nil result)))))
+
 (defun gnus-subsetp (list1 list2)
   "Return t if LIST1 is a subset of LIST2.
 Similar to `subsetp' but use member for element test so that this works for
@@ -2006,6 +2014,13 @@ lists of strings."
             (gnus-subsetp (cdr list1) list2))
       t)))
 
+(defun gnus-setdiff (list1 list2)
+  "Return member-based set difference of LIST1 and LIST2."
+  (when (and list1 (listp list1) (listp list2))
+    (if (member (car list1) list2)
+       (gnus-setdiff (cdr list1) list2)
+      (cons (car list1) (gnus-setdiff (cdr list1) list2)))))
+
 (provide 'gnus-util)
 
 ;;; gnus-util.el ends here
index dbd3162..f5bfcec 100644 (file)
@@ -25,7 +25,9 @@
 
 (eval-when-compile (require 'cl))
 
-(autoload 'gnus-subsetp "gnus-util")
+(require 'gnus-util)
+(require 'epg)
+
 (autoload 'mail-strip-quoted-names "mail-utils")
 (autoload 'mml2015-sign "mml2015")
 (autoload 'mml2015-encrypt "mml2015")
@@ -40,6 +42,7 @@
 (autoload 'mml-smime-encrypt-query "mml-smime")
 (autoload 'mml-smime-verify "mml-smime")
 (autoload 'mml-smime-verify-test "mml-smime")
+(autoload 'epa--select-keys "epa")
 
 (defvar mml-sign-alist
   '(("smime"     mml-smime-sign-buffer     mml-smime-sign-query)
@@ -91,7 +94,7 @@ signs and encrypt the message in one step.
 
 Note that the output generated by using a `combined' mode is NOT
 understood by all PGP implementations, in particular PGP version
-2 does not support it!  See Info node `(message)Security' for
+2 does not support it!  See Info node `(message) Security' for
 details."
   :version "22.1"
   :group 'message
@@ -111,7 +114,9 @@ details."
   (if (boundp 'password-cache)
       password-cache
     t)
-  "If t, cache passphrase."
+  "If t, cache OpenPGP or S/MIME passphrases inside Emacs.
+Passphrase caching in Emacs is NOT recommended.  Use gpg-agent instead.
+See Info node `(message) Security'."
   :group 'message
   :type 'boolean)
 
@@ -425,6 +430,528 @@ If called with a prefix argument, only encrypt (do NOT sign)."
   (interactive "P")
   (mml-secure-message "pgpauto" (if dontsign 'encrypt 'signencrypt)))
 
+;;; Common functionality for mml1991.el, mml2015.el, mml-smime.el
+
+(define-obsolete-variable-alias 'mml1991-signers 'mml-secure-openpgp-signers)
+(define-obsolete-variable-alias 'mml2015-signers 'mml-secure-openpgp-signers)
+(defcustom mml-secure-openpgp-signers nil
+  "A list of your own key ID(s) which will be used to sign OpenPGP messages.
+If set, it is added to the setting of `mml-secure-openpgp-sign-with-sender'."
+  :group 'mime-security
+  :type '(repeat (string :tag "Key ID")))
+
+(define-obsolete-variable-alias 'mml-smime-signers 'mml-secure-smime-signers)
+(defcustom mml-secure-smime-signers nil
+  "A list of your own key ID(s) which will be used to sign S/MIME messages.
+If set, it is added to the setting of `mml-secure-smime-sign-with-sender'."
+  :group 'mime-security
+  :type '(repeat (string :tag "Key ID")))
+
+(define-obsolete-variable-alias
+  'mml1991-encrypt-to-self 'mml-secure-openpgp-encrypt-to-self)
+(define-obsolete-variable-alias
+  'mml2015-encrypt-to-self 'mml-secure-openpgp-encrypt-to-self)
+(defcustom mml-secure-openpgp-encrypt-to-self nil
+  "List of own key ID(s) or t; determines additional recipients with OpenPGP.
+If t, also encrypt to key for message sender; if list, encrypt to those keys.
+With this variable, you can ensure that you can decrypt your own messages.
+Alternatives to this variable include Bcc'ing the message to yourself or
+using the encrypt-to or hidden-encrypt-to option in gpg.conf (see man gpg(1)).
+Note that this variable and the encrypt-to option give away your identity
+for *every* encryption without warning, which is not what you want if you are
+using, e.g., remailers.
+Also, use of Bcc gives away your identity for *every* encryption without
+warning, which is a bug, see:
+https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18718"
+  :group 'mime-security
+  :type '(choice (const :tag "None" nil)
+                (const :tag "From address" t)
+                (repeat (string :tag "Key ID"))))
+
+(define-obsolete-variable-alias
+  'mml-smime-encrypt-to-self 'mml-secure-smime-encrypt-to-self)
+(defcustom mml-secure-smime-encrypt-to-self nil
+  "List of own key ID(s) or t; determines additional recipients with S/MIME.
+If t, also encrypt to key for message sender; if list, encrypt to those keys.
+With this variable, you can ensure that you can decrypt your own messages.
+Alternatives to this variable include Bcc'ing the message to yourself or
+using the encrypt-to option in gpgsm.conf (see man gpgsm(1)).
+Note that this variable and the encrypt-to option give away your identity
+for *every* encryption without warning, which is not what you want if you are
+using, e.g., remailers.
+Also, use of Bcc gives away your identity for *every* encryption without
+warning, which is a bug, see:
+https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18718"
+  :group 'mime-security
+  :type '(choice (const :tag "None" nil)
+                (const :tag "From address" t)
+                (repeat (string :tag "Key ID"))))
+
+(define-obsolete-variable-alias
+  'mml2015-sign-with-sender 'mml-secure-openpgp-sign-with-sender)
+;mml1991-sign-with-sender did never exist.
+(defcustom mml-secure-openpgp-sign-with-sender nil
+  "If t, use message sender to find an OpenPGP key to sign with."
+  :group 'mime-security
+  :type 'boolean)
+
+(define-obsolete-variable-alias
+  'mml-smime-sign-with-sender 'mml-secure-smime-sign-with-sender)
+(defcustom mml-secure-smime-sign-with-sender nil
+  "If t, use message sender to find an S/MIME key to sign with."
+  :group 'mime-security
+  :type 'boolean)
+
+(defcustom mml-secure-smime-sign-with-sender nil
+  "If t, use message sender to find an S/MIME key to sign with."
+  :group 'mime-security
+  :type 'boolean)
+(define-obsolete-variable-alias
+  'mml2015-always-trust 'mml-secure-openpgp-always-trust)
+;mml1991-always-trust did never exist.
+(defcustom mml-secure-openpgp-always-trust t
+  "If t, skip key validation of GnuPG on encryption."
+  :group 'mime-security
+  :type 'boolean)
+
+(defcustom mml-secure-fail-when-key-problem nil
+  "If t, raise an error if some key is missing or several keys exist.
+Otherwise, ask the user."
+  :group 'mime-security
+  :type 'boolean)
+
+(defcustom mml-secure-key-preferences
+  '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt)))
+  "Protocol- and usage-specific fingerprints of preferred keys.
+This variable is only relevant if a recipient owns multiple key pairs (for
+encryption) or you own multiple key pairs (for signing).  In such cases,
+you will be asked which key(s) should be used, and your choice can be
+customized in this variable."
+  :group 'mime-security
+  :type '(alist :key-type (symbol :tag "Protocol") :value-type
+               (alist :key-type (symbol :tag "Usage") :value-type
+                      (alist :key-type (string :tag "Name") :value-type
+                             (repeat (string :tag "Fingerprint"))))))
+
+(defun mml-secure-cust-usage-lookup (context usage)
+  "Return preferences for CONTEXT and USAGE."
+  (let* ((protocol (epg-context-protocol context))
+        (protocol-prefs (cdr (assoc protocol mml-secure-key-preferences))))
+    (assoc usage protocol-prefs)))
+
+(defun mml-secure-cust-fpr-lookup (context usage name)
+  "Return fingerprints of preferred keys for CONTEXT, USAGE, and NAME."
+  (let* ((usage-prefs (mml-secure-cust-usage-lookup context usage))
+        (fprs (assoc name (cdr usage-prefs))))
+    (when fprs
+      (cdr fprs))))
+
+(defun mml-secure-cust-record-keys (context usage name keys &optional save)
+  "For CONTEXT, USAGE, and NAME record fingerprint(s) of KEYS.
+If optional SAVE is not nil, save customized fingerprints.
+Return keys."
+  (assert keys)
+  (let* ((usage-prefs (mml-secure-cust-usage-lookup context usage))
+        (curr-fprs (cdr (assoc name (cdr usage-prefs))))
+        (key-fprs (mapcar 'mml-secure-fingerprint keys))
+        (new-fprs (cl-union curr-fprs key-fprs :test 'equal)))
+    (if curr-fprs
+       (setcdr (assoc name (cdr usage-prefs)) new-fprs)
+      (setcdr usage-prefs (cons (cons name new-fprs) (cdr usage-prefs))))
+    (when save
+       (customize-save-variable
+        'mml-secure-key-preferences mml-secure-key-preferences))
+    keys))
+
+(defun mml-secure-cust-remove-keys (context usage name)
+  "Remove keys for CONTEXT, USAGE, and NAME.
+Return t if a customization for NAME was present (and has been removed)."
+  (let* ((usage-prefs (mml-secure-cust-usage-lookup context usage))
+        (current (assoc name usage-prefs)))
+    (when current
+      (setcdr usage-prefs (remove current (cdr usage-prefs)))
+      t)))
+
+(defvar mml-secure-secret-key-id-list nil)
+
+(defun mml-secure-add-secret-key-id (key-id)
+  "Record KEY-ID in list of secret keys."
+  (add-to-list 'mml-secure-secret-key-id-list key-id))
+
+(defun mml-secure-clear-secret-key-id-list ()
+  "Remove passwords from cache and clear list of secret keys."
+  ;; Loosely based on code inside mml2015-epg-encrypt,
+  ;; mml2015-epg-clear-decrypt, and mml2015-epg-decrypt
+  (dolist (key-id mml-secure-secret-key-id-list nil)
+    (password-cache-remove key-id))
+  (setq mml-secure-secret-key-id-list nil))
+
+(defun mml-secure-cache-passphrase-p (protocol)
+  "Return t if OpenPGP or S/MIME passphrases should be cached for PROTOCOL.
+Passphrase caching in Emacs is NOT recommended.  Use gpg-agent instead."
+  (or (and (eq 'OpenPGP protocol)
+          (or mml-secure-cache-passphrase
+              (and (boundp 'mml2015-cache-passphrase)
+                   mml2015-cache-passphrase)
+              (and (boundp 'mml1991-cache-passphrase)
+                   mml1991-cache-passphrase)))
+      (and (eq 'CMS protocol)
+          (or mml-secure-cache-passphrase
+              (and (boundp 'mml-smime-cache-passphrase)
+                   mml-smime-cache-passphrase)))))
+
+(defun mml-secure-cache-expiry-interval (protocol)
+  "Return time in seconds to cache passphrases for PROTOCOL.
+Passphrase caching in Emacs is NOT recommended.  Use gpg-agent instead."
+  (or (and (eq 'OpenPGP protocol)
+          (or (and (boundp 'mml2015-passphrase-cache-expiry)
+                   mml2015-passphrase-cache-expiry)
+              (and (boundp 'mml1991-passphrase-cache-expiry)
+                   mml1991-passphrase-cache-expiry)
+              mml-secure-passphrase-cache-expiry))
+      (and (eq 'CMS protocol)
+          (or (and (boundp 'mml-smime-passphrase-cache-expiry)
+                   mml-smime-passphrase-cache-expiry)
+              mml-secure-passphrase-cache-expiry))))
+
+(defun mml-secure-passphrase-callback (context key-id standard)
+  "Ask for passphrase in CONTEXT for KEY-ID for STANDARD.
+The passphrase is read and cached."
+  ;; Based on mml2015-epg-passphrase-callback.
+  (if (eq key-id 'SYM)
+      (epg-passphrase-callback-function context key-id nil)
+    (let* ((password-cache-key-id
+           (if (eq key-id 'PIN)
+               "PIN"
+              key-id))
+          (entry (assoc key-id epg-user-id-alist))
+          (passphrase
+           (password-read
+            (if (eq key-id 'PIN)
+                "Passphrase for PIN: "
+              (if entry
+                  (format "Passphrase for %s %s: " key-id (cdr entry))
+                (format "Passphrase for %s: " key-id)))
+            ;; TODO: With mml-smime.el, password-cache-key-id is not passed
+            ;; as argument to password-read.
+            ;; Is that on purpose?  If so, the following needs to be placed
+            ;; inside an if statement.
+            password-cache-key-id)))
+      (when passphrase
+       (let ((password-cache-expiry (mml-secure-cache-expiry-interval
+                                     (epg-context-protocol context))))
+         (password-cache-add password-cache-key-id passphrase))
+       (mml-secure-add-secret-key-id password-cache-key-id)
+       (copy-sequence passphrase)))))
+
+(defun mml-secure-check-user-id (key recipient)
+  "Check whether KEY has a non-revoked, non-expired UID for RECIPIENT."
+  ;; Based on mml2015-epg-check-user-id.
+  (let ((uids (epg-key-user-id-list key)))
+    (catch 'break
+      (dolist (uid uids nil)
+       (if (and (stringp (epg-user-id-string uid))
+                (equal (car (mail-header-parse-address
+                             (epg-user-id-string uid)))
+                       (car (mail-header-parse-address
+                             recipient)))
+                (not (memq (epg-user-id-validity uid)
+                           '(revoked expired))))
+           (throw 'break t))))))
+
+(defun mml-secure-secret-key-exists-p (context subkey)
+  "Return t if keyring for CONTEXT contains secret key for public SUBKEY."
+  (let* ((fpr (epg-sub-key-fingerprint subkey))
+        (candidates (epg-list-keys context fpr 'secret))
+        (candno (length candidates)))
+    ;; If two or more subkeys with the same fingerprint exist, something is
+    ;; terribly wrong.
+    (when (>= candno 2)
+      (error "Found %d secret keys with same fingerprint %s" candno fpr))
+    (= 1 candno)))
+
+(defun mml-secure-check-sub-key (context key usage &optional fingerprint)
+  "Check whether in CONTEXT the public KEY has a usable subkey for USAGE.
+This is the case if KEY is not disabled, and there is a subkey for
+USAGE that is neither revoked nor expired.  Additionally, if optional
+FINGERPRINT is present and if it is not the primary key's fingerprint, then
+the returned subkey must have that FINGERPRINT.  FINGERPRINT must consist of
+hexadecimal digits only (no leading \"0x\" allowed).
+If USAGE is not `encrypt', then additionally an appropriate secret key must
+be present in the keyring."
+  ;; Based on mml2015-epg-check-sub-key, extended by
+  ;; - check for secret keys if usage is not 'encrypt and
+  ;; - check for new argument FINGERPRINT.
+  (let* ((subkeys (epg-key-sub-key-list key))
+        (primary (car subkeys))
+        (fpr (epg-sub-key-fingerprint primary)))
+    ;; The primary key will be marked as disabled, when the entire
+    ;; key is disabled (see 12 Field, Format of colon listings, in
+    ;; gnupg/doc/DETAILS)
+    (unless (memq 'disabled (epg-sub-key-capability primary))
+      (catch 'break
+       (dolist (subkey subkeys nil)
+         (if (and (memq usage (epg-sub-key-capability subkey))
+                  (not (memq (epg-sub-key-validity subkey)
+                             '(revoked expired)))
+                  (or (eq 'encrypt usage) ; Encryption works with public key.
+                      ;; In contrast, signing requires secret key.
+                      (mml-secure-secret-key-exists-p context subkey))
+                  (or (not fingerprint)
+                      (string-match-p (concat fingerprint "$") fpr)
+                      (string-match-p (concat fingerprint "$")
+                                      (epg-sub-key-fingerprint subkey))))
+             (throw 'break t)))))))
+
+(defun mml-secure-find-usable-keys (context name usage &optional justone)
+  "In CONTEXT return a list of keys for NAME and USAGE.
+If USAGE is `encrypt' public keys are returned, otherwise secret ones.
+Only non-revoked and non-expired keys are returned whose primary key is
+not disabled.
+NAME can be an e-mail address or a key ID.
+If NAME just consists of hexadecimal digits (possibly prefixed by \"0x\"), it
+is treated as key ID for which at most one key must exist in the keyring.
+Otherwise, NAME is treated as user ID, for which no keys are returned if it
+is expired or revoked.
+If optional JUSTONE is not nil, return the first key instead of a list."
+  (let* ((keys (epg-list-keys context name))
+        (iskeyid (string-match "\\(0x\\)?\\([0-9a-fA-F]\\{8,\\}\\)" name))
+        (fingerprint (match-string 2 name))
+        result)
+    (when (and iskeyid (>= (length keys) 2))
+      (error
+       "Name %s (for %s) looks like a key ID but multiple keys found"
+       name usage))
+    (catch 'break
+      (dolist (key keys result)
+       (if (and (or iskeyid
+                    (mml-secure-check-user-id key name))
+                (mml-secure-check-sub-key context key usage fingerprint))
+           (if justone
+               (throw 'break key)
+             (push key result)))))))
+
+(defun mml-secure-select-preferred-keys (context names usage)
+  "Return list of preferred keys in CONTEXT for NAMES and USAGE.
+This inspects the keyrings to find keys for each name in NAMES.  If several
+keys are found for a name, `mml-secure-select-keys' is used to look for
+customized preferences or have the user select preferable ones.
+When `mml-secure-fail-when-key-problem' is t, fail with an error in
+case of missing, outdated, or multiple keys."
+  ;; Loosely based on code appearing inside mml2015-epg-sign and
+  ;; mml2015-epg-encrypt.
+  (mapcan
+   (lambda (name)
+     (let* ((keys (mml-secure-find-usable-keys context name usage))
+           (keyno (length keys)))
+       (cond ((= 0 keyno)
+             (when (or mml-secure-fail-when-key-problem
+                       (not (y-or-n-p
+                             (format "No %s key for %s; skip it? "
+                                     usage name))))
+               (error "No %s key for %s" usage name)))
+            ((= 1 keyno) keys)
+            (t (mml-secure-select-keys context name keys usage)))))
+   names))
+
+(defun mml-secure-fingerprint (key)
+  "Return fingerprint for public KEY."
+  (epg-sub-key-fingerprint (car (epg-key-sub-key-list key))))
+
+(defun mml-secure-filter-keys (keys fprs)
+  "Filter KEYS to subset with fingerprints in FPRS."
+  (when keys
+    (if (member (mml-secure-fingerprint (car keys)) fprs)
+       (cons (car keys) (mml-secure-filter-keys (cdr keys) fprs))
+      (mml-secure-filter-keys (cdr keys) fprs))))
+
+(defun mml-secure-normalize-cust-name (name)
+  "Normalize NAME to be used for customization.
+Currently, remove ankle brackets."
+  (if (string-match "^<\\(.*\\)>$" name)
+      (match-string 1 name)
+    name))
+
+(defun mml-secure-select-keys (context name keys usage)
+  "In CONTEXT for NAME select among KEYS for USAGE.
+KEYS should be a list with multiple entries.
+NAME is normalized first as customized keys are inspected.
+When `mml-secure-fail-when-key-problem' is t, fail with an error in case of
+outdated or multiple keys."
+  (let* ((nname (mml-secure-normalize-cust-name name))
+        (fprs (mml-secure-cust-fpr-lookup context usage nname))
+        (usable-fprs (mapcar 'mml-secure-fingerprint keys)))
+    (if fprs
+       (if (gnus-subsetp fprs usable-fprs)
+           (mml-secure-filter-keys keys fprs)
+         (mml-secure-cust-remove-keys context usage nname)
+         (let ((diff (gnus-setdiff fprs usable-fprs)))
+           (if mml-secure-fail-when-key-problem
+               (error "Customization of %s keys for %s outdated" usage nname)
+             (mml-secure-select-keys-1
+              context nname keys usage (format "\
+Customized keys
+ (%s)
+for %s not available any more.
+Select anew.  "
+                                              diff nname)))))
+      (if mml-secure-fail-when-key-problem
+         (error "Multiple %s keys for %s" usage nname)
+       (mml-secure-select-keys-1
+        context nname keys usage (format "\
+Multiple %s keys for:
+ %s
+Select preferred one(s).  "
+                                        usage nname))))))
+
+(defun mml-secure-select-keys-1 (context name keys usage message)
+  "In CONTEXT for NAME let user select among KEYS for USAGE, showing MESSAGE.
+Return selected keys."
+  (let* ((selected (epa--select-keys message keys))
+        (selno (length selected))
+        ;; TODO: y-or-n-p does not always resize the echo area but may
+        ;; truncate the message.  Why?  The following does not help.
+        ;; yes-or-no-p shows full message, though.
+        (message-truncate-lines nil))
+    (if selected
+       (if (y-or-n-p
+            (format "%d %s key(s) selected.  Store for %s? "
+                    selno usage name))
+           (mml-secure-cust-record-keys context usage name selected 'save)
+         selected)
+      (unless (y-or-n-p
+              (format "No %s key for %s; skip it? " usage name))
+       (error "No %s key for %s" usage name)))))
+
+(defun mml-secure-signer-names (protocol sender)
+  "Determine signer names for PROTOCOL and message from SENDER.
+Returned names may be e-mail addresses or key IDs and are determined based
+on `mml-secure-openpgp-signers' and `mml-secure-openpgp-sign-with-sender' with
+OpenPGP or `mml-secure-smime-signers' and `mml-secure-smime-sign-with-sender'
+with S/MIME."
+  (if (eq 'OpenPGP protocol)
+      (append mml-secure-openpgp-signers
+             (if (and mml-secure-openpgp-sign-with-sender sender)
+                 (list (concat "<" sender ">"))))
+    (append mml-secure-smime-signers
+           (if (and mml-secure-smime-sign-with-sender sender)
+               (list (concat "<" sender ">"))))))
+
+(defun mml-secure-signers (context signer-names)
+  "Determine signing keys in CONTEXT from SIGNER-NAMES.
+If `mm-sign-option' is `guided', the user is asked to choose.
+Otherwise, `mml-secure-select-preferred-keys' is used."
+  ;; Based on code appearing inside mml2015-epg-sign and
+  ;; mml2015-epg-encrypt.
+  (if (eq mm-sign-option 'guided)
+      (epa-select-keys context "\
+Select keys for signing.
+If no one is selected, default secret key is used.  "
+                      signer-names t)
+    (mml-secure-select-preferred-keys context signer-names 'sign)))
+
+(defun mml-secure-self-recipients (protocol sender)
+  "Determine additional recipients based on encrypt-to-self variables.
+PROTOCOL specifies OpenPGP or S/MIME for a message from SENDER."
+  (let ((encrypt-to-self
+        (if (eq 'OpenPGP protocol)
+            mml-secure-openpgp-encrypt-to-self
+          mml-secure-smime-encrypt-to-self)))
+    (when encrypt-to-self
+      (if (listp encrypt-to-self)
+         encrypt-to-self
+       (list sender)))))
+
+(defun mml-secure-recipients (protocol context config sender)
+  "Determine encryption recipients.
+PROTOCOL specifies OpenPGP or S/MIME with matching CONTEXT and CONFIG
+for a message from SENDER."
+  ;; Based on code appearing inside mml2015-epg-encrypt.
+  (let ((recipients
+        (apply #'nconc
+               (mapcar
+                (lambda (recipient)
+                  (or (epg-expand-group config 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,]+")))))
+    (nconc recipients (mml-secure-self-recipients protocol sender))
+    (if (eq mm-encrypt-option 'guided)
+       (setq recipients
+             (epa-select-keys context "\
+Select recipients for encryption.
+If no one is selected, symmetric encryption will be performed.  "
+                              recipients))
+      (setq recipients
+           (mml-secure-select-preferred-keys context recipients 'encrypt))
+      (unless recipients
+       (error "No recipient specified")))
+    recipients))
+
+(defun mml-secure-epg-encrypt (protocol cont &optional sign)
+  ;; Based on code appearing inside mml2015-epg-encrypt.
+  (let* ((context (epg-make-context protocol))
+        (config (epg-configuration))
+        (sender (message-options-get 'message-sender))
+        (recipients (mml-secure-recipients protocol context config sender))
+        (signer-names (mml-secure-signer-names protocol sender))
+        cipher signers)
+    (when sign
+      (setq signers (mml-secure-signers context signer-names))
+      (epg-context-set-signers context signers))
+    (when (eq 'OpenPGP protocol)
+      (epg-context-set-armor context t)
+      (epg-context-set-textmode context t))
+    (when (mml-secure-cache-passphrase-p protocol)
+      (epg-context-set-passphrase-callback
+       context
+       (cons 'mml-secure-passphrase-callback protocol)))
+    (condition-case error
+       (setq cipher
+             (if (eq 'OpenPGP protocol)
+                 (epg-encrypt-string context (buffer-string) recipients sign
+                                     mml-secure-openpgp-always-trust)
+               (epg-encrypt-string context (buffer-string) recipients))
+             mml-secure-secret-key-id-list nil)
+      (error
+       (mml-secure-clear-secret-key-id-list)
+       (signal (car error) (cdr error))))
+    cipher))
+
+(defun mml-secure-epg-sign (protocol mode)
+  ;; Based on code appearing inside mml2015-epg-sign.
+  (let* ((context (epg-make-context protocol))
+        (sender (message-options-get 'message-sender))
+        (signer-names (mml-secure-signer-names protocol sender))
+        (signers (mml-secure-signers context signer-names))
+        signature micalg)
+    (when (eq 'OpenPGP protocol)
+      (epg-context-set-armor context t)
+      (epg-context-set-textmode context t))
+    (epg-context-set-signers context signers)
+    (when (mml-secure-cache-passphrase-p protocol)
+      (epg-context-set-passphrase-callback
+       context
+       (cons 'mml-secure-passphrase-callback protocol)))
+    (condition-case error
+       (setq signature
+             (if (eq 'OpenPGP protocol)
+                 (epg-sign-string context (buffer-string) mode)
+               (epg-sign-string context
+                                (mm-replace-in-string (buffer-string)
+                                                      "\n" "\r\n") t))
+             mml-secure-secret-key-id-list nil)
+      (error
+       (mml-secure-clear-secret-key-id-list)
+       (signal (car error) (cdr error))))
+    (if (epg-context-result-for context 'sign)
+       (setq micalg (epg-new-signature-digest-algorithm
+                     (car (epg-context-result-for context 'sign)))))
+    (cons signature micalg)))
+
 (provide 'mml-sec)
 
 ;;; mml-sec.el ends here
index b19c9e8..b9f4a54 100644 (file)
 (autoload 'message-narrow-to-headers "message")
 (autoload 'message-fetch-field "message")
 
+;; Prefer epg over openssl if it is available as epg uses GnuPG's gpgsm,
+;; which features full-fledged certificate management, while openssl requires
+;; major manual efforts for certificate revocation and expiry and has bugs
+;; as documented under man smime(1).
+(ignore-errors (require 'epg))
+
 (defcustom mml-smime-use (if (featurep 'epg) 'epg 'openssl)
-  "Whether to use OpenSSL or EPG to decrypt S/MIME messages.
-Defaults to EPG if it's loaded."
+  "Whether to use OpenSSL or EasyPG (EPG) to handle S/MIME messages.
+Defaults to EPG if it's available.
+If you think about using OpenSSL, please read the BUGS section in the manual
+for the `smime' command coming with OpenSSL first.  EasyPG is recommended."
   :group 'mime-security
   :type '(choice (const :tag "EPG" epg)
                  (const :tag "OpenSSL" openssl)))
@@ -57,6 +65,9 @@ Defaults to EPG if it's loaded."
   "If t, cache passphrase."
   :group 'mime-security
   :type 'boolean)
+(make-obsolete-variable 'mml-smime-cache-passphrase
+                       'mml-secure-cache-passphrase
+                       "25.0.50")
 
 (defcustom mml-smime-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
   "How many seconds the passphrase is cached.
@@ -64,6 +75,9 @@ Whether the passphrase is cached at all is controlled by
 `mml-smime-cache-passphrase'."
   :group 'mime-security
   :type 'integer)
+(make-obsolete-variable 'mml-smime-passphrase-cache-expiry
+                       'mml-secure-passphrase-cache-expiry
+                       "25.0.50")
 
 (defcustom mml-smime-signers nil
   "A list of your own key ID which will be used to sign a message."
@@ -202,7 +216,7 @@ Whether the passphrase is cached at all is controlled by
                                        "")))))
          (if (setq cert (smime-cert-by-dns who))
              (setq result (list 'certfile (buffer-name cert)))
-           (setq bad (gnus-format-message "`%s' not found. " who))))
+           (setq bad (format "`%s' not found. " who))))
       (quit))
     result))
 
@@ -221,7 +235,7 @@ Whether the passphrase is cached at all is controlled by
                                        "")))))
          (if (setq cert (smime-cert-by-ldap who))
              (setq result (list 'certfile (buffer-name cert)))
-           (setq bad (gnus-format-message "`%s' not found. " who))))
+           (setq bad (format "`%s' not found. " who))))
       (quit))
     result))
 
@@ -317,82 +331,28 @@ Whether the passphrase is cached at all is controlled by
 (defvar inhibit-redisplay)
 (defvar password-cache-expiry)
 
-(autoload 'epg-make-context "epg")
-(autoload 'epg-passphrase-callback-function "epg")
-(declare-function epg-context-set-signers "epg" (context signers))
-(declare-function epg-context-result-for "epg" (context name))
-(declare-function epg-new-signature-digest-algorithm "epg" (cl-x) t)
-(declare-function epg-verify-result-to-string "epg" (verify-result))
-(declare-function epg-list-keys "epg" (context &optional name mode))
-(declare-function epg-verify-string "epg"
-                 (context signature &optional signed-text))
-(declare-function epg-sign-string "epg" (context plain &optional mode))
-(declare-function epg-encrypt-string "epg"
-                 (context plain recipients &optional sign always-trust))
-(declare-function epg-context-set-passphrase-callback "epg"
-                 (context passphrase-callback))
-(declare-function epg-sub-key-fingerprint "epg" (cl-x) t)
-(declare-function epg-configuration "epg-config" ())
-(declare-function epg-expand-group "epg-config" (config group))
-(declare-function epa-select-keys "epa"
-                 (context prompt &optional names secret))
-
-(defvar mml-smime-epg-secret-key-id-list nil)
-
-(defun mml-smime-epg-passphrase-callback (context key-id ignore)
-  (if (eq key-id 'SYM)
-      (epg-passphrase-callback-function context key-id nil)
-    (let* (entry
-          (passphrase
-           (password-read
-            (if (eq key-id 'PIN)
-                "Passphrase for PIN: "
-              (if (setq entry (assoc key-id epg-user-id-alist))
-                  (format "Passphrase for %s %s: " key-id (cdr entry))
-                (format "Passphrase for %s: " key-id)))
-            (if (eq key-id 'PIN)
-                "PIN"
-              key-id))))
-      (when passphrase
-       (let ((password-cache-expiry mml-smime-passphrase-cache-expiry))
-         (password-cache-add key-id passphrase))
-       (setq mml-smime-epg-secret-key-id-list
-             (cons key-id mml-smime-epg-secret-key-id-list))
-       (copy-sequence passphrase)))))
-
-(declare-function epg-key-sub-key-list   "epg" (key) t)
-(declare-function epg-sub-key-capability "epg" (sub-key) t)
-(declare-function epg-sub-key-validity   "epg" (sub-key) t)
-
-(defun mml-smime-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)))))
-
-;; XXX: since gpg --list-secret-keys does not return validity of each
-;; key, `mml-smime-epg-find-usable-key' defined above is not enough for
-;; secret keys.  The function `mml-smime-epg-find-usable-secret-key'
-;; below looks at appropriate public keys to check usability.
-(defun mml-smime-epg-find-usable-secret-key (context name usage)
-  (let ((secret-keys (epg-list-keys context name t))
-       secret-key)
-    (while (and (not secret-key) secret-keys)
-      (if (mml-smime-epg-find-usable-key
-          (epg-list-keys context (epg-sub-key-fingerprint
-                                  (car (epg-key-sub-key-list
-                                        (car secret-keys)))))
-          usage)
-         (setq secret-key (car secret-keys)
-               secret-keys nil)
-       (setq secret-keys (cdr secret-keys))))
-    secret-key))
+(eval-when-compile
+  (autoload 'epg-make-context "epg")
+  (autoload 'epg-context-set-armor "epg")
+  (autoload 'epg-context-set-signers "epg")
+  (autoload 'epg-context-result-for "epg")
+  (autoload 'epg-new-signature-digest-algorithm "epg")
+  (autoload 'epg-verify-result-to-string "epg")
+  (autoload 'epg-list-keys "epg")
+  (autoload 'epg-decrypt-string "epg")
+  (autoload 'epg-verify-string "epg")
+  (autoload 'epg-sign-string "epg")
+  (autoload 'epg-encrypt-string "epg")
+  (autoload 'epg-passphrase-callback-function "epg")
+  (autoload 'epg-context-set-passphrase-callback "epg")
+  (autoload 'epg-sub-key-fingerprint "epg")
+  (autoload 'epg-configuration "epg-config")
+  (autoload 'epg-expand-group "epg-config")
+  (autoload 'epa-select-keys "epa"))
+
+(declare-function epg-key-sub-key-list   "ext:epg" (key))
+(declare-function epg-sub-key-capability "ext:epg" (sub-key))
+(declare-function epg-sub-key-validity   "ext:epg" (sub-key))
 
 (autoload 'mml-compute-boundary "mml")
 
@@ -401,146 +361,37 @@ Whether the passphrase is cached at all is controlled by
 (declare-function message-options-set "message" (symbol value))
 
 (defun mml-smime-epg-sign (cont)
-  (let* ((inhibit-redisplay t)
-        (context (epg-make-context 'CMS))
-        (boundary (mml-compute-boundary cont))
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml-smime-signers
-                          (if (and mml-smime-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
-        signer-key
-        (signers
-         (or (message-options-get 'mml-smime-epg-signers)
-             (message-options-set
-              'mml-smime-epg-signers
-              (if (eq mm-sign-option 'guided)
-                  (epa-select-keys context "\
-Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                   signer-names
-                                   t)
-                (if (or sender mml-smime-signers)
-                    (delq nil
-                          (mapcar
-                           (lambda (signer)
-                             (setq signer-key
-                                   (mml-smime-epg-find-usable-secret-key
-                                    context signer '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)
-                           signer-names)))))))
-        signature micalg)
-    (epg-context-set-signers context signers)
-    (if mml-smime-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml-smime-epg-passphrase-callback))
-    (condition-case error
-       (setq signature (epg-sign-string context
-                                        (mm-replace-in-string (buffer-string)
-                                                              "\n" "\r\n")
-                                        t)
-             mml-smime-epg-secret-key-id-list nil)
-      (error
-       (while mml-smime-epg-secret-key-id-list
-        (password-cache-remove (car mml-smime-epg-secret-key-id-list))
-        (setq mml-smime-epg-secret-key-id-list
-              (cdr mml-smime-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
-    (if (epg-context-result-for context 'sign)
-       (setq micalg (epg-new-signature-digest-algorithm
-                     (car (epg-context-result-for context 'sign)))))
+  (let ((inhibit-redisplay t)
+       (boundary (mml-compute-boundary cont)))
     (goto-char (point-min))
-    (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
-                   boundary))
-    (if micalg
-       (insert (format "\tmicalg=%s; "
-                       (downcase
-                        (cdr (assq micalg
-                                   epg-digest-algorithm-alist))))))
-    (insert "protocol=\"application/pkcs7-signature\"\n")
-    (insert (format "\n--%s\n" boundary))
-    (goto-char (point-max))
-    (insert (format "\n--%s\n" boundary))
-    (insert "Content-Type: application/pkcs7-signature; name=smime.p7s
+    (let* ((pair (mml-secure-epg-sign 'CMS cont))
+          (signature (car pair))
+          (micalg (cdr pair)))
+      (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
+                     boundary))
+      (if micalg
+         (insert (format "\tmicalg=%s; "
+                         (downcase
+                          (cdr (assq micalg
+                                     epg-digest-algorithm-alist))))))
+      (insert "protocol=\"application/pkcs7-signature\"\n")
+      (insert (format "\n--%s\n" boundary))
+      (goto-char (point-max))
+      (insert (format "\n--%s\n" boundary))
+      (insert "Content-Type: application/pkcs7-signature; name=smime.p7s
 Content-Transfer-Encoding: base64
 Content-Disposition: attachment; filename=smime.p7s
 
 ")
-    (insert (base64-encode-string signature) "\n")
-    (goto-char (point-max))
-    (insert (format "--%s--\n" boundary))
-    (goto-char (point-max))))
+      (insert (base64-encode-string signature) "\n")
+      (goto-char (point-max))
+      (insert (format "--%s--\n" boundary))
+      (goto-char (point-max)))))
 
 (defun mml-smime-epg-encrypt (cont)
   (let* ((inhibit-redisplay t)
-        (context (epg-make-context 'CMS))
-        (config (epg-configuration))
-        (recipients (message-options-get 'mml-smime-epg-recipients))
-        cipher signers
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml-smime-signers
-                          (if (and mml-smime-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
         (boundary (mml-compute-boundary cont))
-        recipient-key)
-    (unless recipients
-      (setq recipients
-           (apply #'nconc
-                  (mapcar
-                   (lambda (recipient)
-                     (or (epg-expand-group config recipient)
-                         (list recipient)))
-                   (split-string
-                    (or (message-options-get 'message-recipients)
-                        (message-options-set 'message-recipients
-                                             (read-string "Recipients: ")))
-                    "[ \f\t\n\r\v,]+"))))
-      (when mml-smime-encrypt-to-self
-       (unless signer-names
-         (error "Neither message sender nor mml-smime-signers are set"))
-       (setq recipients (nconc recipients signer-names)))
-      (if (eq mm-encrypt-option 'guided)
-         (setq recipients
-               (epa-select-keys context "\
-Select recipients for encryption.
-If no one is selected, symmetric encryption will be performed.  "
-                                recipients))
-       (setq recipients
-             (mapcar
-              (lambda (recipient)
-                (setq recipient-key (mml-smime-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 'mml-smime-epg-recipients recipients))
-    (if mml-smime-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml-smime-epg-passphrase-callback))
-    (condition-case error
-       (setq cipher
-             (epg-encrypt-string context (buffer-string) recipients)
-             mml-smime-epg-secret-key-id-list nil)
-      (error
-       (while mml-smime-epg-secret-key-id-list
-        (password-cache-remove (car mml-smime-epg-secret-key-id-list))
-        (setq mml-smime-epg-secret-key-id-list
-              (cdr mml-smime-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
+        (cipher (mml-secure-epg-encrypt 'CMS cont)))
     (delete-region (point-min) (point-max))
     (goto-char (point-min))
     (insert "\
index 6469636..aa51442 100644 (file)
 
 (defvar mml1991-cache-passphrase mml-secure-cache-passphrase
   "If t, cache passphrase.")
+(make-obsolete-variable 'mml1991-cache-passphrase
+                       'mml-secure-cache-passphrase
+                       "25.0.50")
 
 (defvar mml1991-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
   "How many seconds the passphrase is cached.
 Whether the passphrase is cached at all is controlled by
 `mml1991-cache-passphrase'.")
+(make-obsolete-variable 'mml1991-passphrase-cache-expiry
+                       'mml-secure-passphrase-cache-expiry
+                       "25.0.50")
 
 (defvar mml1991-signers nil
   "A list of your own key ID which will be used to sign a message.")
@@ -75,6 +81,7 @@ Whether the passphrase is cached at all is controlled by
 (defvar mml1991-encrypt-to-self nil
   "If t, add your own key ID to recipient list when encryption.")
 
+
 ;;; mailcrypt wrapper
 
 (autoload 'mc-sign-generic "mc-toplev")
@@ -255,91 +262,9 @@ Whether the passphrase is cached at all is controlled by
 (autoload 'epg-configuration "epg-config")
 (autoload 'epg-expand-group "epg-config")
 
-(defvar mml1991-epg-secret-key-id-list nil)
-
-(defun mml1991-epg-passphrase-callback (context key-id ignore)
-  (if (eq key-id 'SYM)
-      (epg-passphrase-callback-function context key-id nil)
-    (let* ((entry (assoc key-id epg-user-id-alist))
-          (passphrase
-           (password-read
-            (format "GnuPG passphrase for %s: "
-                    (if entry
-                        (cdr entry)
-                      key-id))
-            (if (eq key-id 'PIN)
-                "PIN"
-              key-id))))
-      (when passphrase
-       (let ((password-cache-expiry mml1991-passphrase-cache-expiry))
-         (password-cache-add key-id passphrase))
-       (setq mml1991-epg-secret-key-id-list
-             (cons key-id mml1991-epg-secret-key-id-list))
-       (copy-sequence passphrase)))))
-
-(defun mml1991-epg-find-usable-key (keys usage)
-  (catch 'found
-    (while keys
-      (let ((pointer (epg-key-sub-key-list (car keys))))
-       ;; The primary key will be marked as disabled, when the entire
-       ;; key is disabled (see 12 Field, Format of colon listings, in
-       ;; gnupg/doc/DETAILS)
-       (unless (memq 'disabled (epg-sub-key-capability (car pointer)))
-         (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)))))
-
-;; XXX: since gpg --list-secret-keys does not return validity of each
-;; key, `mml1991-epg-find-usable-key' defined above is not enough for
-;; secret keys.  The function `mml1991-epg-find-usable-secret-key'
-;; below looks at appropriate public keys to check usability.
-(defun mml1991-epg-find-usable-secret-key (context name usage)
-  (let ((secret-keys (epg-list-keys context name t))
-       secret-key)
-    (while (and (not secret-key) secret-keys)
-      (if (mml1991-epg-find-usable-key
-          (epg-list-keys context (epg-sub-key-fingerprint
-                                  (car (epg-key-sub-key-list
-                                        (car secret-keys)))))
-          usage)
-         (setq secret-key (car secret-keys)
-               secret-keys nil)
-       (setq secret-keys (cdr secret-keys))))
-    secret-key))
-
 (defun mml1991-epg-sign (cont)
-  (let ((context (epg-make-context))
-       headers cte signer-key signers signature)
-    (if (eq mm-sign-option 'guided)
-       (setq signers (epa-select-keys context "Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                      mml1991-signers t))
-      (if mml1991-signers
-         (setq signers (delq nil
-                             (mapcar
-                              (lambda (name)
-                                (setq signer-key
-                                      (mml1991-epg-find-usable-secret-key
-                                       context name 'sign))
-                                (unless (or signer-key
-                                            (y-or-n-p
-                                             (format
-                                              "No secret key for %s; skip it? "
-                                              name)))
-                                  (error "No secret key for %s" name))
-                                signer-key)
-                              mml1991-signers)))))
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (epg-context-set-signers context signers)
-    (if mml1991-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml1991-epg-passphrase-callback))
+  (let ((inhibit-redisplay t)
+       headers cte)
     ;; Don't sign headers.
     (goto-char (point-min))
     (when (re-search-forward "^$" nil t)
@@ -352,28 +277,21 @@ If no one is selected, default secret key is used.  "
       (when cte
        (setq cte (intern (downcase cte)))
        (mm-decode-content-transfer-encoding cte)))
-    (condition-case error
-       (setq signature (epg-sign-string context (buffer-string) 'clear)
-             mml1991-epg-secret-key-id-list nil)
-      (error
-       (while mml1991-epg-secret-key-id-list
-        (password-cache-remove (car mml1991-epg-secret-key-id-list))
-        (setq mml1991-epg-secret-key-id-list
-              (cdr mml1991-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
-    (delete-region (point-min) (point-max))
-    (mm-with-unibyte-current-buffer
-      (insert signature)
-      (goto-char (point-min))
-      (while (re-search-forward "\r+$" nil t)
-       (replace-match "" t t))
-      (when cte
-       (mm-encode-content-transfer-encoding cte))
-      (goto-char (point-min))
-      (when headers
-       (insert headers))
-      (insert "\n"))
-    t))
+    (let* ((pair (mml-secure-epg-sign 'OpenPGP 'clear))
+          (signature (car pair)))
+      (delete-region (point-min) (point-max))
+      (mm-with-unibyte-current-buffer
+       (insert signature)
+       (goto-char (point-min))
+       (while (re-search-forward "\r+$" nil t)
+         (replace-match "" t t))
+       (when cte
+         (mm-encode-content-transfer-encoding cte))
+       (goto-char (point-min))
+       (when headers
+         (insert headers))
+       (insert "\n"))
+      t)))
 
 (defun mml1991-epg-encrypt (cont &optional sign)
   (goto-char (point-min))
@@ -386,78 +304,7 @@ If no one is selected, default secret key is used.  "
       (delete-region (point-min) (point))
       (when cte
        (mm-decode-content-transfer-encoding (intern (downcase cte))))))
-  (let ((context (epg-make-context))
-       (recipients
-        (if (message-options-get 'message-recipients)
-            (split-string
-             (message-options-get 'message-recipients)
-             "[ \f\t\n\r\v,]+")))
-       recipient-key signer-key cipher signers config)
-    (when mml1991-encrypt-to-self
-      (unless mml1991-signers
-       (error "mml1991-signers is not set"))
-      (setq recipients (nconc recipients mml1991-signers)))
-    ;; We should remove this check if epg-0.0.6 is released.
-    (if (and (condition-case nil
-                (require 'epg-config)
-              (error))
-            (functionp #'epg-expand-group))
-       (setq config (epg-configuration)
-             recipients
-             (apply #'nconc
-                    (mapcar (lambda (recipient)
-                              (or (epg-expand-group config recipient)
-                                  (list recipient)))
-                            recipients))))
-    (if (eq mm-encrypt-option 'guided)
-       (setq recipients
-             (epa-select-keys context "Select recipients for encryption.
-If no one is selected, symmetric encryption will be performed.  "
-                              recipients))
-      (setq recipients
-           (delq nil (mapcar
-                      (lambda (name)
-                        (setq recipient-key (mml1991-epg-find-usable-key
-                                             (epg-list-keys context name)
-                                             'encrypt))
-                        (unless (or recipient-key
-                                  (y-or-n-p
-                                   (format "No public key for %s; skip it? "
-                                           name)))
-                          (error "No public key for %s" name))
-                        recipient-key)
-                      recipients)))
-      (unless recipients
-       (error "No recipient specified")))
-    (when sign
-      (if (eq mm-sign-option 'guided)
-         (setq signers (epa-select-keys context "Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                        mml1991-signers t))
-       (if mml1991-signers
-           (setq signers (delq nil
-                               (mapcar
-                                (lambda (name)
-                                  (mml1991-epg-find-usable-secret-key
-                                   context name 'sign))
-                                mml1991-signers)))))
-      (epg-context-set-signers context signers))
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (if mml1991-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml1991-epg-passphrase-callback))
-    (condition-case error
-       (setq cipher
-             (epg-encrypt-string context (buffer-string) recipients sign)
-             mml1991-epg-secret-key-id-list nil)
-      (error
-       (while mml1991-epg-secret-key-id-list
-        (password-cache-remove (car mml1991-epg-secret-key-id-list))
-        (setq mml1991-epg-secret-key-id-list
-              (cdr mml1991-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
+  (let ((cipher (mml-secure-epg-encrypt 'OpenPGP cont sign)))
     (delete-region (point-min) (point-max))
     (insert "\n" cipher))
   t)
index 10ba126..136ed80 100644 (file)
@@ -111,6 +111,9 @@ Valid packages include `epg', `pgg' and `mailcrypt'.")
   "If t, cache passphrase."
   :group 'mime-security
   :type 'boolean)
+(make-obsolete-variable 'mml2015-cache-passphrase
+                       'mml-secure-cache-passphrase
+                       "25.0.50")
 
 (defcustom mml2015-passphrase-cache-expiry mml-secure-passphrase-cache-expiry
   "How many seconds the passphrase is cached.
@@ -118,6 +121,9 @@ Whether the passphrase is cached at all is controlled by
 `mml2015-cache-passphrase'."
   :group 'mime-security
   :type 'integer)
+(make-obsolete-variable 'mml2015-passphrase-cache-expiry
+                       'mml-secure-passphrase-cache-expiry
+                       "25.0.50")
 
 (defcustom mml2015-signers nil
   "A list of your own key ID(s) which will be used to sign a message.
@@ -774,99 +780,6 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
 (autoload 'epg-expand-group "epg-config")
 (autoload 'epa-select-keys "epa")
 
-(defvar mml2015-epg-secret-key-id-list nil)
-
-(defun mml2015-epg-passphrase-callback (context key-id ignore)
-  (if (eq key-id 'SYM)
-      (epg-passphrase-callback-function context key-id nil)
-    (let* ((password-cache-key-id
-           (if (eq key-id 'PIN)
-               "PIN"
-              key-id))
-          entry
-          (passphrase
-           (password-read
-            (if (eq key-id 'PIN)
-                "Passphrase for PIN: "
-              (if (setq entry (assoc key-id epg-user-id-alist))
-                  (format "Passphrase for %s %s: " key-id (cdr entry))
-                (format "Passphrase for %s: " key-id)))
-            password-cache-key-id)))
-      (when passphrase
-       (let ((password-cache-expiry mml2015-passphrase-cache-expiry))
-         (password-cache-add password-cache-key-id passphrase))
-       (setq mml2015-epg-secret-key-id-list
-             (cons password-cache-key-id mml2015-epg-secret-key-id-list))
-       (copy-sequence passphrase)))))
-
-(defun mml2015-epg-check-user-id (key recipient)
-  (let ((pointer (epg-key-user-id-list key))
-       result)
-    (while pointer
-      (if (and (equal (car (mail-header-parse-address
-                           (epg-user-id-string (car pointer))))
-                     (car (mail-header-parse-address
-                           recipient)))
-              (not (memq (epg-user-id-validity (car pointer))
-                         '(revoked expired))))
-         (setq result t
-               pointer nil)
-       (setq pointer (cdr pointer))))
-    result))
-
-(defun mml2015-epg-check-sub-key (key usage)
-  (let ((pointer (epg-key-sub-key-list key))
-       result)
-    ;; The primary key will be marked as disabled, when the entire
-    ;; key is disabled (see 12 Field, Format of colon listings, in
-    ;; gnupg/doc/DETAILS)
-    (unless (memq 'disabled (epg-sub-key-capability (car pointer)))
-      (while pointer
-       (if (and (memq usage (epg-sub-key-capability (car pointer)))
-                (not (memq (epg-sub-key-validity (car pointer))
-                           '(revoked expired))))
-           (setq result t
-                 pointer nil)
-         (setq pointer (cdr pointer)))))
-    result))
-
-(defun mml2015-epg-find-usable-key (context name usage
-                                           &optional name-is-key-id)
-  (let ((keys (epg-list-keys context name))
-       key)
-    (while keys
-      (if (and (or name-is-key-id
-                  ;; Non email user-id can be supplied through
-                  ;; mml2015-signers if mml2015-encrypt-to-self is set.
-                  ;; Treat it as valid, as it is user's intention.
-                  (not (string-match "\\`<" name))
-                  (mml2015-epg-check-user-id (car keys) name))
-              (mml2015-epg-check-sub-key (car keys) usage))
-         (setq key (car keys)
-               keys nil)
-       (setq keys (cdr keys))))
-    key))
-
-;; XXX: since gpg --list-secret-keys does not return validity of each
-;; key, `mml2015-epg-find-usable-key' defined above is not enough for
-;; secret keys.  The function `mml2015-epg-find-usable-secret-key'
-;; below looks at appropriate public keys to check usability.
-(defun mml2015-epg-find-usable-secret-key (context name usage)
-  (let ((secret-keys (epg-list-keys context name t))
-       secret-key)
-    (while (and (not secret-key) secret-keys)
-      (if (mml2015-epg-find-usable-key
-          context
-          (epg-sub-key-fingerprint
-           (car (epg-key-sub-key-list
-                 (car secret-keys))))
-          usage
-          t)
-         (setq secret-key (car secret-keys)
-               secret-keys nil)
-       (setq secret-keys (cdr secret-keys))))
-    secret-key))
-
 (autoload 'gnus-create-image "gnus-ems")
 
 (defun mml2015-epg-key-image (key-id)
@@ -921,18 +834,15 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
         mm-security-handle 'gnus-info "Corrupted")
        (throw 'error handle))
       (setq context (epg-make-context))
-      (if mml2015-cache-passphrase
+      (if (or mml2015-cache-passphrase mml-secure-cache-passphrase)
          (epg-context-set-passphrase-callback
           context
-          #'mml2015-epg-passphrase-callback))
+          (cons 'mml-secure-passphrase-callback 'OpenPGP)))
       (condition-case error
          (setq plain (epg-decrypt-string context (mm-get-part child))
-               mml2015-epg-secret-key-id-list nil)
+               mml-secure-secret-key-id-list nil)
        (error
-        (while mml2015-epg-secret-key-id-list
-          (password-cache-remove (car mml2015-epg-secret-key-id-list))
-          (setq mml2015-epg-secret-key-id-list
-                (cdr mml2015-epg-secret-key-id-list)))
+        (mml-secure-clear-secret-key-id-list)
         (mm-set-handle-multipart-parameter
          mm-security-handle 'gnus-info "Failed")
         (if (eq (car error) 'quit)
@@ -968,18 +878,15 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
   (let ((inhibit-redisplay t)
        (context (epg-make-context))
        plain)
-    (if mml2015-cache-passphrase
+    (if (or mml2015-cache-passphrase mml-secure-cache-passphrase)
        (epg-context-set-passphrase-callback
         context
-        #'mml2015-epg-passphrase-callback))
+        (cons 'mml-secure-passphrase-callback 'OpenPGP)))
     (condition-case error
        (setq plain (epg-decrypt-string context (buffer-string))
-             mml2015-epg-secret-key-id-list nil)
+             mml-secure-secret-key-id-list nil)
       (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
+       (mml-secure-clear-secret-key-id-list)
        (mm-set-handle-multipart-parameter
        mm-security-handle 'gnus-info "Failed")
        (if (eq (car error) 'quit)
@@ -1065,176 +972,37 @@ If set, it overrides the setting of `mml2015-sign-with-sender'."
       (mml2015-extract-cleartext-signature))))
 
 (defun mml2015-epg-sign (cont)
-  (let* ((inhibit-redisplay t)
-        (context (epg-make-context))
-        (boundary (mml-compute-boundary cont))
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml2015-signers
-                          (if (and mml2015-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
-        signer-key
-        (signers
-         (or (message-options-get 'mml2015-epg-signers)
-             (message-options-set
-              'mml2015-epg-signers
-              (if (eq mm-sign-option 'guided)
-                  (epa-select-keys context "\
-Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                   signer-names
-                                   t)
-                (if (or sender mml2015-signers)
-                    (delq nil
-                          (mapcar
-                           (lambda (signer)
-                             (setq signer-key
-                                   (mml2015-epg-find-usable-secret-key
-                                    context signer '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)
-                           signer-names)))))))
-        signature micalg)
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (epg-context-set-signers context signers)
-    (if mml2015-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml2015-epg-passphrase-callback))
+  (let ((inhibit-redisplay t)
+       (boundary (mml-compute-boundary cont)))
     ;; Signed data must end with a newline (RFC 3156, 5).
     (goto-char (point-max))
     (unless (bolp)
       (insert "\n"))
-    (condition-case error
-       (setq signature (epg-sign-string context (buffer-string) t)
-             mml2015-epg-secret-key-id-list nil)
-      (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
-    (if (epg-context-result-for context 'sign)
-       (setq micalg (epg-new-signature-digest-algorithm
-                     (car (epg-context-result-for context 'sign)))))
-    (goto-char (point-min))
-    (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
-                   boundary))
-    (if micalg
-       (insert (format "\tmicalg=pgp-%s; "
-                       (downcase
-                        (cdr (assq micalg
-                                   epg-digest-algorithm-alist))))))
-    (insert "protocol=\"application/pgp-signature\"\n")
-    (insert (format "\n--%s\n" boundary))
-    (goto-char (point-max))
-    (insert (format "\n--%s\n" boundary))
-    (insert "Content-Type: application/pgp-signature; name=\"signature.asc\"\n\n")
-    (insert signature)
-    (goto-char (point-max))
-    (insert (format "--%s--\n" boundary))
-    (goto-char (point-max))))
+    (let* ((pair (mml-secure-epg-sign 'OpenPGP t))
+          (signature (car pair))
+          (micalg (cdr pair)))
+      (goto-char (point-min))
+      (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
+                     boundary))
+      (if micalg
+         (insert (format "\tmicalg=pgp-%s; "
+                         (downcase
+                          (cdr (assq micalg
+                                     epg-digest-algorithm-alist))))))
+      (insert "protocol=\"application/pgp-signature\"\n")
+      (insert (format "\n--%s\n" boundary))
+      (goto-char (point-max))
+      (insert (format "\n--%s\n" boundary))
+      (insert "Content-Type: application/pgp-signature; name=\"signature.asc\"\n\n")
+      (insert signature)
+      (goto-char (point-max))
+      (insert (format "--%s--\n" boundary))
+      (goto-char (point-max)))))
 
 (defun mml2015-epg-encrypt (cont &optional sign)
   (let* ((inhibit-redisplay t)
-        (context (epg-make-context))
         (boundary (mml-compute-boundary cont))
-        (config (epg-configuration))
-        (recipients (message-options-get 'mml2015-epg-recipients))
-        cipher
-        (sender (message-options-get 'message-sender))
-        (signer-names (or mml2015-signers
-                          (if (and mml2015-sign-with-sender sender)
-                              (list (concat "<" sender ">")))))
-        signers
-        recipient-key signer-key)
-    (unless recipients
-      (setq recipients
-           (apply #'nconc
-                  (mapcar
-                   (lambda (recipient)
-                     (or (epg-expand-group config 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 signer-names
-         (error "Neither message sender nor mml2015-signers are set"))
-       (setq recipients (nconc recipients signer-names)))
-      (if (eq mm-encrypt-option 'guided)
-         (setq recipients
-               (epa-select-keys context "\
-Select recipients for encryption.
-If no one is selected, symmetric encryption will be performed.  "
-                                recipients))
-       (setq recipients
-             (delq nil
-                   (mapcar
-                    (lambda (recipient)
-                      (setq recipient-key (mml2015-epg-find-usable-key
-                                           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
-           (or (message-options-get 'mml2015-epg-signers)
-               (message-options-set
-                'mml2015-epg-signers
-                (if (eq mm-sign-option 'guided)
-                    (epa-select-keys context "\
-Select keys for signing.
-If no one is selected, default secret key is used.  "
-                                     signer-names
-                                     t)
-                  (if (or sender mml2015-signers)
-                      (delq nil
-                            (mapcar
-                             (lambda (signer)
-                               (setq signer-key
-                                     (mml2015-epg-find-usable-secret-key
-                                      context signer '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)
-                             signer-names)))))))
-      (epg-context-set-signers context signers))
-    (epg-context-set-armor context t)
-    (epg-context-set-textmode context t)
-    (if mml2015-cache-passphrase
-       (epg-context-set-passphrase-callback
-        context
-        #'mml2015-epg-passphrase-callback))
-    (condition-case error
-       (setq cipher
-             (epg-encrypt-string context (buffer-string) recipients sign
-                                 mml2015-always-trust)
-             mml2015-epg-secret-key-id-list nil)
-      (error
-       (while mml2015-epg-secret-key-id-list
-        (password-cache-remove (car mml2015-epg-secret-key-id-list))
-        (setq mml2015-epg-secret-key-id-list
-              (cdr mml2015-epg-secret-key-id-list)))
-       (signal (car error) (cdr error))))
+        (cipher (mml-secure-epg-encrypt 'OpenPGP cont sign)))
     (delete-region (point-min) (point-max))
     (goto-char (point-min))
     (insert (format "Content-Type: multipart/encrypted; boundary=\"%s\";\n"
diff --git a/lisp/tests/gnustest-gnus-util.el b/lisp/tests/gnustest-gnus-util.el
new file mode 100644 (file)
index 0000000..b40ad85
--- /dev/null
@@ -0,0 +1,100 @@
+;;; gnustest-gnus-util.el --- Selectived tests only.
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Jens Lechtenbörger <jens.lechtenboerger@fsfe.org>
+
+;; This file is not part of GNU Emacs.
+
+;; 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 3, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The tests here are restricted to three functions:
+;; gnus-test-list, gnus-subsetp, gnus-setdiff
+;;
+;; Run as follows:
+;; emacs -Q -batch -L .. -l gnustest-gnus-util.el -f ert-run-tests-batch-and-exit
+
+;;; Code:
+
+(require 'ert)
+(require 'gnus-util)
+
+(ert-deftest test-list ()
+  ;; False for non-lists.
+  (should-not (gnus-test-list 1 'listp))
+  (should-not (gnus-test-list "42" 'listp))
+
+  ;; False for empty lists.
+  (should-not (gnus-test-list '() 'listp))
+  (should-not (gnus-test-list '() 'nlistp))
+
+  ;; Real tests for other lists.
+  (should (gnus-test-list '(()) 'listp))
+  (should (gnus-test-list '(() ()) 'listp))
+  (should-not (gnus-test-list '(1) 'listp))
+  (should-not (gnus-test-list '(() 1) 'listp))
+  (should-not (gnus-test-list '(1 ()) 'listp))
+  (should-not (gnus-test-list '(() 1 ()) 'listp))
+  )
+
+(ert-deftest subsetp ()
+  ;; False for non-lists.
+  (should-not (gnus-subsetp "1" "1"))
+  (should-not (gnus-subsetp "1" '("1")))
+  (should-not (gnus-subsetp '("1") "1"))
+
+  ;; Real tests.
+  (should (gnus-subsetp '() '()))
+  (should (gnus-subsetp '() '("1")))
+  (should (gnus-subsetp '("1") '("1")))
+  (should (gnus-subsetp '(42) '("1" 42)))
+  (should (gnus-subsetp '(42) '(42 "1")))
+  (should (gnus-subsetp '(42) '("1" 42 2)))
+  (should-not (gnus-subsetp '("1") '()))
+  (should-not (gnus-subsetp '("1") '(2)))
+  (should-not (gnus-subsetp '("1" 2) '(2)))
+  (should-not (gnus-subsetp '(2 "1") '(2)))
+  (should-not (gnus-subsetp '("1" 2) '(2 3)))
+
+  ;; Duplicates don't matter for sets.
+  (should (gnus-subsetp '("1" "1") '("1")))
+  (should (gnus-subsetp '("1" 2 "1") '(2 "1")))
+  (should (gnus-subsetp '("1" 2 "1") '(2 "1" "1" 2)))
+  (should-not (gnus-subsetp '("1" 2 "1" 3) '(2 "1" "1" 2))))
+
+(ert-deftest setdiff ()
+  ;; False for non-lists.
+  (should-not (gnus-setdiff "1" "1"))
+  (should-not (gnus-setdiff "1" '()))
+  (should-not (gnus-setdiff '() "1"))
+
+  ;; Real tests.
+  (should-not (gnus-setdiff '() '()))
+  (should-not (gnus-setdiff '() '("1")))
+  (should-not (gnus-setdiff '("1") '("1")))
+  (should (equal '("1") (gnus-setdiff '("1") '())))
+  (should (equal '("1") (gnus-setdiff '("1") '(2))))
+  (should (equal '("1") (gnus-setdiff '("1" 2) '(2))))
+  (should (equal '("1") (gnus-setdiff '("1" 2 3) '(3 2))))
+  (should (equal '("1") (gnus-setdiff '(2 "1" 3) '(3 2))))
+  (should (equal '("1") (gnus-setdiff '(2 3 "1") '(3 2))))
+  (should (equal '(2 "1") (gnus-setdiff '(2 3 "1") '(3))))
+
+  ;; Duplicates aren't touched for sets if they are not removed.
+  (should-not (gnus-setdiff '("1" "1") '("1")))
+  (should (equal '("1") (gnus-setdiff '(2 "1" 2) '(2))))
+  (should (equal '("1" "1") (gnus-setdiff '(2 "1" 2 "1") '(2)))))
+
+;;; gnustest-gnus-util.el ends here
diff --git a/lisp/tests/gnustest-mml-sec.README b/lisp/tests/gnustest-mml-sec.README
new file mode 100644 (file)
index 0000000..f8920f5
--- /dev/null
@@ -0,0 +1,45 @@
+* Prerequisites
+I tested against Emacs versions 24.3 (precompiled on my system, with Gnus
+v5.13) and 25.0.50 (compiled from git source, with the included v5.13 and with
+Ma Gnus v0.14).
+In general, I recommend that you use GnuPG version 1.x for tests.  Obviously,
+for gpgsm you need 2.x, which works for me with 2.0.x but not 2.1.x; see
+mml-secure-run-tests-with-gpg2 in gnustest-mml-sec.el.  When running tests
+with different versions of GnuPG make sure that proper versions of gpg-agent
+are running (kill all prior to testing, if in doubt).
+
+
+* Test keys
+The subdirectory mml-gpghome contains OpenPGP and S/MIME test keyrings for
+GnuPG’s gpg and gpgsm commands.  In addition, it contains a file
+gpg-agent.conf where all options are commented out.  In particular, by
+activating the debug settings one can verify whether the correct version of
+gpg-agent is running and whether pinentry problems arise with the current
+setup.
+
+Most keys in the test keyrings come with empty passphrases, while the keys
+associated with the user ID “No Expiry two UIDs” have the passphrase
+“Passphrase”.  You can see all public keys and user IDs as follows:
+$ gpg --homedir ./mml-gpghome --fingerprint -k --list-options show-unusable-subkeys,show-unusable-uids
+$ gpgsm --homedir ./mml-gpghome -k
+
+
+* Running tests
+To run all tests:
+$ cd <path-to-gnus/lisp/test>
+$ emacs -Q -batch -L .. -l gnustest-mml-sec.el -f mml-secure-run-tests
+
+However, in the above case gpgsm will ask for passphrases, even empty ones.
+To omit those tests:
+$ emacs -Q -batch -L .. -l gnustest-mml-sec.el -f mml-secure-run-tests-without-smime
+
+To run all tests with epg-gpg-program set to "gpg2":
+$ emacs -Q -batch -L .. -l gnustest-mml-sec.el -f mml-secure-run-tests-with-gpg2
+
+To check an issue with truncation of y-or-n-p questions:
+$ emacs -Q -L .. -l gnustest-mml-sec.el -f mml-secure-select-preferred-keys-todo
+Then mark one or two keys and select “OK”.  The following question should be
+truncated.  Answer “n” to avoid storage of that choice in your ~/.emacs.
+
+To see that question entirely (outside an encryption context):
+$ emacs -Q -L .. -l gnustest-mml-sec.el -f mml-secure-select-preferred-keys-ok
diff --git a/lisp/tests/gnustest-mml-sec.el b/lisp/tests/gnustest-mml-sec.el
new file mode 100644 (file)
index 0000000..a64269f
--- /dev/null
@@ -0,0 +1,859 @@
+;;; gnustest-mml-sec.el --- Tests mml-sec.el, see README-mml-secure.txt.
+;; Copyright (C) 2015 Free Software Foundation, Inc.
+
+;; Author: Jens Lechtenbörger <jens.lechtenboerger@fsfe.org>
+
+;; This file is not part of GNU Emacs.
+
+;; 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 3, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'ert)
+
+(require 'cl); mapcan
+(require 'message)
+(require 'epa)
+(require 'epg)
+(require 'mml-sec)
+(require 'gnus-sum)
+
+(defvar with-smime t
+  "If nil, exclude S/MIME from tests as passphrases need to entered manually.
+Mostly, the empty passphrase is used.  However, the keys for
+ \"No Expiry two UIDs\" have the passphrase \"Passphrase\" (for OpenPGP as well
+ as S/MIME).")
+
+(defun enc-standards ()
+  (if with-smime '(enc-pgp enc-pgp-mime enc-smime)
+    '(enc-pgp enc-pgp-mime)))
+(defun enc-sign-standards ()
+  (if with-smime
+      '(enc-sign-pgp enc-sign-pgp-mime enc-sign-smime)
+    '(enc-sign-pgp enc-sign-pgp-mime)))
+(defun sign-standards ()
+  (if with-smime
+      '(sign-pgp sign-pgp-mime sign-smime)
+    '(sign-pgp sign-pgp-mime)))
+
+(defun mml-secure-test-fixture (body &optional interactive)
+  "Setup GnuPG home containing test keys and prepare environment for BODY.
+If optional INTERACTIVE is non-nil, allow questions to the user in case of
+key problems.
+This fixture temporarily unsets GPG_AGENT_INFO to enable passphrase tests,
+which will neither work with gpgsm nor GnuPG 2.1 any longer, I guess.
+Actually, I'm not sure why people would want to cache passwords in Emacs
+instead of gpg-agent."
+  (unwind-protect
+      (let ((agent-info (getenv "GPG_AGENT_INFO"))
+           (gpghome (getenv "GNUPGHOME")))
+       (condition-case error
+           (let ((epg-gpg-home-directory
+                  (expand-file-name
+                   "mml-gpghome" (getenv "EMACS_TEST_DIRECTORY")))
+                 (mml-smime-use 'epg)
+                 ;; Create debug output in empty epg-debug-buffer.
+                 (epg-debug t)
+                 (epg-debug-buffer (get-buffer-create " *epg-test*"))
+                 (mml-secure-fail-when-key-problem (not interactive)))
+             (with-current-buffer epg-debug-buffer
+               (erase-buffer))
+             ;; Unset GPG_AGENT_INFO to enable passphrase caching inside Emacs.
+             ;; Just for testing.  Jens does not recommend this for daily use.
+             (setenv "GPG_AGENT_INFO")
+             ;; Set GNUPGHOME as gpg-agent started by gpgsm does
+             ;; not look in the proper places otherwise, see:
+             ;; https://bugs.gnupg.org/gnupg/issue2126
+             (setenv "GNUPGHOME" epg-gpg-home-directory)
+             (funcall body))
+         (error
+          (setenv "GPG_AGENT_INFO" agent-info)
+          (setenv "GNUPGHOME" gpghome)
+          (signal (car error) (cdr error))))
+       (setenv "GPG_AGENT_INFO" agent-info)
+       (setenv "GNUPGHOME" gpghome))))
+
+(defun mml-secure-test-message-setup (method to from &optional text bcc)
+  "Setup a buffer with MML METHOD, TO, and FROM headers.
+Optionally, a message TEXT and BCC header can be passed."
+  (with-temp-buffer
+    (when bcc (insert (format "Bcc: %s\n" bcc)))
+    (insert (format "To: %s
+From: %s
+Subject: Test
+%s\n" to from mail-header-separator))
+    (if text
+       (insert (format "%s" text))
+      (spook))
+    (cond ((eq method 'enc-pgp-mime)
+          (mml-secure-message-encrypt-pgpmime 'nosig))
+         ((eq method 'enc-sign-pgp-mime)
+          (mml-secure-message-encrypt-pgpmime))
+         ((eq method 'enc-pgp) (mml-secure-message-encrypt-pgp 'nosig))
+         ((eq method 'enc-sign-pgp) (mml-secure-message-encrypt-pgp))
+         ((eq method 'enc-smime) (mml-secure-message-encrypt-smime 'nosig))
+         ((eq method 'enc-sign-smime) (mml-secure-message-encrypt-smime))
+         ((eq method 'sign-pgp-mime) (mml-secure-message-sign-pgpmime))
+         ((eq method 'sign-pgp) (mml-secure-message-sign-pgp))
+         ((eq method 'sign-smime) (mml-secure-message-sign-smime))
+         (t (error "Unknown method")))
+    (buffer-string)))
+
+(defun mml-secure-test-mail-fixture (method to from body2
+                                           &optional interactive)
+  "Setup buffer encrypted using METHOD for TO from FROM, call BODY2.
+Pass optional INTERACTIVE to mml-secure-test-fixture."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((context (if (memq method '(enc-smime enc-sign-smime sign-smime))
+                       (epg-make-context 'CMS)
+                     (epg-make-context 'OpenPGP)))
+          ;; Verify and decrypt by default.
+          (mm-verify-option 'known)
+          (mm-decrypt-option 'known)
+          (plaintext "The Magic Words are Squeamish Ossifrage"))
+       (with-temp-buffer
+        (insert (mml-secure-test-message-setup method to from plaintext))
+        (message-options-set-recipient)
+        (message-encode-message-body)
+        ;; Replace separator line with newline.
+        (goto-char (point-min))
+        (re-search-forward
+         (concat "^" (regexp-quote mail-header-separator) "\n"))
+        (replace-match "\n")
+        ;; The following treatment of handles, plainbuf, and multipart
+        ;; resulted from trial-and-error.
+        ;; Someone with more knowledge on how to decrypt messages and verify
+        ;; signatures might know more appropriate functions to invoke
+        ;; instead.
+        (let* ((handles (or (mm-dissect-buffer)
+                            (mm-uu-dissect)))
+               (isplain (bufferp (car handles)))
+               (ismultipart (equal (car handles) "multipart/mixed"))
+               (plainbuf (if isplain
+                             (car handles)
+                           (if ismultipart
+                               (car (cadadr handles))
+                             (caadr handles))))
+               (decrypted
+                (with-current-buffer plainbuf (buffer-string)))
+               (gnus-info
+                (if isplain
+                    nil
+                  (if ismultipart
+                      (or (mm-handle-multipart-ctl-parameter
+                           (cadr handles) 'gnus-details)
+                          (mm-handle-multipart-ctl-parameter
+                           (cadr handles) 'gnus-info))
+                    (mm-handle-multipart-ctl-parameter
+                     handles 'gnus-info)))))
+          (funcall body2 gnus-info plaintext decrypted)))))
+   interactive))
+
+;; TODO If the variable BODY3 is renamed to BODY, an infinite recursion
+;; occurs.  Emacs bug?
+(defun mml-secure-test-key-fixture (body3)
+  "Customize unique keys for sub@example.org and call BODY3.
+For OpenPGP, we have:
+- 1E6B FA97 3D9E 3103 B77F  D399 C399 9CF1 268D BEA2
+  uid                  Different subkeys <sub@example.org>
+- 1463 2ECA B9E2 2736 9C8D  D97B F7E7 9AB7 AE31 D471
+  uid                  Second Key Pair <sub@example.org>
+
+For S/MIME:
+          ID: 0x479DC6E2
+      Subject: /CN=Second Key Pair
+          aka: sub@example.org
+  fingerprint: 0E:58:22:9B:80:EE:33:95:9F:F7:18:FE:EF:25:40:2B:47:9D:C6:E2
+
+           ID: 0x5F88E9FC
+      Subject: /CN=Different subkeys
+          aka: sub@example.org
+  fingerprint: 4F:96:2A:B7:F4:76:61:6A:78:3D:72:AA:40:35:D5:9B:5F:88:E9:FC
+
+In both cases, the first key is customized for signing and encryption."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let* ((mml-secure-key-preferences
+            '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt))))
+           (pcontext (epg-make-context 'OpenPGP))
+           (pkey (epg-list-keys pcontext "C3999CF1268DBEA2"))
+           (scontext (epg-make-context 'CMS))
+           (skey (epg-list-keys scontext "0x479DC6E2")))
+       (mml-secure-cust-record-keys pcontext 'encrypt "sub@example.org" pkey)
+       (mml-secure-cust-record-keys pcontext 'sign "sub@example.org" pkey)
+       (mml-secure-cust-record-keys scontext 'encrypt "sub@example.org" skey)
+       (mml-secure-cust-record-keys scontext 'sign "sub@example.org" skey)
+       (funcall body3)))))
+
+(ert-deftest mml-secure-key-checks ()
+  "Test mml-secure-check-user-id and mml-secure-check-sub-key on sample keys."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let* ((context (epg-make-context 'OpenPGP))
+           (keys1 (epg-list-keys context "expired@example.org"))
+           (keys2 (epg-list-keys context "no-exp@example.org"))
+           (keys3 (epg-list-keys context "sub@example.org"))
+           (keys4 (epg-list-keys context "revoked-uid@example.org"))
+           (keys5 (epg-list-keys context "disabled@example.org"))
+           (keys6 (epg-list-keys context "sign@example.org"))
+           (keys7 (epg-list-keys context "jens.lechtenboerger@fsfe"))
+           )
+       (should (and (= 1 (length keys1)) (= 1 (length keys2))
+                   (= 2 (length keys3))
+                   (= 1 (length keys4)) (= 1 (length keys5))
+                   ))
+       ;; key1 is expired
+       (should-not (mml-secure-check-user-id (car keys1) "expired@example.org"))
+       (should-not (mml-secure-check-sub-key context (car keys1) 'encrypt))
+       (should-not (mml-secure-check-sub-key context (car keys1) 'sign))
+
+       ;; key2 does not expire, but does not have the UID expired@example.org
+       (should-not (mml-secure-check-user-id (car keys2) "expired@example.org"))
+       (should (mml-secure-check-user-id (car keys2) "no-exp@example.org"))
+       (should (mml-secure-check-sub-key context (car keys2) 'encrypt))
+       (should (mml-secure-check-sub-key context (car keys2) 'sign))
+
+       ;; Two keys exist for sub@example.org.
+       (should (mml-secure-check-user-id (car keys3) "sub@example.org"))
+       (should (mml-secure-check-sub-key context (car keys3) 'encrypt))
+       (should (mml-secure-check-sub-key context (car keys3) 'sign))
+       (should (mml-secure-check-user-id (cadr keys3) "sub@example.org"))
+       (should (mml-secure-check-sub-key context (cadr keys3) 'encrypt))
+       (should (mml-secure-check-sub-key context (cadr keys3) 'sign))
+
+       ;; The UID revoked-uid@example.org is revoked.  The key itself is
+       ;; usable, though (with the UID sub@example.org).
+       (should-not
+       (mml-secure-check-user-id (car keys4) "revoked-uid@example.org"))
+       (should (mml-secure-check-sub-key context (car keys4) 'encrypt))
+       (should (mml-secure-check-sub-key context (car keys4) 'sign))
+       (should (mml-secure-check-user-id (car keys4) "sub@example.org"))
+
+       ;; The next key is disabled and, thus, unusable.
+       (should (mml-secure-check-user-id (car keys5) "disabled@example.org"))
+       (should-not (mml-secure-check-sub-key context (car keys5) 'encrypt))
+       (should-not (mml-secure-check-sub-key context (car keys5) 'sign))
+
+       ;; The next key has multiple subkeys.
+       ;; 42466F0F is valid sign subkey, 501FFD98 is expired
+       (should (mml-secure-check-sub-key context (car keys6) 'sign "42466F0F"))
+       (should-not
+       (mml-secure-check-sub-key context (car keys6) 'sign "501FFD98"))
+       ;; DC7F66E7 is encrypt subkey
+       (should
+       (mml-secure-check-sub-key context (car keys6) 'encrypt "DC7F66E7"))
+       (should-not
+       (mml-secure-check-sub-key context (car keys6) 'sign "DC7F66E7"))
+       (should-not
+       (mml-secure-check-sub-key context (car keys6) 'encrypt "42466F0F"))
+
+       ;; The final key is just a public key.
+       (should (mml-secure-check-sub-key context (car keys7) 'encrypt))
+       (should-not (mml-secure-check-sub-key context (car keys7) 'sign))
+       ))))
+
+(ert-deftest mml-secure-find-usable-keys-1 ()
+  "Make sure that expired and disabled keys and revoked UIDs are not used."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((context (epg-make-context 'OpenPGP)))
+       (should-not
+       (mml-secure-find-usable-keys context "expired@example.org" 'encrypt))
+       (should-not
+       (mml-secure-find-usable-keys context "expired@example.org" 'sign))
+
+       (should-not
+       (mml-secure-find-usable-keys context "disabled@example.org" 'encrypt))
+       (should-not
+       (mml-secure-find-usable-keys context "disabled@example.org" 'sign))
+
+       (should-not
+       (mml-secure-find-usable-keys
+        context "<revoked-uid@example.org>" 'encrypt))
+       (should-not
+       (mml-secure-find-usable-keys
+        context "<revoked-uid@example.org>" 'sign))
+       ;; Same test without ankles.  Will fail for Ma Gnus v0.14 and earlier.
+       (should-not
+       (mml-secure-find-usable-keys
+        context "revoked-uid@example.org" 'encrypt))
+
+       ;; Expired key should not be usable.
+       ;; Will fail for Ma Gnus v0.14 and earlier.
+       ;; sign@example.org has the expired subkey 0x501FFD98.
+       (should-not
+       (mml-secure-find-usable-keys context "0x501FFD98" 'sign))
+
+       (should
+       (mml-secure-find-usable-keys context "no-exp@example.org" 'encrypt))
+       (should
+       (mml-secure-find-usable-keys context "no-exp@example.org" 'sign))
+       ))))
+
+(ert-deftest mml-secure-find-usable-keys-2 ()
+  "Test different ways to search for keys."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((context (epg-make-context 'OpenPGP)))
+       ;; Plain substring search is not supported.
+       (should
+       (= 0 (length
+             (mml-secure-find-usable-keys context "No Expiry" 'encrypt))))
+       (should
+       (= 0 (length
+             (mml-secure-find-usable-keys context "No Expiry" 'sign))))
+
+       ;; Search for e-mail addresses works with and without ankle brackets.
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "<no-exp@example.org>" 'encrypt))))
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "<no-exp@example.org>" 'sign))))
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "no-exp@example.org" 'encrypt))))
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "no-exp@example.org" 'sign))))
+
+       ;; Use full UID string.
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "No Expiry <no-exp@example.org>" 'encrypt))))
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "No Expiry <no-exp@example.org>" 'sign))))
+
+       ;; If just the public key is present, only encryption is possible.
+       ;; Search works with key IDs, with and without prefix "0x".
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "A142FD84" 'encrypt))))
+       (should
+       (= 1 (length (mml-secure-find-usable-keys
+                     context "0xA142FD84" 'encrypt))))
+       (should
+       (= 0 (length (mml-secure-find-usable-keys
+                     context "A142FD84" 'sign))))
+       (should
+       (= 0 (length (mml-secure-find-usable-keys
+                     context "0xA142FD84" 'sign))))
+       ))))
+
+(ert-deftest mml-secure-select-preferred-keys-1 ()
+  "If only one key exists for an e-mail address, it is the preferred one."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((context (epg-make-context 'OpenPGP)))
+       (should (equal "832F3CC6518D37BC658261B802372A42CA6D40FB"
+                     (mml-secure-fingerprint
+                      (car (mml-secure-select-preferred-keys
+                            context '("no-exp@example.org") 'encrypt)))))))))
+
+(ert-deftest mml-secure-select-preferred-keys-2 ()
+  "If multiple keys exists for an e-mail address, customization is necessary."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let* ((context (epg-make-context 'OpenPGP))
+           (mml-secure-key-preferences
+            '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt))))
+           (pref (car (mml-secure-find-usable-keys
+                       context "sub@example.org" 'encrypt))))
+       (should-error (mml-secure-select-preferred-keys
+                     context '("sub@example.org") 'encrypt))
+       (mml-secure-cust-record-keys
+       context 'encrypt "sub@example.org" (list pref))
+       (should (mml-secure-select-preferred-keys
+               context '("sub@example.org") 'encrypt))
+       (should-error (mml-secure-select-preferred-keys
+                     context '("sub@example.org") 'sign))
+       (should (mml-secure-select-preferred-keys
+                       context '("sub@example.org") 'encrypt))
+       (should
+       (equal (list (mml-secure-fingerprint pref))
+              (mml-secure-cust-fpr-lookup context 'encrypt "sub@example.org")))
+       (should (mml-secure-cust-remove-keys context 'encrypt "sub@example.org"))
+       (should-error (mml-secure-select-preferred-keys
+                             context '("sub@example.org") 'encrypt))))))
+
+(ert-deftest mml-secure-select-preferred-keys-3 ()
+  "Expired customized keys are removed if multiple keys are available."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((context (epg-make-context 'OpenPGP))
+          (mml-secure-key-preferences
+           '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt)))))
+       ;; sub@example.org has two keys (268DBEA2, AE31D471).
+       ;; Normal preference works.
+       (mml-secure-cust-record-keys
+               context 'encrypt "sub@example.org" (epg-list-keys context "268DBEA2"))
+       (should (mml-secure-select-preferred-keys
+               context '("sub@example.org") 'encrypt))
+       (mml-secure-cust-remove-keys context 'encrypt "sub@example.org")
+
+       ;; Fake preference for expired (unrelated) key CE15FAE7,
+       ;; results in error (and automatic removal of outdated preference).
+       (mml-secure-cust-record-keys
+               context 'encrypt "sub@example.org" (epg-list-keys context "CE15FAE7"))
+       (should-error (mml-secure-select-preferred-keys
+                     context '("sub@example.org") 'encrypt))
+       (should-not
+       (mml-secure-cust-remove-keys context 'encrypt "sub@example.org"))))))
+
+(ert-deftest mml-secure-select-preferred-keys-4 ()
+  "Multiple keys can be recorded per recipient or signature."
+  (mml-secure-test-fixture
+   (lambda ()
+     (let ((pcontext (epg-make-context 'OpenPGP))
+          (scontext (epg-make-context 'CMS))
+          (pkeys '("1E6BFA973D9E3103B77FD399C3999CF1268DBEA2"
+                   "14632ECAB9E227369C8DD97BF7E79AB7AE31D471"))
+          (skeys  '("0x5F88E9FC" "0x479DC6E2"))
+          (mml-secure-key-preferences
+           '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt)))))
+
+       ;; OpenPGP preferences via pcontext
+       (dolist (key pkeys nil)
+        (mml-secure-cust-record-keys
+         pcontext 'encrypt "sub@example.org" (epg-list-keys pcontext key))
+        (mml-secure-cust-record-keys
+         pcontext 'sign "sub@example.org" (epg-list-keys pcontext key 'secret)))
+       (let ((p-e-fprs (mml-secure-cust-fpr-lookup
+                       pcontext 'encrypt "sub@example.org"))
+            (p-s-fprs (mml-secure-cust-fpr-lookup
+                       pcontext 'sign "sub@example.org")))
+        (should (= 2 (length p-e-fprs)))
+        (should (= 2 (length p-s-fprs)))
+        (should (member "1E6BFA973D9E3103B77FD399C3999CF1268DBEA2" p-e-fprs))
+        (should (member "14632ECAB9E227369C8DD97BF7E79AB7AE31D471" p-e-fprs))
+        (should (member "1E6BFA973D9E3103B77FD399C3999CF1268DBEA2" p-s-fprs))
+        (should (member "14632ECAB9E227369C8DD97BF7E79AB7AE31D471" p-s-fprs)))
+       ;; Duplicate record does not change anything.
+       (mml-secure-cust-record-keys
+       pcontext 'encrypt "sub@example.org"
+       (epg-list-keys pcontext "1E6BFA973D9E3103B77FD399C3999CF1268DBEA2"))
+       (mml-secure-cust-record-keys
+       pcontext 'sign "sub@example.org"
+       (epg-list-keys pcontext "1E6BFA973D9E3103B77FD399C3999CF1268DBEA2"))
+       (let ((p-e-fprs (mml-secure-cust-fpr-lookup
+                       pcontext 'encrypt "sub@example.org"))
+            (p-s-fprs (mml-secure-cust-fpr-lookup
+                       pcontext 'sign "sub@example.org")))
+        (should (= 2 (length p-e-fprs)))
+        (should (= 2 (length p-s-fprs))))
+
+       ;; S/MIME preferences via scontext
+       (dolist (key skeys nil)
+        (mml-secure-cust-record-keys
+         scontext 'encrypt "sub@example.org"
+         (epg-list-keys scontext key))
+        (mml-secure-cust-record-keys
+         scontext 'sign "sub@example.org"
+         (epg-list-keys scontext key 'secret)))
+       (let ((s-e-fprs (mml-secure-cust-fpr-lookup
+                       scontext 'encrypt "sub@example.org"))
+            (s-s-fprs (mml-secure-cust-fpr-lookup
+                       scontext 'sign "sub@example.org")))
+        (should (= 2 (length s-e-fprs)))
+        (should (= 2 (length s-s-fprs))))
+       ))))
+
+(defun mml-secure-test-en-decrypt
+    (method to from
+           &optional checksig checkplain enc-keys expectfail interactive)
+  "Encrypt message using METHOD, addressed to TO, from FROM.
+If optional CHECKSIG is non-nil, it must be a number, and a signature check is
+performed; the number indicates how many signatures are expected.
+If optional CHECKPLAIN is non-nil, the expected plaintext should be obtained
+via decryption.
+If optional ENC-KEYS is non-nil, it is a list of pairs of encryption keys (for
+OpenPGP and S/SMIME) expected in `epg-debug-buffer'.
+If optional EXPECTFAIL is non-nil, a decryption failure is expected.
+Pass optional INTERACTIVE to mml-secure-test-mail-fixture."
+  (mml-secure-test-mail-fixture method to from
+   (lambda (gnus-info plaintext decrypted)
+     (if expectfail
+        (should-not (equal plaintext decrypted))
+       (when checkplain
+        (should (equal plaintext decrypted)))
+       (let ((protocol (if (memq method
+                                '(enc-smime enc-sign-smime sign-smime))
+                          'CMS
+                        'OpenPGP)))
+        (when checksig
+          (let* ((context (epg-make-context protocol))
+                 (signer-names (mml-secure-signer-names protocol from))
+                 (signer-keys (mml-secure-signers context signer-names))
+                 (signer-fprs (mapcar 'mml-secure-fingerprint signer-keys)))
+            (should (eq checksig (length signer-fprs)))
+            (if (eq checksig 0)
+                ;; First key in keyring
+                (should (string-match-p
+                         (concat "Good signature from "
+                                 (if (eq protocol 'CMS)
+                                     "0E58229B80EE33959FF718FEEF25402B479DC6E2"
+                                   "02372A42CA6D40FB"))
+                         gnus-info)))
+            (dolist (fpr signer-fprs nil)
+              ;; OpenPGP: "Good signature from 02372A42CA6D40FB No Expiry <no-exp@example.org> (trust undefined) created ..."
+              ;; S/MIME:  "Good signature from D06AA118653CC38E9D0CAF56ED7A2135E1582177 /CN=No Expiry (trust full) ..."
+              (should (string-match-p
+                       (concat "Good signature from "
+                               (if (eq protocol 'CMS)
+                                   fpr
+                                 (substring fpr -16 nil)))
+                       gnus-info)))))
+        (when enc-keys
+          (with-current-buffer epg-debug-buffer
+            (goto-char (point-min))
+            ;; The following regexp does not necessarily match at the
+            ;; start of the line as a path may or may not be present.
+            ;; Also note that gpg.* matches gpg2 and gpgsm as well.
+            (let* ((line (concat "gpg.*--encrypt.*$"))
+                   (end (re-search-forward line))
+                   (match (match-string 0)))
+              (should (and end match))
+              (dolist (pair enc-keys nil)
+                (let ((fpr (if (eq protocol 'OpenPGP)
+                               (car pair)
+                             (cdr pair))))
+                  (should (string-match-p (concat "-r " fpr) match))))
+              (goto-char (point-max))
+              ))))))
+   interactive))
+
+(defun mml-secure-test-en-decrypt-with-passphrase
+    (method to from checksig jl-passphrase do-cache
+           &optional enc-keys expectfail)
+  "Call mml-secure-test-en-decrypt with changed passphrase caching.
+Args METHOD, TO, FROM, CHECKSIG are passed to mml-secure-test-en-decrypt.
+JL-PASSPHRASE is fixed as return value for `read-passwd',
+boolean DO-CACHE determines whether to cache the passphrase.
+If optional ENC-KEYS is non-nil, it is a list of encryption keys expected
+in `epg-debug-buffer'.
+If optional EXPECTFAIL is non-nil, a decryption failure is expected."
+  (let ((mml-secure-cache-passphrase do-cache)
+       (mml1991-cache-passphrase do-cache)
+       (mml2015-cache-passphrase do-cache)
+       (mml-smime-cache-passphrase do-cache)
+       )
+    (cl-letf (((symbol-function 'read-passwd)
+              (lambda (prompt &optional confirm default) jl-passphrase)))
+      (mml-secure-test-en-decrypt method to from checksig t enc-keys expectfail)
+      )))
+
+(ert-deftest mml-secure-en-decrypt-1 ()
+  "Encrypt message; then decrypt and test for expected result.
+In this test, the single matching key is chosen automatically."
+  (dolist (method (enc-standards) nil)
+    ;; no-exp@example.org with single encryption key
+    (mml-secure-test-en-decrypt
+     method "no-exp@example.org" "sub@example.org" nil t
+     (list (cons "02372A42CA6D40FB" "ED7A2135E1582177")))))
+
+(ert-deftest mml-secure-en-decrypt-2 ()
+  "Encrypt message; then decrypt and test for expected result.
+In this test, the encryption key needs to fixed among multiple ones."
+  ;; sub@example.org with multiple candidate keys,
+  ;; fixture customizes preferred ones.
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (dolist (method (enc-standards) nil)
+       (mml-secure-test-en-decrypt
+       method "sub@example.org" "no-exp@example.org" nil t
+       (list (cons "C3999CF1268DBEA2" "EF25402B479DC6E2")))))))
+
+(ert-deftest mml-secure-en-decrypt-3 ()
+  "Encrypt message; then decrypt and test for expected result.
+In this test, encrypt-to-self variables are set to t."
+  ;; sub@example.org with multiple candidate keys,
+  ;; fixture customizes preferred ones.
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (let ((mml-secure-openpgp-encrypt-to-self t)
+          (mml-secure-smime-encrypt-to-self t))
+       (dolist (method (enc-standards) nil)
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "no-exp@example.org" nil t
+         (list (cons "C3999CF1268DBEA2" "EF25402B479DC6E2")
+               (cons "02372A42CA6D40FB" "ED7A2135E1582177"))))))))
+
+(ert-deftest mml-secure-en-decrypt-4 ()
+  "Encrypt message; then decrypt and test for expected result.
+In this test, encrypt-to-self variables are set to lists."
+  ;; Send from sub@example.org, which has two keys; encrypt to both.
+  (let ((mml-secure-openpgp-encrypt-to-self
+        '("C3999CF1268DBEA2" "F7E79AB7AE31D471"))
+       (mml-secure-smime-encrypt-to-self
+        '("EF25402B479DC6E2" "4035D59B5F88E9FC")))
+    (dolist (method (enc-standards) nil)
+      (mml-secure-test-en-decrypt
+       method "no-exp@example.org" "sub@example.org" nil t
+       (list (cons "C3999CF1268DBEA2" "EF25402B479DC6E2")
+            (cons "F7E79AB7AE31D471" "4035D59B5F88E9FC"))))))
+
+(ert-deftest mml-secure-en-decrypt-sign-1 ()
+  "Sign and encrypt message; then decrypt and test for expected result.
+In this test, just multiple encryption and signing keys may be available."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (let ((mml-secure-openpgp-sign-with-sender t)
+          (mml-secure-smime-sign-with-sender t))
+       (dolist (method (enc-sign-standards) nil)
+        ;; no-exp with just one key
+        (mml-secure-test-en-decrypt
+         method "no-exp@example.org" "no-exp@example.org" 1 t)
+        ;; customized choice for encryption key
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "no-exp@example.org" 1 t)
+        ;; customized choice for signing key
+        (mml-secure-test-en-decrypt
+         method "no-exp@example.org" "sub@example.org" 1 t)
+        ;; customized choice for both keys
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "sub@example.org" 1 t)
+        )
+
+       ;; Now use both keys to sign.  The customized one via sign-with-sender,
+       ;; the other one via the following setting.
+       (let ((mml-secure-openpgp-signers '("F7E79AB7AE31D471"))
+            (mml-secure-smime-signers '("0x5F88E9FC")))
+        (dolist (method (enc-sign-standards) nil)
+          (mml-secure-test-en-decrypt
+           method "no-exp@example.org" "sub@example.org" 2 t)
+        )))
+
+     ;; Now use both keys for sub@example.org to sign an e-mail from
+     ;; a different address (without associated keys).
+     (let ((mml-secure-openpgp-sign-with-sender nil)
+          (mml-secure-smime-sign-with-sender nil)
+          (mml-secure-openpgp-signers
+           '("F7E79AB7AE31D471" "C3999CF1268DBEA2"))
+          (mml-secure-smime-signers '("0x5F88E9FC" "0x479DC6E2")))
+       (dolist (method (enc-sign-standards) nil)
+        (mml-secure-test-en-decrypt
+         method "no-exp@example.org" "no-keys@example.org" 2 t)
+        )))))
+
+(ert-deftest mml-secure-en-decrypt-sign-2 ()
+  "Sign and encrypt message; then decrypt and test for expected result.
+In this test, lists of encryption and signing keys are customized."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (let ((mml-secure-key-preferences
+           '((OpenPGP (sign) (encrypt)) (CMS (sign) (encrypt))))
+          (pcontext (epg-make-context 'OpenPGP))
+          (scontext (epg-make-context 'CMS))
+          (mml-secure-openpgp-sign-with-sender t)
+          (mml-secure-smime-sign-with-sender t))
+       (dolist (key '("F7E79AB7AE31D471" "C3999CF1268DBEA2") nil)
+        (mml-secure-cust-record-keys
+         pcontext 'encrypt "sub@example.org" (epg-list-keys pcontext key))
+        (mml-secure-cust-record-keys
+         pcontext 'sign "sub@example.org" (epg-list-keys pcontext key t)))
+       (dolist (key '("0x5F88E9FC" "0x479DC6E2") nil)
+        (mml-secure-cust-record-keys
+         scontext 'encrypt "sub@example.org" (epg-list-keys scontext key))
+        (mml-secure-cust-record-keys
+         scontext 'sign "sub@example.org" (epg-list-keys scontext key t)))
+       (dolist (method (enc-sign-standards) nil)
+        ;; customized choice for encryption key
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "no-exp@example.org" 1 t)
+        ;; customized choice for signing key
+        (mml-secure-test-en-decrypt
+         method "no-exp@example.org" "sub@example.org" 2 t)
+        ;; customized choice for both keys
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "sub@example.org" 2 t)
+        )))))
+
+(ert-deftest mml-secure-en-decrypt-sign-3 ()
+  "Sign and encrypt message; then decrypt and test for expected result.
+Use sign-with-sender and encrypt-to-self."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (let ((mml-secure-openpgp-sign-with-sender t)
+          (mml-secure-openpgp-encrypt-to-self t)
+          (mml-secure-smime-sign-with-sender t)
+          (mml-secure-smime-encrypt-to-self t))
+       (dolist (method (enc-sign-standards) nil)
+        (mml-secure-test-en-decrypt
+         method "sub@example.org" "no-exp@example.org" 1 t
+         (list (cons "C3999CF1268DBEA2" "EF25402B479DC6E2")
+               (cons "02372A42CA6D40FB" "ED7A2135E1582177"))))
+       ))))
+
+(ert-deftest mml-secure-sign-verify-1 ()
+  "Sign message with sender; then verify and test for expected result."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (dolist (method (sign-standards) nil)
+       (let ((mml-secure-openpgp-sign-with-sender t)
+            (mml-secure-smime-sign-with-sender t))
+        ;; A single signing key for sender sub@example.org is customized
+        ;; in the fixture.
+        (mml-secure-test-en-decrypt
+         method "uid1@example.org" "sub@example.org" 1 nil)
+
+        ;; From sub@example.org, sign with two keys;
+        ;; sign-with-sender and one from signers-variable:
+        (let ((mml-secure-openpgp-signers '("02372A42CA6D40FB"))
+              (mml-secure-smime-signers
+               '("D06AA118653CC38E9D0CAF56ED7A2135E1582177")))
+          (mml-secure-test-en-decrypt
+           method "no-exp@example.org" "sub@example.org" 2 nil))
+        )))))
+
+(ert-deftest mml-secure-sign-verify-2 ()
+  "Sign message without sender; then verify and test for expected result."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (dolist (method (sign-standards) nil)
+       (let ((mml-secure-openpgp-sign-with-sender nil)
+            (mml-secure-smime-sign-with-sender nil))
+        ;; A single signing key for sender sub@example.org is customized
+        ;; in the fixture, but not used here.
+        ;; By default, gpg uses the first secret key in the keyring, which
+        ;; is 02372A42CA6D40FB (OpenPGP) or
+        ;; 0E58229B80EE33959FF718FEEF25402B479DC6E2 (S/MIME) here.
+        (mml-secure-test-en-decrypt
+         method "uid1@example.org" "sub@example.org" 0 nil)
+
+        ;; From sub@example.org, sign with specified key:
+        (let ((mml-secure-openpgp-signers '("02372A42CA6D40FB"))
+              (mml-secure-smime-signers
+               '("D06AA118653CC38E9D0CAF56ED7A2135E1582177")))
+          (mml-secure-test-en-decrypt
+           method "no-exp@example.org" "sub@example.org" 1 nil))
+
+        ;; From sub@example.org, sign with different specified key:
+        (let ((mml-secure-openpgp-signers '("C3999CF1268DBEA2"))
+              (mml-secure-smime-signers
+               '("0E58229B80EE33959FF718FEEF25402B479DC6E2")))
+          (mml-secure-test-en-decrypt
+           method "no-exp@example.org" "sub@example.org" 1 nil))
+        )))))
+
+(ert-deftest mml-secure-sign-verify-3 ()
+  "Try to sign message with expired OpenPGP subkey, which raises an error.
+With Ma Gnus v0.14 and earlier a signature would be created with a wrong key."
+  (should-error
+   (mml-secure-test-key-fixture
+    (lambda ()
+      (let ((with-smime nil)
+           (mml-secure-openpgp-sign-with-sender nil)
+           (mml-secure-openpgp-signers '("501FFD98")))
+       (dolist (method (sign-standards) nil)
+         (mml-secure-test-en-decrypt
+          method "no-exp@example.org" "sign@example.org" 1 nil)
+         ))))))
+
+;; TODO Passphrase passing and caching in Emacs does not seem to work
+;; with gpgsm at all.
+;; Independently of caching settings, a pinentry dialogue is displayed.
+;; Thus, the following tests require the user to enter the correct gpgsm
+;; passphrases at the correct points in time.  (Either empty string or
+;; "Passphrase".)
+(ert-deftest mml-secure-en-decrypt-passphrase-cache ()
+  "Encrypt message; then decrypt and test for expected result.
+In this test, a key is used that requires the passphrase \"Passphrase\".
+In the first decryption this passphrase is hardcoded, in the second one it
+ is taken from a cache."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (dolist (method (enc-standards) nil)
+       (mml-secure-test-en-decrypt-with-passphrase
+       method "uid1@example.org" "sub@example.org" nil
+       ;; Beware!  For passphrases copy-sequence is necessary, as they may
+       ;; be erased, which actually changes the function's code and causes
+       ;; multiple invokations to fail.  I was surprised...
+       (copy-sequence "Passphrase") t)
+       (mml-secure-test-en-decrypt-with-passphrase
+       method "uid1@example.org" "sub@example.org" nil
+       (copy-sequence "Incorrect") t)))))
+
+(defun mml-secure-en-decrypt-passphrase-no-cache (method)
+  "Encrypt message with METHOD; then decrypt and test for expected result.
+In this test, a key is used that requires the passphrase \"Passphrase\".
+In the first decryption this passphrase is hardcoded, but caching disabled.
+So the second decryption fails."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (mml-secure-test-en-decrypt-with-passphrase
+      method "uid1@example.org" "sub@example.org" nil
+      (copy-sequence "Passphrase") nil)
+     (mml-secure-test-en-decrypt-with-passphrase
+      method "uid1@example.org" "sub@example.org" nil
+      (copy-sequence "Incorrect") nil nil t))))
+
+(ert-deftest mml-secure-en-decrypt-passphrase-no-cache-openpgp-todo ()
+  "Passphrase caching with OpenPGP only for GnuPG 1.x."
+  (skip-unless (string< (cdr (assq 'version (epg-configuration))) "2"))
+  (mml-secure-en-decrypt-passphrase-no-cache 'enc-pgp)
+  (mml-secure-en-decrypt-passphrase-no-cache 'enc-pgp-mime))
+
+(ert-deftest mml-secure-en-decrypt-passphrase-no-cache-smime-todo ()
+  "Passphrase caching does not work with S/MIME (and gpgsm)."
+  :expected-result :failed
+  (if with-smime
+      (mml-secure-en-decrypt-passphrase-no-cache 'enc-smime)
+    (should nil)))
+
+
+;; Test truncation of question in y-or-n-p.
+(defun mml-secure-select-preferred-keys-todo ()
+  "Manual customization with truncated question."
+  (mml-secure-test-key-fixture
+   (lambda ()
+     (mml-secure-test-en-decrypt
+      'enc-pgp-mime
+      "jens.lechtenboerger@informationelle-selbstbestimmung-im-internet.de"
+      "no-exp@example.org" nil t nil nil t))))
+
+(defun mml-secure-select-preferred-keys-ok ()
+  "Manual customization with entire question."
+  (mml-secure-test-fixture
+   (lambda ()
+     (mml-secure-select-preferred-keys
+      (epg-make-context 'OpenPGP)
+      '("jens.lechtenboerger@informationelle-selbstbestimmung-im-internet.de")
+      'encrypt))
+   t))
+
+
+;; ERT entry points
+(defun mml-secure-run-tests ()
+    "Run all tests with defaults."
+  (ert-run-tests-batch))
+
+(defun mml-secure-run-tests-with-gpg2 ()
+  "Run all tests with gpg2 instead of gpg."
+  (let* ((epg-gpg-program "gpg2"); ~/local/gnupg-2.1.9/PLAY/inst/bin/gpg2
+        (gpg-version (cdr (assq 'version (epg-configuration))))
+        ;; Empty passphrases do not seem to work with gpgsm in 2.1.x:
+        ;; https://lists.gnupg.org/pipermail/gnupg-users/2015-October/054575.html
+        (with-smime (string< gpg-version "2.1")))
+    (ert-run-tests-batch)))
+
+(defun mml-secure-run-tests-without-smime ()
+    "Skip S/MIME tests (as they require manual passphrase entry)."
+  (let ((with-smime nil))
+    (ert-run-tests-batch)))
+
+;;; gnustest-mml-sec.el ends here
diff --git a/lisp/tests/mml-gpghome/.gpg-v21-migrated b/lisp/tests/mml-gpghome/.gpg-v21-migrated
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lisp/tests/mml-gpghome/gpg-agent.conf b/lisp/tests/mml-gpghome/gpg-agent.conf
new file mode 100644 (file)
index 0000000..2019299
--- /dev/null
@@ -0,0 +1,5 @@
+# pinentry-program /usr/bin/pinentry-gtk-2
+
+# verbose
+# log-file /tmp/gpg-agent.log
+# debug-all
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/02089CDDC6DFE93B8EA10D9E876F983E61FEC476.key b/lisp/tests/mml-gpghome/private-keys-v1.d/02089CDDC6DFE93B8EA10D9E876F983E61FEC476.key
new file mode 100644 (file)
index 0000000..58fd0b5
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/02089CDDC6DFE93B8EA10D9E876F983E61FEC476.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/171B444DE92BEF997229000D9784118A94EEC1C9.key b/lisp/tests/mml-gpghome/private-keys-v1.d/171B444DE92BEF997229000D9784118A94EEC1C9.key
new file mode 100644 (file)
index 0000000..62f4ab2
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/171B444DE92BEF997229000D9784118A94EEC1C9.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/19FFEBC04DF3E037E16F6A4474DCB7984406975D.key b/lisp/tests/mml-gpghome/private-keys-v1.d/19FFEBC04DF3E037E16F6A4474DCB7984406975D.key
new file mode 100644 (file)
index 0000000..2a8ce13
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/19FFEBC04DF3E037E16F6A4474DCB7984406975D.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/1E36D27DF9DAB96302D35268DADC5CE73EF45A2A.key b/lisp/tests/mml-gpghome/private-keys-v1.d/1E36D27DF9DAB96302D35268DADC5CE73EF45A2A.key
new file mode 100644 (file)
index 0000000..9f8de71
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/1E36D27DF9DAB96302D35268DADC5CE73EF45A2A.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/293109315BE584AB2EFEFCFCAD64666221D8B36C.key b/lisp/tests/mml-gpghome/private-keys-v1.d/293109315BE584AB2EFEFCFCAD64666221D8B36C.key
new file mode 100644 (file)
index 0000000..6e4a4e5
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/293109315BE584AB2EFEFCFCAD64666221D8B36C.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/335689599E1C0F66D73ADCF51E03EE36C97D121F.key b/lisp/tests/mml-gpghome/private-keys-v1.d/335689599E1C0F66D73ADCF51E03EE36C97D121F.key
new file mode 100644 (file)
index 0000000..cff58ed
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/335689599E1C0F66D73ADCF51E03EE36C97D121F.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/40BF94E540E3726CB150A1ADF7C1B514444B3FA6.key b/lisp/tests/mml-gpghome/private-keys-v1.d/40BF94E540E3726CB150A1ADF7C1B514444B3FA6.key
new file mode 100644 (file)
index 0000000..14af866
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/40BF94E540E3726CB150A1ADF7C1B514444B3FA6.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/515D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key b/lisp/tests/mml-gpghome/private-keys-v1.d/515D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key
new file mode 100644 (file)
index 0000000..207a723
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/515D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/5A11B1935C46D0B227A73978DCA1293A85604F1D.key b/lisp/tests/mml-gpghome/private-keys-v1.d/5A11B1935C46D0B227A73978DCA1293A85604F1D.key
new file mode 100644 (file)
index 0000000..85ca78d
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/5A11B1935C46D0B227A73978DCA1293A85604F1D.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/62643CEBC7AEBE6817577A34399483700D76BD64.key b/lisp/tests/mml-gpghome/private-keys-v1.d/62643CEBC7AEBE6817577A34399483700D76BD64.key
new file mode 100644 (file)
index 0000000..79f3cd2
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/62643CEBC7AEBE6817577A34399483700D76BD64.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/680D01F368916A0021C14E3453B27B3C5F900683.key b/lisp/tests/mml-gpghome/private-keys-v1.d/680D01F368916A0021C14E3453B27B3C5F900683.key
new file mode 100644 (file)
index 0000000..776ddf7
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/680D01F368916A0021C14E3453B27B3C5F900683.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/6DF2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key b/lisp/tests/mml-gpghome/private-keys-v1.d/6DF2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key
new file mode 100644 (file)
index 0000000..2b464f0
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/6DF2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/78C17E134E86E691297F7B719B2F2CDF41976234.key b/lisp/tests/mml-gpghome/private-keys-v1.d/78C17E134E86E691297F7B719B2F2CDF41976234.key
new file mode 100644 (file)
index 0000000..28a0766
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/78C17E134E86E691297F7B719B2F2CDF41976234.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/7F714F4D9D9676638214991E96D45704E4FFC409.key b/lisp/tests/mml-gpghome/private-keys-v1.d/7F714F4D9D9676638214991E96D45704E4FFC409.key
new file mode 100644 (file)
index 0000000..1376596
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/7F714F4D9D9676638214991E96D45704E4FFC409.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/854752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key b/lisp/tests/mml-gpghome/private-keys-v1.d/854752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key
new file mode 100644 (file)
index 0000000..c99824c
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/854752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/93FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key b/lisp/tests/mml-gpghome/private-keys-v1.d/93FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key
new file mode 100644 (file)
index 0000000..49c2dc5
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/93FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/A3BA94EAE83509CC90DB1B77B54A51959D8DABEA.key b/lisp/tests/mml-gpghome/private-keys-v1.d/A3BA94EAE83509CC90DB1B77B54A51959D8DABEA.key
new file mode 100644 (file)
index 0000000..ca12840
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/A3BA94EAE83509CC90DB1B77B54A51959D8DABEA.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/A73E9D01F0465B518E8E7D5AD529077AAC1603B4.key b/lisp/tests/mml-gpghome/private-keys-v1.d/A73E9D01F0465B518E8E7D5AD529077AAC1603B4.key
new file mode 100644 (file)
index 0000000..3f14b40
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/A73E9D01F0465B518E8E7D5AD529077AAC1603B4.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/AE6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key b/lisp/tests/mml-gpghome/private-keys-v1.d/AE6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key
new file mode 100644 (file)
index 0000000..06adc06
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/AE6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/C072AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key b/lisp/tests/mml-gpghome/private-keys-v1.d/C072AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key
new file mode 100644 (file)
index 0000000..cf9a60d
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/C072AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/C43E1A079B28DFAEBB39CBA01793BDE11EF4B490.key b/lisp/tests/mml-gpghome/private-keys-v1.d/C43E1A079B28DFAEBB39CBA01793BDE11EF4B490.key
new file mode 100644 (file)
index 0000000..0ed3517
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/C43E1A079B28DFAEBB39CBA01793BDE11EF4B490.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/C67DAD345455EAD6D51368008FC3A53B8D195B5A.key b/lisp/tests/mml-gpghome/private-keys-v1.d/C67DAD345455EAD6D51368008FC3A53B8D195B5A.key
new file mode 100644 (file)
index 0000000..090059d
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/C67DAD345455EAD6D51368008FC3A53B8D195B5A.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/CB5E00CE582C2645D2573FC16B2F14F85A7F47AA.key b/lisp/tests/mml-gpghome/private-keys-v1.d/CB5E00CE582C2645D2573FC16B2F14F85A7F47AA.key
new file mode 100644 (file)
index 0000000..9061f67
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/CB5E00CE582C2645D2573FC16B2F14F85A7F47AA.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/CC68630A06B048F5A91136C162C7A3273E20DE6F.key b/lisp/tests/mml-gpghome/private-keys-v1.d/CC68630A06B048F5A91136C162C7A3273E20DE6F.key
new file mode 100644 (file)
index 0000000..89f6013
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/CC68630A06B048F5A91136C162C7A3273E20DE6F.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/E7E73903E1BF93481DE0E7C9769D6C31E1863CFF.key b/lisp/tests/mml-gpghome/private-keys-v1.d/E7E73903E1BF93481DE0E7C9769D6C31E1863CFF.key
new file mode 100644 (file)
index 0000000..41dac37
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/E7E73903E1BF93481DE0E7C9769D6C31E1863CFF.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/F0117468BE801ED4B81972E159A98FDD4814DCEC.key b/lisp/tests/mml-gpghome/private-keys-v1.d/F0117468BE801ED4B81972E159A98FDD4814DCEC.key
new file mode 100644 (file)
index 0000000..5df7b4a
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/F0117468BE801ED4B81972E159A98FDD4814DCEC.key differ
diff --git a/lisp/tests/mml-gpghome/private-keys-v1.d/F4C5EFD5779BE892CAFD5B721D68DED677C9B151.key b/lisp/tests/mml-gpghome/private-keys-v1.d/F4C5EFD5779BE892CAFD5B721D68DED677C9B151.key
new file mode 100644 (file)
index 0000000..03daf80
Binary files /dev/null and b/lisp/tests/mml-gpghome/private-keys-v1.d/F4C5EFD5779BE892CAFD5B721D68DED677C9B151.key differ
diff --git a/lisp/tests/mml-gpghome/pubring.gpg b/lisp/tests/mml-gpghome/pubring.gpg
new file mode 100644 (file)
index 0000000..6bd1699
Binary files /dev/null and b/lisp/tests/mml-gpghome/pubring.gpg differ
diff --git a/lisp/tests/mml-gpghome/pubring.gpg~ b/lisp/tests/mml-gpghome/pubring.gpg~
new file mode 100644 (file)
index 0000000..6bd1699
Binary files /dev/null and b/lisp/tests/mml-gpghome/pubring.gpg~ differ
diff --git a/lisp/tests/mml-gpghome/pubring.kbx b/lisp/tests/mml-gpghome/pubring.kbx
new file mode 100644 (file)
index 0000000..399a041
Binary files /dev/null and b/lisp/tests/mml-gpghome/pubring.kbx differ
diff --git a/lisp/tests/mml-gpghome/pubring.kbx~ b/lisp/tests/mml-gpghome/pubring.kbx~
new file mode 100644 (file)
index 0000000..0635271
Binary files /dev/null and b/lisp/tests/mml-gpghome/pubring.kbx~ differ
diff --git a/lisp/tests/mml-gpghome/random_seed b/lisp/tests/mml-gpghome/random_seed
new file mode 100644 (file)
index 0000000..8e48315
Binary files /dev/null and b/lisp/tests/mml-gpghome/random_seed differ
diff --git a/lisp/tests/mml-gpghome/secring.gpg b/lisp/tests/mml-gpghome/secring.gpg
new file mode 100644 (file)
index 0000000..b323c07
Binary files /dev/null and b/lisp/tests/mml-gpghome/secring.gpg differ
diff --git a/lisp/tests/mml-gpghome/trustdb.gpg b/lisp/tests/mml-gpghome/trustdb.gpg
new file mode 100644 (file)
index 0000000..09ebd8d
Binary files /dev/null and b/lisp/tests/mml-gpghome/trustdb.gpg differ
diff --git a/lisp/tests/mml-gpghome/trustlist.txt b/lisp/tests/mml-gpghome/trustlist.txt
new file mode 100644 (file)
index 0000000..f886572
--- /dev/null
@@ -0,0 +1,26 @@
+# This is the list of trusted keys.  Comment lines, like this one, as
+# well as empty lines are ignored.  Lines have a length limit but this
+# is not a serious limitation as the format of the entries is fixed and
+# checked by gpg-agent.  A non-comment line starts with optional white
+# space, followed by the SHA-1 fingerpint in hex, followed by a flag
+# which may be one of 'P', 'S' or '*' and optionally followed by a list of
+# other flags.  The fingerprint may be prefixed with a '!' to mark the
+# key as not trusted.  You should give the gpg-agent a HUP or run the
+# command "gpgconf --reload gpg-agent" after changing this file.
+
+
+# Include the default trust list
+include-default
+
+
+# CN=No Expiry
+D0:6A:A1:18:65:3C:C3:8E:9D:0C:AF:56:ED:7A:21:35:E1:58:21:77 S relax
+
+# CN=Second Key Pair
+0E:58:22:9B:80:EE:33:95:9F:F7:18:FE:EF:25:40:2B:47:9D:C6:E2 S relax
+
+# CN=No Expiry two UIDs
+D4:CA:78:E1:47:0B:9F:C2:AE:45:D7:84:64:9B:8C:E6:4E:BB:32:0C S relax
+
+# CN=Different subkeys
+4F:96:2A:B7:F4:76:61:6A:78:3D:72:AA:40:35:D5:9B:5F:88:E9:FC S relax
index b86c01e..b5e7916 100644 (file)
@@ -938,16 +938,82 @@ Libidn} installed in order to use this functionality.
 @cindex encrypt
 @cindex secure
 
-Using the @acronym{MML} language, Message is able to create digitally
-signed and digitally encrypted messages.  Message (or rather
-@acronym{MML}) currently support @acronym{PGP} (RFC 1991),
-@acronym{PGP/MIME} (RFC 2015/3156) and @acronym{S/MIME}.
+By default, e-mails are transmitted without any protection around the
+Internet, which implies that they can be read and changed by lots of
+different parties.  In particular, they are analyzed under bulk
+surveillance, which violates basic human rights.  To defend those
+rights, digital self-defense is necessary (in addition to legal
+changes), and encryption and digital signatures are powerful
+techniques for self-defense.  In essence, encryption ensures that
+only the intended recipient will be able to read a message, while
+digital signatures make sure that modifications to messages can be
+detected by the recipient.
+
+Nowadays, there are two major incompatible e-mail encryption
+standards, namely @acronym{OpenPGP} and @acronym{S/MIME}.  Both of
+these standards are implemented by the @uref{https://www.gnupg.org/,
+GNU Privacy Guard (GnuPG)}, which needs to be installed as external
+software in addition to GNU Emacs.  Before you can start to encrypt,
+decrypt, and sign messages, you need to create a so-called key-pair,
+which consists of a private key and a public key.  Your @emph{public} key
+(also known as @emph{certificate}, in particular with @acronym{S/MIME}), is
+used by others (a) to encrypt messages intended for you and (b) to verify
+digital signatures created by you.  In contrast, you use your @emph{private}
+key (a) to decrypt messages and (b) to sign messages.  (You may want to
+think of your public key as an open safe that you offer to others such
+that they can deposit messages and lock the door, while your private
+key corresponds to the opening combination for the safe.)
+
+Thus, you need to perform the following steps for e-mail encryption,
+typically outside Emacs.  See, for example, the
+@uref{https://www.gnupg.org/gph/en/manual.html, The GNU Privacy
+Handbook} for details covering the standard @acronym{OpenPGP} with
+@acronym{GnuPG}.
+@enumerate
+@item
+Install GnuPG.
+@item
+Create a key-pair for your own e-mail address.
+@item
+Distribute your public key, e.g., via upload to key servers.
+@item
+Import the public keys for the recipients to which you want to send
+encrypted e-mails.
+@end enumerate
+
+Whether to use the standard @acronym{OpenPGP} or @acronym{S/MIME} is
+beyond the scope of this documentation.  Actually, you can use one
+standard for one set of recipients and the other standard for
+different recipients (depending their preferences or capabilities).
+
+In case you are not familiar with all those acronyms: The standard
+@acronym{OpenPGP} is also called @acronym{PGP} (Pretty Good Privacy).
+The command line tools offered by @acronym{GnuPG} for
+@acronym{OpenPGP} are called @command{gpg} and @command{gpg2}, while
+the one for @acronym{S/MIME} is called @command{gpgsm}.  An
+alternative, but discouraged, tool for @acronym{S/MIME} is
+@command{openssl}.  To make matters worse, e-mail messages can be
+formed in two different ways with @acronym{OpenPGP}, namely
+@acronym{PGP} (RFC 1991/4880) and @acronym{PGP/MIME} (RFC 2015/3156).
+
+The good news, however, is the following: In GNU Emacs, Message
+supports all those variants, comes with reasonable defaults that can
+be customized according to your needs, and invokes the proper command
+line tools behind the scenes for encryption, decryption, as well as
+creation and verification of digital signatures.
+
+Message uses the @acronym{MML} language for the creation of signed
+and/or encrypted messages as explained in the following.
+
 
 @menu
 * Signing and encryption::      Signing and encrypting commands.
 * Using S/MIME::                Using S/MIME
-* Using PGP/MIME::              Using PGP/MIME
+* Using OpenPGP::               Using OpenPGP
+* Passphrase caching::          How to cache passphrases
 * PGP Compatibility::           Compatibility with older implementations
+* Encrypt-to-self::             Reading your own encrypted messages
+* Bcc Warning::                 Do not use encryption with Bcc headers
 @end menu
 
 @node Signing and encryption
@@ -1041,11 +1107,45 @@ programs are required to make things work, and some small general hints.
 @node Using S/MIME
 @subsection Using S/MIME
 
-@emph{Note!}  This section assume you have a basic familiarity with
-modern cryptography, @acronym{S/MIME}, various PKCS standards, OpenSSL and
-so on.
+@acronym{S/MIME} requires an external implementation, such as
+@uref{https://www.gnupg.org/, GNU Privacy Guard} or
+@uref{https://www.openssl.org/, OpenSSL}.  The default Emacs interface
+to the S/MIME implementation is EasyPG (@pxref{Top,,EasyPG Assistant
+User's Manual, epa, EasyPG Assistant User's Manual}), which has been
+included in Emacs since version 23 and which relies on the command
+line tool @command{gpgsm} provided by @acronym{GnuPG}.  That tool
+implements certificate management, including certificate revocation
+and expiry, while such tasks need to be performed manually, if OpenSSL
+is used.
+
+The choice between EasyPG and OpenSSL is controlled by the variable
+@code{mml-smime-use}, which needs to be set to the value @code{epg}
+for EasyPG.  Depending on your version of Emacs that value may be the
+default; if not, you can either customize that variable or place the
+following line in your @file{.emacs} file (that line needs to be
+placed above other code related to message/gnus/encryption):
+
+@lisp
+(require 'epg)
+@end lisp
+
+Moreover, you may want to customize the variables
+@code{mml-default-encrypt-method} and
+@code{mml-default-sign-method} to the string @code{"smime"}.
+
+That's all if you want to use S/MIME with EasyPG, and that's the
+recommended way of using S/MIME with Message.
+
+If you think about using OpenSSL instead of EasyPG, please read the
+BUGS section in the manual for the @command{smime} command coming with
+OpenSSL first.  If you still want to use OpenSSL, the following
+applies.
+
+@emph{Note!}  The remainder of this section assumes you have a basic
+familiarity with modern cryptography, @acronym{S/MIME}, various PKCS
+standards, OpenSSL and so on.
 
-The @acronym{S/MIME} support in Message (and @acronym{MML}) require
+The @acronym{S/MIME} support in Message (and @acronym{MML}) can use
 OpenSSL@.  OpenSSL performs the actual @acronym{S/MIME} sign/encrypt
 operations.  OpenSSL can be found at @uref{http://www.openssl.org/}.
 OpenSSL 0.9.6 and later should work.  Version 0.9.5a cannot extract mail
@@ -1101,26 +1201,44 @@ you use unencrypted keys (e.g., if they are on a secure storage, or if
 you are on a secure single user machine) simply press @code{RET} at
 the passphrase prompt.
 
-@node Using PGP/MIME
-@subsection Using PGP/MIME
+@node Using OpenPGP
+@subsection Using OpenPGP
 
-@acronym{PGP/MIME} requires an external OpenPGP implementation, such
-as @uref{http://www.gnupg.org/, GNU Privacy Guard}. Pre-OpenPGP
+Use of OpenPGP requires an external software, such
+as @uref{https://www.gnupg.org/, GNU Privacy Guard}.  Pre-OpenPGP
 implementations such as PGP 2.x and PGP 5.x are also supported.  The
 default Emacs interface to the PGP implementation is EasyPG
 (@pxref{Top,,EasyPG Assistant User's Manual, epa, EasyPG Assistant
 User's Manual}), but PGG (@pxref{Top, ,PGG, pgg, PGG Manual}) and
 Mailcrypt are also supported.  @xref{PGP Compatibility}.
 
+As stated earlier, messages encrypted with OpenPGP can be formatted
+according to two different standards, namely @acronym{PGP} or
+@acronym{PGP/MIME}.  The variables
+@code{mml-default-encrypt-method} and
+@code{mml-default-sign-method} determine which variant to prefer,
+@acronym{PGP/MIME} by default.
+
+@node Passphrase caching
+@subsection Passphrase caching
+
 @cindex gpg-agent
-Message internally calls GnuPG (the @command{gpg} command) to perform
+Message with EasyPG internally calls GnuPG (the @command{gpg} or
+@command{gpgsm} command) to perform
 data encryption, and in certain cases (decrypting or signing for
-example), @command{gpg} requires user's passphrase.  Currently the
-recommended way to supply your passphrase to @command{gpg} is to use the
+example), @command{gpg}/@command{gpgsm} requires user's passphrase.
+Currently the recommended way to supply your passphrase is to use the
 @command{gpg-agent} program.
 
-To use @command{gpg-agent} in Emacs, you need to run the following
-command from the shell before starting Emacs.
+In particular, the @command{gpg-agent} program supports passphrase
+caching so that you do not need to enter your passphrase for every
+decryption/sign operation.  @xref{Agent Options, , , gnupg, Using the
+GNU Privacy Guard}.
+
+How to use @command{gpg-agent} in Emacs depends on your version of
+GnuPG.  With GnuPG version 2.1, @command{gpg-agent} is started
+automatically if necessary.  With older versions you may need to run
+the following command from the shell before starting Emacs.
 
 @example
 eval `gpg-agent --daemon`
@@ -1135,11 +1253,10 @@ GNU Privacy Guard}.
 Once your @command{gpg-agent} is set up, it will ask you for a
 passphrase as needed for @command{gpg}.  Under the X Window System,
 you will see a new passphrase input dialog appear.  The dialog is
-provided by PIN Entry (the @command{pinentry} command), and as of
-version 0.7.2, @command{pinentry} cannot cooperate with Emacs on a
-single tty.  So, if you are using a text console, you may need to put
-a passphrase into gpg-agent's cache beforehand.  The following command
-does the trick.
+provided by PIN Entry (the @command{pinentry} command), reasonably
+recent versions of which can also cooperate with Emacs on a text
+console.  If that does not work, you may need to put a passphrase into
+gpg-agent's cache beforehand.  The following command does the trick.
 
 @example
 gpg --use-agent --sign < /dev/null > /dev/null
@@ -1181,6 +1298,38 @@ message that can be understood by PGP version 2.
 (Refer to @uref{http://www.gnupg.org/gph/en/pgp2x.html} for more
 information about the problem.)
 
+@node Encrypt-to-self
+@subsection Encrypt-to-self
+
+By default, messages are encrypted to all recipients (@code{To},
+@code{Cc}, @code{Bcc} headers).  Thus, you will not be able to decrypt
+your own messages.  To make sure that messages are also encrypted to
+your own key(s), several alternative solutions exist:
+@enumerate
+@item
+Use the @code{encrypt-to} option in the file @file{gpg.conf} (for
+OpenPGP) or @file{gpgsm.conf} (for @acronym{S/MIME} with EasyPG).
+@xref{Invoking GPG, , , gnupg, Using the GNU Privacy Guard}, or
+@xref{Invoking GPGSM, , , gnupg, Using the GNU Privacy Guard}.
+@item
+Include your own e-mail address (for which you created a key-pair)
+among the recipients.
+@item
+Customize the variable @code{mml-secure-openpgp-encrypt-to-self} (for
+OpenPGP) or @code{mml-secure-smime-encrypt-to-self} (for
+@acronym{S/MIME} with EasyPG).
+@end enumerate
+
+@node Bcc Warning
+@subsection Bcc Warning
+
+The @code{Bcc} header is meant to hide recipients of messages.
+However, when encrypted messages are used, the e-mail addresses of all
+@code{Bcc}-headers are given away to all recipients without
+warning, which is a bug, see
+@uref{https://debbugs.gnu.org/cgi/bugreport.cgi?bug=18718}.
+
+
 @node Various Commands
 @section Various Commands