From: Kai Lüke Date: Wed, 29 Apr 2026 04:57:56 +0000 (+0900) Subject: import: Trust subkeys included in signature X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=80e12bf88cd2bba7a0e9294d8259ced45cc6a7db;p=thirdparty%2Fsystemd.git import: Trust subkeys included in signature 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. --- diff --git a/README b/README index 5553915be00..4d03c8f0122 100644 --- 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/ diff --git a/src/import/pull-common.c b/src/import/pull-common.c index a2629392d08..9799ae959df 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -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;