Mercurial > emacs
changeset 111982:f2d8b7a80f04
partial checking with substantial progress towards epg passphrase
callback arrangements. several reasons to drop the special provisions:
- gpg v1 is required for passphrase callback operation - so allout
passphrase hinting and verification requires that
- exposes passphrase to emacs code, which is much much less secure than
sticking with gpg v2 and leaving all passphrase handling in gpg
- leaving all passphrase handling to gpg removes a lot of complexity from
allout code
- gpg v2 connection to gpg-agent requires no user provisions, so is simpler
and provides some convenience that makes up for the lack of hinting and
verification
this checkin includes a partially developed version of
allout-epg-passphrase-callback-function, with hinting and ready to
implement the passphrase verification. but there's a lot to go there, and
in working through the twisty flow to adjust the verifier and hint string,
etc. not worth it, considering the above trade-offs.
author | Ken Manheimer <ken.manheimer@gmail.com> |
---|---|
date | Wed, 08 Dec 2010 14:57:06 -0500 |
parents | a5bad7af3181 |
children | a348c631aeb8 |
files | lisp/allout.el |
diffstat | 1 files changed, 205 insertions(+), 215 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/allout.el Fri Nov 26 16:34:17 2010 -0500 +++ b/lisp/allout.el Wed Dec 08 14:57:06 2010 -0500 @@ -43,9 +43,8 @@ ;; - Symmetric-key and key-pair topic encryption, plus symmetric passphrase ;; mnemonic support, with verification against an established passphrase ;; (using a stashed encrypted dummy string) and user-supplied hint -;; maintenance. (See allout-toggle-current-subtree-encryption docstring. -;; Currently only GnuPG encryption is supported -;;PGG and integration with gpg-agent is not yet implemented.) +;; maintenance. Encryption is via the Emacs 'epg' library. See +;; allout-toggle-current-subtree-encryption docstring. ;; - Automatic topic-number maintenance ;; - "Hot-spot" operation, for single-keystroke maneuvering and ;; exposure control (see the allout-mode docstring) @@ -84,11 +83,9 @@ ;;;_* Dependency autoloads (require 'overlay) (eval-when-compile - ;; Most of the requires here are for stuff covered by autoloads. - ;; Since just byte-compiling doesn't trigger autoloads, so that - ;; "function not found" warnings would occur without these requires. - (require 'pgg) - (require 'pgg-gpg) + ;; Most of the requires here are for stuff covered by autoloads, which + ;; byte-compiling doesn't trigger. + (require 'epa) (require 'overlay) ;; `cl' is required for `assert'. `assert' is not covered by a standard ;; autoload, but it is a macro, so that eval-when-compile is sufficient @@ -1536,6 +1533,12 @@ "Horrible hack used to prevent invalid multiple triggering of outline mode from prop-line file-var activation. Used by `allout-mode' function to track repeats.") +;;;_ = allout-epg-protocol +(defvar allout-epg-protocol 'OpenPGP + "*The default protocol. +The value can be either 'OpenPGP or 'CMS. + +You should bind this variable with `let', but do not set it globally.") ;;;_ = allout-passphrase-verifier-string (defvar allout-passphrase-verifier-string nil "Setting used to test solicited encryption passphrases against the one @@ -1596,15 +1599,15 @@ (defvar allout-encryption-ciphertext-rejection-regexps nil "Variable for regexps matching plaintext to remove before encryption. -This is for the sake of redoing encryption in cases where the ciphertext -incidentally contains strings that would disrupt mode operation -- -for example, a line that happens to look like an allout-mode topic prefix. +This is used to detect strings in encryption results that would +register as allout mode structural elements, for exmple, as a +topic prefix. Entries must be symbols that are bound to the desired regexp values. -The encryption will be retried up to -`allout-encryption-ciphertext-rejection-limit' times, after which an error -is raised.") +Encryptions that result in matches will be retried, up to +`allout-encryption-ciphertext-rejection-limit' times, after which +an error is raised.") (make-variable-buffer-local 'allout-encryption-ciphertext-rejection-regexps) ;;;_ = allout-encryption-ciphertext-rejection-ceiling @@ -6033,9 +6036,6 @@ The encryption passphrase is solicited if not currently available in the passphrase cache from a recent encryption action. -;;PGG The solicited passphrase is retained for reuse in a cache, if enabled. See -;;PGG `pgg-cache-passphrase' and `pgg-passphrase-cache-expiry' for details. - Symmetric Passphrase Hinting and Verification If the file previously had no associated passphrase, or had a different @@ -6115,6 +6115,7 @@ (if was-encrypted "de" "en")) nil)) ;; Assess key parameters: + ;;PGG rework key-info! (key-info (or ;; detect the type by which it is already encrypted (and was-encrypted @@ -6152,7 +6153,6 @@ (allout-encrypt-string subject-text was-encrypted (current-buffer) for-key-type for-key-identity - ;;PGG fetch-pass )) ;; Replace the subtree with the processed product. @@ -6184,65 +6184,29 @@ (insert "*")))) (run-hook-with-args 'allout-structure-added-hook bullet-pos subtree-end)))) -;;;_ > allout-encrypt-string (text decrypt allout-buffer key-type for-key -;;; ;;PGG fetch-pass -;;; &optional retried verifying -;;; passphrase) -(defun allout-encrypt-string (text decrypt allout-buffer key-type for-key - ;;PGG fetch-pass - &optional retried rejected - verifying passphrase) +;;;_ > allout-encrypt-string (text decrypt allout-buffer) +(defun allout-encrypt-string (text decrypt allout-buffer &optional rejected) "Encrypt or decrypt message TEXT. +Returns the resulting string, or nil if the transformation fails. + If DECRYPT is true (default false), then decrypt instead of encrypt. -KEY-TYPE, either `symmetric' or `keypair', specifies which type -of cypher to use. - -FOR-KEY is human readable identification of the first of the user's -eligible secret keys a keypair decryption targets, or else nil. - -;;PGG FETCH-PASS (default false) forces fresh prompting for the passphrase. - -Optional RETRIED is for internal use -- conveys the number of failed keys -that have been solicited in sequence leading to this current call. - -Optional PASSPHRASE enables explicit delivery of the decryption passphrase, -for verification purposes. +ALLOUT-BUFFER identifies the buffer containing the text. Optional REJECTED is for internal use -- conveys the number of rejections due to matches against `allout-encryption-ciphertext-rejection-regexps', as limited by `allout-encryption-ciphertext-rejection-ceiling'. - -Returns the resulting string, or nil if the transformation fails." - - (require 'epa) - (require 'pgg) +" + + (require 'epg) (let* ((epg-context (epg-make-context epa-protocol t)) - ;;PGG (scheme (upcase - ;;PGG (format "%s" (or pgg-scheme pgg-default-scheme "GPG")))) - (for-key (and (equal key-type 'keypair) - (or for-key - (split-string (read-string - (format "%s message recipients: " - epa-protocol)) - "[ \t,]+")))) - (target-prompt-id (if (equal key-type 'keypair) - (if (= (length for-key) 1) - (car for-key) for-key) - (buffer-name allout-buffer))) - ;;PGG (target-cache-id (format "%s-%s" - ;;PGG key-type - ;;PGG (if (equal key-type 'keypair) - ;;PGG target-prompt-id - ;;PGG (or (buffer-file-name allout-buffer) - ;;PGG target-prompt-id)))) (encoding (with-current-buffer allout-buffer buffer-file-coding-system)) (multibyte (with-current-buffer allout-buffer - enable-multibyte-characters)) + enable-multibyte-characters)) (strip-plaintext-regexps (if (not decrypt) (allout-get-configvar-values @@ -6254,160 +6218,186 @@ (rejected (or rejected 0)) (rejections-left (- allout-encryption-ciphertext-rejection-ceiling rejected)) - result-text status + massaged-text result-text ) - ;;PGG (if (and fetch-pass (not passphrase)) - ;;PGG ;; Force later fetch by evicting passphrase from the cache. - ;;PGG (pgg-remove-passphrase-from-cache target-cache-id t)) - - (catch 'encryption-failed - - ;; We handle only symmetric-key passphrase caching. - (if (and (not passphrase) - (not (equal key-type 'keypair))) - (setq passphrase (allout-obtain-passphrase for-key - ;;PGG target-cache-id - target-prompt-id - key-type - allout-buffer - retried - ;;PGG fetch-pass - ))) - - (with-temp-buffer - - (insert text) - - ;; convey the text characteristics of the original buffer: - (allout-set-buffer-multibyte multibyte) - (when encoding - (set-buffer-file-coding-system encoding) - (if (not decrypt) - (encode-coding-region (point-min) (point-max) encoding))) - - (when (and strip-plaintext-regexps (not decrypt)) - (dolist (re strip-plaintext-regexps) - (let ((re (if (listp re) (car re) re)) - (replacement (if (listp re) (cadr re) ""))) - (goto-char (point-min)) - (save-match-data - (while (re-search-forward re nil t) - (replace-match replacement nil nil)))))) - - (cond - - ;; symmetric: - ((equal key-type 'symmetric) - (setq status - (if decrypt - - (pgg-decrypt (point-min) (point-max) passphrase) - - (pgg-encrypt-symmetric (point-min) (point-max) - passphrase))) - - (if status - (pgg-situate-output (point-min) (point-max)) - ;; failed -- handle passphrase caching - (if verifying - (throw 'encryption-failed nil) - ;;PGG (pgg-remove-passphrase-from-cache target-cache-id t) - (error "Symmetric-cipher %scryption failed -- %s" - (if decrypt "de" "en") - "try again with different passphrase")))) - - ;; encrypt `keypair': - ((not decrypt) - - (setq status - - (pgg-encrypt for-key - nil (point-min) (point-max) passphrase)) - - (if status - (pgg-situate-output (point-min) (point-max)) - (error ;;PGG (pgg-remove-passphrase-from-cache target-cache-id t) - (error "encryption failed")))) - - ;; decrypt `keypair': - (t - - (setq status - (pgg-decrypt (point-min) (point-max) passphrase)) - - (if status - (pgg-situate-output (point-min) (point-max)) - (error ;;PGG (pgg-remove-passphrase-from-cache target-cache-id t) - (error "decryption failed"))))) - - (setq result-text - (buffer-substring-no-properties - 1 (- (point-max) (if decrypt 0 1)))) - ) - - ;; validate result -- non-empty - (cond ((not result-text) - (if verifying - nil - ;; transform was fruitless, retry w/new passphrase. - ;;PGG (pgg-remove-passphrase-from-cache target-cache-id t) - (allout-encrypt-string text decrypt allout-buffer - key-type for-key - ;;PGG nil - (if retried (1+ retried) 1) - rejected verifying nil))) - - ;; Retry (within limit) if ciphertext contains rejections: - ((and (not decrypt) - ;; Check for disqualification of this ciphertext: - (let ((regexps reject-ciphertext-regexps) - reject-it) - (while (and regexps (not reject-it)) - (setq reject-it (string-match (car regexps) - result-text)) - (pop regexps)) - reject-it)) - (setq rejections-left (1- rejections-left)) - (if (<= rejections-left 0) - (error (concat "Ciphertext rejected too many times" - " (%s), per `%s'") - allout-encryption-ciphertext-rejection-ceiling - 'allout-encryption-ciphertext-rejection-regexps) - (allout-encrypt-string text decrypt allout-buffer - key-type for-key - ;;PGG nil - retried (1+ rejected) - verifying passphrase))) - ;; Barf if encryption yields extraordinary control chars: - ((and (not decrypt) - (string-match "[\C-a\C-k\C-o-\C-z\C-@]" - result-text)) - (error (concat "Encryption produced non-armored text, which" - "conflicts with allout mode -- reconfigure!"))) - - ;; valid result and just verifying or non-symmetric: - ((or verifying (not (equal key-type 'symmetric))) - ;;PGG (if (or verifying decrypt) - ;;PGG (pgg-add-passphrase-to-cache target-cache-id - ;;PGG passphrase t)) - result-text) - - ;; valid result and regular symmetric -- "register" - ;; passphrase with mnemonic aids/cache. - (t - (set-buffer allout-buffer) - ;;PGG (if passphrase - ;;PGG (pgg-add-passphrase-to-cache target-cache-id - ;;PGG passphrase t)) - (allout-update-passphrase-mnemonic-aids for-key passphrase - allout-buffer) - result-text) - ) - ) + ;; Massage the subject text for encoding and filtering. + (with-temp-buffer + (insert text) + ;; convey the text characteristics of the original buffer: + (allout-set-buffer-multibyte multibyte) + (when encoding + (set-buffer-file-coding-system encoding) + (if (not decrypt) + (encode-coding-region (point-min) (point-max) encoding))) + + ;; remove sanitization regexps matches before encrypting: + (when (and strip-plaintext-regexps (not decrypt)) + (dolist (re strip-plaintext-regexps) + (let ((re (if (listp re) (car re) re)) + (replacement (if (listp re) (cadr re) ""))) + (goto-char (point-min)) + (save-match-data + (while (re-search-forward re nil t) + (replace-match replacement nil nil)))))) + (setq massaged-text (buffer-substring-no-properties (point-min) + (point-max)))) + (setq result-text + + (if decrypt + + (epg-decrypt-string epg-context + (encode-coding-string massaged-text + (or encoding 'utf-8))) + + (if (equal key-type 'symmetric) + ;; establish the passphrase callback. it will only be used + ;; with gpgv1, but then it will handle hinting and verification. + (allout-set-epg-passphrase-callback epg-context allout-buffer)) + + (epg-encrypt-string epg-context + (encode-coding-string massaged-text + (or encoding 'utf-8)) + nil))) + + ;; validate result -- non-empty + (if (not result-text) + (error "%scryption failed." (if decrypt "De" "En")) + + ;; Retry (within limit) if ciphertext contains rejections: + ((and (not decrypt) + ;; Check for disqualification of this ciphertext: + (let ((regexps reject-ciphertext-regexps) + reject-it) + (while (and regexps (not reject-it)) + (setq reject-it (string-match (car regexps) result-text)) + (pop regexps)) + reject-it)) + (setq rejections-left (1- rejections-left)) + (if (<= rejections-left 0) + (error (concat "Ciphertext rejected too many times" + " (%s), per `%s'") + allout-encryption-ciphertext-rejection-ceiling + 'allout-encryption-ciphertext-rejection-regexps) + ;; try again: + ;; XXX alas, we depend on external caching for the passphrase. + (allout-encrypt-string text decrypt allout-buffer + (1+ rejected)))) + + ;; Barf if encryption yields extraordinary control chars: + ((and (not decrypt) + (string-match "[\C-a\C-k\C-o-\C-z\C-@]" + result-text)) + (error (concat "Encryption produced non-armored text, which" + "conflicts with allout mode -- reconfigure!"))) + + (t result-text) + ) ) ) -;;;_ > allout-obtain-passphrase (for-key ;;PGG cache-id +;;;_ . epg passphrase callback handling (epg uses only for GnuPG v1) +;;;_ > allout-epg-passphrase-callback-function (context key-id state) +(defun allout-epg-passphrase-callback-function (context key-id state) + "Handle allout passphrase prompting when used with the emacs epg library. + +Note that epg's passphrase callback provision only works when +operating with GnuPG v1. Check your GnuPG version using 'gpg +--version' from the command line. + +CONTEXT is an epg context object, per 'epg-make-context'. + +KEY-ID is apparently either 'SYM, for symmetric passphrase, or +something else for a key pair, per 'epg-passphrase-callback-function'. + +STATE is an allout passphrase state construct, per +'allout-make-passphrase-state'." + (message "allout-passphrase-callback-function: in")(sit-for 1) + (let* ((allout-buffer (allout-passphrase-state-buffer state)) + (provided (allout-passphrase-state-buffer state))) + (if (eq key-id 'SYM) + (if provided + provided + (let* + ((hint-string + (with-current-buffer allout-buffer + (if (and (not (string= allout-passphrase-hint-string + "")) + (or (equal allout-passphrase-hint-handling 'always) + (and (equal allout-passphrase-hint-handling + 'needed) + retried))) + (format " [%s]" allout-passphrase-hint-string) + ""))) + (verifier-string (allout-get-encryption-passphrase-verifier)) + (passphrase (read-passwd + (format "Passphrase for %s symmetric encryption%s: " + (buffer-name allout-buffer) hint-string)))) + (if allout-passphrase-verifier-handling + (if verifier-string + ;; try verifying against existing verifier. + ;; - successful: return the passphrase. + ;; - unsuccessful: offer to change the verifier + ;; - if change accepted, change verifier and continue + ;; - if change refused, raise an encryption error. + (if (condition-case err + (epg-decrypt-string + (allout-context-epg-passphrase-callback + epg-context allout-buffer passphrase) + verifier-string) + (error nil)) + ;;(allout-update-passphrase-mnemonic-aids for-key passphrase + ;; allout-buffer) + + ) + (read-passwd + (if (eq key-id 'PIN) + "Passphrase for PIN: " + (let ((entry (assoc key-id epg-user-id-alist))) + (if entry + (format "Passphrase for %s %s: " key-id (cdr entry)) + (format "Passphrase for %s: " key-id))))))) +;;;_ > allout-context-epg-passphrase-callback (epg-context buffer +;;; &optional passphrase) +(defun allout-context-epg-passphrase-callback (epg-context buffer + &optional passphrase) + "Return an epg-context which uses allout's passphrase callback with state. + +NOTE that epg's passphrase callback provision only works when +operating with GnuPG v1. Check your GnuPG version using 'gpg +--version' from the command line. + +A deep copy of the specified EPG-CONTEXT, per 'epg-make-context', +is used as a template. + +BUFFER is the allout outline buffer containing the target text. + +Optional PASSPHRASE is an already obtained passphrase to be used for +multiple decryptions, eg when verifying symmetric passphrases." + (let ((new-epg-context (copy-tree epg-context))) + (epg-context-set-passphrase-callback + new-epg-context + (cons #'allout-epg-passphrase-callback-function + (allout-make-passphrase-state buffer passphrase))) + new-epg-context)) +;;;_ > allout-make-passphrase-state (buffer &optional passphrase) +(defun allout-make-passphrase-state (buffer &optional passphrase) + "Return an allout passphrase state construct. + +BUFFER is the allout outline buffer. + +Optional PASSPHRASE is used when decrypting to convey an already +obtained passphrase for doing multiple decryptions, eg when doing +verification as part of symmetric passphrse decryption." + (cons buffer passphrase)) +;;;_ > allout-passphrase-state-buffer (state) +(defun allout-passphrase-state-buffer (state) + "Given an allout passphrase STATE construct, return the buffer." + (car state)) +;;;_ > allout-passphrase-state-passphrase (state) +(defun allout-passphrase-state-passphrase (state) + "Given an allout passphrase STATE construct, return the passphrase or nil." + (cdr state)) +;;;_ > ;;PGG allout-obtain-passphrase (for-key ;;PGG cache-id ;;; prompt-id key-type allout-buffer retried ;;; ;;PGG fetch-pass) (defun allout-obtain-passphrase (for-key ;;PGG cache-id @@ -6541,7 +6531,7 @@ (save-match-data (looking-at "\\*"))) ) ) -;;;_ > allout-encrypted-key-info (text) +;;;_ > ;;PGG allout-encrypted-key-info (text) ;; XXX gpg-specific, alas (defun allout-encrypted-key-info (text) "Return a pair of the key type and identity of a recipient's secret key. @@ -6558,7 +6548,7 @@ (with-temp-buffer (insert text) (let* ((parsed-armor (pgg-parse-armor-region (point-min) (point-max))) - (type (if (pgg-gpg-symmetric-key-p parsed-armor) + (type (if (assq 'symmetric-key-algorithm (car (cdr parsed-armor))) 'symmetric 'keypair)) secret-keys first-secret-key for-key-owner)