From b8d320e51cd2b549ab453682da8b00119e051c3d Mon Sep 17 00:00:00 2001 From: Jens Lechtenboerger Date: Sun, 13 Dec 2015 16:12:30 +0100 Subject: [PATCH] Refactor mml-smime.el, mml1991.el, mml2015.el 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. --- lisp/gnus-util.el | 15 + lisp/mml-sec.el | 533 ++++++++++- lisp/mml-smime.el | 273 ++---- lisp/mml1991.el | 203 +---- lisp/mml2015.el | 306 +------ lisp/tests/gnustest-gnus-util.el | 100 ++ lisp/tests/gnustest-mml-sec.README | 45 + lisp/tests/gnustest-mml-sec.el | 859 ++++++++++++++++++ lisp/tests/mml-gpghome/.gpg-v21-migrated | 0 lisp/tests/mml-gpghome/gpg-agent.conf | 5 + ...089CDDC6DFE93B8EA10D9E876F983E61FEC476.key | Bin 0 -> 797 bytes ...1B444DE92BEF997229000D9784118A94EEC1C9.key | Bin 0 -> 526 bytes ...FFEBC04DF3E037E16F6A4474DCB7984406975D.key | Bin 0 -> 841 bytes ...36D27DF9DAB96302D35268DADC5CE73EF45A2A.key | Bin 0 -> 797 bytes ...3109315BE584AB2EFEFCFCAD64666221D8B36C.key | Bin 0 -> 526 bytes ...5689599E1C0F66D73ADCF51E03EE36C97D121F.key | Bin 0 -> 797 bytes ...BF94E540E3726CB150A1ADF7C1B514444B3FA6.key | Bin 0 -> 797 bytes ...5D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key | Bin 0 -> 798 bytes ...11B1935C46D0B227A73978DCA1293A85604F1D.key | Bin 0 -> 798 bytes ...643CEBC7AEBE6817577A34399483700D76BD64.key | Bin 0 -> 526 bytes ...0D01F368916A0021C14E3453B27B3C5F900683.key | Bin 0 -> 710 bytes ...F2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key | Bin 0 -> 798 bytes ...C17E134E86E691297F7B719B2F2CDF41976234.key | Bin 0 -> 527 bytes ...714F4D9D9676638214991E96D45704E4FFC409.key | Bin 0 -> 798 bytes ...4752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key | Bin 0 -> 526 bytes ...FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key | Bin 0 -> 709 bytes ...BA94EAE83509CC90DB1B77B54A51959D8DABEA.key | Bin 0 -> 797 bytes ...3E9D01F0465B518E8E7D5AD529077AAC1603B4.key | Bin 0 -> 710 bytes ...6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key | Bin 0 -> 841 bytes ...72AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key | Bin 0 -> 841 bytes ...3E1A079B28DFAEBB39CBA01793BDE11EF4B490.key | Bin 0 -> 527 bytes ...7DAD345455EAD6D51368008FC3A53B8D195B5A.key | Bin 0 -> 710 bytes ...5E00CE582C2645D2573FC16B2F14F85A7F47AA.key | Bin 0 -> 797 bytes ...68630A06B048F5A91136C162C7A3273E20DE6F.key | Bin 0 -> 710 bytes ...E73903E1BF93481DE0E7C9769D6C31E1863CFF.key | Bin 0 -> 797 bytes ...117468BE801ED4B81972E159A98FDD4814DCEC.key | Bin 0 -> 797 bytes ...C5EFD5779BE892CAFD5B721D68DED677C9B151.key | Bin 0 -> 841 bytes lisp/tests/mml-gpghome/pubring.gpg | Bin 0 -> 13883 bytes lisp/tests/mml-gpghome/pubring.gpg~ | Bin 0 -> 13883 bytes lisp/tests/mml-gpghome/pubring.kbx | Bin 0 -> 3076 bytes lisp/tests/mml-gpghome/pubring.kbx~ | Bin 0 -> 2346 bytes lisp/tests/mml-gpghome/random_seed | Bin 0 -> 600 bytes lisp/tests/mml-gpghome/secring.gpg | Bin 0 -> 17362 bytes lisp/tests/mml-gpghome/trustdb.gpg | Bin 0 -> 1880 bytes lisp/tests/mml-gpghome/trustlist.txt | 26 + texi/message.texi | 195 +++- 46 files changed, 1876 insertions(+), 684 deletions(-) create mode 100644 lisp/tests/gnustest-gnus-util.el create mode 100644 lisp/tests/gnustest-mml-sec.README create mode 100644 lisp/tests/gnustest-mml-sec.el create mode 100644 lisp/tests/mml-gpghome/.gpg-v21-migrated create mode 100644 lisp/tests/mml-gpghome/gpg-agent.conf create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/02089CDDC6DFE93B8EA10D9E876F983E61FEC476.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/171B444DE92BEF997229000D9784118A94EEC1C9.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/19FFEBC04DF3E037E16F6A4474DCB7984406975D.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/1E36D27DF9DAB96302D35268DADC5CE73EF45A2A.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/293109315BE584AB2EFEFCFCAD64666221D8B36C.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/335689599E1C0F66D73ADCF51E03EE36C97D121F.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/40BF94E540E3726CB150A1ADF7C1B514444B3FA6.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/515D4637EFC6C09DB1F78BE8C2F2A3D63E7756C3.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/5A11B1935C46D0B227A73978DCA1293A85604F1D.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/62643CEBC7AEBE6817577A34399483700D76BD64.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/680D01F368916A0021C14E3453B27B3C5F900683.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/6DF2D9DF7AED06F0524BEB642DF0FB48EFDBDB93.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/78C17E134E86E691297F7B719B2F2CDF41976234.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/7F714F4D9D9676638214991E96D45704E4FFC409.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/854752F5D8090CA36EFBDD79C72BDFF6FA2D1FF0.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/93FF37C268FDBF0767F5FFDC49409DDAC9388B2C.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/A3BA94EAE83509CC90DB1B77B54A51959D8DABEA.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/A73E9D01F0465B518E8E7D5AD529077AAC1603B4.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/AE6A24B17A8D0CAF9B7E000AA77F0B41D7BFFFCF.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/C072AF82DCCCB9A7F1B85FFA10B802DC4ED16703.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/C43E1A079B28DFAEBB39CBA01793BDE11EF4B490.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/C67DAD345455EAD6D51368008FC3A53B8D195B5A.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/CB5E00CE582C2645D2573FC16B2F14F85A7F47AA.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/CC68630A06B048F5A91136C162C7A3273E20DE6F.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/E7E73903E1BF93481DE0E7C9769D6C31E1863CFF.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/F0117468BE801ED4B81972E159A98FDD4814DCEC.key create mode 100644 lisp/tests/mml-gpghome/private-keys-v1.d/F4C5EFD5779BE892CAFD5B721D68DED677C9B151.key create mode 100644 lisp/tests/mml-gpghome/pubring.gpg create mode 100644 lisp/tests/mml-gpghome/pubring.gpg~ create mode 100644 lisp/tests/mml-gpghome/pubring.kbx create mode 100644 lisp/tests/mml-gpghome/pubring.kbx~ create mode 100644 lisp/tests/mml-gpghome/random_seed create mode 100644 lisp/tests/mml-gpghome/secring.gpg create mode 100644 lisp/tests/mml-gpghome/trustdb.gpg create mode 100644 lisp/tests/mml-gpghome/trustlist.txt diff --git a/lisp/gnus-util.el b/lisp/gnus-util.el index ea5f31554..31645fcd3 100644 --- a/lisp/gnus-util.el +++ b/lisp/gnus-util.el @@ -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 diff --git a/lisp/mml-sec.el b/lisp/mml-sec.el index dbd31629f..f5bfcecca 100644 --- a/lisp/mml-sec.el +++ b/lisp/mml-sec.el @@ -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 diff --git a/lisp/mml-smime.el b/lisp/mml-smime.el index b19c9e89b..b9f4a542f 100644 --- a/lisp/mml-smime.el +++ b/lisp/mml-smime.el @@ -32,9 +32,17 @@ (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 "\ diff --git a/lisp/mml1991.el b/lisp/mml1991.el index 646963645..aa514425c 100644 --- a/lisp/mml1991.el +++ b/lisp/mml1991.el @@ -63,11 +63,17 @@ (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) diff --git a/lisp/mml2015.el b/lisp/mml2015.el index 10ba126ae..136ed808f 100644 --- a/lisp/mml2015.el +++ b/lisp/mml2015.el @@ -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 index 000000000..b40ad8574 --- /dev/null +++ b/lisp/tests/gnustest-gnus-util.el @@ -0,0 +1,100 @@ +;;; gnustest-gnus-util.el --- Selectived tests only. +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Jens Lechtenbörger + +;; 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 . + +;;; 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 index 000000000..f8920f597 --- /dev/null +++ b/lisp/tests/gnustest-mml-sec.README @@ -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 +$ 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 index 000000000..a64269f5b --- /dev/null +++ b/lisp/tests/gnustest-mml-sec.el @@ -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 + +;; 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 . + +;;; 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 +- 1463 2ECA B9E2 2736 9C8D D97B F7E7 9AB7 AE31 D471 + uid Second Key Pair + +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 "" 'encrypt)) + (should-not + (mml-secure-find-usable-keys + context "" '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 "" 'encrypt)))) + (should + (= 1 (length (mml-secure-find-usable-keys + context "" '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 " 'encrypt)))) + (should + (= 1 (length (mml-secure-find-usable-keys + context "No Expiry " '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 (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 index 000000000..e69de29bb diff --git a/lisp/tests/mml-gpghome/gpg-agent.conf b/lisp/tests/mml-gpghome/gpg-agent.conf new file mode 100644 index 000000000..20192990c --- /dev/null +++ b/lisp/tests/mml-gpghome/gpg-agent.conf @@ -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 index 0000000000000000000000000000000000000000..58fd0b5edbc0fae0642c30d4c807d8cb4eb1cacd GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|?EPCfa6}^X`A# z_8bczDHp$y{^rxyxQwB*EtEl7(?^+!b@8cpwLFv3EAFWskY%}{rAq?h}4%pUhXjG-KmUy4AbUs(_%YZAF-@Bseh+J z#Il&aIn&d3EXdOGaJ#+qTc2B!r6$lNsm4}}42+r@mIxOcnpou*q~;Z*7wG0CmSmQt zY8YC=MUZ`CZdI0ARGgWgXJ}=jsbON3n3JAwY!wvj2;vk2{cC6yKPWs3bO+d*VE0&9 z2_4(@=JloLLJ9A?)B74%gv|3k!@StKXYv)nT%pG7h5yPQ{aE{ncj`qOq1j9eY`F4Y zUes4TVVusts!CwKAq(H@^R*HCD!+CaDm)a~v}>8l)tqxHJ2$RAviUg6zs_7Mrb9Zj(4>vfzCXb^L=nOMcD~40)wrlg2zTtIp@|F0z zD48iG`{s)mo6Bu8rfA+s^)BDdb8JS;vVbenaf@Qj_1bFK%{JZn`1SYvcG}i6n6@Ccss(pS(PtY~k)Ku-6uKKQW>uDz^@z0kvI-^-| zV9MG%1~PB7io;fzGi0h*DSQVRV`9a*@_$K>cizDVNjvXxo%`qa&7e{K?u1`A1x~-a z60{&?b?vK!=Fe-sOKT+Fvk#~`9W-x_5C4@{T{Gs-u#AytO6~wfd2(@SuBnxYp_!!t VFliaUQWP+Om{{fI=cQ_b006cISnU7+ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..62f4ab25a69bf2c1c815bb4c2b8d135516a7aeae GIT binary patch literal 526 zcmV+p0`dJQF)=!Da%py9bY(4TWqBwwI&yPiC^0&2F)}$i0OuQPEWQFU{Y#Sar)T{C zgOB-P@YPS?pCA)p7fRoC^Xri#%b4V-w@Sp|*rc@0^qB~Y`3iQj=A5!dSi&Wue7yqL zHrSF5j|Xw&O2-TE>~l?rlisq%O+_9cfE}pe3QE=`yr+b!jgnpjDr2V19kKm+4J z)+ipbU1+`*;muKsgf92TXFe0~c`xs~bb=75X*wVen7*%aZ#jT^YVhdfks&wv|`v1meaue_<}FL zPt4u94Rd(ee%XG1QM7wED}%?q=5~g&E-oI&CmKAt^Ho~d<@J)5o7>w;x2~F~<7Oi` zd(+P)JAWQ!-^bPP%_R59*=paWd%V~EPXO&67#Rm5M1r8)EoWiIE#YdhRK>hm!uk7F)}b}YFHv%Y-nPYUyzztkY1phmspZn zma1WB2^T^3jk#4>YEf}!ex9L~iKd2$RbozhzOhwMup@|54D_#|Rs5jvD9{~XZ-TsQ zVqx{nJmkT~3-_1&PUb9^%Fl4|3!NC7VsgypW$(cao8&lVoYL9)b)V4JNteIAZCDy< z#+?3Po|dGKWm(sRWbRo$j~AbG5_A8(;J2@w>HUd8+u9a{UNAUz;=FI$H192+C+54c zHJmkgZ?RrndqwQmrF{1`pXNJxQn}%pqT2rHap%;P9mO1aTbe}c0>XTYruD9WmUiWU z`|G35x9vB^C6`u+cwD(VK||7OcUo%83%7(e9ha<(nw3twcI?x*?3ifwsjX8<_2HVW z+&=F%#vZ9qpHZgSXqc($`pI3Xace;RiA!fzeG%7K>-lj-Y}c(y-hZ`|{zo@+?crL! zBJ0{sbp@SUC(q1^nX9vR>*0r+*KXMz`It{|e~i`IUD^8=PBZ8YFjSqz`13`fDc>HZ z6}3Y4o1$dTZC|H)!`eZv^Wh)PS(5fsPwezOBdlqC>gm&WuYFPwv|`x%^J`b<4W=yl z$(IVcuHMKB_TOvMCjH&Dr#omt;C z?4*J zwJ!BGNjolX%(M!hB=JtCMCEhJ+?ro3VjmyOuwQWz=nOMcD~7}Gl@5vSTJkP?XNC8( zqv18o8jj{F$3?i(HB^3o*4QQfJ4&IjzudxLotamHy}PN6jQN&UjkYPD}jAVPcAOaHMKG^FgG^@ VCM^S4iUKAO6RW)Zyi`pP0023eS402+ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..6e4a4e548fdde4735084c0d105afa9438fd68342 GIT binary patch literal 526 zcmV+p0`dJQF)=!Da%py9bY(4TWqBwwI&yPiC^0&2F)}$i0QH;OtrA*l2ay$dPifP} zg7f?1bfZx#n#x7gwqB(*d()JGj+4?8D3}%awS~{A8I6a{q!dac_sB#I7?Tae=m( zDJU^Ia5gnM0QO%Osv2owt&S4|f0uIcF(RoPhgC{AKoxdv_>eaQpiENVgmITu|AFVx z{H*%;GFHMe#VcM7+p_Tpml+6@^(iPZI&n5NIspGQBDoNk1sd~@ya@!{Y*q&KyWq}j zYy@Yj&JX80u)JSMC2bb3)ue5>I`daV$xj+!IBq7#9EekT^l2i~wv|_kpxa=FdE*fW-uQrLT4?5$s zslDy;I*mnITIV;#-`yM$pqIOGs^%vx(Id?HTm%1x8U=L5?$7W3;)QH;wa zPN#j?{Bp;3{+_MBlYhBv+7J}6*;fmH?|52b_8*Xf&MkLiXRjn1-b+5O|W|` ztQ0?gRycl7BJ|O#$rqo$St4?}&aGu{&q>WqPxf!T!WojJvCq%=^udchnTuK9Sej^W z`Eq-&S9YOpNbGbECW%{1RI=wz=ReYDxZXwOvDuO5?iTWq&E-2jDSeQb|516We%bfP zhovj(Q%-JuB;dule?$0usk!=wlDb=y&sSI5a4bHN2y}*-sTIRZlS`fLvLa_zM`Zpj zvQ3-Ho_gvZBkz%Ya-3>I}SOzM;!S8Er|v`bk2cYm>G z%yzeHtXtmn#l=f}J?ipx`4p={kTE7!CaOX2(wA>w{_MhN!7dcIU0PU4q`*CkJjyTOP{hRm8kjakcx@A7S5R966d-{9X1edBH7ElqVOL=9*d=TUwZ! W1Cy2kEJXnmh>2BReqO332mk==r&&V) literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..14af8662f79b75cbf855b2a07a438d59b6f03b64 GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|@OXD3(3z^Xb=< zZo5m)I9@BT`2Pfy&0rm)?cZ)_EBQN(#2(U0a?mVJyuO#FZ6SH=Aodde>rLI zd*2_(jS)@|diHwygvIxJ`ZxbbwJg0j!?W7#ok*m?uM1t~{=4+A&(_tRpEU26UB7Rn z`Yy4{Oa2(<&EDqFcW&)6i?o{6B9}FRE=e`EVq{>{)UZUj*wDl(zaTZQAiY30FR>)E zELFqM5-x)58*{6&)S}|d{5(S|6HN^htHhl2d}FJiU`G(A80cR^tN20TQJ_1(-UPeH z!b+(kA*Jnsm5G3?<9(au|E`~z{Nv)P&I^Y7Z{0a5c4*53_L4>yhEwm0PCH+_y+HQ6 z-19H9{_&oPS>bYTt?aowTU#fX4vSyiic)_>1B`br1ZwpnuG^#7P zFS7pFvqQfuA2X?n2r9Gu4DHE^Q zeE$kH=Fj6rYSXyFs%_UB8K1dayXnWDe+|JO`z+d(uCG?my;{@zANSSfucOQxHQ+)%EZvp X*Z`Qc3}7h=m_SUd^78XiH9-IXUP)=v literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..207a7237d3abdd001aec142d990e53356df1bcdd GIT binary patch literal 798 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|_lvz4-9Q$&HgX zm)AH5teVZ7yB@E;+c%VNS)_AazZkOHz%k7#SEfH7pS>HZ-xyFG$TRNH5UMODxGO zOVu#6go_~i#@wnbwWv5VKhMz0L{r1WDlsQL-`FZB*b&4j2Kv{~Dt=IS6zC4HH^J_) zuu@v^_=n(6om7jM-)4*d+)1&y7Up#$AE!hOZ{h ze0nISxR7twJh`fU=hyAs%OOxI)i=4RpmXtC)}=nbwQtOP%*$^m8rCYq!FA!kIQN5p zq3kS6*0-GW4(FHieg8K+asIK8#R?ws{1IWx8PA5E1vOMm+pR*ID2&_(DwD9iQ?An%zdXhqRX7+hi ze$7y0{-xCl_nhBY{7XZn=f`WEgn1FR#_b1x&0pD|lQQ9FPvcC1HBA~dAGdvE@Q^>6 z@o?_n46o)r3l2`C@!z4Y4ee>g%Ep6s)G#r<`)$SF{qCl{CInp&9{8X6e^ V)0P1&NdZ%ciB(>HUaBSt005eJW#|9^ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..85ca78da04d8069634542ae7fab0cccba6662362 GIT binary patch literal 798 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|`wLrh01Q_EPa^ zVdZn>fh^H0o+VW?WZinYO<#7;Qg!z=d#mkTU0mf`{++k_EtM?eecEH@nZiZyR_O8M z7aeG@72Mq-y+TIDQMN(*)>ik&H)M|l_FL+hz1Wy@jO*0jbi1AWFJIh$>y}%3P;;yE z=N4m`PX2}Sr`MFu@_Z%!Nq%AzvnJ3bsm4}}42+r@mIxOcnpou*q~;Z*7wG0CmSmQt zY8YC=MUZ`CZdI0ARGgWgXJ}=jsbON3n3JAwY!wvj2;vk2{cC6yKPWs3bO+d*VE0&9 zakc$E%G>jl|B1H6gsDmf4>O;q#x-zkVDk1X)JY0|^m)-qcgGbIQxxvx)XutfHXvVJ zJx*fs{D0eDWbJ&Lq33ez{fkeJCs~~~qgRYC zj$|BozSFK}mn@Il4$m8YKc9x~2{uTsb9*qy{%il!=N!#hMdzn+|4A23;F+~6?9nOb zIcuTDy#3xGIh#0Gh&kOE+$Um-fE#;b^_=kHEtG0i+y+M0P-QwpL?rWBp z2&eDu-g5c(Tk}(@6=h!n<#*ZV!;HyTu6L++^WM|zD|Y_Ni&K~w6Qx`F1mx*uN{T=TbU8J?xdgwqV<-HN+NgYlkJ36h<@ALb~|p3<@Z?knx! zr=G@>F3F-(tb9Pc)=wtR>g*QBpt-F2uwTveZ#z+ibwehDZ=bJahm(SdS!m{*afNbJ zeT<=K!+ZbDktrxKI%P9D0RRChC^0%@F)}zh1t(+xb0*AZ#eDIW;u=Nw0u;O=g6+7y z-VJ46kMFn#xlXr24OMPW)Kytz+={J3DX3zAtc}x~@sAmfvW+uO{qq9x0G?S>?ciV3 zGvEdBmY{J2%2RAqzADPuhT76E`$r!pD%T@^=lS9TZg(!F5u%83PEU2Gj4SLrFIoz4EXxXfac1_#fl5xSdi!C+;rAV=r3YA?s6<6 z2ef|cui12=5sZWGx-(tH;n9pmTPY|pI&n5NIsowgx0O#2cHo3Ok@PjERF*_g9OTB922NHS#D|?O88D6{DB*6aMJE;T9Gm Q)vr*t>cfW}F1{%#DO7g*umAu6 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..776ddf7e9e2f186e3893a1d8753b0e96015d1432 GIT binary patch literal 710 zcmV;%0y+ICGBG-Ea&L5HV{~O?EpT#ac42g7Eo)_YC^I^8b73ekI&LvCIXVEwN8)42 z^!s@xK)&&wZxRfWvQOaUX#^Z!_|wN(jXMRb%thIOXlqrMeCyDbfkl<-GSlMSbxuW} z=`9A1P@M2>kd8huZA3C6Zjxqfue?dcH8O#4Vg6KYTu8tT8bTeu{H?_41PE@wX4dIR zM=B1OFW~Hn9bvd_LB_z+MoXtDC^0%^GdckP0VyasI&gAtbY)|7Wn?lnI&W}gZg6LC zEpsw!Gc9vyVKFUXWpgcKVq+*MG&*x=VKF#5R~4QV1CS}>I65&fG&nUkHZUnMHadwJ zI4K#Djmfv#XM`^rrMxLLFfcm&Hb^2_MB~ixsklEHJEHX{OMoBP{o0|-ZfrwqwjKCa zVAelsGk!ymMl*EZhwegnhGBco=8o70DNBW#>L2PwxWPhwwG7n<+xPccJWlbek`ujP zzV;I~lKcR}%MHs#-oyh{jyq%Ifz>SukJ1K;Hl|6Ha=dyi{x7FSrZ4i{%?>@$)Lx{W zJnu{gxypp)$^+qLlb+i5a^DD)CFZa6w-q@i%|0{-981M>!0J#}dk(uhG@3($Xcsqn z64civORjd5KmDhjW)!VnkQHpZEUKE~NS>&v4Zru(0G3Ngd5GYZ`b(*O>=q)p($xQ3 z`&paGqhWXD#{x`DDqgA6NiUH5XXLe6FvCBj0m;;oi=np1WxLrcHw=l)K=B0iSWn*+@WG!KIF*Q0eFflbSIWjO*F*PwbGc+kFDU0+&GXMYp literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..2b464f0ccbe4b2fe503e513672d8835ae4cbf96b GIT binary patch literal 798 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|>2=@oI+1aW2m} zl~F7LRuMCmZl6fFktKU0VZUb}=SlH59kVIv zW~NSvKldu&AM?~rn*=r;IV-!w_oB`DmK~oRa|@k)XRnxkb%D_7pz9J3JSTG+`?w@L z{UH1D$l)*dd4$i#u4oOq`O-Bq>%1n=C8@?%j0}vL8kPtb8=6?<7o_GDq!;MsC6;8C zrD_;j!bOmMV{TQJT2!2wpJ!-gqN!nGm6(&BZ)_D5>#54-s#{_q<_A_vw@vnAIdFT;{*_x*ofb0%?wIqrG4{~*$*cTDRGA-y1Xl0eq@)cf~cAs<;BL8>r-}0*RNh}2sLK)eXZ#SzwV54*s{OGzA$?3ikV0EK0k1QbzgpT z*yoKR*Urstxu>){DJg7?X;0BtE6-fLd7^ z5ZUXgj4-$1`gseoVNO_$i>4J6KkK#sAe+_ z=d7Bdd&uhuhbbsAI%P9D0RRChC^0%@F)}zh79!~M=pMdAm3piq@u6U=)oup5L>rj_ ziq&q;2!rRp$9ysTtBn77I-ePy9h`>qRJDAu=_3D}*Bg)BkwnYq{;MRqCNULKaRsE= zEN`Zi0jKA)_jstRqYIhg!!^(L4&9lK;|!< Rpw6b$yIbP0v^UNvDJjAx{2KrO literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..137659693bd3b69fe3b51aa9916890277865733c GIT binary patch literal 798 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|`vVW_U^?`n1Dp zrzg+cwHF3z{k{3#eetAuI`-eIHoE)nt^7N2!7Q12%{DV#k@VZ*0s2xi8*8lGY#;Rp z9g28oRof}PHsHU56-U+l6Bjc~4BBqrIqKb7VR%nqL(NLz#EJO=@+LF$mVWt@*}R!! zo8PYJId^a5yqwZop|C?I>?NPQyC%>jsm4}}42+r@mIxOcnpou*q~;Z*7wG0CmSmQt zY8YC=MUZ`CZdI0ARGgWgXJ}=jsbON3n3JAwY!wvj2;vk2{cC6yKPWs3bO+d*VE0&9 zxtDqd`pBhkza6}1?}g(_|WP*H3K` zU72t#J*v1#lDYhI?lF%Cv!*0@y2bpNKiyjTZB|Xols1n*qb>a6%hqu=nD9!8d|ATA z{=(Sv^Hwvz%U=%8bt)EInVj`}?$MvATeh9!iC*dmbcUI!6~hth-&6nIj0%%qyZXRs zw@EJlTaH)2v)AFDD2tWm669;7MrKOFqN*siu5e zc;x|auN&Jc^dgttYT2{oQA=o;^`>ppS`*DqA7Q+r)u4GU5ftai#ihBXRwf1(W=6oY TWdKW3z!YL)m6xBFstE!BP8C`u literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..c99824ccd43b4c22a344af3c9074d38a736c50ad GIT binary patch literal 526 zcmV+p0`dJQF)=!Da%py9bY(4TWqBwwI&yPiC^0&2F)}$i0M3U=WY|w|OJ759@*5}@ z?M5FrJ%PS827pcfAsNa}+`5FyN8RIa?OwyKzC`#$)B`)jv70hutA_GK5_GMR{@!eT z!k=6aK%)vHSTN!x)tr8{eVD7E(5@%_>PAPwKgUyhVMsTwa2fPH6S~}zib`YPhGgQk z)CnWREJ%@;xhW_yI%P9D0RRChC^0%@F)}zh5LP2N7UVI##EVfQ)q)mcZfS|N6V5l| zi^89)zMg^}bE1u1q3@f&D*w-ty3+(q-aaGf>!G}jW*8g&sn_I zuI!%sBXOap8Wk8^Y2z{i#v%^br70*eI&n5NIsop>l;8k~B>5xWtAya!(bt1lj1SIC zlU<#9Pk1++9u5gTP+w+xNy3S4_AL{l28J=F?<4il@0$ZL+&%7=!i6a)F*piwpcP*j`yT&C#MQzk14c*~_74SR3`cNzBVI760Nft~ zpW^c8enZU~#E!84jdd@V!3$ci%v5n^RfxKW&t0va>BOcd7P?Qlz`IV7Eqp85zCPkx zl0=_W>B8Z+oi6Ql=ppaCfl5?qP|6hgE@D3&ssig)7RHZM9_0}c1sN%w1&A}flKTp-4gr|BGmP( zhP6Dx@{ha7uawAisVg_Su%Xxq57G(5V9kqmIu?aNmhbi% z3&@z(SyirI(b@BK0EBTI*VwO3^j zd&;_lo0uQ@OJ5pr)6rDqfPox?Km`djg32gxZ z^8&|f&T9VJ1r&D~Ut;I6s|$ixlFb>kIbXO#{pU;xu6S3U1*sM)Qw~?14l`te#u>Do rVsI%aF)})Ea&L5HV{~O?En##qH99gdF*PtbGB8vzH8D6eIVmYA&z?I* literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..ca1284089520e7ef6a2e7397685c0902bca3aaf5 GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|?B##BN!+jzRSM z_S+{LuPyPfNWFC6dn#AN$AAN@KUe7I7lm&+6!hJ}V$OTsZGF3fCUg{MC{|j_2GmcL z-WixXM?+%ED!t_wGZP=K73%AY)cLk7B3eEqK)G>Io@v?cGc|Y58a@$JVAN=TuvBs4 zyb3@0L(w*S{d(#Q138-VCvfd$)&#mF)!2%Wfl*V#65(P)6RZ4!)VzZ90^PjClFYJH z4MR(~2(oX?t;$l1iZk=`46RHwHB77$bJFvTt%8CbL7ZZse+{kT2Zcw0?f`oe>>dj% z=CEMP@AV#2RgcgUR$JHlVCc{FzT3McH`d-{I1!Hd%g zN6$YKntHmzXl1LvgJxUawI+S<)a(0BPw8hhEKmeG!_3r*;Q)6|g8Iu#Uj@^zZ(=_1 z%Xju4F6ydvKMJd-*Eww*P`X?Cm zY@^khD}p~H53i7P2IFfcm4*7FVH$jcX-cu8xBA@H^`li_*hHNOQYmV6(dNLH=2 z>cxTcprhUuBgZIC$h(J2rHouc)pr5?t7;5@%m0PN;8|~2z5bDdiYLx)De6>UWdYA}yr`eV^-ar-??eCDl%!j1u4k zTf{^%yAy;jKKKgyF7J&UCuoztW3tkTk0Erob@S}6pcf*GR)*w}CEhD?C_HZApuzzo s(LbIkC^0fRaB^>SWn*+@WG!KIF*Q0eFflbSIWjO*F*PwcFf%DBDRa>~>Hq)$ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..06adc06c42781ba133ce6234145c113b040d876e GIT binary patch literal 841 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|@PAe7e+s)xKNP z{&QDq@QG}YSe<_UTJe?7mp$hmTJbZY@%Y+Rk79OkZpzuezmI)ZgM{B7d*!Z~HQKj- zuerX}=~?b-(}v{yx`j&@?crbfWWw})9xoVY^+ht(TlGA>o;0a(vNWrbsD`}H^!B}C zJmQx7@5fv7$^P1=s5X7}{fCJ{)UZUj*wDl(zaTZQAiY30FR>)E zELFqM5-x)58*{6&)S}|d{5(S|6HN^htHhl2d}FJiU`G(A80cR^tN20TQJ_1(-UNBq z#KKBz2d{gmY16OsX7NXuy6r+E-lsMwy*&_dxqd?1#ffs4bmGI4#p~W(o~JRV>C5DU zq1!4}il<&?T$dGoXFL@c}JTVJuRDXGzWdoD)nK;bNxNoTyuyII$(hwc0r zz*(2RYR0YRy4%Mu`0#GJT=Ur^MX_AnFvRk{U{Q2glf3pMp6ZGEq0GK zr1rNfKvADuT$*cWWdKZG2C!5GOeDZ0k&$R-K)v_C8~>88d+JIX@UR%wBUB1 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..cf9a60d233b86ea419e4b616e349bbed1cc0cc1f GIT binary patch literal 841 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|`wDV2!4z!scs^ zuKy2iQ)%N~$-VYK(D~p0-&gml<=%Vp;)(Y(*)BobiTv*R>^c5f*8^^{-u$WS_)F{& zi;}%WVEP>vg}A$OLw{9;N*-c~W<2^!?wOPMvdgwJJ)Yk> zJNt0Xs^%^GF1WMROC5iedAQ0bNnaD_l2l_WMg~Sr4NHWJ4Na`_3sUn6(hGF+5=%16 zQZ)=M;UdVsF}Es9Eh^5;&oi_#(bO=pO3X>mH?|52b_8*Xf&MkLiXRjn1-b+5O^|m@ zEUYx1ovHabtD{``W5TcHLX-7dc8R^;5&iI8)L-lD)p}R0CM3QvY|}HgDg5~Fnk3)f zq9WVZ>r*+G&fgtYn%}!^kG+l0+)DX&g^w%xe$6Sred+y=Z5Nz#E9PWXKAy)S-c!lx z_TbRVr_<$2%eS35%)sC6uxRTF-yO|Idmfv9T_{!Z^UCt}SnXwV?o0KYk;qWUF8*av zJax~R$wF_pe^FTOzQ!}nr{d?oxEn`|W9RekJ>&3q+Q*Q<;FtqvJXrU9>uk`tzVf26 z8|#z=;g_7}>YpT=ac%4mo2K-)>UNrMnOaF$quQaOpoXa3?H{LP^|d8@h;ity-ZAYy zkMNE=!D+fbwuh;2dRXTdal%P%^V$_@GZ<#=sh)CVXVizoIu~wrsq%k&?AB&^N8}){ zm;V0<{*3|+*K()KWIdpNYh8i-*Tv>G?$x(N|LiT{;@v*u%Z0{`tG=f`%vt#8UHy*u zr_Ww&7Zd?SeR6SWuA!9yFnJlkQWY?f0Fy*UqM@mkvtw|GsfAT5dqLd$l)X%bR>me) Z!5)r=7FKt!ChwQ1E?#M5Wof1f0s!aOfdBvi literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..0ed35172fe01e0021a8365a11eb0870b3722d474 GIT binary patch literal 527 zcmV+q0`UDPF)=!Da%py9bY(4TWqBwwI&yPiC^0&2F)}$i0MUKpgWQh6p1{630)l*u zku3MiycceiY-DjLWu(cWl~c}6EBaV3YE}z4S%)j`8#-333{AP1yVeBg)E!GS;aXH7 zvFN$y7ax>ZHXZ(U@jwY%y64)L@7)tuiSl|FkWK+h7-p- z51S+$sve#t054y~u$V70o}E!PX(=c%I&n5NIso#=82aW;MkLbotc9JjIX6Tk&8F9u zRGg)$D2Bl*ABOtst8k>V)ZK^sF@kp=a2;>rlK{_olEsW|!Nj-AK@TY?F*=yt4 literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..090059d9e8122e9799b78badef12e555a4541ee8 GIT binary patch literal 710 zcmV;%0y+ICGBG-Ea&L5HV{~O?EpT#ac42g7Eo)_YC^I^8b73ekI&LvCIXVEsBpFuD z!<^sTRa(F~XkcGvt2-(S{cPq$O&qkkqBzd&YAw{~uhm2q1yo4gG{G&E zsIjLJSNbz|G6Y>#jQ&R32p?kY<@NeY6mpt_f_8)^=7*hXo95^?>&k8vHLWT?9(Pt1 zMK|7unyX=3Yf=)2_iLBY6HK`&C^0%^GdckP0VyasI&gAtbY)|7Wn?lnI&W}gZg6LC zEpsw!Gc9vyVKFUXWpgcKVq+*MG&*x=VKF#5vRMrkg|Byq{Vc%~A} z#x<>GJks^Se-DJ=(hw*|mTtWC82=G{Uy`qnf+B?=bhFbX93%(P;rkF+ECjO@JT8=P zBerA%7E?XR7S=cOkTHN%gjM%1P?QrYD- z2LUJ&N*~<)YGwuid@QCd1(u{(tnuy!YxhEcv6IC#JIbC%&TBUKcOLu^^<53W^exK( zX9=qWpz$I+%-NBk1jbne-Xhm5Ukt9b^*+}~{5Jy=?8RQ4I`%9AqfvkFd~$*tf5Id8 zx9t@YJwksX*o4o8Vyz2`^mKguTMlsIX`lx$^7SP zUubbP{6F~1fBBDdlmQ=U;Hg+qngH~6yzz-g$8?)Qg!PEKN3x5hh1$mGj~^`KsR?EN z_V7R_R0yb)#-rpTqCkR$6hc~>705?X;-nk;b#xG)>;GvK)c2Dyf+v?w4c)Ooo$X90 sT9*tdC^0fRaB^>SWn*+@WG!KIF*Q0eFflbSIWjO*F*PwcGBPPCDWOI;_W%F@ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..9061f67512102ffeb5f7a213a37cfb2b0cbcf07a GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|`vKpV$1*=RNo5 z)IY)^%qynGuGQVS?Cj0yAFiaCr2npayJoAYbE?%{xd*+!{@m19aE`6_RF;TE$hCK2 zN1Uf`eEznqi-N6t3f0Sk3?{MSD@%=sf z-`%J*W$Xygx8+N?6e@J*+!;RgL}i|GOIQ1)Dm_oi(_UsRJ7-clfyc>h_RIwaOApW3 zllf`uf7ylxmMM&k3Ga-|Zm88S$z87QRHasaopaOD>p*9inOZR%U+ML-Xh&;++5@X? zOq;)Y@<>r#w^#q4%uIueB7Z-5R-Re%*x|Btx7v2I{Pv~M z5n9KsCNy*15@1fyGO-W4&+=7oJ;)dnEBQZ*uk=64mE`n4(i}gL`@+o5yBVj~Z9g!( zJnKf@PBq_+Gpu*yayQ7t+DIMh+wipW&H3VSs*~DoM0HI+XQ4i&{lQiC_9Aa5jd*5>9jZ?ugtQkvtDuQkE2%uk$3QV z75m``j{TfM4&Art^mK$6nxGiLiJsLOV-f)$O0w*!8@ti&Dk(?=#B#`sD>VUjh~oB! z;w_uMkco(@CtV!uzXYG6T?;uWC^0%^GdckP0VyasI&gAtbY)|7Wn?lnI&W}gZg6LC zEpsw!Gc9vyVKFUXWpgcKVq+*MG&*x=VKF#57y=x+>4!`rW0a@lt?7^P0x*dIb#V>xKtKnv z?|d@)<6>el0<~Z^Jd?Uyb6~HTspd3_ZH>z(k`#3o9jM)jJA>S<%6ubq47Zs*o^MvKt(-Tu_maF&whLNgkl>bXn+lwT!A_Q3 z>iGFC5yHNQ^ou+nexYnTyHFn5XT1ozzMZnuYAzi26^=54KX z=n)VP*bG_?iOmKv&rAY5NZd7|-OG@_v__pQ{Nc=o1ZPx0L%8Odw5tCrouqf4wJRcw s*>IjIC^0fRaB^>SWn*+@WG!KIF*Q0eFflbTFflb$F*7wdH8&|KDYo1~Q2+n{ literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..41dac37574e3f714572fc2a70cd5a8dbbd55fcc5 GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|>2dWjk3<=H|6e z-A|0{_T{P1<~i||*=CK@;G)QGU@EG_Lq87ZrQTDj}rBs8Yl~NhMB1q!=tVBFU*X0_Pgtwx|IAC zms6j9TvqU!hnd;Evz~?v?CiWYA5L_0^=-Lz_4KNlFKy&+cf1O6V6s>g`h8k%TT62| z)R>o+OQtZ-7YbeA8(kZ=>cN4c-Fps7J}aCrs$VL2U&DlVvZqVIj5Q+B&b!2BDy@|_ zHMBU)ykJMfmN_ObRi^$6%>@}_Vr4HQ(A*f_5aQ&c9pdA2?)9ZzfzBQt4>V1h9$Q!~ z?wEAm+0f%~MfDetrRlSGZSZ+pInCy0l53C5#O|4C^RFKD07ZFnacQopm9d49g%L1m T8NgB$FoBp@<>lw4YJva&uMJV; literal 0 HcmV?d00001 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 index 0000000000000000000000000000000000000000..5df7b4a59534892f3eef8aa6c0c29ef5210480c8 GIT binary patch literal 797 zcmdNeGPEiv$}dSxE=f(%Ehx$?ODsv%%}%Ywv|`xj|M{xM^Wf;B z<^H|xfzd~NRa@9TlvlWx-OacAJITZ#O8WH!$F=WIESy`bx@LLWnXS?-+g?YPR*5E7 zzOd)Y4HWzEU3l7zUDf-i__8cImQnG%T#4WG)>ZwPpT5VumH?|52b_8*Xf&MkLiXRjn1-b+5O|W|` ztRn7m<}^>K$?~r6k0=%DI#JJ@$mCR?H~Gt-z?EiQANNg=xzIlEb5lOo=O6w~>rSzV z%Kg51`K)A~{gm#H$+H6eJ#9rlEb|JxK6`twm0I7f*hiP`|Cc;JXRBh9#eL9Ve%YfR z?e5DWi!Q8tzP?Dx?bllKjDXs{-r|tJ0JkU8tyFb^&M-5zVz|-25LIwChT? zP-7nasB98iSn$uYL`na*>1kWf2G%XdbDcO+!WLdq ze9Lp@ysRr4pDUC3*6}2n#fxj}iOq9ko1gE&J7H@Cwv|{*DlKr&(Ze)bB zy~(Z_lg}_MZ;Y0y-TTK_YRx`pk%aZ1)T(wV|L^FZt7fLN_0zTP5F_rg#qxKQ)<0p? z_>%dm%X+2a^V##m>)tZQK8beT(6RT0#iY6q|D zmNLb%_Pu7(a^}R0g|68#f^Ao}Mrs0Gl4@+l$iS$nVTo|Dp@~&~L26z>dVy|UVo7FM zs)nH@Tm;!S=2m5?Ma7x3G%Lq zg%#5~XBpG?egEEAp1LN}y|3`hqYFNzVei>23rqV?sI@)iHg8IA?3%kOCDM4qi!G1k z&DtcZK2NY*^DxCN;fmI8>xoSEXMe9*mB4F|BVg5aO3QB`c-$oyjfgX%8AqZ-)oO{pBDNp-=}*)$u-JX;iq)_9g{sO zn>gqB-i`Wj@FUxV=O5kfaHKIVm9sF3wmVyEmYB1m)#qkM!2PW!l@8>$OCRL;)cAhM zJH_1vi3(a#f4PM`%0BQ-df=0!q;%qd?Mb$4M{acVZMA1+;F+grdg_MNx^~8%wd_16 z>)&&Rax*fE?vy#T#VI&D`Ic=^Lf%n9)r2*z*Z*(bTA*Bf)M1ImvoBB27o1rZsL^zB z&M)=p3aQMV$2;OryC?=(xXnE+dstF$mf_U*Ds^$^j#_LgsCM&qnxs8Ht=u*2lJcY~ z`>CFws822~%{8<#046U3SgHah5@3?ZNHjFHa&`<3F}1MLy=UP1OPq0$p_Q?TRj`Mn Wp@mh_tAhQt*BXu+Sy`HCf&c(Uq;6vX literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/pubring.gpg b/lisp/tests/mml-gpghome/pubring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..6bd169963df7d73f1c1808ee82925a7e53bd6c18 GIT binary patch literal 13883 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 literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/pubring.gpg~ b/lisp/tests/mml-gpghome/pubring.gpg~ new file mode 100644 index 0000000000000000000000000000000000000000..6bd169963df7d73f1c1808ee82925a7e53bd6c18 GIT binary patch literal 13883 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 literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/pubring.kbx b/lisp/tests/mml-gpghome/pubring.kbx new file mode 100644 index 0000000000000000000000000000000000000000..399a0414fd20ced74f3b18a964c2fdb3af1c64a2 GIT binary patch literal 3076 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 literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/random_seed b/lisp/tests/mml-gpghome/random_seed new file mode 100644 index 0000000000000000000000000000000000000000..8e483157f5e20ed8bfc689c91f604db1783944c0 GIT binary patch literal 600 zcmV-e0;m1E$YHGgjts%4XJhRZLbAy&&zs7czo4AFFZ(}C?DTKk#au4gJg_AfD`X1v z?`Lg;SjprZo!TfBE@Wfs#Ip*`DHOJZo8auSMj>f*ZK&Bw|I~dP8j+}3mV0WB)t#oev`V>Bu7Lm*%Z8TRrD2y2AMeEyeCdEVT=(` zg;H=eq^*20En|hmH&5{o{E6nx27&q~B9*7PYmP792k|Ot zkl|h74dFru&@FepBa04!9}-?SLyC$3$rrKp8jpfD5>7JX30wqVBO#)LNnf<;`V8qGPqaMNl literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/secring.gpg b/lisp/tests/mml-gpghome/secring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..b323c072c04c25591abcabdbc3d801ad61472ce0 GIT binary patch literal 17362 zcmch;V|1q5wyyogwr$&}SQR^|xMHJXRBYR}?WCfLZQC{~PQKKfv-VoMowLr~zrJ62 z@wBw}dB)wZevjV!NB~*{m3@zQ0s;k;B13SwRszuOn^uN==JVxTjHbKpjiA-8fCK~p z0)RY$s48*YdxT>L87%WK4ew+`z|CXanU;0i>OR`?17s73Fqb+$UHDMYpAWWW$2-a74Fn zVjnqS#D%#eKu&-v(0bmNLCR>MVKw}T9yK%8gS~;W%ozYjpwh%`-$ilT&M)% ze4ABiK(A=_Vu%=EIHi~3^RW-{C~N3;hVx`xG?%e&NE-V!+~u=K519D=sinmLJ-CF! zpG1~yOMaWnF%Ns|yAQ}}jWO9i^+cjB-^}cc3<;%-TnTxM{(d8108 zXy`cF(}BM~$h*tn&AC_CrbWi*t2VgOFK#NirYr2@U@{o=HCbj~mCZvA7(88O*(o`V zo+tCP@zB@BoTe(uVYw~HBflnuui)xL#(&`r#M}S6j6#Bvzeh&cGLKt)F0mu}>65nE zHGZ^$XFFD|BwkmZMjP<^H&>Y>>4u>eN$`z~-D)o6sDjsOU;rTFUkYx2ybBFk1U4s# zoBkF$w`i^;*Ul4&vw02E)*2kIKdKmjgkwT|(t(UcfhaJxfo|;J2EGIr&6)~v$!_Vf z;}>3LKc%qMopcHN1T`fwxp|GSI4=ZUCk=?sXmZtyprRc2 z9m||Xn)w2D+YMJu3*p5-3VubaOV10EPG$!c`$oYCdtPqWd|H_^B;p-&Qa+@rgSt)~T9bwsVt!A_hpj}(^6Df5y(H4=*0+;7yl_z)6Py6v84o|)@aQkJ3gM(_ zAck`(gxqz9rK!<6>>=XCqu)GLW5)d@7W16ef%L{O!~F>q{c1Crzjz85&~riDjnBOm^BZK+ zG}(jDQ?MziDLTJUQ}RxpzT6FbZWE^+bS9zs__UNB=-Ub zCkKrCWeuR)R=P{6e;h`obxB0$32U@bZ3vS3TZIW9Dpa;eZ2sWrPboj%8HOS_ADlN1 zN0y_m2(T1ErQ_6ApZ%s*g0GlbH%%5;nFT}sGqAgBp+Cg^t}>OOVlz4j_9@3Rr)%EN zY0fr_GW+A`hjNw5-38j_<;cg6PtgamTgsoRzmDD7d{PK-6ejnx$*M96it`OoH{DOS z4X{)82+uL?`Tjl@^N$Lj!6*y?_T-}`eePF@+t{M_z^HA+zGuzt>u^S2qb`~uT4bAq z;8d*TW>wS;S3@g=sfzWz%OhseuA;CaYa>|XUT=V}0GaAaXPaMVSvJA060>bVhm|aq zJE(`30+**5o<9?iQ!kKs9z1S?SUb}fAms)(6_(vJP{E|VQ2ggOHgOMU${oCrmbUc; zWklG)LTb!?DzYoXmr$2}KyM6H*JKLe_g})2h$Dqb*sdwRoT)PiqU9ysqP#s-r&Xqz zrn)v6kd?gj{SalzV}cp2+CoPso~x2?Jnjj?qwmP~smGVKA^Z!a9~8B=q5A;oe}`!E zUl1h*1O6?54~YIh@d?a9Ei`N;@Qfu7@@|!_hU`v0nWF>a(PB0NeT;^hbb%2#wbB=6 zUe!WR=XuN*P)#1uQNH@=(ckh}iRotOAy-`|5^~5gTdlT@=1b&C69y}tZ|0JDAfFEz zLOl1c#{mtjAI?Qn6)}ZwFpvUyOy1g0S6{9DunzwU-HsTd(bx5ydC;#w*@}mYgTs z!)4G9oDtOFlRmCV0fEZ-1X7k^snDWgvkRM@^hdbhFM(%C zCT(&Pb6h@m3~x8ioeJr#;KM;$7aUev$>Rw>jN$;OC%S~*Z5W#?bbHaMY7%)6nnkVx zAUlHdl=!vUrXA>8V+x`E^!EcMd?=T$3>QBfd=ZPyxCs%GJF4t#xV+uWFAsNPhF9ik4)KBe3%P+LRL{DCK? z0f&wD8~tFyD`pqAt=QL@`1X+GmU2!9iar=0vmrxZpnD^SEHHiLsdVY>qfGALtiR|9 z^+%Vmzq$EGeu~y0k3dX;ja<{fZHDM)_@4$U!)CP71Z7g+$h8B~0>R>(dyJS@SDDlZ zAUDs>ZYMzL>Z&2L>!=4ap1ebFVvwFWOK`H%v#U{@xaU-nQzh>{?Ko>eYt{L}go#gZ z!f$*Rr|0fbnmurk*~R{GwqJm2!5u;Q=3cm_Dg1|tM*{<;Br;1tW~;jgV@#hXffe z>}c+my5`PV9Ng6oZHaE&vNPI}K{1)jRX6Vml3}G?B!GjWYy6d7&B5WfnbI4d$_D#- z&`~V1Mv9GD;10@DbH5LsKnDHE-sr@Kw}<#0A_;Eg#8)}GWpmcxts*!nK%sNijE-ind_+OFPju9_I|Q z(M>;J2Bo24KYQjYuOIFwIeWX~bBlZ3;JBfwK5wK)du}Z%QzNpTQk&)>eQhU}m94H@)H09IER%YyHly z5J9Znw@w>iz)G6Pu-D^}Xv0?Amok0srq20pK!1I0Un%+ zO%KsKk#Jw@IECNM;xAU%GODFEGhjfcrCwtmr9s6Us}Y_2vxqs{uf z;FA%8dG}Zal{45t|HoPrLBC=QfqjOI?D4|cADY|M^&I0RLG>geX2!-wc1G3?g!Yd5 z7Dlf2gghTF{sTOJtKffvXHQD{)dvuBd4xa#Ayp z4e~$t67YcXtXgo7RfOeTrVza z`ET{st4kVL+Y?F~8JId4S?k*v*_jyG5%QRSd`NHk-#!#Dvo^M|v(j@gv#~a^v^1i# zH?q{XchEPocQCWEa;|H#Fi4{;#Z}if^6vzkM+0z##9YO5fF6 zJn!gOPM_fR)gVDqP)J&WbDZ@OgD{7YM*c|cK~&BDJF6^$$^rJ$Kf?M@58Wd5X5=Jx9Zi;g<`Z9(j5 z8S2bGVf{wzqH7qq$<2z0C%C~=^tLb>_b^o&GRf4rygG#5UB3z8;3EPUzOWk+o?pqv zyT?DcO?`tK)hZTQufQ8$g@$NSZWhO?sdR9(aim&ScQ54l^6giKLy4l&3bNY~v!}xb zYE}P!_Y>C+Fc~Tm3FxD$n!QQJEj2j+RML4d9^)$|xAeytJ4&(a!J7B=ePA+*O zwCZ=>DrT0^8IRhBmp5;vt4Piu;)OCodUzbSf?7!NUP)9=`2cIM{`$s^4a=-n%!)IB zmoJdhf`1XvyNi}A7351671*```NXdZrI9w)42AC`sprnwYV-{yqWG;TsZHXYJ2yAt zD-qPW@=CL~yO5*QLnkGqDl#Sb9eKn5GUdM)7#Qcnlml(C{Ixaw6P}F(pn!Nh{oZ0) zx;j6rE-Y*_E`%C&@O}Has-d=)<|m*D(qCzQWXr`Ea|>m@;Xen*cQ6&cJLb3!?aJ>K z8*-M#T9FU5DIzTxv8_;TK32bfi?Ubq|lBy&q}Au#UGh zw52r4AIeEk8kePcQe!Yzh2c~WqB+OmRxO8>X-WPu4|+I@C&jv@p-fbB*m8hD5U;jt z$C1;%N8C^7ovBal>};u#@w{Uy1T0{MK(GO%X(qDVL2NYy(~Uy+y-=~g>gUQS^xP7~ z9~F|Z1H>CAvTH{;I(G3sIjH&2$#%mDJ7IK;WDbAfcy-U2;j^;QHY{xTaqjvpl@^o| zrwD6SH<+n{O%3W*2gQ{wGHQ*Dor1IaNsZYF1u8#GP#0_jN+o-WC}X%QvjWSPAy%nSwgkJNBIfcdm9D@Hnf zTON>6%f%r=I##%lsGMFGn20~!8bgc?Kr@MYiwV*)z99h z5kUlCz`3wQJ3GFxoOzFe@(3uz)exAogO6QhikM<$&Gcr)`sM={bFO`cNCRtOpjFd|D?dZ-@5wC8AAVjS<4;Gk; zkUcxTTR5!B%{j6hzKa9#g%_G>@mo?Fh;`XFqOf+Oz#WALSPFMv;$Q37c@@6kkLi(n zGBgzI)iO}*qyN$$56OrA7MkpPuHt-Rt6345jfM8ls3wSQtQsUXPB3Jg^%P(5Mj!*V zpw)YLV)eV!kZ0kx@&SW|ZrnQTTJQZiv$dKNZ;=ThuBY(<2ae4p4K)tA2nZ9Q+jVmr znybZkL4!4tSnQ?9yLI8W5qR6oe8h>qjJU{kjz#ANN}&SjE@O)Sr9a~Aod={xS|d)a zXZF08Rb$RK`$a~BMae`-(#4%p4vH%F^vsta)aa0pCjssXr=(39^NFq>~G5TN;0{zFn<5b$L#UccyN z^(*HQiW4BHsTKi^seCx&CFGidE?*sGp*|Ki)5zRGv~LpKeX(s2UG12tv3vL!bgroh z9ZZtaW6|-5WhS{fYZVqA{xS4e$b9Vl)^4(Xz<@Vn50vnU8v7?o((s29THQ=FUY4Mr zPgy12w1&}-;XWyd<-U~eSB+Nyae_Y6t&CSwNx!!2uGlRo1sCQxt%wf=`?S!;`0bBx zjym5|IT-$Y1xsYUT5T+xisG{K@Eyg|io|awk4K%Q9fRuMwWy;v@wk71R3_Z*;5&x7 zD?=U5&ixk!%i*$-np~XSfcmbpGgCA5Z)|!!nV$1Tn^6dP=yBo)~Rr_)!eXL|nS$yYv04u*{ zi;jMa*GWs=)(C0=JXnJ%D#Q+_-Q1|OYN4~Q{rHOHXn!EZTyvAIz*@2KTZIWgZy!6n zq^J)S-a1(91+A&83G=hICdZ8e7x`%7dbGc=V3&6Yqv}>&le@K&yaz{Rkh9Y^T+fFo zGs8IL5v&qd?E+EWnjMFJDj_;Z%lhJZ2BN+DVc&z>4>@B!FW<0N>bQ&<7FOyhtK*IW zQlOpW%v3KD4db~yE~xa2K=e#=L@l(C0%sf4|Dxa<2newL@wZNodG7&Tx^+g;Ck&um z;B#SIwu{KOQ?B88+^`PY(d|iTN7V~(E?dW_LGr*o7!F?(pU{j_Lv@zw!}=Wp_CPGR zn`o}`EkiNgS?cFcF~Iy|&*i1MaEz9S+@bRnnCmF$rR{UE=>=8qwCKL7?~F+l-Q&>W z?p3;|whG8|0e5tr1nfFahJzyv_a1#=BgoGLk#v*9q8c)Uk2pcAtZk&VP?<7o8kaw> zz@rbY#8x4VfeP?BSvC2_JlORtivy&Wg5@_FhKRdtf^dF9Ui)0+pA-G~rbkGv@d|@1 z?sN$syD6fKq`00zqrR9j+Hr zvM^ZYRn)caLn6~H4_J6_X!u5A-zSjIxqrJi@oltFfzo#>{fQ!p&v*v2Oec67^*NDquJ`+YqWqh9jF|6s*`tL1-koIcBI@>MSVg!TNOfTVM|fjyxEd)m)nPxxo=N2}0hl|qQK=2(OZ zbh0>28spMN-!d0pI)Rt^Put@XXUqdvwYN;?VRq7PrRbbizzsXpii`3xFWhOrP8&9L zn4YS-fYh{A{J@S?0gj5?6CWO=$r$Z`1InVVN2NTOy5nL#o|il?xsEs3qs2ThGmdBvWfnn%ONNMlg^0r37ZT0DVN+c*8Vss@S0nwWM z@j-f33SO{`HlVKDIrEjL{LGsZTWP z-plD##(dfF9^9Ss(ifqt*7Vubq*4*BfJY<%u6o-@btpjVKulBK^Y4W6U!nYi)`)=H zJ=su*KknsGolu}ST^F-x1=NstQq^&n@{|8Q5@+d;qk2xls?pk#Ivv}SsJt&AG0vB6 zXwO@ak+11O@M}$nw+{Z8so9u6Lo(;E4UmBdBx_GgaDGgkQ&Wk9_Sy}G(XatI1#kZf z+@?$Gj1CDdrO?Jb?F7xr)rR5vKSNnFDwBO9>J$Y6x2so&%r`zE0#&dqT}2O`@?m^u zkW})`xUTCR_*vv_zH}9_waGN`=#wj=KzC}|VimQ8-%7iuT~(Yc2F+-XM|70FM1S2N z9(Tkp!cxm*=Xle!MI}|h>BzvD5*<730oSNf#SDIt7=#-1n9j_hdXF#h8!E0BINAwA zGSsF-fAI#(bKVwx?#9jFi3k-x$M<>g^fpNENtRxgTM9GnK}Ye3uyNd)3vjm1!yBMf z*<6SB%)XM6X8S6yKBI1|_(tos$>D$$71%6Ni(zZY9}LF2pC}b3ZRdJwIzix`ORc;& z${E?#W~6xgJOekw9U8gOORyVRF>oD{w}Q&137m*@@+q?HG@pN}^Q^p2noNI_9TlK5 zxNJOhFWTu2>OcBs?)zeHCin`%2LELqEx{oJ=dGYZ(K`UabO%R`G#0j`PNOo&B=$g7 zvA;}tx0layx^#=Na0(aVU$*D(82!uk{0T+RIFNVwo*rT)AU|>%I51&RAOzA9>r^k* z9uUH>hTN7>{)8{@ToX9NtFqyzwbJ1c*FU+IVZ>3o_=EyGop@O(31yJFCO5S?2KD1{ zl51rZc4=7jAGZr&7KMUl*V1;Bjun{X;{pOM%y3~!>c;Wm5;o56b zs5$-mJK=oP!flk=J`&Dd+DZkohB-tGnyZ}IOuz5V^NvHF0!`|$=xn1-0o(VPc%R6) zX%xz2w1ZRg=${=lG^SO1G2q%`_QJjh=X3;`P5vF)^F(I1W>zp)V}? zpF^D=RIYEp9OSp8m}wV>z+3j<5yZSFY(v%G>o0r;iGo9LLsQoNW4p=wimPrS=Lk2@5j*WApdUM%;p!1crSR)dmH+YN-l-_hWP1 z(h}69bR6M;ySnYGdxQQSs{6u$T;Oz~&;z2%NxzSe8^owb6-xhG>*f&9yUE&w3(Kso zQOixh5c>*5(gI0Uz0drU#>G>M0AejE(Yz2H4D(K`GNK|Qo#NNx6+?}*AM20nW)__8 zTuMuq@2T%$FTgp>(l&Yy9K){1kotsC?)Yx1ib?hnn?RZNn<~6OZN=EZudv zLPnIU>Ey)Xdv#h5@K2aYD3-imEjsDMTR`UqRG__7f=xBIgJwN@iHa(cstf2R>eBPSb+kEW@LnIR#M-QREMK9c2sNFyqXYJC6Oo8%w? z1&k?#t1kXZ!n{~2 z(l<{68+VvIuANGI9%S3a-hdTUG07CgnOn;}pkX$RZLhj;cw-nin+O}TBVX=4ycAZ$ z&yd%oQ!SYAx`Sz{upQ!*BgB|9>i5_5N=kOx7sKyApny7T6xqT>W+i zWJ&W3>Ff4Yggm$n=2wC{YLzbA?~<|+hz3g`RGRisFPYfxc}G-T!0Bl-H4n<&dHHzv zZJ+|o;Q-nr%co9WBv-_Hll-^f9n}V1YXRj&>2CY8?`Pen5Sy7-pzzllfzgqK2L6Lv zo;oxn64;{YFyHXPng7nO|3$9xAPK`oxo=Wmg}+_nP-2{k5peMObg+SY%e)g|4a=^! z`VW6z+xd2;(^J_Y@*pw`@~O8bjoF|q@LB}fp(5sO-O{5wOAApT51K@cahq%|-Lt_X zxh!Y0lH&DP?DJ3l0j2MH;vALVux@!#T32I3B@H$M4YwO+p-6gZ#bc6M{vp>ZqACd| z`Yuy@ZJ>SE^h3$>9%YD+$j73*bT|oKzxD{fE{YI|;t05(uqC9k&1T|8l z>Zr4s%y{ZlrnHu#7@bKSc#Gjj`*qDEFT9tBd46X~a3Ftx5E+e&n5=TKNj{iyH{VpM z$yfD+D}sIebV9vtzVbBlnZ!1y2pbJk%WQ`kxStq)@uQt`J{rFX2qAqWSugY7T$oz$ z`x1^YE*+d180KM;EUf z4W%SKbSHK-4CaM#QAeF)YOeb3ek|& z=trsDnkEpC>_86Nzh%z9u=H2v{6im13s68d>Z`MoIBn0038|&p2EhcPt8J4)e}#cu zdWSh1m05o+xmG3<(nFVHRTq`p&zyw-`nw{g3b~6bqP9XPWlB;tA>(TxlSp;2A0RGM zDnEg`NV7OqOL8b$Pir)aGKs&>8BdP+TNMwu>;mzsUB_P@<_+Xcsqks8KJ6U1rK95h z$($;S8h9U>bCs#}qF~6WxiWRLnoZbf)ywHiJ%+{LK;9tm?OrQbw|xj2;mLS22Ucj)rx9$GJi5B(ajCvz&fVN_Ho?Q!D&9~ zh^Lr;d-xDl?QUnyER?c7Wrr-w$j>Qhvpld@cQ+m&`n3tKv6?Idwv%;kJii;N_M zULU3^5z-y@-hRXP*UWP*r+lZ1-oz-8murnB#u;>~L zCcb~?fy^8H=!P7zPKN~Hp^lcRnZ4Ta8Ye&)4Zi`w_3E#Kn^qzKTm{;iV)}^2jXL;U zprv2c1VP${TD05{_|CeCQXME!(=&hpS4~RBHDj}L4isII2bQ^-i3Pwt3a!m~pUnZB zo`>*-tm8OJgF=iPryD;oFz6Fski$nFF4V~d};K>=asKdVu@nym)pKEc2h-c=%* zL~yiih|!1RfpBRuqZiX8|cEhocCnW!Gk zQtzWJdqVh!p8u$~?`qBzbpTo--Bkk=ky|Z=cx6y`(Hw74w^Kp^Y6#<0Jk0}?a0j9L zCBhV$+?Ou260LR|?@GFg=>*NpDEjO8vHTlILTxQ#ZSNRfzRVQQgn#pmpp~t#Oj>c$ zTsHEE;sWNTL>v^0w21EBCEihQ&+P^*%9wU>fa21TsJi*YKk9uKKZaw_=zQ8Et#eyO zSLWy{!3m6$1?^fX?<{E`6UqZtXimTJ(e7@FE0sen_HWVL6SF>qM2jn&`s&Y|HF7b| z=H+9i)BVXgRO=g`*^bR66u>d4P9uMWFINZ?Z`_eIEi@G; z@cl%kkn^EFjFu<;!Em5ILqtcYJpCK8DZeFq$&^n?m0=5xS6SId$+v8SI_c+Ki;h5j zrvtyOW=3&JU;9DVBv^-yrs_=%>il?y|2pvxeSOt(wouzcO1eRllKpS%?ATfZ^XgqS zjmCv*|Dxyb1^%bp`bU3ev>@+am{*zV^|4>5kJEDNuP@@Ui8659HRW@6K#@D~Yk(Ao zRE9&Is+WFwD&6kGf1hgLQvi}^YUkwuUm!imMEcb1V_!lIqgANHq)O7Z{%TJ7_`2 zO-DC0J=a5iXe$Nu%X^f>S%W3-|=yRdE-DKrKl^wR&Qw4{qyhmh&y|RM5*lrRx8Qq8e zqMFZo4*aE`9kWf914I;k!SCUbK(^Rn$Ze;Qio&h7G+c(M6}FjJky&fM8<~5!szd2W ztn8o+2QyYAE)eqq12!LAeUWp%y-7F_Gd#16@Je`t)teAG)CZae%IJM%BCfEtUC52n zVc|qCVTY2n|2!c-Nci)_>0OJpeDQ5L8v*EzSw!zOD=11Ax>kO^J{~4M#kB{OwFi2z z*8%*CgZ;8ZN~ZZN<&CQWd?l&@GKi~xabBopx!l_7Q;_>Fg8rr8 zqEk(tRN)_QF#M1?+a&gX=JlvvjypvH>F2|y9e~8FMr!aLFSSB@ z{iqf~wL-1P^dyt!Y;$UHv6X^fkz)FP*LMIQugLwarzg_~$hsF{CXqhH%9&E5AbFp{ z<7SbpkbNw2-ki4|Dz4!Yrg)IkfU|j^ZH}fG$Oc(V5K7$;vzS2PPbNInI$ZBUnFzN~ zYg-FR=PbudLTbu->n_z4tlljK%Gy~5Y^)atFcjz=%}s}BslJpZx#U^3)JJ_`94NPj z^Z^D8oA6dx7o5Tg3CUo-<{AJSS}YwN);aT<##1dCNjd+3?N8P!l%K`X$<^kd4|IoM zuU);md(JXYws$b`?s<{}2Fx0CfLaR~Re2;fj5<@4+7dpXw0az@(j$o~v9Xcw(^0fK ze@YyRMaaTq9;N!$6<@K+h-E163R)7Kz4clqB5`dgTP z`OiQ4E4l=ER||lcXuzO4&{!gwR)sL2Y0e`OBh?IA*0N#q$HZ?K=q4?2S~rN~pc{~| z#D%y6wE;FKvVW8lhapHzSg(>h|cdkKnOj13|IYl?Nj?TeXT9b}_yt7iJz zqUBM1gbQIat?Cgx|K~ph{p={AAdPR*w4&73J~@z2aBVZQ3SuXXj_gULh#M1P`xp#K2a$sS&9<4-o-bBOR7YoGs#;RX@Gc{gcLQLx=E9n!?nYp`ve4w}jb3Fn^ zVI`C|D;zfJZ9X`}A&bOK1H0_Z?avG$d!!Nr^Mj1ON2B`2aahd)L0~}dr{{#%B@I=A zin6W|u~?Bef<~>zg=DE7G*HAu=I5|%evFbi)QXv@)?vO%WgZMiI_^`&TibrKO|kEB zrh8Q%!(ig8oy}jmwIEk)_4Mpe9fewFQRWxJOW&z`O!o z?j2*0sKO>Ef`e;yQ*=6OKl*4`5j$9HMvs6&`yWLk6@Zhe1jFk3(Ho4yU%NXuesS%R zl3Z+bp;s?l_HWDI_E%B;RNI&spvOMa3p7jZO*O8Um03^mXBzyYu<=3Af5yRoS2q6d z1pR-p6AV~o(K79K%XQ)~5#ZMDJ$T#=@&Z#cbvk)STddtD6u7VEF$2@cw$Azu9qHKG zNnBdj;>ZqLZHz)vIs&9mbFUgFSh*WBhjXm^A{Pm7I9c5RQbe*pxxF`e-q2o9 zocD0zwr3qfBhjqz-X(6Ge8WrPk`l7L2gD&MmHd?H2hr{0sHWA|`ul@0$FSHi)awrg zgZwoO{`!Y!*&`-!Sv7>+O~BQu9vS|1AM+Nx zqM<=K7G}-$F%6#Zru422c0$X140PCQ+I7&v7v72oE7mNrDEqjl(x${{;3>A5&VLuR zcL3>UZa0w#^J0Rg3!B=ix;-qMUI7d^yA=`7{UvV@+^Mam#T|3-ZS~S-fOOt)9tq=e z9kaCQcBPso-!t!Ea)Zj*0mY;}tbXSCjANM0#pzaopq1a|<3E@d(RWAGwU1P}w7kBK zp285;a!A$z0;dEzx>$|R0;O16cv({-Ty)`Z7k!)YexBh42uq%dZEhHv`!SAEcb6Ie zi=e*|^$$V+=&unP{A1 zDFk=Vt!G51J)7AuRkrMV;o=ZL+CiVcz!>-5G~5n_FI?)CAsI-HqWQvv6{FoQ-RFar zocd4`*_(hUJqoqLApBe|slf|;&8yBqW*xRF{q&Y37YcaO*ML76{Nv}Xd=PXzlsAqZ zcWM4QY?qm@!~pe;UBK($Zf>J8Ng;g&M|3BI)OK7y zWt7RK81yTO;r$e3fKWMg*Wed+tS^$PyJJ_+D`@OyF!VesKfGQeT|T>ut3Q@78{t#F z6gGkN8I>Z-7RAp((3UuGsZYTCL(pdkcYlfH%aHz6@zDtuYtS5$Z9IW&$s<+_Gpp>& ziEY5fn&e9qq6#sHT&@UbMlb`|l^nH-?FI)9IJH`6PLRS^U2Z3_*H#Go*H8Xo4rS3I z#Xx~uijz77V%o(v^y~EXPs4Z} zva_^AeP&=2ad^eRr$`#WLz)+7XI~$aV9Ms>ldS8}6BCv3U!EH&O*!HrSP4E>6aePT zRk=^J3iS8;@=*ie%8v)hLRQI^(ezL)-R!da(e@K--!==4!4YYLk`*paLgZU=IjW{` zD|!lX1pylqXAekrvs!&R>k(Wg%%AWoha0lj&9kVR7+IDpube zPVT(m*a=}K@gA4&x8emRX|2`LXRe7nkNTqFxG`ZV##wWH;t8Q4K*IlZ9N}YyfpLKU znS3h#7X~v3L9u{<0pK4e7CtWd4aoIjc;L2zY*uT}kAn&vCE!D0``Lb%jzyo&weiG8 z5jZ>4H?Ri2^xZ^TP~qd=;o-V6hTpu|1L0|n%ZX+cD??*Eb+f*+WNxbzp_WpJKu&BJ z?b9!i!4h5N${eFsu^vrt`i=UGm3+d)UZLy{iA@xE*;vLJ_lcV#Em{#Y+Pw2lw!o>nUph z!O9R>>8BAtIy}|!Ngt$+h|3B=ApYQ!=Q=q=k2sgaPvr$-GVlA7)^aR3ui$~(t7TGq zbxcNy@&~|*58@EI;mdLI9XDyvrSaYO%^=NVF4dDcWSPHuwZ9>)vS>l}5hS$))_z;k z7ce(QFDR%W#R=LKbS;HrOG%fr#;hjKo@eC^AH^^3HT=zpr~ZjjVRKYY<@s~f zUg4ygi;F?oq-%}nxJ_xJ4!*Cnc&CyAFc+_AOh$!mpo!>Jwighe(dq^PrquDDAbF$Q+#iWI@5QJIx#+gRznY>cO~?>t|0WdjQSV#5osJFSAeIz9?dp+^Gi%q7ZSKpMX5Yk<0Tp_Nm*ITeZ`c0;Mr<(B#rFmS>iZu*MPrQv`JB) zrl*D7K{(090S4^qOCa4BKby-~RXR)`iyM+vwlloprZ7&SzFteXj^^(O%;1EPH?i9U z6=UN^>?} zv~D4nsY(9SYz5vf4!(9-UFD$@q2pAPekzyI}XuK{}m(vAO&y0 z|20T{U-COhA`NBwTf*9GE1y6CQMSa&Fg%MJ;GZ%*V+=)J+$^@lq9ZfGoyXOymx}D} zS><#sKrkt>dLkOSIJOw+?f`gqY22N*H7Q@siG1>d4idVA%VEEBzDmPv4N1zFCgwxf z(jXaE=cW_Sk4yzkz!D-azsuk?g672DZ#Cr80}K9C$i>Xq z`o@p!sK0{b4a5lev#bhGJ+6|Oi8Z0}$K>)y^Y7#3f7pNj{_X!S5BYoDXH!qXoJ0(^ zhd(;kx~HCxxi768c_)YR zF-qT-_%zSRq>_tX3Vy1s5dkOqhZ)2-eW|!mSQ+A3#PJq(V{%kiS=|voXsgNN&(Xld zl2X~(4OXz#9%fjb|c^Q`jfr?dFIWkmfj#clQIa>T5B2J>2Tmr zy!VH8KYoA##Mk|T0AnH|;sY^^yQ_CNC?Y5M{!2}Pjt!+Tv|>C+X|<2}EsuQ=9njvt zSgm%)zRUQo!!5`SDX44AM6~)>RVTZdz4n7ZkSqpm+;+;Qn<4yaMZ(NlNJc7?B9749 z9sngos{T_!dVyPI)%e_ZRv~n|ne@Kdq={gBjKw|P{YJ7goVe(_?XdMm)ncw*G*Y7W zFnQs&`AcE!0cvOrIdMuAQ}_tSxFqS$CG&Qat$u|EQ7)xNz3CV2@Mi&-wR^=t)TGMWNbU3uHWM0X0EXZyf^mCA#w~A z#w?d#@Amf<0*s5^+U>qWe9A7KR}#(j?C9FVhNpBQ>VW zY~hClMR2qYRv3Z}Qf(+v*+ZG{-);q2N)G?!9{(2RU;FPr`tzOy1>_=ilL;rf3<$o! zp@n`beVE|%XV(l#zw42zon{yhLoAGSr6o8#8BU$lO{QhR4{#y6LuC9ay@T1*(0MB3 zFT5l%&gPjp4({H3PYnP5t26)6Wyy(2nOI1jGBq==LQjJ#%`LD+fLRgVBeg-k{(us3 zn4TgXmv=7UMxYV0m;R6bWZ&a%KVrVVtDFjrux?&Z^!;ZeX&_7qOhuRC#Pj>b_Xu*U z_1(i=sYJX`B;FW=&kPWja^?&2OJGZnbb?Rl+n~h!mN_SsgL`2Q9GmsHG3zt@S7Kbd zqn~ex$crZMe>SSGkb6r3-qdp0U5K$XRC#wZQ${UV)O0fO^ax=Tb(bv@M-ffz*|NmW zzbU$yLAE6g-`_7W(MyN@G?bFe(FiLs@oP!xh&Z0(M>A*F;`5#K=EEpMaE_zYATGWD zx?%O`0H&VHLFrOC4(;tTsBlt@i5@Uuzmh(X8#ESy#GLYtE|b#a2_a=hpxOh15T-Ffdbw))Q4%_M{qpELW$C=AV6q+5&o4 z$iN$wJ@*|6j0U2l`K`QACeTCBa5A@Xi~dX6Chh|56$vp2QT%Z zPVMA?_+I_?v0`P_CNgE#jGYcU%gDdK8Hu!ta^2!Ebon{|mge>CS>*Dr#-!xD4#$*3 zZGZZxXTEaBMQoO1QnVS&Uw+9lq(;F+kWBFaH@!%i>JDx(uqA>om6Q1tQ}$1g$Zuob zd<4k_$cUp{X~%NnJB%wOJZd#&naRYw{p`(*L`HY_p zfpk((J31@|SLeppPw;q8jYQ;0X&YO5w1X^vwpd_k7mOy!oa{!BJ>1mAt{0P_MnA>( zZ~&q!*yc5eE_Xt0I@zM~z3LuG%Z3lT4f3lkr7gu{V6sn)c{9B@98);9Vmi2oc5}yS z;T+l+Kd|z$JDU;$t&Y)1O|ST7Fofxri@%N?41H;qay9{%{wV>bTpF5`O|9R%k~PN+ z;=J`SUUbnE3-RuyT?|l4ASgiDewrcyR^{|+?(N<@b`Yz=ic1X@Beb$|@Wp9Nj{6~v z&LD7uEh`$Gy_S28&4=2H1q`?!%D;DQ{D$v6nL)f(A8|dkYzWChl&WL{n=qo*Tj*Yn z2$T$u9;^atL(%ULJ`bTP4>DJuJDcvIP}{U*mQ3>j>BAwT%(@!Wj%#iX)d>NzMEw)> zvz09cK%Rjp>fsQa&Zqoe?eE3p2Vj|Qz;Y;Dj6#Xz8YB>ywwZIaW1_o_~od!5w;StFO+hBKM)T6ap;Nb+j_0R44HtmjGaTNO-Y+3>5$l$?|PKA8S65P zR9>Kfj4*cpqxBte^iw@IUqm3&331Bdu$SeW+k8eJy&_rgaWDPBv0ARVXG#D2$+E3} z6GgMbtwHd03Ecyf3;Ps)YitTaxx?vaeVQ>Nfd{Pw$wo3uHrF-8vbv^Cr7Y@8NbRrK k8J0Fbyk>lLi*>RsUUe8VGv#)or5xyfx_WJC{0@)*56Dhy3;+NC literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/trustdb.gpg b/lisp/tests/mml-gpghome/trustdb.gpg new file mode 100644 index 0000000000000000000000000000000000000000..09ebd8db114842ac1824a58e0c1c7ed2c80bb98f GIT binary patch literal 1880 zcmZQfFGy!*W@Ke#VqgeU=uQR+-$OciUhfvSU zz_-UsZO&h_wO_5;-Wi3(a`jkV*$Y*NP{zQ^z;a$eG1#|uy~9hl>C<<<*3y0UwH2gJ z0ZA3eUX9ozTl9<8Klh2)d*&0P>i4!KlUB4r)Tkho@-m1npSUyYVov87iyrL`VdE3# zX~)YE@-P-J!<+dRZM&BSz4@BxcAoRfr1MdAnOPuvm0?Q2G!MfI^V5s>XkIL5)!>VN zQ!jo+F=~Sq)E{aHrMwI`nq17T9N5`=EA@z#pIFr!!?{QFpz09Hfc`L9s`h2#n~jgA z3zgs=W;-pz1Uc3V9hCIpbn`T^`Le5&oZe)PX5&kGZobrg^*!3;)czGjHd; zfyHJktwPhya_nY$&aA!(}k&G o0LCvi^RW42wqD=XyS|gkCmfd!yO~t*yW)d^14x}B!Z08U07YlC6951J literal 0 HcmV?d00001 diff --git a/lisp/tests/mml-gpghome/trustlist.txt b/lisp/tests/mml-gpghome/trustlist.txt new file mode 100644 index 000000000..f886572d2 --- /dev/null +++ b/lisp/tests/mml-gpghome/trustlist.txt @@ -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 diff --git a/texi/message.texi b/texi/message.texi index b86c01e54..b5e79161f 100644 --- a/texi/message.texi +++ b/texi/message.texi @@ -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 -- 2.25.1