diff --git a/0025-tests-Add-tests-for-dnf_keyring_add_public_key.patch b/0025-tests-Add-tests-for-dnf_keyring_add_public_key.patch new file mode 100644 index 0000000..14f5f97 --- /dev/null +++ b/0025-tests-Add-tests-for-dnf_keyring_add_public_key.patch @@ -0,0 +1,307 @@ +From 72252383942769f8ad7858e3d5abe2cb64488371 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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 + #include + #include + #include + #include ++#include ++#include ++#include ++#include ++ ++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 + diff --git a/0026-Move-importing-a-key-from-a-memory-block-into-a-sepa.patch b/0026-Move-importing-a-key-from-a-memory-block-into-a-sepa.patch new file mode 100644 index 0000000..f894cc2 --- /dev/null +++ b/0026-Move-importing-a-key-from-a-memory-block-into-a-sepa.patch @@ -0,0 +1,184 @@ +From 49df864cde21f9d160bfd422058a3e33970222cc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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 + diff --git a/0027-Log-identifiers-of-keys-imported-by-dnf_keyring_add_.patch b/0027-Log-identifiers-of-keys-imported-by-dnf_keyring_add_.patch new file mode 100644 index 0000000..1817076 --- /dev/null +++ b/0027-Log-identifiers-of-keys-imported-by-dnf_keyring_add_.patch @@ -0,0 +1,169 @@ +From 435b2a678af4f428d66044f7c5035c640abbc780 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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 + diff --git a/0028-Fix-dnf_keyring_add_public_key-to-add-all-keys-from-.patch b/0028-Fix-dnf_keyring_add_public_key-to-add-all-keys-from-.patch new file mode 100644 index 0000000..bee817d --- /dev/null +++ b/0028-Fix-dnf_keyring_add_public_key-to-add-all-keys-from-.patch @@ -0,0 +1,221 @@ +From 569f317bbf22e4ccc3a473cd3ee895a2891852f1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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----- ++ ++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 ++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 + diff --git a/0029-Fix-dnf_keyring_add_public_key-to-add-keys-from-all-.patch b/0029-Fix-dnf_keyring_add_public_key-to-add-keys-from-all-.patch new file mode 100644 index 0000000..e5ac98a --- /dev/null +++ b/0029-Fix-dnf_keyring_add_public_key-to-add-keys-from-all-.patch @@ -0,0 +1,338 @@ +From 3b3c8910837d51431e9cc79e131671f48ea5f04b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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 ++#include + #include + #include + #include +@@ -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 + diff --git a/0030-Fix-formatting-error-messages-when-importing-subkeys.patch b/0030-Fix-formatting-error-messages-when-importing-subkeys.patch new file mode 100644 index 0000000..8148ad1 --- /dev/null +++ b/0030-Fix-formatting-error-messages-when-importing-subkeys.patch @@ -0,0 +1,66 @@ +From 60ab51a163451fe6da0173ae7a3a05d8689ff7ef Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= +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 + diff --git a/libdnf.spec b/libdnf.spec index 3eae40d..472bae2 100644 --- a/libdnf.spec +++ b/libdnf.spec @@ -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 - 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 - 0.73.1-14 - Mark all protected packages as user installed for all transactions (RHEL-128445) - Add `filterUnneededExtraUserinstalled` and Python version to the API