Use Cache of Emacs Auth-Source Interface to Password-Store as Alternative to SSH-Agent for Magit?
Let's say you use Git and sign commits with an SSH key. Your SSH key may have a password you may not want to type in every time you commit. You'd usually launch an SSH-agent. (I guess the same applies to the case when you use a GPG key to sign commits. You'd then spawn a GPG-agent.)
If you use GNU Emacs, Magit as interface to Git, and Auth-Source as interface to Password-Store, you can use the cache of Auth-Source to avoid repeating your password for each commit. Is this a safe from an information-security point of view? Let me know in a comment in Fediverse or per email.
Update: wasamasa explains how the setup described here indeed imposes a security risk. Please check out the comment on Fediverse and the linked article on emacshorrors.com.
Here's how to set it up: Use different SSH keys for different platforms and safe them all in the same directory:
/home/user/.ssh/keys/ ├── codeberg ├── codeberg.pub ├── github └── github.pub
For each of these SSH keys, add an entry to Password-Store with an
additional .ssh
suffix:
$ pass Password Store ├── codeberg.ssh └── github.ssh
Configure Git (repositories) to use one of those SSH keys to sign commits; e.g. for a Github-hosted Git repository that you contribute to:
[commit] gpgsign = true [gpg] format = ssh [user] signingkey = /home/user/.ssh/keys/github
In your Emacs initialization file, make Magit recognize SSH password prompts. Let's first take a look at this docstring:
magit-process-find-password-functions is a variable defined in `magit-process.el'.
List of functions to try in sequence to get a password.
These functions may be called when git asks for a password, which is detected using `magit-process-password-prompt-regexps'. They are called if and only if matching the prompt resulted in the value of the 99th submatch to be non-nil. Therefore users can control for which prompts these functions should be called by putting the host name in the 99th submatch, or not.
Accordingly, we teach Magit to also match the name of the key as 99th submatch:
(require 'magit-process) (require 'rx) (add-to-list 'magit-process-password-prompt-regexps (rx line-start "Enter passphrase for " "\"/home/user/.ssh/keys/" (submatch-n 99 (one-or-more (not (any "\"")))) "\": " line-end)) (add-to-list 'magit-process-password-prompt-regexps (rx line-start "Enter passphrase for key " "'/home/user/.ssh/keys/" (submatch-n 99 (one-or-more (not (any "'")))) "': " line-end))
Now we need to customize the user option
magit-process-find-password-functions
so that the password for the
relevant SSH key (e.g. codeberg
or github
, that had been
recognized as 99th submatch from one of above regular expressions) is
correctly queried from Password-Store. Write an Elisp function and
add its symbol to the mentioned variable: (Note that for some setups
you might be able to use magit-process-password-auth-source
instead
of a new Elisp function.)
(defun magit-process-find-password (key) (when-let* ((secret (auth-source-search :max 1 :host (concat key ".ssh"))) (secret (plist-get (car secret) :secret))) (if (functionp secret) (funcall secret) secret))) (setopt magit-process-find-password-functions (list #'magit-process-find-password))
When you now attempt a Git commit through Magit, you will be asked for
the password of your Password-Store in the minibuffer. (I assume you
set epg-pinentry-mode
variable to 'loopback
like a sane Emacs
user.) If you attempt to a Git commit again soon enough, you'll hit
the cache of Auth-Source and thus won't be prompted for a password.
Sometimes you might want to clear the cache manually with M-x
auth-source-forget-all-cached RET
. Good luck.