Unlock GnuPG keys on login
A PAM module that hands over your login password to gpg-agent
, which can be
useful if you are using a GnuPG-based password manager like
pass.
Requires GnuPG 2.1, and probably only works on Linux.
The code was written mainly by looking at and occasionally copying from Gnome Keyring's PAM module and pam_mount and is based on a somewhat mediocre understanding of the details of both PAM and C. You should be aware that there may be potentially dangerous bugs lurking.
The usual
./autogen.sh
./configure
make
make install
should work. configure
takes an option --with-moduledir
to set the
installation path of the PAM module. It defaults to /lib/security
, but your
distribution might use a different path.
For services that open a new session (gdm, sddm, login, ...), add the lines
auth optional pam_gnupg.so store-only session optional pam_gnupg.so
at the end (¹) of the corresponding file in /etc/pam.d
, or in one of the
files included from there, e.g. system-local-login
on Arch.
When opening the session, gpg-agent will be autostarted if necessary. If you
want to start it by other means, e.g. as a systemd user service, make sure it
is up before pam_gnupg.so
is run, e.g. by putting the lines somewhere below
pam_systemd.so
, and (optionally) add no-autostart
to the session
line.
(¹): The end is usually a good place, but details depend on your PAM setup.
In particular, modules declared sufficient
can terminate the PAM stack
early. At least, pam_gnupg.so
should come after pam_unix.so
,
pam_systemd_home.so
, pam_systemd.so
and pam_env.so
in case you use
those modules.
For services that only authenticate (i3lock, physlock, ...), use
auth optional pam_gnupg.so
For screen lockers, this only really makes sense if you arrange for the password cache to be cleared prior to locking the screen by calling
gpg-connect-agent --no-autostart reloadagent /bye
During authentication, the agent will never be autostarted.
Add
allow-preset-passphrase
to ~/.gnupg/gpg-agent.conf
. Optionally, customize the cache timeout via
max-cache-ttl
, e.g. set
max-cache-ttl 86400
to have it expire after a day.
Run
gpg -K --with-keygrip
The output should look something like this:
sec rsa2048 2018-11-16 [SC]
9AB5DD43C5E5FD40475FA6DA0D776275F7F5B2E7
Keygrip = 6F4ABB77A88E922406BCE6627AFEEE2363914B76
uid [ultimate] Chris Ruegge <mail@cxcs.de>
ssb rsa2048 2018-11-16 [E]
Keygrip = FBDEAD7B0C484CDC85F1CF70352833EB0C921D58
Write the keygrip for the encryption subkey marked [E]
– shown in boldface
in the output above – into ~/.pam-gnupg
. If you want to unlock multiple
keys or subkeys, add all keygrips on separate lines.
Keygrips are exactly 40 characters in length. Leading whitespace, lines
starting with #
and everything after the keygrip is ignored.
If ~/.pam-gnupg
does not exists, $XDG_CONFIG_HOME/pam-gnupg
will be
tried, with XDG_CONFIG_HOME
defaulting to ~/.config
as usual. If you want
to customize this variable, read the section on environment variables below.
Set the same password for your gpg key and your user account. All pam-gnupg does is to send the password as entered to gpg-agent. It is therefore not compatible with auto-login of any kind; you actually have to type your password for things to work.
GNUPGHOME
If you change your gnupg directory from the default ~/.gnupg
by setting
GNUPGHOME
, this variable needs to be made available to pam-gnupg when
presetting. Since PAM usually runs before your init scripts, it needs to obtain
the variable in a different way.
To set it, add the path to the config file on a separate line before any
keygrips, either as absolute path or starting with ~/
for paths relative to
the home directory. The connection to the agent will be opened when the first
keygrip is read, so setting GNUPGHOME
after that will have no effect.
Note that the variable is only used for connecting to and optionally autostarting the agent. It is not passed down to your login shell or desktop session, so you also need to set it in your init scripts. Additionally, if you start the agent via systemd, you need to adjust the various service and socket units separately.
If you use systemd-homed
, you can modify env vars via homectl --setenv
, and
they will be made available to PAM by pam_systemd_home.so
.
Another way is to run pam_env.so
with user_readenv=1
before pam_gnupg.so
,
so you can set env vars from ~/.pam_environment
, e.g.
GNUPGHOME DEFAULT=@{HOME}/path/to/your/gnupg
You can also modify XDG_CONFIG_HOME
this way. Unfortunately, user_readenv
is
deprecated and will go away in some future version of pam_env
.
SSH key support is indirect via gpg-agent's built-in SSH support (there's no
SSH specific code in pam-gnupg). The full details are in gpg-agent(1)
, but
here's a basic step-by-step guide:
Add
enable-ssh-support
to ~/.gnupg/gpg-agent.conf
. (This is not actually strictly necessary in all
setups, but doesn't hurt either.)
Set the SSH_AUTH_SOCK
variable to gpg-agent's SSH socket by putting
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
into your relevant init script.
Add your SSH keys to the agent using ssh-add
as usual. This only needs to
be done once. The agent will re-encrypt the keys and store them in
~/.gnupg
, independent of the ones in ~/.ssh
.
Get the SSH keygrips using
gpg-connect-agent 'keyinfo --ssh-list' /bye
The output should look like
S KEYINFO DBB0B60CFE5F23716ABEE8787C6184C27E2486E1 D - - - P - - S OK
with one keygrip per line. Alternatively, get them from ~/.gnupg/sshcontrol
.
Add the keygrips to ~/.pam-gnupg
the same way as for the gpg keys.
Both the auth
and the session
module take a debug
option to enable some
basic debug logging to syslog / journal.
Using pass
during startup of systemd user services has a racing condition
even if the service declares After=gpg-agent.socket
, because systemd does
not know about pam-gnupg and will start the service right after the socket is
up, but maybe before the key has been unlocked. Until I figure out a cleaner
solution, you can circumvent this by adding a small startup delay to the
service, e.g.
ExecStartPre=/usr/bin/sleep 5
Screen lockers need to call pam_setcred
after authentication to actually
send the passphrase. Those who don't will not work with pam-gnupg.
Specifically for suckless' slock with the
pam-auth
patch,
you have to set user
and group
to your user name and your primary group
(as displayed by id -gn
) in slock's config.h
, which will therefore not work for multiple users. Alternatively, you can try the (untested) steps outlined in this issue comment.
gpg -K
output above is real, so the second line is the actual fingerprint.