Fix dnf_keyring_add_public_key() to add multiple keys from a single file

Resolves: RHEL-156063
This commit is contained in:
Petr Písař 2026-04-09 15:56:37 +02:00
parent 3c1fc4f3a0
commit 867d98ee9f
7 changed files with 1296 additions and 1 deletions

View File

@ -0,0 +1,307 @@
From 72252383942769f8ad7858e3d5abe2cb64488371 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Fri, 13 Mar 2026 17:40:15 +0100
Subject: [PATCH 25/30] tests: Add tests for dnf_keyring_add_public_key()
Upstream commit: dc3f0098e30a9154f530ea4fc31c500927a7ad01
This patch covers a behaviour of the current code.
---
CMakeLists.txt | 4 +
data/tests/dnf_keyring_add_public_key/input | 1 +
.../dnf_keyring_add_public_key/input.rsa.sig | 16 ++
data/tests/dnf_keyring_add_public_key/rsa.pub | 28 +++
tests/libdnf/dnf-self-test.c | 178 ++++++++++++++++++
5 files changed, 227 insertions(+)
create mode 100644 data/tests/dnf_keyring_add_public_key/input
create mode 100644 data/tests/dnf_keyring_add_public_key/input.rsa.sig
create mode 100644 data/tests/dnf_keyring_add_public_key/rsa.pub
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6444c374..601a0e6a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,6 +65,10 @@ pkg_check_modules(REPO REQUIRED librepo>=1.15.0)
include_directories(${REPO_INCLUDE_DIRS})
link_directories(${REPO_LIBRARY_DIRS})
pkg_check_modules(RPM REQUIRED rpm>=4.15.0)
+if (RPM_VERSION VERSION_GREATER_EQUAL "5.99.90")
+ add_definitions(-DRPM_HAS_KEYIDASHEX)
+endif()
+
pkg_check_modules(SMARTCOLS REQUIRED smartcols)
pkg_check_modules(SQLite3 REQUIRED sqlite3)
diff --git a/data/tests/dnf_keyring_add_public_key/input b/data/tests/dnf_keyring_add_public_key/input
new file mode 100644
index 00000000..8e27be7d
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/input
@@ -0,0 +1 @@
+text
diff --git a/data/tests/dnf_keyring_add_public_key/input.rsa.sig b/data/tests/dnf_keyring_add_public_key/input.rsa.sig
new file mode 100644
index 00000000..1a7b17a2
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/input.rsa.sig
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCgAdFiEEmwLYgf5BhenqUueIiM2DpLXlaUUFAmmz/J4ACgkQiM2DpLXl
+aUVvjBAAzHQ3zM2rF8Wl7O8Gpl0N2Kk38vrggX10BAAOr1Wzk3mdt1DNFvd14NBQ
++kTxRV/aJeT4Ke1ANmoFOWKVxC4tzk7+uuggCG/DspanvwsG+//V9myQjEnJa90S
+iBGOc1Lav1KjmqL0gfaFPFAYmCjIKMD5tFGTS6RFvhMe4ubcxXwCPIZ3HBvgWbV7
+ytREarfh/yF/piPr5HTS/4FhBXM6zN1AzdOjUSE47tySwg86P6q9uhi3UBNU9gDd
+OFoG6S226O2Ei0yXh/3I5LkFGk6MSY+WWsmQVrF5Sjl1gelpQItkDOXguCc33e61
+cIvAbpxQQTesYJCeAKSqjumU6JbsEMHxlei9gFDJN222gbne43kKJaO6L+AUwzqX
+cgZcD/7QUgibQZ/oOHt4TAtjMBAx5mpuehS9YocS66NcDgaJwIesKLGzcZ4x9s+B
+xZzOLacf/cqiw1QPxqg17TezIluGrOcmlOvcGF+OosiTQ9NXm3pFWhiltYducXrI
+4RrHDsVK8omTbGOv1s07fxOD/Xfz0KcCiMkQ+PF8lk/xBp/J3jDRvYBnZ51WV1b5
+LIshhdfBgz3hDrwnFLFzhFmOhcQeadlExwHkI1PVe9QfvEUjB4Q8SlnGp93NlFey
+MepVIf+b6t/SSxfZ6RR9UgWRGEUkAq0iiakM4V7MHMCoW6cpav0=
+=IvX3
+-----END PGP SIGNATURE-----
diff --git a/data/tests/dnf_keyring_add_public_key/rsa.pub b/data/tests/dnf_keyring_add_public_key/rsa.pub
new file mode 100644
index 00000000..3bd696a6
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/rsa.pub
@@ -0,0 +1,28 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGlveh0BEADjFxE8ZyYa/LJ0eEQqgKgapOhSXjGGSZxQ3nlNF853lpsg9ywj
+oMRkej4QLk1QpYHCbubIPQShiigqp4Z556+ZBGZhb0neT5BsnHgwkFPmrK0F4H5S
+alZpJg8nR3dZJURAwEdJk0HFyN+ULmAtQKY/+aYXPGpDsBl31nxOVqH4XZfaPFPX
+pJy23cDGZWTOvzpfPt4b/IpoMD0czbeD8hBBTGl2Es3cf4g9g8TTB3m3H/irYxzh
+W8THDI8jWGfKSklMywuG101AFIzhm2wAv6pZe5AOyVwPMAWoXuRbdYf47LDPKc+k
+1gOTMRJ19+I0aNw0nIdxv6Jygcv8PyCvqzJcQTWlHb3LAu9Bgvg3DrjlioT2IJve
+ZxrgoPhPg7d8UC8uM8advwc/1Mf0T62pQ9EoyvHk6ZlB4ls7Blo5kk7UNk39ZLdj
+28aeYMtBx+ej3OYVrdbezL+3ph9FEaPdfzS4Gc/vDOYqkVaAaKdAGvDArSG9Ipvp
+CDb/mvHspb+krkov3OLd3YqZy1lJhkG0C/RCISaulXjOOp1jnIEZw2JkiJcfKpi7
+GudrnuQ5DUig5ouFPYsjG2aBt7WJlhdw5O6D23tojqC2z2NSlI7vdBIUle1ptvpS
+V9U5YWq+ZkbraGe5mMn38jMMy+VX1LPYDrDnt1zN74+X9oHOzxJTRJjw0QARAQAB
+tBh0ZXN0IDx0ZXN0UlNBQGxvY2FsaG9zdD6JAlEEEwEKADsWIQSbAtiB/kGF6epS
+54iIzYOkteVpRQUCaW96HQIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAK
+CRCIzYOkteVpRYzKD/9rCuXbl5oYcRRyRRriCL1+TmookI8tpv8aUURPtKfBx0xf
+OQWj79UbKYeGLgPPyjvHAuP5eCSoVKwhHcDobrAHPjiJuJDbnc9b0/BD2XqRwbZJ
+iEOZD1uYLz2y98e2OnbtLOxI7ZjVDQs0xPhF1hvcUcHIXRk+dQpGf6mDqx5Oo702
+5GwtwtWU6tJP/xyO8VQdt19/6R1ogHa5sOu+HgW7V7lZBHOJSx+Q7bNwuAbpd4B6
+aj8+wh6nKQiEhxr3NKQwADsymXtI3/+nKNvYTSYSfNYPg/r/YbMXxywC2s3HmrCd
+N/MP387Hx/ZIbsSEgAsZm8ooyJkhkcEgwPDo4/uRPqAYE/TVgFlehzinT08hVhxk
+Ca7+mBPSvUeLj9hzGbftdFFuWmubPPXqI4NsXP4yso2/edXLFjvLJyIw1tVrkXOP
+Pzd9O/LfGJlSWAA+kItePgAQOZqJbtWvlengGsRP2wBUKJJSlF4XYKZULwkr9wf/
+SuUlQNFXnGO2bQ0l9/5XpBPN0Izm1sgSlK6EC9ChY/T2XyN0PvPahBYFkeZVhco2
+qxJ9lftw9nX+lPaBPUE//m/Gsa3uqwJcccbgZmH38nIBBNGk+HFZwl97KZ2MwpHT
+E5O5sl98hspB3TFKDmpNXbbC7NZOgr176q4j9ZclDPwe3C9HY+W50pwYF5MDAw==
+=+Uwk
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/libdnf/dnf-self-test.c b/tests/libdnf/dnf-self-test.c
index 452cf20a..b171d948 100644
--- a/tests/libdnf/dnf-self-test.c
+++ b/tests/libdnf/dnf-self-test.c
@@ -22,10 +22,18 @@
#include "libdnf/libdnf.h"
+#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>
#include <fcntl.h>
#include <glib/gstdio.h>
+#include <rpm/rpmcrypto.h>
+#include <rpm/rpmlib.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmts.h>
+
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(rpmts, rpmtsFree, NULL)
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(rpmKeyring, rpmKeyringFree, NULL)
/**
* cd_test_get_filename:
@@ -57,6 +65,174 @@ dnf_lock_state_changed_cb(DnfLock *lock, guint bitfield, gpointer user_data)
_dnf_lock_state_changed++;
}
+/* Verify a signature in @signature_filename for data in @data_filename using
+ * RPM keyring @keyring.
+ * Return 0 on successul verification, 1 on failed verification, -1 on
+ * internal error. */
+static int
+verifyfile(rpmKeyring keyring, const char *data_filename, const char *signature_filename)
+{
+ gchar *data = NULL;
+ size_t data_size;
+ gchar *signature = NULL;
+ size_t signature_size;
+ pgpDigParams parsed_signature = NULL;
+ DIGEST_CTX digest_context = NULL;
+ rpmRC verification_result;
+
+ if (!g_file_get_contents(data_filename, &data, &data_size, NULL)) {
+ printf("Failed to load data from %s\n", data_filename);
+ return -1;
+ }
+
+ if (!g_file_get_contents(signature_filename, &signature, &signature_size, NULL)) {
+ printf("Failed to load a signature\n");
+ g_free(data);
+ return -1;
+ }
+
+ if (0 != pgpPrtParams((uint8_t *)signature, signature_size, PGPTAG_SIGNATURE, &parsed_signature)) {
+ printf("Failed to parse an OpenPGP signature in %s\n", signature_filename);
+ g_free(signature);
+ g_free(data);
+ return -1;
+ }
+
+ digest_context = rpmDigestInit(pgpDigParamsAlgo(parsed_signature, PGPVAL_HASHALGO), RPMDIGEST_NONE);
+ if (digest_context == NULL) {
+ printf("Failed to initialize a digest context\n");
+ (void)pgpDigParamsFree(parsed_signature);
+ g_free(signature);
+ g_free(data);
+ return -1;
+ }
+
+ if (0 != rpmDigestUpdate(digest_context, data, data_size)) {
+ printf("Failed to compute a digest from the data\n");
+ (void)rpmDigestFinal(digest_context, NULL, NULL, 0);
+ (void)pgpDigParamsFree(parsed_signature);
+ g_free(signature);
+ g_free(data);
+ return -1;
+ }
+
+ verification_result = rpmKeyringVerifySig(keyring, parsed_signature, digest_context);
+
+ (void)rpmDigestFinal(digest_context, NULL, NULL, 0);
+ (void)pgpDigParamsFree(parsed_signature);
+ g_free(signature);
+ g_free(data);
+
+ return (verification_result == RPMRC_OK ? 0 : 1);
+}
+
+/* This function imports keys from @key_filename with
+ * dnf_keyring_add_public_key() and then verifies a signature in
+ * @signature_filename on data in @data_filename against the imported keys.
+ * Key import status returns in @import_passed and @import_error arguments,
+ * signature verification status in @verification_passed argument. Dies on
+ * internal errors. */
+static void
+import_and_verify(gboolean *import_passed, GError **import_error, gboolean *verification_passed,
+ const char *key_filename, const char *data_filename, const char *signature_filename) {
+ g_assert_nonnull(import_passed);
+ g_assert_nonnull(import_error);
+ g_assert_nonnull(verification_passed);
+ g_assert_nonnull(key_filename);
+ g_assert_nonnull(data_filename);
+ g_assert_nonnull(signature_filename);
+
+ g_assert_cmpint(0, ==, rpmReadConfigFiles(NULL, NULL));
+
+ g_auto(rpmts) ts = rpmtsCreate();
+ g_assert_nonnull(ts);
+
+ g_assert_cmpint(0, ==, rpmtsSetRootDir(ts, NULL));
+
+ g_auto(rpmKeyring) keyring = rpmtsGetKeyring(ts, 1);
+ g_assert_nonnull(keyring);
+
+ /* Make sure a signature verification fails before the import. */
+ g_debug("File %s verification against signature %s before importing %s should fail",
+ data_filename, signature_filename, key_filename);
+ g_assert_cmpint (0, !=, verifyfile(keyring, data_filename, signature_filename));
+
+ /* Do the import. */
+ *import_passed = dnf_keyring_add_public_key(keyring, key_filename, import_error);
+ if (*import_passed) {
+ g_debug("Import passed.\n");
+ } else {
+ g_debug("Import failed\n");
+ }
+
+ /* Perform the the signature verification after the import and return the result. */
+ g_debug("File %s verification against signature %s after importing %s:\n",
+ data_filename, signature_filename, key_filename);
+ *verification_passed = (0 == verifyfile(keyring, data_filename, signature_filename));
+ if (*verification_passed) {
+ g_debug("Verification passed.\n");
+ } else {
+ g_debug("Verification failed\n");
+ }
+}
+
+/* This test suite exhibits dnf_keyring_add_public_key() which imports keys
+ * from a file to an in-memory keyring. This keyring is never stored into RPM
+ * database, or its adjacent in-filesystem key store. Therefore one cannot
+ * test an effect of the import by inspecting the RPM database for the
+ * synthetic gpg-pubkey packages.
+ *
+ * RPM 6 supports inspecting in-memory keyrings with rpmKeyringInitIterator()
+ * and rpmKeyringIteratorNext() functions. But we want support RPM older than
+ * that. Therefore this test suite does that by verifying a signature of data
+ * signed with the supposedly imported keys. */
+
+/* Test importing a valid key. */
+static void
+dnf_keyring_add_public_key_valid(void)
+{
+ g_autofree gchar *key_filename = NULL;
+ g_autofree gchar *data_filename = NULL;
+ g_autofree gchar *signature_filename = NULL;
+ gboolean import_passed;
+ g_autoptr(GError) import_error = NULL;
+ gboolean verification_passed;
+
+ key_filename = dnf_test_get_filename("dnf_keyring_add_public_key/rsa.pub");
+ data_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.rsa.sig");
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, signature_filename);
+ g_assert_true(import_passed);
+ g_assert_no_error(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+}
+
+/* Test importing an invalid key. */
+static void
+dnf_keyring_add_public_key_invalid(void)
+{
+ g_autofree gchar *key_filename = NULL;
+ g_autofree gchar *data_filename = NULL;
+ g_autofree gchar *signature_filename = NULL;
+ gboolean import_passed;
+ g_autoptr(GError) import_error = NULL;
+ gboolean verification_passed;
+
+ key_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ data_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.rsa.sig");
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, signature_filename);
+ g_assert_false(import_passed);
+ g_assert_nonnull(import_error);
+ g_clear_error(&import_error);
+ g_assert_false(verification_passed);
+}
+
static void
dnf_lock_func(void)
{
@@ -1274,6 +1450,8 @@ main(int argc, char **argv)
g_test_add_func("/libdnf/repo_loader{cache-dir-check}", dnf_repo_loader_cache_dir_check_func);
g_test_add_func("/libdnf/context", dnf_context_func);
g_test_add_func("/libdnf/context{cache-clean-check}", dnf_context_cache_clean_check_func);
+ g_test_add_func("/libdnf/dnf_keyring_add_public_key[valid]", dnf_keyring_add_public_key_valid);
+ g_test_add_func("/libdnf/dnf_keyring_add_public_key[invalid]", dnf_keyring_add_public_key_invalid);
g_test_add_func("/libdnf/lock", dnf_lock_func);
g_test_add_func("/libdnf/lock[threads]", dnf_lock_threads_func);
g_test_add_func("/libdnf/split_releasever", dnf_split_releasever_func);
--
2.53.0

View File

@ -0,0 +1,184 @@
From 49df864cde21f9d160bfd422058a3e33970222cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Fri, 13 Mar 2026 17:40:15 +0100
Subject: [PATCH 26/30] Move importing a key from a memory block into a
separate function
Upstream commit: 89b6944551c08c17c5be12db17211201264350a1
---
libdnf/dnf-keyring.cpp | 125 +++++++++++++++++++++++++++--------------
1 file changed, 82 insertions(+), 43 deletions(-)
diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 5f6c7d7f..2daabd7d 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -43,67 +43,40 @@
#include "dnf-utils.h"
/**
- * dnf_keyring_add_public_key:
+ * dnf_keyring_add_public_key_from_memory:
* @keyring: a #rpmKeyring instance.
- * @filename: The public key filename.
+ * @filename: a public key filename.
+ * @pkt: a memory block with dearmored single OpenPGP public key packet
+ * @len: a length of the memory block
* @error: a #GError or %NULL.
*
* Adds a specific public key to the keyring.
*
* Returns: %TRUE for success, %FALSE otherwise
- *
- * Since: 0.1.0
**/
-gboolean
-dnf_keyring_add_public_key(rpmKeyring keyring,
- const gchar *filename,
- GError **error) try
+static gboolean
+dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
+ const gchar *filename,
+ const uint8_t *pkt,
+ size_t len,
+ GError **error) try
{
gboolean ret = TRUE;
int rc;
- gsize len;
- pgpArmor armor;
rpmPubkey pubkey = NULL;
rpmPubkey *subkeys = NULL;
int nsubkeys = 0;
- uint8_t *pkt = NULL;
- g_autofree gchar *data = NULL;
- /* ignore symlinks and directories */
- if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
- goto out;
- if (g_file_test(filename, G_FILE_TEST_IS_SYMLINK))
- goto out;
-
- /* get data */
- ret = g_file_get_contents(filename, &data, &len, error);
- if (!ret)
- goto out;
-
- /* rip off the ASCII armor and parse it */
- armor = pgpParsePkts(data, &pkt, &len);
- if (armor < 0) {
+ if (pkt == NULL || len == 0) {
ret = FALSE;
g_set_error(error,
DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "failed to parse PKI file %s",
- filename);
- goto out;
- }
-
- /* make sure it's something we can add to rpm */
- if (armor != PGPARMOR_PUBKEY) {
- ret = FALSE;
- g_set_error(error,
- DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "PKI file %s is not a public key",
- filename);
+ DNF_ERROR_INTERNAL_ERROR,
+ "empty memory block passed to dnf_keyring_add_public_key_from_memory()");
goto out;
}
- /* test each one */
+ /* Parse the public key */
pubkey = rpmPubkeyNew(pkt, len);
if (pubkey == NULL) {
ret = FALSE;
@@ -149,8 +122,6 @@ dnf_keyring_add_public_key(rpmKeyring keyring,
g_debug("added missing public key %s to rpmdb", filename);
ret = TRUE;
out:
- if (pkt != NULL)
- free(pkt); /* yes, free() */
if (pubkey != NULL)
rpmPubkeyFree(pubkey);
if (subkeys != NULL) {
@@ -162,6 +133,74 @@ out:
return ret;
} CATCH_TO_GERROR(FALSE)
+/**
+ * dnf_keyring_add_public_key:
+ * @keyring: a #rpmKeyring instance.
+ * @filename: The public key filename.
+ * @error: a #GError or %NULL.
+ *
+ * Adds a specific public key to the keyring.
+ *
+ * Returns: %TRUE for success, %FALSE otherwise
+ *
+ * Since: 0.1.0
+ **/
+gboolean
+dnf_keyring_add_public_key(rpmKeyring keyring,
+ const gchar *filename,
+ GError **error) try
+{
+ gboolean ret = TRUE;
+ gsize len;
+ pgpArmor armor;
+ uint8_t *pkt = NULL;
+ g_autofree gchar *data = NULL;
+
+ /* ignore symlinks and directories */
+ if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
+ goto out;
+ if (g_file_test(filename, G_FILE_TEST_IS_SYMLINK))
+ goto out;
+
+ /* get data */
+ ret = g_file_get_contents(filename, &data, &len, error);
+ if (!ret)
+ goto out;
+
+ /* rip off the ASCII armor and parse it */
+ armor = pgpParsePkts(data, &pkt, &len);
+ if (armor < 0) {
+ ret = FALSE;
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to parse PKI file %s",
+ filename);
+ goto out;
+ }
+
+ /* make sure it's something we can add to rpm */
+ if (armor != PGPARMOR_PUBKEY) {
+ ret = FALSE;
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "PKI file %s is not a public key",
+ filename);
+ goto out;
+ }
+
+ ret = dnf_keyring_add_public_key_from_memory(keyring, filename, pkt, len, error);
+ if (ret) {
+ /* success */
+ g_debug("added missing public key %s to rpmdb", filename);
+ }
+out:
+ if (pkt != NULL)
+ free(pkt); /* yes, free() */
+ return ret;
+} CATCH_TO_GERROR(FALSE)
+
/**
* dnf_keyring_add_public_keys:
* @keyring: a #rpmKeyring instance.
--
2.53.0

View File

@ -0,0 +1,169 @@
From 435b2a678af4f428d66044f7c5035c640abbc780 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Wed, 18 Mar 2026 13:36:18 +0100
Subject: [PATCH 27/30] Log identifiers of keys imported by
dnf_keyring_add_public_key()
Upstream commit: e0c56202ebde444bb73065843ac3dc2f7e4a8f3a
This patch enhances debugging messages to mention identifiers of the processed keys.
---
libdnf/dnf-keyring.cpp | 105 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 94 insertions(+), 11 deletions(-)
diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 2daabd7d..07789ed6 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -42,6 +42,40 @@
#include "dnf-keyring.h"
#include "dnf-utils.h"
+/* Return a key ID as a hexadecimal string.
+ * @key: a public key
+ * Returns: A pointer to be freed, NULL on error. */
+static char *formatkeyid(rpmPubkey key) {
+ char *string = NULL;
+#ifdef RPM_HAS_KEYIDASHEX
+ string = strdup(rpmPubkeyKeyIDAsHex(key));
+#else
+ /* A fallback implementation for rpmPubkeyKeyIDAsHex() which is available
+ * since RPM 6. */
+ static const char table[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ pgpDigParams parameters = NULL; /* weak pointer */
+ const uint8_t *keyid = NULL; /* weak pointer */
+
+ if (!key)
+ return NULL;
+ string = (char*)malloc(PGP_KEYID_LEN*2+1);
+ if (!string)
+ return NULL;
+ parameters = rpmPubkeyPgpDigParams(key);
+ if (!parameters) {
+ free(string);
+ return NULL;
+ }
+ keyid = pgpDigParamsSignID(parameters);
+ for (int i = 0; i < PGP_KEYID_LEN; i++) {
+ string[i*2] = table[keyid[i] >> 4];
+ string[i*2 + 1] = table[keyid[i] & 0x0f];
+ }
+ string[PGP_KEYID_LEN*2] = '\0';
+#endif
+ return string;
+}
+
/**
* dnf_keyring_add_public_key_from_memory:
* @keyring: a #rpmKeyring instance.
@@ -66,6 +100,7 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
rpmPubkey pubkey = NULL;
rpmPubkey *subkeys = NULL;
int nsubkeys = 0;
+ char *keyid = NULL;
if (pkt == NULL || len == 0) {
ret = FALSE;
@@ -87,33 +122,79 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
filename);
goto out;
}
+ keyid = formatkeyid(pubkey);
/* add to in-memory keyring */
rc = rpmKeyringAddKey(keyring, pubkey);
if (rc == 1) {
ret = TRUE;
- g_debug("%s is already added", filename);
+ if (keyid == NULL)
+ g_debug("a key from %s is already added", filename);
+ else
+ g_debug("0x%s key from %s is already added", keyid, filename);
goto out;
} else if (rc < 0) {
ret = FALSE;
- g_set_error(error,
- DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "failed to add public key %s to rpmdb",
- filename);
+ if (keyid == NULL)
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add a public key from %s to rpmdb",
+ filename);
+ else
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add 0x%s public key from %s to rpmdb",
+ keyid,
+ filename);
goto out;
}
+ if (keyid == NULL)
+ g_debug("added missing public key from %s to rpmdb", filename);
+ else
+ g_debug("added missing 0x%s public key from %s to rpmdb", keyid, filename);
subkeys = rpmGetSubkeys(pubkey, &nsubkeys);
for (int i = 0; i < nsubkeys; i++) {
rpmPubkey subkey = subkeys[i];
if (rpmKeyringAddKey(keyring, subkey) < 0) {
+ char *subkeyid = formatkeyid(subkey);
ret = FALSE;
- g_set_error(error,
- DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "failed to add subkeys for %s to rpmdb",
- filename);
+ if (keyid == NULL)
+ if (subkey == NULL)
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add a subkey from %s to rpmdb",
+ filename);
+ else
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add 0x%s subkey from %s to rpmdb",
+ subkeyid,
+ keyid,
+ filename);
+ else
+ if (subkeyid == NULL)
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add a subkey for 0x%s primary key from %s to rpmdb",
+ subkeyid,
+ keyid,
+ filename);
+ else
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to add 0x%s subkey for 0x%s primary key from %s to rpmdb",
+ subkeyid,
+ keyid,
+ filename);
+ if (subkeyid != NULL)
+ free(subkeyid);
goto out;
}
}
@@ -122,6 +203,8 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
g_debug("added missing public key %s to rpmdb", filename);
ret = TRUE;
out:
+ if (keyid != NULL)
+ free(keyid);
if (pubkey != NULL)
rpmPubkeyFree(pubkey);
if (subkeys != NULL) {
--
2.53.0

View File

@ -0,0 +1,221 @@
From 569f317bbf22e4ccc3a473cd3ee895a2891852f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Wed, 18 Mar 2026 13:36:18 +0100
Subject: [PATCH 28/30] Fix dnf_keyring_add_public_key() to add all keys from
an ASCII-armored block
Upstream commit: 21e58e48d73a0edba62510bac8d49392c5166ebb
The function was used to import only the first public key OpenPGP
certificate into an in-memory keyring. That broke a scenario when
multiple keys were serialized into a single ASCII-armored OpenPGP
block in a file.
This patch iterates over all public keys in an ASCII-armored OpenPGP
block.
If an error is encountered, it continues importing next keys, but at
the end raises the error. This is to be able to skip unsupported keys
and to mimic what rpmkeys tool does.
This patch also adds a test into test_libdnf_main test suite. It does
not add any half-negative test (ie. first packet corrupted, second
packet valid) as I did not find a way of triggering that behaviour.
Resolves: https://redhat.atlassian.net/browse/RHEL-156063
---
.../tests/dnf_keyring_add_public_key/edsa.pub | 13 +++++++
.../dnf_keyring_add_public_key/input.edsa.sig | 7 ++++
.../dnf_keyring_add_public_key/twopackets.pub | 37 +++++++++++++++++++
libdnf/dnf-keyring.cpp | 37 ++++++++++++++++++-
tests/libdnf/dnf-self-test.c | 34 +++++++++++++++++
5 files changed, 127 insertions(+), 1 deletion(-)
create mode 100644 data/tests/dnf_keyring_add_public_key/edsa.pub
create mode 100644 data/tests/dnf_keyring_add_public_key/input.edsa.sig
create mode 100644 data/tests/dnf_keyring_add_public_key/twopackets.pub
diff --git a/data/tests/dnf_keyring_add_public_key/edsa.pub b/data/tests/dnf_keyring_add_public_key/edsa.pub
new file mode 100644
index 00000000..37c8e87d
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/edsa.pub
@@ -0,0 +1,13 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEZ9rrThYJKwYBBAHaRw8BAQdAgo3RmWKGpGqA5EiHxShDeWtrHZoQsNwbs/vt
+y/TxVi20FnRlc3QyIDx0ZXN0QGxvY2FsaG9zdD6IkwQTFgoAOwIbAwULCQgHAgIi
+AgYVCgkICwIEFgIDAQIeBwIXgBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLaJ
+AAoJEElgmdvisUXzhG8A/1GH9JdZXlxOgCmdpAWWlGTyupjoxd/DSi94XCT1f9wB
+AP4ooHOPquqm27HqMsngcy3iHmRIXfMGMOpc4bqUC7t0Dbg4BGfa604SCisGAQQB
+l1UBBQEBB0BvhdjE5RKOBDP+xi0YXKir7BBFIkF14hDkdpJb5iJkIgMBCAeIeAQY
+FgoAIAIbDBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLcHAAoJEElgmdvisUXz
+JEYBAPPfpwiY0vGV1m+XjXeIgxtN+RZydzi3Dz6dN5hLeOLIAP9NeeDGlB9G2M4W
+QOEU+BFdJuURqeyx1rOk/HLw/QlQAQ==
+=D40+
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/data/tests/dnf_keyring_add_public_key/input.edsa.sig b/data/tests/dnf_keyring_add_public_key/input.edsa.sig
new file mode 100644
index 00000000..f568bbd7
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/input.edsa.sig
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+
+iHUEABYKAB0WIQQRHhHhZOYbUaH2Kr5JYJnb4rFF8wUCabP8yAAKCRBJYJnb4rFF
+81DMAQDF8/qX0jkJyrD/6v6qDJiM8dsb3JBNmZ5C3gDQ3c7uIAEAiiFSl+KbopAa
+bvzjr00099kCEUfkkBlNgdBJ2Cr/oQs=
+=WUbC
+-----END PGP SIGNATURE-----
diff --git a/data/tests/dnf_keyring_add_public_key/twopackets.pub b/data/tests/dnf_keyring_add_public_key/twopackets.pub
new file mode 100644
index 00000000..37dd15b2
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/twopackets.pub
@@ -0,0 +1,37 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEZ9rrThYJKwYBBAHaRw8BAQdAgo3RmWKGpGqA5EiHxShDeWtrHZoQsNwbs/vt
+y/TxVi20FnRlc3QyIDx0ZXN0QGxvY2FsaG9zdD6IkwQTFgoAOwIbAwULCQgHAgIi
+AgYVCgkICwIEFgIDAQIeBwIXgBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLaJ
+AAoJEElgmdvisUXzhG8A/1GH9JdZXlxOgCmdpAWWlGTyupjoxd/DSi94XCT1f9wB
+AP4ooHOPquqm27HqMsngcy3iHmRIXfMGMOpc4bqUC7t0Dbg4BGfa604SCisGAQQB
+l1UBBQEBB0BvhdjE5RKOBDP+xi0YXKir7BBFIkF14hDkdpJb5iJkIgMBCAeIeAQY
+FgoAIAIbDBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLcHAAoJEElgmdvisUXz
+JEYBAPPfpwiY0vGV1m+XjXeIgxtN+RZydzi3Dz6dN5hLeOLIAP9NeeDGlB9G2M4W
+QOEU+BFdJuURqeyx1rOk/HLw/QlQAZkCDQRpb3odARAA4xcRPGcmGvyydHhEKoCo
+GqToUl4xhkmcUN55TRfOd5abIPcsI6DEZHo+EC5NUKWBwm7myD0EoYooKqeGeeev
+mQRmYW9J3k+QbJx4MJBT5qytBeB+UmpWaSYPJ0d3WSVEQMBHSZNBxcjflC5gLUCm
+P/mmFzxqQ7AZd9Z8Tlah+F2X2jxT16Sctt3AxmVkzr86Xz7eG/yKaDA9HM23g/IQ
+QUxpdhLN3H+IPYPE0wd5tx/4q2Mc4VvExwyPI1hnykpJTMsLhtdNQBSM4ZtsAL+q
+WXuQDslcDzAFqF7kW3WH+OywzynPpNYDkzESdffiNGjcNJyHcb+icoHL/D8gr6sy
+XEE1pR29ywLvQYL4Nw645YqE9iCb3mca4KD4T4O3fFAvLjPGnb8HP9TH9E+tqUPR
+KMrx5OmZQeJbOwZaOZJO1DZN/WS3Y9vGnmDLQcfno9zmFa3W3sy/t6YfRRGj3X80
+uBnP7wzmKpFWgGinQBrwwK0hvSKb6Qg2/5rx7KW/pK5KL9zi3d2KmctZSYZBtAv0
+QiEmrpV4zjqdY5yBGcNiZIiXHyqYuxrna57kOQ1IoOaLhT2LIxtmgbe1iZYXcOTu
+g9t7aI6gts9jUpSO73QSFJXtabb6UlfVOWFqvmZG62hnuZjJ9/IzDMvlV9Sz2A6w
+57dcze+Pl/aBzs8SU0SY8NEAEQEAAbQYdGVzdCA8dGVzdFJTQUBsb2NhbGhvc3Q+
+iQJRBBMBCgA7FiEEmwLYgf5BhenqUueIiM2DpLXlaUUFAmlveh0CGwMFCwkIBwIC
+IgIGFQoJCAsCBBYCAwECHgcCF4AACgkQiM2DpLXlaUWMyg//awrl25eaGHEUckUa
+4gi9fk5qKJCPLab/GlFET7SnwcdMXzkFo+/VGymHhi4Dz8o7xwLj+XgkqFSsIR3A
+6G6wBz44ibiQ253PW9PwQ9l6kcG2SYhDmQ9bmC89svfHtjp27SzsSO2Y1Q0LNMT4
+RdYb3FHByF0ZPnUKRn+pg6seTqO9NuRsLcLVlOrST/8cjvFUHbdff+kdaIB2ubDr
+vh4Fu1e5WQRziUsfkO2zcLgG6XeAemo/PsIepykIhIca9zSkMAA7Mpl7SN//pyjb
+2E0mEnzWD4P6/2GzF8csAtrNx5qwnTfzD9/Ox8f2SG7EhIALGZvKKMiZIZHBIMDw
+6OP7kT6gGBP01YBZXoc4p09PIVYcZAmu/pgT0r1Hi4/Ycxm37XRRblprmzz16iOD
+bFz+MrKNv3nVyxY7yyciMNbVa5Fzjz83fTvy3xiZUlgAPpCLXj4AEDmaiW7Vr5Xp
+4BrET9sAVCiSUpReF2CmVC8JK/cH/0rlJUDRV5xjtm0NJff+V6QTzdCM5tbIEpSu
+hAvQoWP09l8jdD7z2oQWBZHmVYXKNqsSfZX7cPZ1/pT2gT1BP/5vxrGt7qsCXHHG
+4GZh9/JyAQTRpPhxWcJfeymdjMKR0xOTubJffIbKQd0xSg5qTV22wuzWToK9e+qu
+I/WXJQz8HtwvR2PludKcGBeTAwM=
+=DS1h
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 07789ed6..6e2ae8a7 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -234,6 +234,7 @@ dnf_keyring_add_public_key(rpmKeyring keyring,
GError **error) try
{
gboolean ret = TRUE;
+ bool importable_certificates_found = FALSE;
gsize len;
pgpArmor armor;
uint8_t *pkt = NULL;
@@ -273,7 +274,41 @@ dnf_keyring_add_public_key(rpmKeyring keyring,
goto out;
}
- ret = dnf_keyring_add_public_key_from_memory(keyring, filename, pkt, len, error);
+ {
+ /* Iterate over all public keys in this dearmored block */
+ uint8_t *tpkt = pkt;
+ size_t cert_len;
+ while (len > 0) {
+ if (pgpPubKeyCertLen(tpkt, len, &cert_len))
+ break;
+ if (cert_len > len)
+ break;
+
+ if (!dnf_keyring_add_public_key_from_memory(keyring, filename, tpkt, cert_len,
+ /* Remember first error message */
+ error == NULL || *error != NULL ? NULL : error))
+ ret = FALSE;
+
+ tpkt += cert_len;
+ len -= cert_len;
+ importable_certificates_found = TRUE;
+ }
+ }
+
+ if (!ret)
+ /* Prevent overwriting error messages */
+ goto out;
+
+ if (!importable_certificates_found) {
+ ret = FALSE;
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "PKI file %s contains no valid public key",
+ filename);
+ goto out;
+ }
+
if (ret) {
/* success */
g_debug("added missing public key %s to rpmdb", filename);
diff --git a/tests/libdnf/dnf-self-test.c b/tests/libdnf/dnf-self-test.c
index b171d948..6eadeb94 100644
--- a/tests/libdnf/dnf-self-test.c
+++ b/tests/libdnf/dnf-self-test.c
@@ -233,6 +233,39 @@ dnf_keyring_add_public_key_invalid(void)
g_assert_false(verification_passed);
}
+/* Test importing two keys from a file with an ASCII-armored block with two
+ * public key OpenPGP packets. */
+static void
+dnf_keyring_add_public_key_two_packets(void)
+{
+ g_autofree gchar *key_filename = NULL;
+ g_autofree gchar *data_filename = NULL;
+ g_autofree gchar *rsa_signature_filename = NULL;
+ g_autofree gchar *edsa_signature_filename = NULL;
+ gboolean import_passed;
+ g_autoptr(GError) import_error = NULL;
+ gboolean verification_passed;
+
+ key_filename = dnf_test_get_filename("dnf_keyring_add_public_key/twopackets.pub");
+ data_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ rsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.rsa.sig");
+ edsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.edsa.sig");
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, rsa_signature_filename);
+ g_assert_true(import_passed);
+ g_assert_no_error(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, edsa_signature_filename);
+ g_assert_true(import_passed);
+ g_assert_no_error(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+}
+
static void
dnf_lock_func(void)
{
@@ -1452,6 +1485,7 @@ main(int argc, char **argv)
g_test_add_func("/libdnf/context{cache-clean-check}", dnf_context_cache_clean_check_func);
g_test_add_func("/libdnf/dnf_keyring_add_public_key[valid]", dnf_keyring_add_public_key_valid);
g_test_add_func("/libdnf/dnf_keyring_add_public_key[invalid]", dnf_keyring_add_public_key_invalid);
+ g_test_add_func("/libdnf/dnf_keyring_add_public_key[two-packets]", dnf_keyring_add_public_key_two_packets);
g_test_add_func("/libdnf/lock", dnf_lock_func);
g_test_add_func("/libdnf/lock[threads]", dnf_lock_threads_func);
g_test_add_func("/libdnf/split_releasever", dnf_split_releasever_func);
--
2.53.0

View File

@ -0,0 +1,338 @@
From 3b3c8910837d51431e9cc79e131671f48ea5f04b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Wed, 18 Mar 2026 15:46:30 +0100
Subject: [PATCH 29/30] Fix dnf_keyring_add_public_key() to add keys from all
ASCII-armored blocks
Upstream commit: af94b4cc5b7f96829ab8cb9d7dc3fbc92fa56d92
The function was used to import only the keys found in the first
ASCII-armored block into an in-memory keyring. That broke a scenario
when multiple ASCII-armored blocks were concatenated into a file.
This patch fixes it by iterating over all ASCII-armored OpenPGP public
key blocks.
If an error is encountered, it continues importing next keys, but at
the end raises the error. This is to be able to skip unsupported keys
and to mimic what rpmkeys tool does.
This patch also adds tests into test_libdnf_main test suite.
Resolves: https://redhat.atlassian.net/browse/RHEL-156063
---
.../blocks_invalid_valid.pub | 41 ++++++++
.../dnf_keyring_add_public_key/twoblocks.pub | 41 ++++++++
libdnf/dnf-keyring.cpp | 95 +++++++++++--------
tests/libdnf/dnf-self-test.c | 69 ++++++++++++++
4 files changed, 205 insertions(+), 41 deletions(-)
create mode 100644 data/tests/dnf_keyring_add_public_key/blocks_invalid_valid.pub
create mode 100644 data/tests/dnf_keyring_add_public_key/twoblocks.pub
diff --git a/data/tests/dnf_keyring_add_public_key/blocks_invalid_valid.pub b/data/tests/dnf_keyring_add_public_key/blocks_invalid_valid.pub
new file mode 100644
index 00000000..cccf9a6a
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/blocks_invalid_valid.pub
@@ -0,0 +1,41 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+XmQINBGlveh0BEADjFxE8ZyYa/LJ0eEQqgKgapOhSXjGGSZxQ3nlNF853lpsg9ywj
+oMRkej4QLk1QpYHCbubIPQShiigqp4Z556+ZBGZhb0neT5BsnHgwkFPmrK0F4H5S
+alZpJg8nR3dZJURAwEdJk0HFyN+ULmAtQKY/+aYXPGpDsBl31nxOVqH4XZfaPFPX
+pJy23cDGZWTOvzpfPt4b/IpoMD0czbeD8hBBTGl2Es3cf4g9g8TTB3m3H/irYxzh
+W8THDI8jWGfKSklMywuG101AFIzhm2wAv6pZe5AOyVwPMAWoXuRbdYf47LDPKc+k
+1gOTMRJ19+I0aNw0nIdxv6Jygcv8PyCvqzJcQTWlHb3LAu9Bgvg3DrjlioT2IJve
+ZxrgoPhPg7d8UC8uM8advwc/1Mf0T62pQ9EoyvHk6ZlB4ls7Blo5kk7UNk39ZLdj
+28aeYMtBx+ej3OYVrdbezL+3ph9FEaPdfzS4Gc/vDOYqkVaAaKdAGvDArSG9Ipvp
+CDb/mvHspb+krkov3OLd3YqZy1lJhkG0C/RCISaulXjOOp1jnIEZw2JkiJcfKpi7
+GudrnuQ5DUig5ouFPYsjG2aBt7WJlhdw5O6D23tojqC2z2NSlI7vdBIUle1ptvpS
+V9U5YWq+ZkbraGe5mMn38jMMy+VX1LPYDrDnt1zN74+X9oHOzxJTRJjw0QARAQAB
+tBh0ZXN0IDx0ZXN0UlNBQGxvY2FsaG9zdD6JAlEEEwEKADsWIQSbAtiB/kGF6epS
+54iIzYOkteVpRQUCaW96HQIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAK
+CRCIzYOkteVpRYzKD/9rCuXbl5oYcRRyRRriCL1+TmookI8tpv8aUURPtKfBx0xf
+OQWj79UbKYeGLgPPyjvHAuP5eCSoVKwhHcDobrAHPjiJuJDbnc9b0/BD2XqRwbZJ
+iEOZD1uYLz2y98e2OnbtLOxI7ZjVDQs0xPhF1hvcUcHIXRk+dQpGf6mDqx5Oo702
+5GwtwtWU6tJP/xyO8VQdt19/6R1ogHa5sOu+HgW7V7lZBHOJSx+Q7bNwuAbpd4B6
+aj8+wh6nKQiEhxr3NKQwADsymXtI3/+nKNvYTSYSfNYPg/r/YbMXxywC2s3HmrCd
+N/MP387Hx/ZIbsSEgAsZm8ooyJkhkcEgwPDo4/uRPqAYE/TVgFlehzinT08hVhxk
+Ca7+mBPSvUeLj9hzGbftdFFuWmubPPXqI4NsXP4yso2/edXLFjvLJyIw1tVrkXOP
+Pzd9O/LfGJlSWAA+kItePgAQOZqJbtWvlengGsRP2wBUKJJSlF4XYKZULwkr9wf/
+SuUlQNFXnGO2bQ0l9/5XpBPN0Izm1sgSlK6EC9ChY/T2XyN0PvPahBYFkeZVhco2
+qxJ9lftw9nX+lPaBPUE//m/Gsa3uqwJcccbgZmH38nIBBNGk+HFZwl97KZ2MwpHT
+E5O5sl98hspB3TFKDmpNXbbC7NZOgr176q4j9ZclDPwe3C9HY+W50pwYF5MDAw==
+=+Uwk
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEZ9rrThYJKwYBBAHaRw8BAQdAgo3RmWKGpGqA5EiHxShDeWtrHZoQsNwbs/vt
+y/TxVi20FnRlc3QyIDx0ZXN0QGxvY2FsaG9zdD6IkwQTFgoAOwIbAwULCQgHAgIi
+AgYVCgkICwIEFgIDAQIeBwIXgBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLaJ
+AAoJEElgmdvisUXzhG8A/1GH9JdZXlxOgCmdpAWWlGTyupjoxd/DSi94XCT1f9wB
+AP4ooHOPquqm27HqMsngcy3iHmRIXfMGMOpc4bqUC7t0Dbg4BGfa604SCisGAQQB
+l1UBBQEBB0BvhdjE5RKOBDP+xi0YXKir7BBFIkF14hDkdpJb5iJkIgMBCAeIeAQY
+FgoAIAIbDBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLcHAAoJEElgmdvisUXz
+JEYBAPPfpwiY0vGV1m+XjXeIgxtN+RZydzi3Dz6dN5hLeOLIAP9NeeDGlB9G2M4W
+QOEU+BFdJuURqeyx1rOk/HLw/QlQAQ==
+=D40+
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/data/tests/dnf_keyring_add_public_key/twoblocks.pub b/data/tests/dnf_keyring_add_public_key/twoblocks.pub
new file mode 100644
index 00000000..58c25684
--- /dev/null
+++ b/data/tests/dnf_keyring_add_public_key/twoblocks.pub
@@ -0,0 +1,41 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGlveh0BEADjFxE8ZyYa/LJ0eEQqgKgapOhSXjGGSZxQ3nlNF853lpsg9ywj
+oMRkej4QLk1QpYHCbubIPQShiigqp4Z556+ZBGZhb0neT5BsnHgwkFPmrK0F4H5S
+alZpJg8nR3dZJURAwEdJk0HFyN+ULmAtQKY/+aYXPGpDsBl31nxOVqH4XZfaPFPX
+pJy23cDGZWTOvzpfPt4b/IpoMD0czbeD8hBBTGl2Es3cf4g9g8TTB3m3H/irYxzh
+W8THDI8jWGfKSklMywuG101AFIzhm2wAv6pZe5AOyVwPMAWoXuRbdYf47LDPKc+k
+1gOTMRJ19+I0aNw0nIdxv6Jygcv8PyCvqzJcQTWlHb3LAu9Bgvg3DrjlioT2IJve
+ZxrgoPhPg7d8UC8uM8advwc/1Mf0T62pQ9EoyvHk6ZlB4ls7Blo5kk7UNk39ZLdj
+28aeYMtBx+ej3OYVrdbezL+3ph9FEaPdfzS4Gc/vDOYqkVaAaKdAGvDArSG9Ipvp
+CDb/mvHspb+krkov3OLd3YqZy1lJhkG0C/RCISaulXjOOp1jnIEZw2JkiJcfKpi7
+GudrnuQ5DUig5ouFPYsjG2aBt7WJlhdw5O6D23tojqC2z2NSlI7vdBIUle1ptvpS
+V9U5YWq+ZkbraGe5mMn38jMMy+VX1LPYDrDnt1zN74+X9oHOzxJTRJjw0QARAQAB
+tBh0ZXN0IDx0ZXN0UlNBQGxvY2FsaG9zdD6JAlEEEwEKADsWIQSbAtiB/kGF6epS
+54iIzYOkteVpRQUCaW96HQIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAK
+CRCIzYOkteVpRYzKD/9rCuXbl5oYcRRyRRriCL1+TmookI8tpv8aUURPtKfBx0xf
+OQWj79UbKYeGLgPPyjvHAuP5eCSoVKwhHcDobrAHPjiJuJDbnc9b0/BD2XqRwbZJ
+iEOZD1uYLz2y98e2OnbtLOxI7ZjVDQs0xPhF1hvcUcHIXRk+dQpGf6mDqx5Oo702
+5GwtwtWU6tJP/xyO8VQdt19/6R1ogHa5sOu+HgW7V7lZBHOJSx+Q7bNwuAbpd4B6
+aj8+wh6nKQiEhxr3NKQwADsymXtI3/+nKNvYTSYSfNYPg/r/YbMXxywC2s3HmrCd
+N/MP387Hx/ZIbsSEgAsZm8ooyJkhkcEgwPDo4/uRPqAYE/TVgFlehzinT08hVhxk
+Ca7+mBPSvUeLj9hzGbftdFFuWmubPPXqI4NsXP4yso2/edXLFjvLJyIw1tVrkXOP
+Pzd9O/LfGJlSWAA+kItePgAQOZqJbtWvlengGsRP2wBUKJJSlF4XYKZULwkr9wf/
+SuUlQNFXnGO2bQ0l9/5XpBPN0Izm1sgSlK6EC9ChY/T2XyN0PvPahBYFkeZVhco2
+qxJ9lftw9nX+lPaBPUE//m/Gsa3uqwJcccbgZmH38nIBBNGk+HFZwl97KZ2MwpHT
+E5O5sl98hspB3TFKDmpNXbbC7NZOgr176q4j9ZclDPwe3C9HY+W50pwYF5MDAw==
+=+Uwk
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEZ9rrThYJKwYBBAHaRw8BAQdAgo3RmWKGpGqA5EiHxShDeWtrHZoQsNwbs/vt
+y/TxVi20FnRlc3QyIDx0ZXN0QGxvY2FsaG9zdD6IkwQTFgoAOwIbAwULCQgHAgIi
+AgYVCgkICwIEFgIDAQIeBwIXgBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLaJ
+AAoJEElgmdvisUXzhG8A/1GH9JdZXlxOgCmdpAWWlGTyupjoxd/DSi94XCT1f9wB
+AP4ooHOPquqm27HqMsngcy3iHmRIXfMGMOpc4bqUC7t0Dbg4BGfa604SCisGAQQB
+l1UBBQEBB0BvhdjE5RKOBDP+xi0YXKir7BBFIkF14hDkdpJb5iJkIgMBCAeIeAQY
+FgoAIAIbDBYhBBEeEeFk5htRofYqvklgmdvisUXzBQJpeLcHAAoJEElgmdvisUXz
+JEYBAPPfpwiY0vGV1m+XjXeIgxtN+RZydzi3Dz6dN5hLeOLIAP9NeeDGlB9G2M4W
+QOEU+BFdJuURqeyx1rOk/HLw/QlQAQ==
+=D40+
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 6e2ae8a7..6ada3560 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -31,6 +31,7 @@
#include <stdlib.h>
+#include <string.h>
#include <glib.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmts.h>
@@ -235,9 +236,8 @@ dnf_keyring_add_public_key(rpmKeyring keyring,
{
gboolean ret = TRUE;
bool importable_certificates_found = FALSE;
- gsize len;
- pgpArmor armor;
uint8_t *pkt = NULL;
+ gsize len;
g_autofree gchar *data = NULL;
/* ignore symlinks and directories */
@@ -251,47 +251,60 @@ dnf_keyring_add_public_key(rpmKeyring keyring,
if (!ret)
goto out;
- /* rip off the ASCII armor and parse it */
- armor = pgpParsePkts(data, &pkt, &len);
- if (armor < 0) {
- ret = FALSE;
- g_set_error(error,
- DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "failed to parse PKI file %s",
- filename);
- goto out;
- }
+ /* Iterate over multiple ASCII-armored blocks.
+ * There is no function for it in the RPM library yet. */
+ for (
+ const gchar *block = data;
+ NULL != (block = strstr(block, "-----BEGIN PGP PUBLIC KEY BLOCK-----"));
+ free(pkt), pkt = NULL, block++) {
+ pgpArmor armor;
+
+ /* rip off the ASCII armor and parse it */
+ armor = pgpParsePkts(block, &pkt, &len);
+ if (armor < 0) {
+ ret = FALSE;
+ if (error && !*error) {
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "failed to parse PKI file %s",
+ filename);
+ }
+ continue;
+ }
- /* make sure it's something we can add to rpm */
- if (armor != PGPARMOR_PUBKEY) {
- ret = FALSE;
- g_set_error(error,
- DNF_ERROR,
- DNF_ERROR_GPG_SIGNATURE_INVALID,
- "PKI file %s is not a public key",
- filename);
- goto out;
- }
+ /* make sure it's something we can add to rpm */
+ if (armor != PGPARMOR_PUBKEY) {
+ ret = FALSE;
+ if (error && !*error) {
+ g_set_error(error,
+ DNF_ERROR,
+ DNF_ERROR_GPG_SIGNATURE_INVALID,
+ "PKI file %s is not a public key",
+ filename);
+ }
+ continue;
+ }
- {
- /* Iterate over all public keys in this dearmored block */
- uint8_t *tpkt = pkt;
- size_t cert_len;
- while (len > 0) {
- if (pgpPubKeyCertLen(tpkt, len, &cert_len))
- break;
- if (cert_len > len)
- break;
-
- if (!dnf_keyring_add_public_key_from_memory(keyring, filename, tpkt, cert_len,
- /* Remember first error message */
- error == NULL || *error != NULL ? NULL : error))
- ret = FALSE;
-
- tpkt += cert_len;
- len -= cert_len;
- importable_certificates_found = TRUE;
+ {
+ /* Iterate over all public keys in this dearmored block */
+ uint8_t *tpkt = pkt;
+ size_t cert_len;
+ while (len > 0) {
+ if (pgpPubKeyCertLen(tpkt, len, &cert_len))
+ break;
+ if (cert_len > len)
+ break;
+
+ if (!dnf_keyring_add_public_key_from_memory(keyring, filename, tpkt, cert_len,
+ /* Remember first error message */
+ error == NULL || *error != NULL ? NULL : error))
+ ret = FALSE;
+
+ tpkt += cert_len;
+ len -= cert_len;
+ importable_certificates_found = TRUE;
+ }
}
}
diff --git a/tests/libdnf/dnf-self-test.c b/tests/libdnf/dnf-self-test.c
index 6eadeb94..5ec63504 100644
--- a/tests/libdnf/dnf-self-test.c
+++ b/tests/libdnf/dnf-self-test.c
@@ -266,6 +266,73 @@ dnf_keyring_add_public_key_two_packets(void)
g_assert_true(verification_passed);
}
+/* Test importing two keys from a file with two ASCII-armored blocks, each
+ * with a public key OpenPGP packet. */
+static void
+dnf_keyring_add_public_key_two_blocks(void)
+{
+ g_autofree gchar *key_filename = NULL;
+ g_autofree gchar *data_filename = NULL;
+ g_autofree gchar *rsa_signature_filename = NULL;
+ g_autofree gchar *edsa_signature_filename = NULL;
+ gboolean import_passed;
+ g_autoptr(GError) import_error = NULL;
+ gboolean verification_passed;
+
+ key_filename = dnf_test_get_filename("dnf_keyring_add_public_key/twoblocks.pub");
+ data_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ rsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.rsa.sig");
+ edsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.edsa.sig");
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, rsa_signature_filename);
+ g_assert_true(import_passed);
+ g_assert_no_error(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, edsa_signature_filename);
+ g_assert_true(import_passed);
+ g_assert_no_error(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+}
+
+/* Test importing two ASCII-armored blocks, first invalid, second valid. Test
+ * that the second valid key was imported despite importing the first one
+ * failed. */
+static void
+dnf_keyring_add_public_key_blocks_invalid_valid(void)
+{
+ g_autofree gchar *key_filename = NULL;
+ g_autofree gchar *data_filename = NULL;
+ g_autofree gchar *rsa_signature_filename = NULL;
+ g_autofree gchar *edsa_signature_filename = NULL;
+ gboolean import_passed;
+ g_autoptr(GError) import_error = NULL;
+ gboolean verification_passed;
+
+ key_filename = dnf_test_get_filename("dnf_keyring_add_public_key/blocks_invalid_valid.pub");
+ data_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input");
+ rsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.rsa.sig");
+ edsa_signature_filename = dnf_test_get_filename("dnf_keyring_add_public_key/input.edsa.sig");
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, rsa_signature_filename);
+ g_assert_false(import_passed);
+ g_assert_nonnull(import_error);
+ g_clear_error(&import_error);
+ g_assert_false(verification_passed);
+
+ import_and_verify(&import_passed, &import_error, &verification_passed,
+ key_filename, data_filename, edsa_signature_filename);
+ g_assert_false(import_passed);
+ g_assert_nonnull(import_error);
+ g_clear_error(&import_error);
+ g_assert_true(verification_passed);
+}
+
static void
dnf_lock_func(void)
{
@@ -1486,6 +1553,8 @@ main(int argc, char **argv)
g_test_add_func("/libdnf/dnf_keyring_add_public_key[valid]", dnf_keyring_add_public_key_valid);
g_test_add_func("/libdnf/dnf_keyring_add_public_key[invalid]", dnf_keyring_add_public_key_invalid);
g_test_add_func("/libdnf/dnf_keyring_add_public_key[two-packets]", dnf_keyring_add_public_key_two_packets);
+ g_test_add_func("/libdnf/dnf_keyring_add_public_key[two-blocks]", dnf_keyring_add_public_key_two_blocks);
+ g_test_add_func("/libdnf/dnf_keyring_add_public_key[valid-and-invalid-blocks]", dnf_keyring_add_public_key_blocks_invalid_valid);
g_test_add_func("/libdnf/lock", dnf_lock_func);
g_test_add_func("/libdnf/lock[threads]", dnf_lock_threads_func);
g_test_add_func("/libdnf/split_releasever", dnf_split_releasever_func);
--
2.53.0

View File

@ -0,0 +1,66 @@
From 60ab51a163451fe6da0173ae7a3a05d8689ff7ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
Date: Thu, 9 Apr 2026 16:26:20 +0200
Subject: [PATCH 30/30] Fix formatting error messages when importing subkeys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Upstream commit: 5207a34fb9b5f9f4853dd5f3c1f03e2881cfb5da
Compilation on RHEL 10, where RPM is older than version 6, warns:
/home/test/rhel/libdnf/libdnf-0.73.1/libdnf/dnf-keyring.cpp: In function gboolean dnf_keyring_add_publ
ic_key_from_memory(rpmKeyring, const gchar*, const uint8_t*, size_t, GError**):
/home/test/rhel/libdnf/libdnf-0.73.1/libdnf/dnf-keyring.cpp:176:33: warning: too many arguments for for
mat [-Wformat-extra-args]
176 | "failed to add 0x%s subkey from %s to rpmdb",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/test/rhel/libdnf/libdnf-0.73.1/libdnf/dnf-keyring.cpp:185:33: warning: too many arguments for for
mat [-Wformat-extra-args]
185 | "failed to add a subkey for 0x%s primary key from %s to rpmdb",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is a real bug introduced in commit
e0c56202ebde444bb73065843ac3dc2f7e4a8f3a ("Log identifiers of keys
imported by dnf_keyring_add_public_key()").
This patch fixes the format string arguments.
It also fixes a typo in a variable name of branching condition.
---
libdnf/dnf-keyring.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp
index 6ada3560..731c663d 100644
--- a/libdnf/dnf-keyring.cpp
+++ b/libdnf/dnf-keyring.cpp
@@ -163,7 +163,7 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
char *subkeyid = formatkeyid(subkey);
ret = FALSE;
if (keyid == NULL)
- if (subkey == NULL)
+ if (subkeyid == NULL)
g_set_error(error,
DNF_ERROR,
DNF_ERROR_GPG_SIGNATURE_INVALID,
@@ -175,7 +175,6 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
DNF_ERROR_GPG_SIGNATURE_INVALID,
"failed to add 0x%s subkey from %s to rpmdb",
subkeyid,
- keyid,
filename);
else
if (subkeyid == NULL)
@@ -183,7 +182,6 @@ dnf_keyring_add_public_key_from_memory(rpmKeyring keyring,
DNF_ERROR,
DNF_ERROR_GPG_SIGNATURE_INVALID,
"failed to add a subkey for 0x%s primary key from %s to rpmdb",
- subkeyid,
keyid,
filename);
else
--
2.53.0

View File

@ -56,7 +56,7 @@
Name: libdnf
Version: %{libdnf_major_version}.%{libdnf_minor_version}.%{libdnf_micro_version}
Release: 14%{?dist}
Release: 15%{?dist}
Summary: Library providing simplified C and Python API to libsolv
License: LGPL-2.1-or-later
URL: https://github.com/rpm-software-management/libdnf
@ -85,6 +85,12 @@ Patch21: 0021-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch
Patch22: 0022-Describe-all-problems-even-when-there-are-protected-.patch
Patch23: 0023-Clearer-error-for-protected-package-broken-dependenc.patch
Patch24: 0024-Goal-set-protected-as-userinstalled-only-for-the-tem.patch
Patch25: 0025-tests-Add-tests-for-dnf_keyring_add_public_key.patch
Patch26: 0026-Move-importing-a-key-from-a-memory-block-into-a-sepa.patch
Patch27: 0027-Log-identifiers-of-keys-imported-by-dnf_keyring_add_.patch
Patch28: 0028-Fix-dnf_keyring_add_public_key-to-add-all-keys-from-.patch
Patch29: 0029-Fix-dnf_keyring_add_public_key-to-add-keys-from-all-.patch
Patch30: 0030-Fix-formatting-error-messages-when-importing-subkeys.patch
BuildRequires: cmake
BuildRequires: gcc
@ -328,6 +334,10 @@ popd
%endif
%changelog
* Mon Apr 13 2026 Petr Pisar <ppisar@redhat.com> - 0.73.1-15
- Fix dnf_keyring_add_public_key() to add multiple keys from a single file
(RHEL-156063)
* Mon Feb 09 2026 Ales Matej <amatej@redhat.com> - 0.73.1-14
- Mark all protected packages as user installed for all transactions (RHEL-128445)
- Add `filterUnneededExtraUserinstalled` and Python version to the API