]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
New PAM module: pam_systemd_loadkey 29776/head
authorJin Liu <m.liu.jin@gmail.com>
Tue, 31 Oct 2023 04:48:24 +0000 (12:48 +0800)
committerJin Liu <m.liu.jin@gmail.com>
Tue, 31 Oct 2023 10:20:23 +0000 (18:20 +0800)
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).

man/pam_systemd_loadkey.xml [new file with mode: 0644]
man/rules/meson.build
src/login/meson.build
src/login/pam_systemd_loadkey.c [new file with mode: 0644]
src/login/pam_systemd_loadkey.sym [new file with mode: 0644]

diff --git a/man/pam_systemd_loadkey.xml b/man/pam_systemd_loadkey.xml
new file mode 100644 (file)
index 0000000..afb41f3
--- /dev/null
@@ -0,0 +1,99 @@
+<?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>
index 106b80f3149dc180522efd7ffc5477dc143093f8..137696d7cf561912d3768371690a6ba8650481c8 100644 (file)
@@ -65,6 +65,7 @@ manpages = [
  ['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'],
index 6fb09b48e2a9b9e89fdc94be6defedbbf356f47d..b5bb1502584778a3bd5c2299d9c1c5d79beb5b0d 100644 (file)
@@ -114,6 +114,14 @@ modules += [
                 '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
diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c
new file mode 100644 (file)
index 0000000..3b4e911
--- /dev/null
@@ -0,0 +1,98 @@
+/* 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;
+}
diff --git a/src/login/pam_systemd_loadkey.sym b/src/login/pam_systemd_loadkey.sym
new file mode 100644 (file)
index 0000000..d611dc1
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+{
+global:
+        pam_sm_authenticate;
+        pam_sm_setcred;
+local: *;
+};