Homepage: https://www.gnu.org/software/emacs
Author: Daiki Ueno
Secure plist store
Plist based data store providing search and partial encryption. By default, this library uses symmetric encryption, which means that you have to enter the password protecting your store more often than you probably expect to. To use public key encryption with this library, create a GnuPG key and customize user option `plstore-encrypt-to' to use it. You can then configure the GnuPG agent to adjust caching and expiration of the passphrase for your store. You can read more on these topics in the EasyPG Assistant user's manual (Info node `(epa)'), of which much information also applies to this library. Most notably: - Info node `(epa)GnuPG version compatibility' - Info node `(epa)GnuPG Pinentry' - Info node `(epa)Caching Passphrases' Use only keyword symbols (starting with a colon) as property names in any plist stored with this library. While this library does not actively enforce the use of keyword symbols, it silently assumes that the first character of all property names can be discarded without sacrificing uniqueness of names (FIXME). Likewise, this library does not enforce that the plists provided as input are actually valid and can behave in undefined ways if they are not (FIXME). Creating: ;; Open a new store associated with ~/.emacs.d/auth.plist. (setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist"))) ;; Both `:host' and `:port' are public property. (plstore-put store "foo" '(:host "foo.example.org" :port 80) nil) ;; No encryption will be needed. (plstore-save store) ;; `:user' is marked as secret. (plstore-put store "bar" '(:host "bar.example.org") '(:user "test")) ;; `:password' is marked as secret. (plstore-put store "baz" '(:host "baz.example.org") '(:password "test")) ;; Those secret properties are encrypted together. (plstore-save store) ;; Kill the buffer visiting ~/.emacs.d/auth.plist. (plstore-close store) Avoid marking one property both as public *and* secret, as the behavior of this library with respect to such duplicate properties is not defined (FIXME). Searching: (setq store (plstore-open (expand-file-name "~/.emacs.d/auth.plist"))) ;; As the entry "foo" associated with "foo.example.org" has no ;; secret properties, no need for decryption. (plstore-find store '(:host ("foo.example.org"))) ;; As the entry "bar" associated with "bar.example.org" has a ;; secret property `:user', Emacs tries to decrypt the secret (and ;; thus you will need to input passphrase). (plstore-find store '(:host ("bar.example.org"))) ;; While the entry "baz" associated with "baz.example.org" has also ;; a secret property `:password', it is encrypted together with ;; `:user' of "bar", so no need to decrypt the secret. (plstore-find store '(:host ("baz.example.org"))) (plstore-close store) Editing: This file also provides `plstore-mode', a major mode for editing the plstore format file. Visit a non-existing file and put the following line: (("foo" :host "foo.example.org" :secret-user "user")) where the prefixing `:secret-' means the property (without `:secret-' prefix) is marked as secret. Thus, when you save the buffer, the `:secret-user' property is encrypted as `:user'. Do not use a property consisting solely of the prefix, as the behavior of this library with respect to such properties is not defined (FIXME). You can toggle the view between encrypted form and the decrypted form with C-c C-c. If you have opened a plstore with `plstore-open' you should not edit its underlying buffer in `plstore-mode' or in any other way at the same time, since your manual changes will be overwritten when `plstore-save' is called on that plstore. Internals: This is information on the internal data structure and functions of this library. None of it should be necessary to actually use it. For easier reading, we usually do not distinguish in this internal documentation between a Lisp object and its printed representation. A plstore corresponds to an alist mapping strings to property lists. Internally, that alist is organized as two alists, one mapping to the non-secret properties and placeholders for the secret properties (called "template alist" with identifier ALIST) and one mapping to the secret properties ("secret alist", SECRET-ALIST). The secret alist is read from and written to file as pgp-encrypted printed representation of the alist ("encrypted data", ENCRYPTED-DATA). During the lifetime of a plstore, a third type of alist may pop up, which maps to the merged non-secret properties and plain-text secret properties ("merged alist", MERGED-ALIST). After executing the "foo", "bar", "baz" example from above the alists described above look like the following: Template Alist: (("foo" :host "foo.example.org" :port 80) ("bar" :secret-user t :host "bar.example.org") ("baz" :secret-password t :host "baz.example.org")) Secret Alist: (("bar" :user "test") ("baz" :password "test")) Merged Alist: (("foo" :host "foo.example.org" :port 80) ("bar" :user "test" :host "bar.example.org") ("baz" :password "test" :host "baz.example.org")) Finally, a plstore requires a buffer ("plstore buffer", BUFFER) for conversion between its Lisp objects and its file representation. It is important to note that this buffer is *not* continuously synchronized as the plstore changes. During the lifetime of a plstore, its buffer is read from in function `plstore-open' and (destructively) written to in `plstore-save', but not touched otherwise. We call the file visited by the plstore buffer the associated file of the plstore. With the identifiers defined above a plstore is a vector with the following elements and accessor functions: [ BUFFER ; plstore--get/set-buffer ALIST ; plstore--get/set-alist ENCRYPTED-DATA ; plstore--get/set-encrypted-data SECRET-ALIST ; plstore--get/set-secret-alist MERGED-ALIST ; plstore--get/set-merged-alist ] When a plstore is created through `plstore-open', its ALIST and ENCRYPTED-DATA are initialized from the contents of BUFFER without any decryption taking place, and MERGED-ALIST is initialized as a copy of ALIST. (Which means that at that stage the merged alist still contains the secret property placeholders!) During on-demand decryption of a plstore through function `plstore--decrypt', SECRET-ALIST is populated from ENCRYPTED-DATA, which is in turn replaced by value nil. (Which further serves as an indicator that the plstore has been decrypted already.) In addition, MERGED-ALIST is recomputed by function `plstore--merge-secret' to replace the secret property placeholders by their plain-text secret property equivalents. The file representation of a plstore consists of two Lisp forms plus markers to introduce them: ;;; public entries ALIST ;;; secret entries ENCRYPTED-DATA Both of these are optional, but the first section must be present if the second one is. If both sections are missing, the plstore is empty. If the second section is missing, it contains only non-secret data. If present, the printed representation of the encrypted data includes the delimiting double quotes. The plstore API (`plstore-open', `plstore-put', etc.) and the plstore mode implemented by `plstore-mode' are orthogonal to each other and should not be mixed up. In particular, encoding and decoding a plstore mode buffer with `plstore-mode-toggle-display' is not related in any way to the state of the plstore buffer.