]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
import: Trust subkeys included in signature
authorKai Lüke <kai@amutable.com>
Wed, 29 Apr 2026 04:57:56 +0000 (13:57 +0900)
committerKai Lüke <kai@amutable.com>
Wed, 1 Jul 2026 13:02:15 +0000 (22:02 +0900)
With gpg sub keys one can rotate signing keys while having a stable
trust anchor. So far one still had to ship the sub key out of band but
a newer gpg has the option to include the sub key in the signature and
import it automatically. This is safe if we only allow importing a sub
key signed by the top key we already have in the key ring.
Add the --auto-key-import argument to gpg to import subkeys but also
set --import-options=merge-only,import-clean to restrict what we import
to only be sub keys signed by the top key we have in the keyring and
discard any irrelevant parts. The ugly part is that we also have to
work on a temporary copy of the keyring because gpg wants to persist
the added key material but we don't what that here.

README
src/import/pull-common.c

diff --git a/README b/README
index 5553915be00216ad1e15211d0347e6e42d147b2b..4d03c8f0122000c7b640249354cf01cb58d19896 100644 (file)
--- a/README
+++ b/README
@@ -270,6 +270,8 @@ REQUIREMENTS:
                 NOTE: If using dbus < 1.9.18, you should override the default
                 policy directory (--with-dbuspolicydir=/etc/dbus-1/system.d).
         polkit (optional)
+        gnupg >= 2.4 optional (for signature verification in: importctl,
+                               systemd-sysupdate)
 
         To build in directory build/:
           meson setup build/ && ninja -C build/
index a2629392d0806dfac27a558757b4d988320d5f0b..9799ae959df33e56b92cc5a12337107364ce58d1 100644 (file)
@@ -5,6 +5,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "copy.h"
 #include "dirent-util.h"
 #include "escape.h"
 #include "fd-util.h"
@@ -409,7 +410,8 @@ static int verify_gpg(
         _cleanup_(rm_rf_physical_and_freep) char *gpg_home = NULL;
         char sig_file_path[] = "/tmp/sigXXXXXX";
         _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
-        const char *keyring_override;
+        _cleanup_free_ char *keyring_copy = NULL;
+        const char *keyring_override, *keyring_source;
         int r;
 
         assert(iovec_is_valid(payload));
@@ -446,6 +448,31 @@ static int verify_gpg(
                 goto finish;
         }
 
+        if (keyring_override)
+                keyring_source = keyring_override;
+        else if (access(USER_KEYRING_PATH, F_OK) >= 0)
+                keyring_source = USER_KEYRING_PATH;
+        else if (access(USER_KEYRING_PATH_LEGACY, F_OK) >= 0)
+                keyring_source = USER_KEYRING_PATH_LEGACY;
+        else
+                keyring_source = VENDOR_KEYRING_PATH;
+
+        /* For whatever reason gpg --auto-key-import writes the key material embedded in the signature
+         * (the signing subkey) back into the keyring it verifies against instead of just using it
+         * temporarily. Verify against a throwaway copy in the tmp home so we never write to the
+         * original keyring. */
+        keyring_copy = path_join(gpg_home, "keyring.gpg");
+        if (!keyring_copy) {
+                r = log_oom();
+                goto finish;
+        }
+
+        r = copy_file(keyring_source, keyring_copy, 0, 0600, /* copy_flags= */ 0);
+        if (r < 0) {
+                log_error_errno(r, "Failed to copy keyring '%s': %m", keyring_source);
+                goto finish;
+        }
+
         r = pidref_safe_fork_full(
                         "(gpg)",
                         (int[]) { gpg_pipe[0], -EBADF, STDERR_FILENO },
@@ -463,6 +490,8 @@ static int verify_gpg(
                         "--no-auto-check-trustdb",
                         "--batch",
                         "--trust-model=always",
+                        "--auto-key-import",
+                        "--import-options=merge-only,import-clean",
                         NULL, /* --homedir= */
                         NULL, /* --keyring= */
                         NULL, /* --verify */
@@ -475,18 +504,7 @@ static int verify_gpg(
                 /* Child */
 
                 cmd[k++] = strjoina("--homedir=", gpg_home);
-
-                if (keyring_override)
-                        cmd[k++] = strjoina("--keyring=", keyring_override);
-                else if (access(USER_KEYRING_PATH, F_OK) >= 0) /* We add the user keyring only to the
-                                                                * command line arguments, if it's around
-                                                                * since gpg fails otherwise. */
-                        cmd[k++] = "--keyring=" USER_KEYRING_PATH;
-                else if (access(USER_KEYRING_PATH_LEGACY, F_OK) >= 0)
-                        cmd[k++] = "--keyring=" USER_KEYRING_PATH_LEGACY;
-                else
-                        cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
-
+                cmd[k++] = strjoina("--keyring=", keyring_copy);
                 cmd[k++] = "--verify";
                 if (signature) {
                         cmd[k++] = sig_file_path;