Org crypt and LOGBOOK: how they can work together for a secure agenda.
Too long; didn't read
Do you use org-crypt to protect your agenda headlines? Do you clock in
your tasks to measure how much time you spend on them? Well if you
edit org-property-drawer-re
dynamically you will spare some of the
clumsiness of org-decrypt (please see the last code snippet at the
bottom for the running solution). Be careful to not store sensitive
information in the heading drawers though, because org-crypt will
leave them in plain text.
The problem
I started using org-crypt recently. This is a mode that substitutes
the contents of your heading with a PGP hash for entries you tag
:crypt:
. The result is amazing: protected agenda files, files still
in plain text (so git can handle them with no problem), and even
compressed in size (the hash reduces the text and so the size of the
file)!
Anyway, a little bug keeps bothering me: sometimes I find I cannot decrypt an headline. When I try to make it visible, it stays encrypted.
The problem is that if I reschedule an entry, I set Org Mode to add
a logbook drawer with the created time of the entry, but then
org-crypt-decrypt-entry
runs on the wrong line.
So for example this entry will stay encrypted after I run the function on it:
* TODO some test heading :crypt: SCHEDULED: <2020-08-23 Sun> :PROPERTIES: :CREATED: [2020-08-22 Sat 16:05] :END: :LOGBOOK: - Rescheduled from "[2020-08-22 Sat]" on [2020-08-22 Sat 16:05] :END: -----BEGIN PGP MESSAGE----- hQEMAwE6qFgoORDaAQgAgzT7pr5uvL6X1KU4WH7yYyk4h0ITtuVT2sT3O0qSlQyN YXv7YQ0MvXSLXxUaXqm+a81cMmox3k13ifAT5/t9rcympNAYOuvWjOXsNA85uglT ZO3NExSJ8jhdcI/NlPLqBxioUEDGEXBo5nBQxrheD3/+j5tlTAwUZM7xPcj7bYcD mq6hVj3PigDO/E+e1LYfRJfVH6nszrYnF36dlONPDlRp2pGyODXM455bwCrWe1WM WuDsPkQN621Ga5P07yXzQYDjQNcBoeFLGQUB7udKutl2g0DyYvTECfUUY2Zx3bnt Noq1wamAmpNMtJru8oZsuLKZ7a77rWkSvOETqKEr6NJJAamY8oUCuDdw2+BMnuDr JGITMd9cWHLS758e0c0x9Gmm7ntn55816RN0qeaCOIw9ap5ie8NxcU4dwDNWh5y9 1iruu+PK9gk2Zg== =qpA1 -----END PGP MESSAGE-----
(You imagine the cold sweating the first time this happened.)
It is a problem indeed
In practice this means that given you use LOGBOOK drawers, any rescheduling, clocking in and out would result in making your entry difficult to decrypt. The immediate workaround is to delete the drawer and try to decrypt again, but that information is useful and I would rather avoid this clumsiness! So exploration time: let's fix this and save any reader the headache.
And there is a solution
Let's examine the function:
(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-----") ; some decrypting logic ;... nil))))))
So roughly we:
- go back to the heading
(org-back-to-heading t)
- move to the end of the
:PROPERTIES:
drawer(org-end-of-meta-data)
- decrypt if we are on the regexp
(when (looking-at "-----BEGIN PGP MESSAGE-----")
Maybe we found a bug in Org Mode? Let's look at org-end-of-meta-data
:
(defun org-end-of-meta-data (&optional full) "Skip planning line and properties drawer in current entry. When optional argument FULL is non-nil, also skip empty lines, clocking lines and regular drawers at the beginning of the entry." (org-back-to-heading t) (forward-line) (when (looking-at-p org-planning-line-re) (forward-line)) (when (looking-at org-property-drawer-re) (goto-char (match-end 0)) (forward-line)) (when (and full (not (org-at-heading-p))) ;; we do not care about the rest, it seems error handling... ))
We move after the property drawer in case we run the bit of code
(looking-at org-property-drawer-re)
. So the problem here is that the
org-property-drawer-re
by default excludes the regexp for
:LOGBOOK:
This seems to nail the problem, but before attempting to fix the function let's think. I found the problem with decryption, but what about encryption?
Let's then look at org-encrypt-entry
:
(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-----") ;... ))))
Mmm, same pattern same problem. It seems that if we fix
(org-end-of-meta-data)
we will indeed fix also the encryption of the
entry.
Now let's try to fix it.
First let's set the right regexp for :LOGBOOK:
. We can do that by
copying the elders and sit on their shoulders (or also just creative
copy-pasting).
Let's inspect the current org-property-drawer-re
value:
^[ ]*:PROPERTIES:[ ]* \(?:[ ]*:\S-+:\(?: .*\)?[ ]* \)*?[ ]*:END:[ ]*$
So most likely adding a new entry that has LOGBOOK instead of
PROPERTIES will make the trick. Time to use regexp-builder
, the
amazing mode to test your regexp live. After a bit of playing around
with it, this is the regexp:
(setq org-property-drawer-re (concat "^[ ]*:[A-Z]*:[ ]*\n" "[[:ascii:]]*" "[ \t]*:END:[ \t]*$"))
The original regexp only matches PROPERTIES
drawers that contains
entries structured like "x:y". I want instead to match
":ANYTHING:anything:END:", where the first anything could be
"PROPERTIES" or "LOGBOOK" or "BLA", and the second "anything" can
be any text enclosed within.
And indeed this fixes the org-crypt problem. However this breaks other functionalities in Org Mode: for example adding a note now keeps creating new LOGBOOK drawers. This calls for an advice (good old aspect oriented programming):
(defun my/with-catching-all-drawers (fn) (let ((org-property-drawer-re (concat "^[ ]*:[A-Z]*:[ ]*\n" "[^*]*" "[ \t]*:END:[ \t]*$"))) (funcall fn))) (advice-add 'org-encrypt-entry :around 'my/with-catching-all-drawers) (advice-add 'org-decrypt-entry :around 'my/with-catching-all-drawers)
Now our regexp applies only in the context of org-encrypt, and other Org Mode functionalities can still work safely.
Conclusion
So give a try and see if this makes your workflow smoother. Just setup org-crypt and run the last snippet of elisp will make your day!
Note that a concern with this solution is that now notes added to a
task tagged with :crypt:
stay as plain text, which may be dangerous
if you store sensitive information in them. So just make sure to avoid
storing your passwords in headlines notes... Use a password manager
for that :)
Be fabulous!