+;;;_ #8 Encryption
+;;;_ > allout-toggle-current-subtree-encryption (&optional fetch-pass)
+(defun allout-toggle-current-subtree-encryption (&optional fetch-pass)
+ "Encrypt clear or decrypt encoded text of visibly-containing topic's contents.
+
+Optional FETCH-PASS universal argument provokes key-pair encryption with
+single universal argument. With doubled universal argument \(value = 16),
+it forces prompting for the passphrase regardless of availability from the
+passphrase cache. With no universal argument, the appropriate passphrase
+is obtained from the cache, if available, else from the user.
+
+Currently only GnuPG encryption is supported.
+
+\**NOTE WELL** that the encrypted text must be ascii-armored. For gnupg
+encryption, include the option ``armor'' in your ~/.gnupg/gpg.conf file.
+
+Both symmetric-key and key-pair encryption is implemented. Symmetric is
+the default, use a single \(x4) universal argument for keypair mode.
+
+Encrypted topic's bullet is set to a `~' to signal that the contents of the
+topic \(body and subtopics, but not heading) is pending encryption or
+encrypted. `*' asterisk immediately after the bullet signals that the body
+is encrypted, its' absence means the topic is meant to be encrypted but is
+not. When a file with topics pending encryption is saved, topics pending
+encryption are encrypted. See allout-encrypt-unencrypted-on-saves for
+auto-encryption specifics.
+
+\**NOTE WELL** that automatic encryption that happens during saves will
+default to symmetric encryption - you must manually \(re)encrypt key-pair
+encrypted topics if you want them to continue to use the key-pair cipher.
+
+Level-1 topics, with prefix consisting solely of an `*' asterisk, cannot be
+encrypted. If you want to encrypt the contents of a top-level topic, use
+\\[allout-shift-in] to increase its depth.
+
+ Passphrase Caching
+
+The encryption passphrase is solicited if not currently available in the
+passphrase cache from a recent encryption action.
+
+The solicited passphrase is retained for reuse in a buffer-specific cache
+for some set period of time \(default, 60 seconds), after which the string
+is nulled. The passphrase cache timeout is customized by setting
+`pgg-passphrase-cache-expiry'.
+
+ Symmetric Passphrase Hinting and Verification
+
+If the file previously had no associated passphrase, or had a different
+passphrase than specified, the user is prompted to repeat the new one for
+corroboration. A random string encrypted by the new passphrase is set on
+the buffer-specific variable `allout-passphrase-verifier-string', for
+confirmation of the passphrase when next obtained, before encrypting or
+decrypting anything with it. This helps avoid mistakenly shifting between
+keys.
+
+If allout customization var `allout-passphrase-verifier-handling' is
+non-nil, an entry for `allout-passphrase-verifier-string' and its value is
+added to an Emacs 'local variables' section at the end of the file, which
+is created if necessary. That setting is for retention of the passphrase
+verifier across emacs sessions.
+
+Similarly, `allout-passphrase-hint-string' stores a user-provided reminder
+about their passphrase, and `allout-passphrase-hint-handling' specifies
+when the hint is presented, or if passphrase hints are disabled. If
+enabled \(see the `allout-passphrase-hint-handling' docstring for details),
+the hint string is stored in the local-variables section of the file, and
+solicited whenever the passphrase is changed."
+ (interactive "P")
+ (save-excursion
+ (allout-back-to-current-heading)
+ (allout-toggle-subtree-encryption fetch-pass)
+ )
+ )
+;;;_ > allout-toggle-subtree-encryption (&optional fetch-pass)
+(defun allout-toggle-subtree-encryption (&optional fetch-pass)
+ "Encrypt clear text or decrypt encoded topic contents \(body and subtopics.)
+
+Optional FETCH-PASS universal argument provokes key-pair encryption with
+single universal argument. With doubled universal argument \(value = 16),
+it forces prompting for the passphrase regardless of availability from the
+passphrase cache. With no universal argument, the appropriate passphrase
+is obtained from the cache, if available, else from the user.
+
+Currently only GnuPG encryption is supported.
+
+\**NOTE WELL** that the encrypted text must be ascii-armored. For gnupg
+encryption, include the option ``armor'' in your ~/.gnupg/gpg.conf file.
+
+See `allout-toggle-current-subtree-encryption' for more details."
+
+ (interactive "P")
+ (save-excursion
+ (allout-end-of-prefix t)
+
+ (if (= (allout-recent-depth) 1)
+ (error (concat "Cannot encrypt or decrypt level 1 topics -"
+ " shift it in to make it encryptable")))
+
+ (let* ((allout-buffer (current-buffer))
+ ;; Asses location:
+ (after-bullet-pos (point))
+ (was-encrypted
+ (progn (if (= (point-max) after-bullet-pos)
+ (error "no body to encrypt"))
+ (allout-encrypted-topic-p)))
+ (was-collapsed (if (not (search-forward "\n" nil t))
+ nil
+ (backward-char 1)
+ (allout-hidden-p)))
+ (subtree-beg (1+ (point)))
+ (subtree-end (allout-end-of-subtree))
+ (subject-text (buffer-substring-no-properties subtree-beg
+ subtree-end))
+ (subtree-end-char (char-after (1- subtree-end)))
+ (subtree-trailing-char (char-after subtree-end))
+ ;; kluge - result-text needs to be nil, but we also want to
+ ;; check for the error condition
+ (result-text (if (or (string= "" subject-text)
+ (string= "\n" subject-text))
+ (error "No topic contents to %scrypt"
+ (if was-encrypted "de" "en"))
+ nil))
+ ;; Assess key parameters:
+ (key-info (or
+ ;; detect the type by which it is already encrypted
+ (and was-encrypted
+ (allout-encrypted-key-info subject-text))
+ (and (member fetch-pass '(4 (4)))
+ '(keypair nil))
+ '(symmetric nil)))
+ (for-key-type (car key-info))
+ (for-key-identity (cadr key-info))
+ (fetch-pass (and fetch-pass (member fetch-pass '(16 (16))))))
+
+ (setq result-text
+ (allout-encrypt-string subject-text was-encrypted
+ (current-buffer)
+ for-key-type for-key-identity fetch-pass))
+
+ ;; Replace the subtree with the processed product.
+ (allout-unprotected
+ (progn
+ (set-buffer allout-buffer)
+ (delete-region subtree-beg subtree-end)
+ (insert result-text)
+ (if was-collapsed
+ (allout-flag-region (1- subtree-beg) (point) t))
+ ;; adjust trailing-blank-lines to preserve topic spacing:
+ (if (not was-encrypted)
+ (if (and (= subtree-end-char ?\n)
+ (= subtree-trailing-char ?\n))
+ (insert subtree-trailing-char)))
+ ;; Ensure that the item has an encrypted-entry bullet:
+ (if (not (string= (buffer-substring-no-properties
+ (1- after-bullet-pos) after-bullet-pos)
+ allout-topic-encryption-bullet))
+ (progn (goto-char (1- after-bullet-pos))
+ (delete-char 1)
+ (insert allout-topic-encryption-bullet)))
+ (if was-encrypted
+ ;; Remove the is-encrypted bullet qualifier:
+ (progn (goto-char after-bullet-pos)
+ (delete-char 1))
+ ;; Add the is-encrypted bullet qualifier:
+ (goto-char after-bullet-pos)
+ (insert "*"))
+ )
+ )
+ )
+ )
+ )
+;;;_ > allout-encrypt-string (text decrypt allout-buffer key-type for-key
+;;; fetch-pass &optional retried verifying
+;;; passphrase)
+(defun allout-encrypt-string (text decrypt allout-buffer key-type for-key
+ fetch-pass &optional retried verifying
+ passphrase)
+ "Encrypt or decrypt message TEXT.
+
+If DECRYPT is true (default false), then decrypt instead of encrypt.
+
+FETCH-PASS (default false) forces fresh prompting for the passphrase.
+
+KEY-TYPE indicates whether to use a 'symmetric or 'keypair cipher.
+
+FOR-KEY is human readable identification of the first of the user's
+eligible secret keys a keypair decryption targets, or else nil.
+
+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.
+
+Returns the resulting string, or nil if the transformation fails."
+
+ (require 'pgg)
+
+ (if (not (fboundp 'pgg-encrypt-symmetric))
+ (error "Allout encryption depends on a newer version of pgg"))
+
+ (let* ((scheme (upcase
+ (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: "
+ scheme))
+ "[ \t,]+"))))
+ (target-prompt-id (if (equal key-type 'keypair)
+ (if (= (length for-key) 1)
+ (car for-key) for-key)
+ (buffer-name allout-buffer)))
+ (target-cache-id (format "%s-%s"
+ key-type
+ (if (equal key-type 'keypair)
+ target-prompt-id
+ (or (buffer-file-name allout-buffer)
+ target-prompt-id))))
+ result-text status)
+
+ (if (and fetch-pass (not passphrase))
+ ;; Force later fetch by evicting passphrase from the cache.
+ (pgg-remove-passphrase-from-cache target-cache-id t))
+
+ (catch 'encryption-failed
+
+ ;; Obtain the passphrase if we don't already have one and we're not
+ ;; doing a keypair encryption:
+ (if (not (or passphrase
+ (and (equal key-type 'keypair)
+ (not decrypt))))
+
+ (setq passphrase (allout-obtain-passphrase for-key
+ target-cache-id
+ target-prompt-id
+ key-type
+ allout-buffer
+ retried fetch-pass)))
+ (with-temp-buffer
+
+ (insert text)
+
+ (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-remove-passphrase-from-cache target-cache-id t)
+ (error "Symmetric-cipher encryption failed - %s"
+ "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-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-remove-passphrase-from-cache target-cache-id t)
+ (error "decryption failed"))))
+ )
+
+ (setq result-text
+ (buffer-substring 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-remove-passphrase-from-cache target-cache-id t)
+ (allout-encrypt-string text allout-buffer decrypt nil
+ (if retried (1+ retried) 1)
+ 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 unusable"
+ " non-armored text - reconfigure!")))
+
+ ;; valid result and just verifying or non-symmetric:
+ ((or verifying (not (equal key-type 'symmetric)))
+ (if (or verifying decrypt)
+ (pgg-add-passphrase-to-cache target-cache-id
+ passphrase t))
+ result-text)
+
+ ;; valid result and regular symmetric - "register"
+ ;; passphrase with mnemonic aids/cache.
+ (t
+ (set-buffer allout-buffer)
+ (if passphrase
+ (pgg-add-passphrase-to-cache target-cache-id
+ passphrase t))
+ (allout-update-passphrase-mnemonic-aids for-key passphrase
+ allout-buffer)
+ result-text)
+ )
+ )
+ )
+ )
+ )
+;;;_ > allout-obtain-passphrase (for-key cache-id prompt-id key-type
+;;; allout-buffer retried fetch-pass)
+(defun allout-obtain-passphrase (for-key cache-id prompt-id key-type
+ allout-buffer retried fetch-pass)
+ "Obtain passphrase for a key from the cache or else from the user.
+
+When obtaining from the user, symmetric-cipher passphrases are verified
+against either, if available and enabled, a random string that was
+encrypted against the passphrase, or else against repeated entry by the
+user for corroboration.
+
+FOR-KEY is the key for which the passphrase is being obtained.
+
+CACHE-ID is the cache id of the key for the passphrase.
+
+PROMPT-ID is the id for use when prompting the user.
+
+KEY-TYPE is either 'symmetric or 'keypair.
+
+ALLOUT-BUFFER is the buffer containing the entry being en/decrypted.
+
+RETRIED is the number of this attempt to obtain this passphrase.
+
+FETCH-PASS causes the passphrase to be solicited from the user, regardless
+of the availability of a cached copy."
+
+ (if (not (equal key-type 'symmetric))
+ ;; do regular passphrase read on non-symmetric passphrase:
+ (pgg-read-passphrase (format "%s passphrase%s: "
+ (upcase (format "%s" (or pgg-scheme
+ pgg-default-scheme
+ "GPG")))
+ (if prompt-id
+ (format " for %s" prompt-id)
+ ""))
+ cache-id t)
+
+ ;; Symmetric hereon:
+
+ (save-excursion
+ (set-buffer allout-buffer)
+ (let* ((hint (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)
+ ""))
+ (retry-message (if retried (format " (%s retry)" retried) ""))
+ (prompt-sans-hint (format "'%s' symmetric passphrase%s: "
+ prompt-id retry-message))
+ (full-prompt (format "'%s' symmetric passphrase%s%s: "
+ prompt-id hint retry-message))
+ (prompt full-prompt)
+ (verifier-string (allout-get-encryption-passphrase-verifier))
+
+ (cached (and (not fetch-pass)
+ (pgg-read-passphrase-from-cache cache-id t)))
+ (got-pass (or cached
+ (pgg-read-passphrase full-prompt cache-id t)))
+
+ confirmation)
+
+ (if (not got-pass)
+ nil
+
+ ;; Duplicate our handle on the passphrase so it's not clobbered by
+ ;; deactivate-passwd memory clearing:
+ (setq got-pass (format "%s" got-pass))
+
+ (cond (verifier-string
+ (save-window-excursion
+ (if (allout-encrypt-string verifier-string 'decrypt
+ allout-buffer 'symmetric
+ for-key nil 0 'verifying
+ got-pass)
+ (setq confirmation (format "%s" got-pass))))
+
+ (if (and (not confirmation)
+ (if (yes-or-no-p
+ (concat "Passphrase differs from established"
+ " - use new one instead? "))
+ ;; deactivate password for subsequent
+ ;; confirmation:
+ (progn
+ (pgg-remove-passphrase-from-cache cache-id t)
+ (setq prompt prompt-sans-hint)
+ nil)
+ t))
+ (progn (pgg-remove-passphrase-from-cache cache-id t)
+ (error "Wrong passphrase."))))
+ ;; No verifier string - force confirmation by repetition of
+ ;; (new) passphrase:
+ ((or fetch-pass (not cached))
+ (pgg-remove-passphrase-from-cache cache-id t))))
+ ;; confirmation vs new input - doing pgg-read-passphrase will do the
+ ;; right thing, in either case:
+ (if (not confirmation)
+ (setq confirmation
+ (pgg-read-passphrase (concat prompt
+ " ... confirm spelling: ")
+ cache-id t)))
+ (prog1
+ (if (equal got-pass confirmation)
+ confirmation
+ (if (yes-or-no-p (concat "spelling of original and"
+ " confirmation differ - retry? "))
+ (progn (setq retried (if retried (1+ retried) 1))
+ (pgg-remove-passphrase-from-cache cache-id t)
+ ;; recurse to this routine:
+ (pgg-read-passphrase prompt-sans-hint cache-id t))
+ (pgg-remove-passphrase-from-cache cache-id t)
+ (error "Confirmation failed.")))
+ ;; reduce opportunity for memory cherry-picking by zeroing duplicate:
+ (dotimes (i (length got-pass))
+ (aset got-pass i 0))
+ )
+ )
+ )
+ )
+ )
+;;;_ > allout-encrypted-topic-p ()
+(defun allout-encrypted-topic-p ()
+ "True if the current topic is encryptable and encrypted."
+ (save-excursion
+ (allout-end-of-prefix t)
+ (and (string= (buffer-substring-no-properties (1- (point)) (point))
+ allout-topic-encryption-bullet)
+ (looking-at "\\*"))
+ )
+ )
+;;;_ > 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.
+
+The key type is one of 'symmetric or 'keypair.
+
+if 'keypair, and some of the user's secret keys are among those for which
+the message was encoded, return the identity of the first. otherwise,
+return nil for the second item of the pair.
+
+An error is raised if the text is not encrypted."
+ (require 'pgg-parse)
+ (save-excursion
+ (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)
+ 'symmetric
+ 'keypair))
+ secret-keys first-secret-key for-key-owner)
+ (if (equal type 'keypair)
+ (setq secret-keys (pgg-gpg-lookup-all-secret-keys)
+ first-secret-key (pgg-gpg-select-matching-key parsed-armor
+ secret-keys)
+ for-key-owner (and first-secret-key
+ (pgg-gpg-lookup-key-owner
+ first-secret-key))))
+ (list type (pgg-gpg-key-id-from-key-owner for-key-owner))
+ )
+ )
+ )
+ )
+;;;_ > allout-create-encryption-passphrase-verifier (passphrase)
+(defun allout-create-encryption-passphrase-verifier (passphrase)
+ "Encrypt random message for later validation of symmetric key's passphrase."
+ ;; use 20 random ascii characters, across the entire ascii range.
+ (random t)
+ (let ((spew (make-string 20 ?\0)))
+ (dotimes (i (length spew))
+ (aset spew i (1+ (random 254))))
+ (allout-encrypt-string spew nil (current-buffer) 'symmetric
+ nil nil 0 passphrase))
+ )
+;;;_ > allout-update-passphrase-mnemonic-aids (for-key passphrase
+;;; outline-buffer)
+(defun allout-update-passphrase-mnemonic-aids (for-key passphrase
+ outline-buffer)
+ "Update passphrase verifier and hint strings if necessary.
+
+See `allout-passphrase-verifier-string' and `allout-passphrase-hint-string'
+settings.
+
+PASSPHRASE is the passphrase being mnemonicized
+
+OUTLINE-BUFFER is the buffer of the outline being adjusted.
+
+These are used to help the user keep track of the passphrase they use for
+symmetric encryption in the file.
+
+Behavior is governed by `allout-passphrase-verifier-handling',
+`allout-passphrase-hint-handling', and also, controlling whether the values
+are preserved on Emacs local file variables,
+`allout-enable-file-variable-adjustment'."
+
+ ;; If passphrase doesn't agree with current verifier:
+ ;; - adjust the verifier
+ ;; - if passphrase hint handling is enabled, adjust the passphrase hint
+ ;; - if file var settings are enabled, adjust the file vars
+
+ (let* ((new-verifier-needed (not (allout-verify-passphrase
+ for-key passphrase outline-buffer)))
+ (new-verifier-string
+ (if new-verifier-needed
+ ;; Collapse to a single line and enclose in string quotes:
+ (subst-char-in-string
+ ?\n ?\C-a (allout-create-encryption-passphrase-verifier
+ passphrase))))
+ new-hint)
+ (when new-verifier-string
+ ;; do the passphrase hint first, since it's interactive
+ (when (and allout-passphrase-hint-handling
+ (not (equal allout-passphrase-hint-handling 'disabled)))
+ (setq new-hint
+ (read-from-minibuffer "Passphrase hint to jog your memory: "
+ allout-passphrase-hint-string))
+ (when (not (string= new-hint allout-passphrase-hint-string))
+ (setq allout-passphrase-hint-string new-hint)
+ (allout-adjust-file-variable "allout-passphrase-hint-string"
+ allout-passphrase-hint-string)))
+ (when allout-passphrase-verifier-handling
+ (setq allout-passphrase-verifier-string new-verifier-string)
+ (allout-adjust-file-variable "allout-passphrase-verifier-string"
+ allout-passphrase-verifier-string))
+ )
+ )
+ )
+;;;_ > allout-get-encryption-passphrase-verifier ()
+(defun allout-get-encryption-passphrase-verifier ()
+ "Return text of the encrypt passphrase verifier, unmassaged, or nil if none.
+
+Derived from value of `allout-passphrase-verifier-string'."
+
+ (let ((verifier-string (and (boundp 'allout-passphrase-verifier-string)
+ allout-passphrase-verifier-string)))
+ (if verifier-string
+ ;; Return it uncollapsed
+ (subst-char-in-string ?\C-a ?\n verifier-string))
+ )
+ )
+;;;_ > allout-verify-passphrase (key passphrase allout-buffer)
+(defun allout-verify-passphrase (key passphrase allout-buffer)
+ "True if passphrase successfully decrypts verifier, nil otherwise.
+
+\"Otherwise\" includes absence of passphrase verifier."
+ (save-excursion
+ (set-buffer allout-buffer)
+ (and (boundp 'allout-passphrase-verifier-string)
+ allout-passphrase-verifier-string
+ (allout-encrypt-string (allout-get-encryption-passphrase-verifier)
+ 'decrypt allout-buffer 'symmetric
+ key nil 0 'verifying passphrase)
+ t)))
+;;;_ > allout-next-topic-pending-encryption (&optional except-mark)
+(defun allout-next-topic-pending-encryption (&optional except-mark)
+ "Return the point of the next topic pending encryption, or nil if none.
+
+EXCEPT-MARK identifies a point whose containing topics should be excluded
+from encryption. This supports 'except-current mode of
+`allout-encrypt-unencrypted-on-saves'.
+
+Such a topic has the allout-topic-encryption-bullet without an
+immediately following '*' that would mark the topic as being encrypted. It
+must also have content."
+ (let (done got content-beg)
+ (while (not done)
+
+ (if (not (re-search-forward
+ (format "\\(\\`\\|\n\\)%s *%s[^*]"
+ (regexp-quote allout-header-prefix)
+ (regexp-quote allout-topic-encryption-bullet))
+ nil t))
+ (setq got nil
+ done t)
+ (goto-char (setq got (match-beginning 0)))
+ (if (looking-at "\n")
+ (forward-char 1))
+ (setq got (point)))
+
+ (cond ((not got)
+ (setq done t))
+
+ ((not (search-forward "\n"))
+ (setq got nil
+ done t))
+
+ ((eobp)
+ (setq got nil
+ done t))
+
+ (t
+ (setq content-beg (point))
+ (backward-char 1)
+ (allout-end-of-subtree)
+ (if (or (<= (point) content-beg)
+ (and except-mark
+ (<= content-beg except-mark)
+ (>= (point) except-mark)))
+ ;; Continue looking
+ (setq got nil)
+ ;; Got it!
+ (setq done t)))
+ )
+ )
+ (if got
+ (goto-char got))
+ )
+ )
+;;;_ > allout-encrypt-decrypted (&optional except-mark)
+(defun allout-encrypt-decrypted (&optional except-mark)
+ "Encrypt topics pending encryption except those containing exemption point.
+
+EXCEPT-MARK identifies a point whose containing topics should be excluded
+from encryption. This supports 'except-current mode of
+`allout-encrypt-unencrypted-on-saves'.
+
+If a topic that is currently being edited was encrypted, we return a list
+containing the location of the topic and the location of the cursor just
+before the topic was encrypted. This can be used, eg, to decrypt the topic
+and exactly resituate the cursor if this is being done as part of a file
+save. See `allout-encrypt-unencrypted-on-saves' for more info."
+
+ (interactive "p")
+ (save-excursion
+ (let* ((current-mark (point-marker))
+ (current-mark-position (marker-position current-mark))
+ was-modified
+ bo-subtree
+ editing-topic editing-point)
+ (goto-char (point-min))
+ (while (allout-next-topic-pending-encryption except-mark)
+ (setq was-modified (buffer-modified-p))
+ (when (save-excursion
+ (and (boundp 'allout-encrypt-unencrypted-on-saves)
+ allout-encrypt-unencrypted-on-saves
+ (setq bo-subtree (re-search-forward "$"))
+ (not (allout-hidden-p))
+ (>= current-mark (point))
+ (allout-end-of-current-subtree)
+ (<= current-mark (point))))
+ (setq editing-topic (point)
+ ;; we had to wait for this 'til now so prior topics are
+ ;; encrypted, any relevant text shifts are in place:
+ editing-point (- current-mark-position
+ (count-trailing-whitespace-region
+ bo-subtree current-mark-position))))
+ (allout-toggle-subtree-encryption)
+ (if (not was-modified)
+ (set-buffer-modified-p nil))
+ )
+ (if (not was-modified)
+ (set-buffer-modified-p nil))
+ (if editing-topic (list editing-topic editing-point))
+ )
+ )
+ )
+