This module reads password from kernel keyring and sets it as PAM authtok.
It's inspired by gdm's pam_gdm, which reads the LUKS password stored by
systemd-cryptsetup, so Gnome Keyring can be automatically unlocked if set
to the same password (when autologin is enabled so the user doesn't enter
a password in gdm).
--- /dev/null
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="pam_systemd_loadkey" conditional='HAVE_PAM' xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>pam_systemd_loadkey</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>pam_systemd_loadkey</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>pam_systemd_loadkey</refname>
+ <refpurpose>Read password from kernel keyring and set it as PAM authtok</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>pam_systemd_loadkey.so</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>pam_systemd_loadkey</command> reads a NUL-separated password list from the kernel keyring,
+ and sets the last password in the list as the PAM authtok.</para>
+
+ <para>The password list is supposed to be stored in the "user" keyring of the root user,
+ by an earlier call to
+ <citerefentry><refentrytitle>systemd-ask-password</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ with <option>--keyname=</option>.
+ You can pass the keyname to <command>pam_systemd_loadkey</command> via the <option>keyname=</option> option.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist class='pam-directives'>
+
+ <varlistentry>
+ <term><varname>keyname=</varname></term>
+
+ <listitem><para>Takes a string argument which sets the keyname to read.
+ The default is <literal>cryptsetup</literal>, which is used by
+ <citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ to store LUKS passphrase during boot.</para>
+
+ <xi:include href="version-info.xml" xpointer="v255"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>debug</varname></term>
+
+ <listitem><para>The module will log debugging information as it operates.</para>
+
+ <xi:include href="version-info.xml" xpointer="v255"/></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+
+ <para>This module is intended to be used when you use LUKS with a passphrase, enable autologin in the display
+ manager, and want to unlock Gnome Keyring / KDE KWallet automatically. So in total, you only enter one password
+ during boot.</para>
+
+ <para>You need to set the password of your Gnome Keyring/KWallet to the same as your LUKS passphrase.
+ Then add the following lines to your display manager's PAM config under <filename>/etc/pam.d/</filename> (e.g. <filename>sddm-autologin</filename>):</para>
+
+ <programlisting>
+-auth optional pam_systemd_loadkey.so
+-session optional pam_gnome_keyring.so auto_start
+-session optional pam_kwallet5.so auto_start
+ </programlisting>
+
+ <para>And add the following lines to your display manager's systemd service file, so it can access root's keyring:</para>
+
+ <programlisting>
+[Service]
+KeyringMode=inherit
+ </programlisting>
+
+ <para>In this setup, early during the boot process,
+ <citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ will ask for the passphrase and store it in the kernel keyring with the keyname <literal>cryptsetup</literal>.
+ Then when the display manager does the autologin, pam_systemd_loadkey will read the passphrase from the kernel keyring,
+ set it as the PAM authtok, and then pam_gnome_keyring and pam_kwallet5 will unlock with the same passphrase.</para>
+ </refsect1>
+
+</refentry>
['org.freedesktop.systemd1', '5', [], ''],
['org.freedesktop.timedate1', '5', [], 'ENABLE_TIMEDATED'],
['os-release', '5', ['extension-release', 'initrd-release'], ''],
+ ['pam_systemd_loadkey', '8', [], 'HAVE_PAM'],
['pam_systemd', '8', [], 'HAVE_PAM'],
['pam_systemd_home', '8', [], 'ENABLE_PAM_HOME'],
['portablectl', '1', [], 'ENABLE_PORTABLED'],
'sources' : files('pam_systemd.c'),
'version-script' : meson.current_source_dir() / 'pam_systemd.sym',
},
+ pam_template + {
+ 'name' : 'pam_systemd_loadkey',
+ 'conditions' : [
+ 'HAVE_PAM',
+ ],
+ 'sources' : files('pam_systemd_loadkey.c'),
+ 'version-script' : meson.current_source_dir() / 'pam_systemd_loadkey.sym',
+ },
]
enable_logind = conf.get('ENABLE_LOGIND') == 1
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <security/_pam_macros.h>
+#include <security/pam_ext.h>
+#include <security/pam_misc.h>
+#include <security/pam_modules.h>
+#include <security/pam_modutil.h>
+
+#include "keyring-util.h"
+#include "macro.h"
+#include "missing_syscall.h"
+#include "nulstr-util.h"
+#include "pam-util.h"
+#include "strv.h"
+
+/* By default, this module retrieves the key stored by systemd-cryptsetup.
+ * This can be overridden by the keyname= parameter. */
+static const char DEFAULT_KEYNAME[] = "cryptsetup";
+
+_public_ int pam_sm_authenticate(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ assert(handle);
+
+ /* Parse argv. */
+
+ assert(argc >= 0);
+ assert(argc == 0 || argv);
+
+ const char *keyname = DEFAULT_KEYNAME;
+ bool debug = false;
+
+ for (int i = 0; i < argc; i++) {
+ const char *p;
+
+ if ((p = startswith(argv[i], "keyname=")))
+ keyname = p;
+ else if (streq(argv[i], "debug"))
+ debug = true;
+ else
+ pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]);
+ }
+
+ pam_debug_syslog(handle, debug, "pam-systemd-loadkey initializing");
+
+ /* Retrieve the key. */
+
+ key_serial_t serial;
+ serial = request_key("user", keyname, NULL, 0);
+ if (serial < 0) {
+ if (errno == ENOKEY) {
+ pam_debug_syslog(handle, debug, "Key not found: %s", keyname);
+ return PAM_AUTHINFO_UNAVAIL;
+ } else if (errno == EKEYEXPIRED) {
+ pam_debug_syslog(handle, debug, "Key expired: %s", keyname);
+ return PAM_AUTHINFO_UNAVAIL;
+ } else
+ return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to look up the key: %m");
+ }
+
+ _cleanup_(erase_and_freep) void *p = NULL;
+ size_t n;
+ int r;
+
+ r = keyring_read(serial, &p, &n);
+ if (r < 0)
+ return pam_syslog_errno(handle, LOG_ERR, r, "Failed to read the key: %m");
+
+ /* Split the key by NUL. Set the last item as authtok. */
+
+ _cleanup_(strv_free_erasep) char **passwords = strv_parse_nulstr(p, n);
+ if (!passwords)
+ return pam_log_oom(handle);
+
+ size_t passwords_len = strv_length(passwords);
+ if (passwords_len == 0) {
+ pam_debug_syslog(handle, debug, "Key is empty");
+ return PAM_AUTHINFO_UNAVAIL;
+ } else if (passwords_len > 1)
+ pam_debug_syslog(handle, debug, "Multiple passwords found in the key. Using the last one");
+
+ r = pam_set_item(handle, PAM_AUTHTOK, passwords[passwords_len - 1]);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@");
+
+ return PAM_SUCCESS;
+}
+
+_public_ int pam_sm_setcred(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ return PAM_SUCCESS;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+{
+global:
+ pam_sm_authenticate;
+ pam_sm_setcred;
+local: *;
+};