summaryrefslogtreecommitdiff
path: root/lisp/org/org-crypt.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org/org-crypt.el')
-rw-r--r--lisp/org/org-crypt.el231
1 files changed, 137 insertions, 94 deletions
diff --git a/lisp/org/org-crypt.el b/lisp/org/org-crypt.el
index 4b46a3145f4..187560c5538 100644
--- a/lisp/org/org-crypt.el
+++ b/lisp/org/org-crypt.el
@@ -1,14 +1,8 @@
;;; org-crypt.el --- Public Key Encryption for Org Entries -*- lexical-binding: t; -*-
+;;
;; Copyright (C) 2007-2020 Free Software Foundation, Inc.
-;; Emacs Lisp Archive Entry
-;; Filename: org-crypt.el
-;; Keywords: org-mode
;; Author: John Wiegley <johnw@gnu.org>
-;; Maintainer: Peter Jones <pjones@pmade.com>
-;; Description: Adds public key encryption to Org buffers
-;; URL: http://www.newartisans.com/software/emacs.html
-;; Compatibility: Emacs22
;; This file is part of GNU Emacs.
;;
@@ -47,9 +41,7 @@
;;
;; 3. To later decrypt an entry, use `org-decrypt-entries' or
;; `org-decrypt-entry'. It might be useful to bind this to a key,
-;; like C-c C-/. I hope that in the future, C-c C-r can be might
-;; overloaded to also decrypt an entry if it's encrypted, since
-;; that fits nicely with the meaning of "reveal".
+;; like C-c C-/.
;;
;; 4. To automatically encrypt all necessary entries when saving a
;; file, call `org-crypt-use-before-save-magic' after loading
@@ -60,10 +52,11 @@
;; - Carsten Dominik
;; - Vitaly Ostanin
-(require 'org)
-
;;; Code:
+(require 'org-macs)
+(require 'org-compat)
+
(declare-function epg-decrypt-string "epg" (context cipher))
(declare-function epg-list-keys "epg" (context &optional name mode))
(declare-function epg-make-context "epg"
@@ -74,6 +67,17 @@
(context plain recipients &optional sign always-trust))
(defvar epg-context)
+(declare-function org-back-over-empty-lines "org" ())
+(declare-function org-back-to-heading "org" (&optional invisible-ok))
+(declare-function org-before-first-heading-p "org" ())
+(declare-function org-end-of-meta-data "org" (&optional full))
+(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
+(declare-function org-entry-get "org" (pom property &optional inherit literal-nil))
+(declare-function org-flag-subtree "org" (flag))
+(declare-function org-make-tags-matcher "org" (match))
+(declare-function org-previous-visible-heading "org" (arg))
+(declare-function org-scan-tags "org" (action matcher todo-only &optional start-level))
+(declare-function org-set-property "org" (property value))
(defgroup org-crypt nil
"Org Crypt."
@@ -90,9 +94,18 @@ See the \"Match syntax\" section of the org manual for more details."
(defcustom org-crypt-key ""
"The default key to use when encrypting the contents of a heading.
-This setting can also be overridden in the CRYPTKEY property."
- :type 'string
- :group 'org-crypt)
+If this variable is nil, always use symmetric encryption, unconditionally.
+
+Otherwise, The string is matched against all keys in the key ring.
+In particular, the empty string matches no key. If no key is found,
+look for the `epa-file-encrypt-to' local variable. Ultimately fall back
+to symmetric encryption.
+
+This setting can be overridden in the CRYPTKEY property."
+ :group 'org-crypt
+ :type '(choice
+ (string :tag "Public key(s) matching")
+ (const :tag "Symmetric encryption" nil)))
(defcustom org-crypt-disable-auto-save 'ask
"What org-decrypt should do if `auto-save-mode' is enabled.
@@ -118,6 +131,36 @@ nil : Leave auto-save-mode enabled.
(const :tag "Ask" ask)
(const :tag "Encrypt" encrypt)))
+(defun org-crypt--encrypted-text (beg end)
+ "Return encrypted text in between BEG and END."
+ ;; Ignore indentation.
+ (replace-regexp-in-string
+ "^[ \t]*" ""
+ (buffer-substring-no-properties beg end)))
+
+(defun org-at-encrypted-entry-p ()
+ "Is the current entry encrypted?
+When the entry is encrypted, return a pair (BEG . END) where BEG
+and END are buffer positions delimiting the encrypted area."
+ (org-with-wide-buffer
+ (unless (org-before-first-heading-p)
+ (org-back-to-heading t)
+ (org-end-of-meta-data 'standard)
+ (let ((case-fold-search nil)
+ (banner-start (rx (seq bol
+ (zero-or-more (any "\t "))
+ "-----BEGIN PGP MESSAGE-----"
+ eol))))
+ (when (looking-at banner-start)
+ (let ((start (point))
+ (banner-end (rx (seq bol
+ (or (group (zero-or-more (any "\t "))
+ "-----END PGP MESSAGE-----"
+ eol)
+ (seq (one-or-more "*") " "))))))
+ (when (and (re-search-forward banner-end nil t) (match-string 1))
+ (cons start (line-beginning-position 2)))))))))
+
(defun org-crypt-check-auto-save ()
"Check whether auto-save-mode is enabled for the current buffer.
@@ -149,93 +192,99 @@ See `org-crypt-disable-auto-save'."
(t nil))))
(defun org-crypt-key-for-heading ()
- "Return the encryption key for the current heading."
- (save-excursion
- (org-back-to-heading t)
- (or (org-entry-get nil "CRYPTKEY" 'selective)
- org-crypt-key
- (and (boundp 'epa-file-encrypt-to) epa-file-encrypt-to)
- (message "No crypt key set, using symmetric encryption."))))
-
-(defun org-encrypt-string (str crypt-key)
- "Return STR encrypted with CRYPT-KEY."
- ;; Text and key have to be identical, otherwise we re-crypt.
- (if (and (string= crypt-key (get-text-property 0 'org-crypt-key str))
- (string= (sha1 str) (get-text-property 0 'org-crypt-checksum str)))
- (get-text-property 0 'org-crypt-text str)
- (setq-local epg-context (epg-make-context nil t t))
- (epg-encrypt-string epg-context str (epg-list-keys epg-context crypt-key))))
-
+ "Return the encryption key(s) for the current heading.
+Assume `epg-context' is set."
+ (and org-crypt-key
+ (or (epg-list-keys epg-context
+ (or (org-entry-get nil "CRYPTKEY" 'selective)
+ org-crypt-key))
+ (bound-and-true-p epa-file-encrypt-to)
+ (progn
+ (message "No crypt key set, using symmetric encryption.")
+ nil))))
+
+;;;###autoload
(defun org-encrypt-entry ()
"Encrypt the content of the current headline."
(interactive)
- (require 'epg)
- (org-with-wide-buffer
- (org-back-to-heading t)
- (setq-local epg-context (epg-make-context nil t t))
- (let ((start-heading (point)))
- (org-end-of-meta-data)
- (unless (looking-at-p "-----BEGIN PGP MESSAGE-----")
- (let ((folded (org-invisible-p))
- (crypt-key (org-crypt-key-for-heading))
- (beg (point)))
+ (unless (org-at-encrypted-entry-p)
+ (require 'epg)
+ (setq-local epg-context (epg-make-context nil t t))
+ (org-with-wide-buffer
+ (org-back-to-heading t)
+ (let ((start-heading (point))
+ (crypt-key (org-crypt-key-for-heading))
+ (folded? (org-invisible-p (line-beginning-position))))
+ (org-end-of-meta-data 'standard)
+ (let ((beg (point))
+ (folded-heading
+ (and folded?
+ (save-excursion
+ (org-previous-visible-heading 1)
+ (point)))))
(goto-char start-heading)
(org-end-of-subtree t t)
(org-back-over-empty-lines)
- (let ((contents (delete-and-extract-region beg (point))))
+ (let* ((contents (delete-and-extract-region beg (point)))
+ (key (get-text-property 0 'org-crypt-key contents))
+ (checksum (get-text-property 0 'org-crypt-checksum contents)))
(condition-case err
- (insert (org-encrypt-string contents crypt-key))
+ (insert
+ ;; Text and key have to be identical, otherwise we
+ ;; re-crypt.
+ (if (and (equal crypt-key key)
+ (string= checksum (sha1 contents)))
+ (get-text-property 0 'org-crypt-text contents)
+ (epg-encrypt-string epg-context contents crypt-key)))
;; If encryption failed, make sure to insert back entry
;; contents in the buffer.
- (error (insert contents) (error (nth 1 err)))))
- (when folded
- (goto-char start-heading)
+ (error
+ (insert contents)
+ (error (error-message-string err)))))
+ (when folded-heading
+ (goto-char folded-heading)
(org-flag-subtree t))
nil)))))
+;;;###autoload
(defun org-decrypt-entry ()
"Decrypt the content of the current headline."
(interactive)
- (require 'epg)
- (unless (org-before-first-heading-p)
- (org-with-wide-buffer
- (org-back-to-heading t)
- (let ((heading-point (point))
- (heading-was-invisible-p
- (save-excursion
- (outline-end-of-heading)
- (org-invisible-p))))
- (org-end-of-meta-data)
- (when (looking-at "-----BEGIN PGP MESSAGE-----")
- (org-crypt-check-auto-save)
- (setq-local epg-context (epg-make-context nil t t))
- (let* ((end (save-excursion
- (search-forward "-----END PGP MESSAGE-----")
- (forward-line)
- (point)))
- (encrypted-text (buffer-substring-no-properties (point) end))
- (decrypted-text
- (decode-coding-string
- (epg-decrypt-string
- epg-context
- encrypted-text)
- 'utf-8)))
- ;; Delete region starting just before point, because the
- ;; outline property starts at the \n of the heading.
- (delete-region (1- (point)) end)
- ;; Store a checksum of the decrypted and the encrypted
- ;; text value. This allows reusing the same encrypted text
- ;; if the text does not change, and therefore avoid a
- ;; re-encryption process.
- (insert "\n" (propertize decrypted-text
- 'org-crypt-checksum (sha1 decrypted-text)
- 'org-crypt-key (org-crypt-key-for-heading)
- 'org-crypt-text encrypted-text))
- (when heading-was-invisible-p
- (goto-char heading-point)
- (org-flag-subtree t))
- nil))))))
+ (pcase (org-at-encrypted-entry-p)
+ (`(,beg . ,end)
+ (require 'epg)
+ (setq-local epg-context (epg-make-context nil t t))
+ (org-with-point-at beg
+ (org-crypt-check-auto-save)
+ (let* ((folded-heading
+ (and (org-invisible-p)
+ (save-excursion
+ (org-previous-visible-heading 1)
+ (point))))
+ (encrypted-text (org-crypt--encrypted-text beg end))
+ (decrypted-text
+ (decode-coding-string
+ (epg-decrypt-string epg-context encrypted-text)
+ 'utf-8)))
+ ;; Delete region starting just before point, because the
+ ;; outline property starts at the \n of the heading.
+ (delete-region (1- (point)) end)
+ ;; Store a checksum of the decrypted and the encrypted text
+ ;; value. This allows reusing the same encrypted text if the
+ ;; text does not change, and therefore avoid a re-encryption
+ ;; process.
+ (insert "\n"
+ (propertize decrypted-text
+ 'org-crypt-checksum (sha1 decrypted-text)
+ 'org-crypt-key (org-crypt-key-for-heading)
+ 'org-crypt-text encrypted-text))
+ (when folded-heading
+ (goto-char folded-heading)
+ (org-flag-subtree t))
+ nil)))
+ (_ nil)))
+;;;###autoload
(defun org-encrypt-entries ()
"Encrypt all top-level entries in the current buffer."
(interactive)
@@ -245,6 +294,7 @@ See `org-crypt-disable-auto-save'."
(cdr (org-make-tags-matcher org-crypt-tag-matcher))
org--matcher-tags-todo-only)))
+;;;###autoload
(defun org-decrypt-entries ()
"Decrypt all entries in the current buffer."
(interactive)
@@ -254,14 +304,7 @@ See `org-crypt-disable-auto-save'."
(cdr (org-make-tags-matcher org-crypt-tag-matcher))
org--matcher-tags-todo-only)))
-(defun org-at-encrypted-entry-p ()
- "Is the current entry encrypted?"
- (unless (org-before-first-heading-p)
- (save-excursion
- (org-back-to-heading t)
- (search-forward "-----BEGIN PGP MESSAGE-----"
- (save-excursion (outline-next-heading)) t))))
-
+;;;###autoload
(defun org-crypt-use-before-save-magic ()
"Add a hook to automatically encrypt entries before a file is saved to disk."
(add-hook