/
githubmirror
/
librepo
Обзор
Документация
Войти
/
githubmirror
/
librepo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/test_gpg.c
422 строки
16 KB
Marek Blaha
test: verify missing key error message consistency across backends
30 июн 2026, 00:14
30 июн 2026, 00:14
54e8496
Код
Авторство
О чём код?
#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "librepo/rcodes.h" #include "librepo/util.h" #include "librepo/gpg.h" #include "fixtures.h" #include "testsys.h" #include "test_gpg.h" START_TEST(test_gpg_check_signature) { gboolean ret; char *key_path, *_key_path; char *data_path, *_data_path; char *signature_path, *_signature_path; char *tmp_home_path; GError *tmp_err = NULL; tmp_home_path = lr_gettmpdir(); key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.key.asc", NULL); _key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml_bad.key.asc", NULL); data_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml", NULL); _data_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml_bad", NULL); signature_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.asc", NULL); _signature_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml_bad.sig", NULL); // Import the first key directly from the file ret = lr_gpg_import_key(key_path, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); // Valid key and data ret = lr_gpg_check_signature(signature_path, data_path, tmp_home_path, &tmp_err); ck_assert_msg(ret, "Checking valid key and data from file failed with \"%s\"", (tmp_err && tmp_err->message) ? tmp_err->message : ""); ck_assert_msg(NULL == tmp_err, "Checking valid key and data from file passed but set error \"%s\"", (tmp_err && tmp_err->message) ? tmp_err->message : ""); // Bad signature signed with unknown key ret = lr_gpg_check_signature(_signature_path, data_path, tmp_home_path, &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); g_clear_error(&tmp_err); // Bad data ret = lr_gpg_check_signature(signature_path, _data_path, tmp_home_path, &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); g_clear_error(&tmp_err); // Load the second key into memory and import it from memory gchar *contents; gsize length; ret = g_file_get_contents(_key_path, &contents, &length, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); ret = lr_gpg_import_key_from_memory(contents, length, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); g_free(contents); // Valid key and data ret = lr_gpg_check_signature(_signature_path, _data_path, tmp_home_path, &tmp_err); ck_assert_msg(ret, "Checking valid key and data from memory failed with \"%s\"", (tmp_err && tmp_err->message) ? tmp_err->message : ""); ck_assert_msg(NULL == tmp_err, "Checking valid key and data from memory passed but set error \"%s\"", (tmp_err && tmp_err->message) ? tmp_err->message : ""); // Bad signature signed with known key ret = lr_gpg_check_signature(_signature_path, data_path, tmp_home_path, &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); g_clear_error(&tmp_err); tmp_err = NULL; // Bad data 2 ret = lr_gpg_check_signature(_signature_path, data_path, tmp_home_path, &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); g_clear_error(&tmp_err); tmp_err = NULL; lr_remove_dir(tmp_home_path); lr_free(key_path); lr_free(_key_path); lr_free(data_path); lr_free(_data_path); lr_free(signature_path); lr_free(_signature_path); g_free(tmp_home_path); } END_TEST static void check_key_import_test_export(const char *key_path) { gboolean ret; char *tmp_home_path; GError *tmp_err = NULL; tmp_home_path = lr_gettmpdir(); // Import the key from file descriptor int key_fd = open(key_path, O_RDONLY); ck_assert(key_fd != -1); ret = lr_gpg_import_key_from_fd(key_fd, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); ck_assert(close(key_fd) != -1); // Export the keys LrGpgKey *keys = lr_gpg_list_keys(TRUE, tmp_home_path, &tmp_err); ck_assert_ptr_nonnull(keys); ck_assert_ptr_null(tmp_err); // Test key user ids char * const *uids = lr_gpg_key_get_userids(keys); ck_assert_ptr_nonnull(uids); ck_assert(g_strcmp0(uids[0], "repository signing key (test key) <noone@example.org>") == 0); ck_assert_ptr_null(uids[1]); // Get subkeys const LrGpgSubkey *subkeys = lr_gpg_key_get_subkeys(keys); ck_assert_ptr_nonnull(subkeys); // Test first subkey const char *id = lr_gpg_subkey_get_id(subkeys); ck_assert(g_strcmp0(id, "97BDB7906441EDF5") == 0); const char *fingerprint = lr_gpg_subkey_get_fingerprint(subkeys); ck_assert(g_strcmp0(fingerprint, "F4FC5B6AC58696023B9DBFB197BDB7906441EDF5") == 0); long timestamp = lr_gpg_subkey_get_timestamp(subkeys); ck_assert(timestamp == 1677863811); gboolean can_sign = lr_gpg_subkey_get_can_sign(subkeys); ck_assert(can_sign); // Get second subkey subkeys = lr_gpg_subkey_get_next(subkeys); ck_assert_ptr_nonnull(subkeys); // Test second subkey id = lr_gpg_subkey_get_id(subkeys); ck_assert(g_strcmp0(id, "C4101E247506302F") == 0); // fingerprint = lr_gpg_subkey_get_fingerprint(subkeys); // ck_assert(g_strcmp0(fingerprint, "FBF903F3B01FFA462C6DBF96C4101E247506302F") == 0); timestamp = lr_gpg_subkey_get_timestamp(subkeys); ck_assert(timestamp == 1677863811); // can_sign = lr_gpg_subkey_get_can_sign(subkeys); // ck_assert(!can_sign); // There are no other subkeys for the key subkeys = lr_gpg_subkey_get_next(subkeys); ck_assert_ptr_null(subkeys); // Test exported raw key const char *raw_key = lr_gpg_key_get_raw_key(keys); ck_assert_ptr_nonnull(raw_key); ck_assert(strstr(raw_key, "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" "\n" "mQENBGQCK4MBCACmfps+HeGv3BnrFmOO5PGPFDgQ0KtmSkfkrw8Sf3dcsfiMS9II\n" "2AOJNLd8NOgOkGGlyymrrf/FKPWnQHQDKxLgOuZ0FvAIfb+VNTjnuj8TfO94y7n1\n" "VpCu7oRKOQLfmRd3OQDe0CofNjYeYKn2l/an/x80o3ikoFMEdHcOkdhPkm/Kf5oc\n" "bJYVvxb81vvtsEOV5/+ybCpJIxXhk1y3DBBhaZGfEYOy9oF5/w2ZmL+mbLQ6KVlK\n" "euTDlADLv3KAtGALy1E/ZHANChPc372JI7osKT6MhRr1fItY6NCFSgXapDtea0x0\n" "sZj6LvOuWISZ5n+YoTM+loMgxjFHXEd7nV93ABEBAAG0NXJlcG9zaXRvcnkgc2ln\n" "bmluZyBrZXkgKHRlc3Qga2V5KSA8bm9vbmVAZXhhbXBsZS5vcmc+iQFOBBMBCgA4\n" "FiEE9PxbasWGlgI7nb+xl723kGRB7fUFAmQCK4MCGwMFCwkIBwIGFQoJCAsCBBYC\n" "AwECHgECF4AACgkQl723kGRB7fUC7Af/YSwbvM2xsjK947FOmVfX63qf5v2fRI7Q\n" "CEil6X8uZw5UXEvqsGDZCqmhYKSKlNnuky392D0/Kp0h3KCJ7KHA9/SCHrnl2PN4\n" "nenX5xunwJd3mhGOtLR2WSYXLBlG5Dh6bkDsWEaR8OPC8rSNNM9OfuuXMWf0OF91\n" "WYwleixUrfMbX9gVKozFzGv/yAospsJ0F48Z3RW+XROYLwbk72G6jXR0hkB1zEpN\n" "gwbpldxQFEagBDNwpvCF8jlwCOLjOQWM3oTx4YUdG4gAEGDKLsJcv4cMTaEUCr0r\n" "FEIHRcFCox7P4f9UFlIUNLj6zWNP9Nr3Yuqfh4bdYJfWE9kK271NErkBDQRkAiuD\n" "AQgAxjdpqh4z8wcURkon/bdxLKeI1weKYEdprNrEEHLlk5hV+qQ5dHtUCHBcf2sH\n" "kAwG4S3s4WAVoNyFP2l1gC5eM6/J/t1K5UgKEsDdCcVQMO1hA+T6breCLGTyS3Ut\n" "Qes/EZloe+zUjH8covZQwQJFuZp5xVaTVK/7IjYzL/zUK6ySjbXGU4W/iYGV07Je\n" "1W401uCwc3nosjptXgmc6Nhawn6U+I5tgPgX4L8hI3tla+fsiUpDD+Xlz3m90f8w\n" "4baHRSajB4b6Llu963JSTUE5tKfmxVVfHbKTe2Y5Nf6coJXfFxy3UuFDTUfK5S/C\n" "rIwB+ZoahvjfhC6eg7gh4LNnawARAQABiQE2BBgBCgAgFiEE9PxbasWGlgI7nb+x\n" "l723kGRB7fUFAmQCK4MCGwwACgkQl723kGRB7fV9Dgf9GvTNb36IxIOhyqWFFXZp\n" "C3NqyyIbRZ6KEXUzLaC88fx2hK1BQkeDASUS0HxccCCdjR1zesk0QfZtGJxVQFw+\n" "AyLWzMGRLDfMvXYRLzjYowYJ++beVadt/EfPzRHckunW75PLHrDd89Hur92r4ikK\n" "42GA3ZDPr9yggpnYvsAXt25lDx37HN3SeVds2Gb6wA01Vo6jXVKQGhRgk9K5T8dB\n" "szn12XdPGxhzo1HXPUFWwSi8SODpG+MkC+IGOrFzPHW5R5kOO7+nbALjzLWSrcfw\n" "zLiMgTqA0Z2O8QcTGSQ7jYQhSaWguWzhT1LsZ0/9xNGSHyWfYkYtXPAogB50qlvM\n" "LQ==\n" "=RI7R\n" "-----END PGP PUBLIC KEY BLOCK-----\n") != NULL); // Only one key is in keyring ck_assert_ptr_null(lr_gpg_key_get_next(keys)); lr_gpg_keys_free(keys); lr_remove_dir(tmp_home_path); g_free(tmp_home_path); } START_TEST(test_gpg_check_armored_key_import_test_export) { char *key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.key.asc", NULL); check_key_import_test_export(key_path); lr_free(key_path); } END_TEST START_TEST(test_gpg_check_binary_key_import_test_export) { char *key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.key", NULL); check_key_import_test_export(key_path); lr_free(key_path); } END_TEST START_TEST(test_gpg_check_import_padded) { // Verify that lr_gpg_import_key_from_memory properly respects the key_len // argument and does not read any garbage beyond the key in memory. gboolean ret; char *key_path; char *tmp_home_path; GError *tmp_err = NULL; tmp_home_path = lr_gettmpdir(); key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml_bad.key.asc", NULL); char *random_prefix_text = "The file with the key can have any text\n"; int prefix_len = strlen(random_prefix_text); // The suffix represents arbitrary memory that is located after the key char suffix_with_garbage_and_null_byte[] = {'\xf4', '\xc2', '\x7f', '\0',}; int suffix_len = sizeof(suffix_with_garbage_and_null_byte); // Load the key into memory gchar *contents; gsize length; ret = g_file_get_contents(key_path, &contents, &length, &tmp_err); ck_assert_ptr_null(tmp_err); ck_assert(ret); // Wrap the loaded key with random text prefix and garbage suffix gchar *padded_contents = g_malloc0(prefix_len + length + suffix_len); memcpy(padded_contents, random_prefix_text, prefix_len); memcpy(padded_contents + prefix_len, contents, length); memcpy(padded_contents + prefix_len + length, suffix_with_garbage_and_null_byte, suffix_len); ret = lr_gpg_import_key_from_memory(padded_contents, prefix_len + length, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); g_free(contents); g_free(padded_contents); lr_remove_dir(tmp_home_path); lr_free(key_path); g_free(tmp_home_path); } END_TEST START_TEST(test_gpg_missing_key_error_message) { // When verifying a signature without the signing key in the keyring, // both backends must report "Signing key not found" so that dnf5's // retry logic can trigger GPG key import. gboolean ret; char *data_path, *signature_path; char *tmp_home_path; GError *tmp_err = NULL; tmp_home_path = lr_gettmpdir(); data_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml", NULL); signature_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.asc", NULL); // Do NOT import any key — the keyring is empty. ret = lr_gpg_check_signature(signature_path, data_path, tmp_home_path, &tmp_err); ck_assert(!ret); ck_assert_ptr_nonnull(tmp_err); ck_assert_msg(g_str_has_suffix(tmp_err->message, "Signing key not found"), "Expected error ending with 'Signing key not found', got: %s", tmp_err->message); g_clear_error(&tmp_err); lr_remove_dir(tmp_home_path); lr_free(data_path); lr_free(signature_path); g_free(tmp_home_path); } END_TEST START_TEST(test_gpgme_expired_signature) { // This test verifies that expired GPG signatures are properly rejected // using the gpgme backend. #ifndef USE_GPGME // Skip this test when not using gpgme backend return; #endif gboolean ret; char *valid_key_path, *valid_data_path, *valid_signature_path; char *tmp_home_path; GError *tmp_err = NULL; tmp_home_path = lr_gettmpdir(); // First test with valid key/signature (sanity check) valid_key_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.key.asc", NULL); valid_data_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml", NULL); valid_signature_path = lr_pathconcat(test_globals.testdata_dir, "repo_yum_01/repodata/repomd.xml.asc", NULL); // Import the valid key first ret = lr_gpg_import_key(valid_key_path, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); // This should pass with a valid signature (sanity check) ret = lr_gpg_check_signature(valid_signature_path, valid_data_path, tmp_home_path, &tmp_err); ck_assert_msg(ret, "Valid signature should pass: %s", (tmp_err && tmp_err->message) ? tmp_err->message : ""); ck_assert_ptr_null(tmp_err); // Clean up for expired key test lr_remove_dir(tmp_home_path); tmp_home_path = lr_gettmpdir(); // Now test with expired key char *expired_key_path = lr_pathconcat(test_globals.testdata_dir, "expired_test/expired_key.asc", NULL); char *expired_data_path = lr_pathconcat(test_globals.testdata_dir, "expired_test/test_data.txt", NULL); char *expired_signature_path = lr_pathconcat(test_globals.testdata_dir, "expired_test/test_data.txt.asc", NULL); // Import the expired key ret = lr_gpg_import_key(expired_key_path, tmp_home_path, &tmp_err); ck_assert(ret); ck_assert_ptr_null(tmp_err); // This should FAIL with expired signature - gpgme will reject it ret = lr_gpg_check_signature(expired_signature_path, expired_data_path, tmp_home_path, &tmp_err); ck_assert_msg(!ret, "Expired signature should be rejected"); ck_assert_ptr_nonnull(tmp_err); // Just verify that we get an appropriate error message (no specific text required) ck_assert_msg(tmp_err->message != NULL, "Error message should not be NULL"); g_debug("GPG verification failed as expected: %s", tmp_err->message); // Cleanup lr_remove_dir(tmp_home_path); lr_free(valid_key_path); lr_free(valid_data_path); lr_free(valid_signature_path); lr_free(expired_key_path); lr_free(expired_data_path); lr_free(expired_signature_path); g_free(tmp_home_path); if (tmp_err) g_error_free(tmp_err); } END_TEST Suite * gpg_suite(void) { Suite *s = suite_create("gpg"); TCase *tc = tcase_create("Main"); tcase_add_test(tc, test_gpg_check_signature); tcase_add_test(tc, test_gpg_check_armored_key_import_test_export); tcase_add_test(tc, test_gpg_check_binary_key_import_test_export); tcase_add_test(tc, test_gpg_check_import_padded); tcase_add_test(tc, test_gpg_missing_key_error_message); tcase_add_test(tc, test_gpgme_expired_signature); suite_add_tcase(s, tc); return s; }