diff --git a/docs/man/rpmkeys.8.md b/docs/man/rpmkeys.8.md index 1b27f06247..468eec5c65 100644 --- a/docs/man/rpmkeys.8.md +++ b/docs/man/rpmkeys.8.md @@ -27,6 +27,8 @@ The general forms of rpm digital signature commands are **rpmkeys** {**-e\|\--erase\|-d\|\--delete**} *FINGERPRINT \...* +**rpmkeys** {**\--rebuild**} + **rpmkeys** {**-K\|\--checksig**} *PACKAGE\_FILE \...* The **\--checksig** option checks all the digests and signatures @@ -64,6 +66,14 @@ Erase the key(s) designated by *FINGERPRINT*. For example: **rpmkeys** **\--erase 771b18d3d7baa28734333c424344591e1964c5fc** +Rebuild the key storage: + +**rpmkeys** **\--rebuild** + +If the storage backend was changed the keys will be moved. Outdated or +no longer supported keys will be dropped. Keys will be stored in the newest +storage format. + SEE ALSO ======== diff --git a/include/rpm/rpmts.h b/include/rpm/rpmts.h index 86ca62e0ef..162cfe26f4 100644 --- a/include/rpm/rpmts.h +++ b/include/rpm/rpmts.h @@ -754,6 +754,8 @@ rpmtsi rpmtsiInit(rpmts ts); */ rpmte rpmtsiNext(rpmtsi tsi, rpmElementTypes types); +rpmRC rpmtsRebuildKeystore(rpmts ts, char ** loadBackends, int flags); + #ifdef __cplusplus } #endif diff --git a/lib/keystore.cc b/lib/keystore.cc index bca10abfe2..112ecb140e 100644 --- a/lib/keystore.cc +++ b/lib/keystore.cc @@ -1,6 +1,8 @@ #include "system.h" +#include #include +#include #include #include @@ -18,11 +20,13 @@ #include #include "rpmts_internal.hh" +#include "rpmmacro_internal.hh" #include "debug.h" using std::string; using namespace rpm; +namespace fs = std::filesystem; static int makePubkeyHeader(rpmts ts, rpmPubkey key, Header * hdrp); @@ -118,6 +122,42 @@ rpmRC rpm::check_backends(rpmtxn txn, rpmts ts) return rc; } +rpmRC rebuild_dir(rpmtxn txn, keystore * store, rpmKeyring keys) +{ + + auto macros = rpm::macros(); + std::string keyringpath = rpm::expand_path({"%{_keyringpath}"}); + std::string path = rpm::expand_path({rpmtxnRootDir(txn), keyringpath}); + std::string oldpath = path + ".rpmold/"; + std::error_code ec = {}; + rpmRC rc = RPMRC_OK; + macros.push("_keyringpath", "", (keyringpath + ".tmp/").c_str(), 0); + + rpmPubkey key = NULL; + auto iter = rpmKeyringInitIterator(keys, 0); + while ((rc == RPMRC_OK) && (key = rpmKeyringIteratorNext(iter))) { + rc = store->import_key(txn, key, 0, 0); + } + rpmKeyringIteratorFree(iter); + + if (rc != RPMRC_OK) + goto exit; + + /* Ignore errors from (re)moving old directory as it might not exist */ + fs::rename(path, oldpath, ec); + fs::rename(path + ".tmp", path, ec); + if (ec) { + rpmlog(RPMLOG_ERR, _("can't move new key store to %s: %s.\n"), path.c_str(), ec.message().c_str()); + rc = RPMRC_FAIL; + } + fs::remove_all(oldpath); + + exit: + macros.pop("_keyringpath"); + + return rc; +} + /*****************************************************************************/ rpmRC keystore_fs::load_keys(rpmtxn txn, rpmKeyring keyring) @@ -171,6 +211,11 @@ rpmRC keystore_fs::import_key(rpmtxn txn, rpmPubkey key, int replace, rpmFlags f return rc; } +rpmRC keystore_fs::rebuild(rpmtxn txn, rpmKeyring keys) +{ + return rebuild_dir(txn, this, keys); +} + /*****************************************************************************/ static int acquire_write_lock(rpmtxn txn) @@ -256,6 +301,12 @@ rpmRC keystore_openpgp_cert_d::import_key(rpmtxn txn, rpmPubkey key, int replace return rc; } +rpmRC keystore_openpgp_cert_d::rebuild(rpmtxn txn, rpmKeyring keys) +{ + return rebuild_dir(txn, this, keys); +} + + /*****************************************************************************/ rpmRC keystore_rpmdb::load_keys(rpmtxn txn, rpmKeyring keyring) @@ -357,6 +408,51 @@ rpmRC keystore_rpmdb::import_key(rpmtxn txn, rpmPubkey key, int replace, rpmFlag return rc; } +rpmRC keystore_rpmdb::rebuild(rpmtxn txn, rpmKeyring keys) +{ + rpmRC rc = RPMRC_OK; + std::set packages = {}; + Header h = NULL; + rpmPubkey key = NULL; + + auto iter = rpmKeyringInitIterator(keys, 0); + while ((key = rpmKeyringIteratorNext(iter))) { + if (makePubkeyHeader(rpmtxnTs(txn), key, &h) != 0) { + rpmlog(RPMLOG_ERR, _("can't create header for key %s\n"), + rpmPubkeyFingerprintAsHex(key)); + rc = RPMRC_FAIL; + break; + } + if ((rc = rpmtsImportHeader(txn, h, 0)) != RPMRC_OK) { + headerFree(h); + rpmlog(RPMLOG_ERR, _("can't add header to rpmdb for key %s\n"), + rpmPubkeyFingerprintAsHex(key)); + break; + } + packages.insert(headerGetInstance(h)); + headerFree(h); + } + rpmKeyringIteratorFree(iter); + + if (rc != RPMRC_OK) { + return rc; + } + + rpmdbMatchIterator mi = rpmtsInitIterator(rpmtxnTs(txn), RPMDBI_NAME, "gpg-pubkey", 0); + while ((h = rpmdbNextIterator(mi)) != NULL) { + if (!packages.count(headerGetInstance(h))) { + rpmRC rrc = rpmdbRemove(rpmtsGetRdb(rpmtxnTs(txn)), headerGetInstance(h)) ? + RPMRC_FAIL : RPMRC_OK; + if (rc != RPMRC_OK) { + rpmlog(RPMLOG_WARNING, "can't remove key %s", headerGetString(h, RPMTAG_NEVR)); + rc = rrc; + } + } + } + rpmdbFreeIterator(mi); + return rc; +} + static void addGpgProvide(Header h, const char *n, const char *v) { rpmsenseFlags pflags = (RPMSENSE_KEYRING|RPMSENSE_EQUAL); diff --git a/lib/keystore.hh b/lib/keystore.hh index e53ec91ff0..c17ecb6996 100644 --- a/lib/keystore.hh +++ b/lib/keystore.hh @@ -16,6 +16,7 @@ public: virtual rpmRC load_keys(rpmtxn txn, rpmKeyring keyring) = 0; virtual rpmRC import_key(rpmtxn txn, rpmPubkey key, int replace = 1, rpmFlags flags = 0) = 0; virtual rpmRC delete_key(rpmtxn txn, rpmPubkey key) = 0; + virtual rpmRC rebuild(rpmtxn txn, rpmKeyring keys) = 0; virtual ~keystore() = default; }; @@ -26,6 +27,7 @@ public: virtual rpmRC load_keys(rpmtxn txn, rpmKeyring keyring); virtual rpmRC import_key(rpmtxn txn, rpmPubkey key, int replace = 1, rpmFlags flags = 0); virtual rpmRC delete_key(rpmtxn txn, rpmPubkey key); + virtual rpmRC rebuild(rpmtxn txn, rpmKeyring keys); private: rpmRC delete_key(rpmtxn txn, const std::string & keyid, const std::string & newname = ""); @@ -37,6 +39,7 @@ public: virtual rpmRC load_keys(rpmtxn txn, rpmKeyring keyring); virtual rpmRC import_key(rpmtxn txn, rpmPubkey key, int replace = 1, rpmFlags flags = 0); virtual rpmRC delete_key(rpmtxn txn, rpmPubkey key); + virtual rpmRC rebuild(rpmtxn txn, rpmKeyring keys); private: rpmRC delete_key(rpmtxn txn, const std::string & keyid, unsigned int newinstance = 0); @@ -48,6 +51,7 @@ public: virtual rpmRC load_keys(rpmtxn txn, rpmKeyring keyring); virtual rpmRC import_key(rpmtxn txn, rpmPubkey key, int replace = 1, rpmFlags flags = 0); virtual rpmRC delete_key(rpmtxn txn, rpmPubkey key); + virtual rpmRC rebuild(rpmtxn txn, rpmKeyring keys); }; }; /* namespace */ diff --git a/lib/rpmts.cc b/lib/rpmts.cc index 701fca131b..e604ed4e70 100644 --- a/lib/rpmts.cc +++ b/lib/rpmts.cc @@ -9,6 +9,8 @@ #include #include +#include + #include #include /* rpmReadPackage etc */ #include @@ -31,6 +33,7 @@ #include "rpmts_internal.hh" #include "rpmte_internal.hh" #include "rpmlog_internal.hh" +#include "rpmmacro_internal.hh" #include "misc.hh" #include "rpmtriggers.hh" @@ -38,6 +41,7 @@ using std::string; using namespace rpm; +namespace fs = std::filesystem; /** * Iterator across transaction elements, forward on install, backward on erase. @@ -396,6 +400,95 @@ rpmRC rpmtxnDeletePubkey(rpmtxn txn, rpmPubkey key) return rc; } +rpmRC rpmtsRebuildKeystore(rpmts ts, char ** loadBackends, int flags) +{ + rpmRC rc = RPMRC_OK; + rpmtxn txn = rpmtxnBegin(ts, RPMTXN_WRITE); + rpmVSFlags oflags = rpmtsVSFlags(ts); + + rpmtsOpenDB(ts, O_RDWR); + + /* XXX keyring wont load if sigcheck disabled, force it temporarily */ + rpmtsSetVSFlags(ts, (oflags & ~RPMVSF_MASK_NOSIGNATURES)); + + keystore * ts_keystore = getKeystore(ts); + rpmKeyring keyring = rpmKeyringNew(); + + keystore_fs ks_fs = {}; + keystore_rpmdb ks_rpmdb = {}; + keystore_openpgp_cert_d ks_opengpg = {}; + + for (keystore * ks: std::vector{&ks_fs, &ks_rpmdb, &ks_opengpg}) { + rpmKeyring kr = rpmKeyringNew(); + ks->load_keys(txn, kr); + + char ** name = loadBackends; + if (name) { + for (;*name; name++) { + if (!strcmp(*name, ks->get_name().c_str())) + break; + + } + } + + /* backend not to be included */ + if (ks->get_name() != ts->keystore->get_name() && (!name || !*name)) { + if (!rpmKeyringIsEmpty(kr) && !flags) { + rpmlog(RPMLOG_ERR, _("public key backend %s is not empty\n"), + ks->get_name().c_str()); + rpmKeyringFree(kr); + rc = RPMRC_FAIL; + goto exit; + } + rpmKeyringFree(kr); + continue; + } + + rpmKeyringIterator iter = rpmKeyringInitIterator(kr, 0); + rpmPubkey key = NULL; + while ((key = rpmKeyringIteratorNext(iter)) != NULL) { + const unsigned char * pkt = NULL; + size_t pktlen = 0; + char *lints = NULL; + + rpmPubkeyRawData(key, &pkt, &pktlen); + int lrc = pgpPubKeyLint(pkt, pktlen, &lints) ; + if (lints) { + if (lrc != RPMRC_OK) + rpmlog(RPMLOG_WARNING, "dropping public key %s:\n", rpmPubkeyFingerprintAsHex(key)); + rpmlog(RPMLOG_WARNING, "%s\n", lints); + free(lints); + if (lrc != RPMRC_OK) + continue; + } + rpmKeyringModify(keyring, key, RPMKEYRING_MERGE); + } + rpmKeyringIteratorFree(iter); + rpmKeyringFree(kr); + } + + rc = ts_keystore->rebuild(txn, keyring); + + if (dynamic_cast(ts_keystore)) { + fs::remove_all(rpm::expand_path({rpmtxnRootDir(txn), + "%{_keyringpath}"})); + } else { + /* remove all gpg-pubkey packages */ + rpmKeyring kr = rpmKeyringNew(); + ks_rpmdb.rebuild(txn, kr); + rpmKeyringFree(kr); + } + + exit: + + ts->keyring = rpmKeyringFree(ts->keyring); + rpmKeyringFree(keyring); + rpmtxnEnd(txn); + rpmtsSetVSFlags(ts, oflags); + + return rc; +} + rpmRC rpmtsImportPubkey(const rpmts ts, const unsigned char * pkt, size_t pktlen) { rpmRC rc = RPMRC_FAIL; diff --git a/tests/rpmsigdig.at b/tests/rpmsigdig.at index c07c712b82..8198f0316d 100644 --- a/tests/rpmsigdig.at +++ b/tests/rpmsigdig.at @@ -1924,3 +1924,171 @@ hello-1.0.tar.gz:(none) ], []) RPMTEST_CLEANUP + +AT_SETUP([keyring rebuild rpmdb]) +AT_KEYWORDS([rpmkeys signature]) +RPMDB_INIT + +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-ed25519-test.pub +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring openpgp" \ + --import /data/keys/rpm.org-rsa-2048-add-subkey.asc +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/alice.asc + +RPMTEST_CHECK([[ +runroot rpmkeys --define "_keyring rpmdb" --rebuild +]], +[2], +[], +[error: public key backend fs is not empty +]) + +RPMTEST_CHECK([[ +runroot rpmkeys --define "_keyring rpmdb" --rebuild --include_backend fs --include_backend openpgp +runroot rpm -qa gpg-pubkey +runroot rpmkeys --define "_keyring rpmdb" --list +echo "===============================================" +runroot rpmkeys --define "_keyring fs" --list +runroot rpmkeys --define "_keyring openpgp" --list +runroot rpmkeys --define "_keyring rpmdb" -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm | grep "Header OpenPGP" +]], +[0], +[gpg-pubkey-771b18d3d7baa28734333c424344591e1964c5fc-58e63918 +gpg-pubkey-152bb32fd9ca982797e835cfb0645aec757bf69e-661d22a8 +gpg-pubkey-b6542f92f30650c36b6f41bcb3a771bfeb04e625-62521e00 +771b18d3d7baa28734333c424344591e1964c5fc rpm.org RSA testkey public key +152bb32fd9ca982797e835cfb0645aec757bf69e rpm.org ed25519 testkey public key +b6542f92f30650c36b6f41bcb3a771bfeb04e625 Alice public key +=============================================== + Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 771b18d3d7baa28734333c424344591e1964c5fc: OK +], +[ignore]) +RPMTEST_CLEANUP + +AT_SETUP([keyring rebuild rpmdb 2]) +AT_KEYWORDS([rpmkeys signature]) +RPMDB_INIT + +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-ed25519-test.pub +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring openpgp" \ + --import /data/keys/rpm.org-rsa-2048-add-subkey.asc +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/alice.asc + +RPMTEST_CHECK([[ +runroot rpmkeys --define "_keyring rpmdb" --rebuild --include_backend openpgp --drop_backends +runroot rpm -qa gpg-pubkey +runroot rpmkeys --define "_keyring rpmdb" --list +echo "===============================================" +runroot rpmkeys --define "_keyring fs" --list +runroot rpmkeys --define "_keyring openpgp" --list +runroot rpmkeys --define "_keyring rpmdb" -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm | grep "Header OpenPGP" +]], +[0], +[gpg-pubkey-771b18d3d7baa28734333c424344591e1964c5fc-58e63918 +gpg-pubkey-152bb32fd9ca982797e835cfb0645aec757bf69e-661d22a8 +771b18d3d7baa28734333c424344591e1964c5fc rpm.org RSA testkey public key +152bb32fd9ca982797e835cfb0645aec757bf69e rpm.org ed25519 testkey public key +=============================================== + Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 771b18d3d7baa28734333c424344591e1964c5fc: OK +], +[ignore]) +RPMTEST_CLEANUP + +AT_SETUP([keyring rebuild fs]) +AT_KEYWORDS([rpmkeys signature]) +RPMDB_INIT + +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-ed25519-test.pub +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring openpgp" \ + --import /data/keys/rpm.org-rsa-2048-add-subkey.asc +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/alice.asc + +RPMTEST_CHECK([[ +runroot rpmkeys --define "_keyring fs" --rebuild --include_backend rpmdb --include_backend openpgp +runroot rpmkeys --define "_keyring fs" --list +echo "===============================================" +runroot rpmkeys --define "_keyring rpmdb" --list +runroot rpmkeys --define "_keyring openpgp" --list +runroot rpmkeys --define "_keyring fs" -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm | grep "Header OpenPGP" +]], +[0], +[771b18d3d7baa28734333c424344591e1964c5fc rpm.org RSA testkey public key +152bb32fd9ca982797e835cfb0645aec757bf69e rpm.org ed25519 testkey public key +b6542f92f30650c36b6f41bcb3a771bfeb04e625 Alice public key +=============================================== + Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 771b18d3d7baa28734333c424344591e1964c5fc: OK +], +[ignore]) + +RPMTEST_CLEANUP + +AT_SETUP([keyring rebuild openpgp]) +AT_KEYWORDS([rpmkeys signature]) +RPMDB_INIT + +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring rpmdb" \ + --import /data/keys/rpm.org-ed25519-test.pub +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/rpm.org-rsa-2048-test.pub +runroot rpmkeys \ + --define "_keyring openpgp" \ + --import /data/keys/rpm.org-rsa-2048-add-subkey.asc +runroot rpmkeys \ + --define "_keyring fs" \ + --import /data/keys/alice.asc + +RPMTEST_CHECK([[ +runroot rpmkeys --define "_keyring openpgp" --rebuild --include_backend fs --include_backend rpmdb +runroot rpmkeys --define "_keyring openpgp" --list +echo "===============================================" +runroot rpmkeys --define "_keyring fs" --list +runroot rpmkeys --define "_keyring rpmdb" --list +runroot rpmkeys --define "_keyring openpgp" -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm | grep "Header OpenPGP" +]], +[0], +[771b18d3d7baa28734333c424344591e1964c5fc rpm.org RSA testkey public key +152bb32fd9ca982797e835cfb0645aec757bf69e rpm.org ed25519 testkey public key +b6542f92f30650c36b6f41bcb3a771bfeb04e625 Alice public key +=============================================== + Header OpenPGP V4 EdDSA/SHA512 signature, key fingerprint: 771b18d3d7baa28734333c424344591e1964c5fc: OK +], +[ignore]) + +RPMTEST_CLEANUP diff --git a/tools/rpmkeys.cc b/tools/rpmkeys.cc index 89a2a7131e..98e23a1214 100644 --- a/tools/rpmkeys.cc +++ b/tools/rpmkeys.cc @@ -15,10 +15,13 @@ enum modes { MODE_DELKEY = (1 << 2), MODE_LISTKEY = (1 << 3), MODE_EXPORTKEY = (1 << 4), + MODE_REBUILD = (1 << 5), }; static int mode = 0; static int test = 0; +static char ** backends = NULL; +static int rebuildflags = 0; static struct poptOption keyOptsTable[] = { { "checksig", 'K', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_CHECKSIG, @@ -35,6 +38,12 @@ static struct poptOption keyOptsTable[] = { N_("Erase keys from RPM keyring"), NULL }, { "list", 'l', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_LISTKEY, N_("list keys from RPM keyring"), NULL }, + { "rebuild", '\0', (POPT_ARG_VAL|POPT_ARGFLAG_OR), &mode, MODE_REBUILD, + N_("rebuild the keyring - convert to current backend"), NULL }, + { "include_backend", '\0', POPT_ARG_ARGV, &backends, 0, + N_("include other backends when rebuilding current backend"), NULL }, + { "drop_backends", '\0', POPT_ARG_NONE, &rebuildflags, 0, + N_("drop keys from other backends when rebuilding"), NULL }, POPT_TABLEEND }; @@ -149,7 +158,8 @@ int main(int argc, char *argv[]) args = (ARGV_const_t) poptGetArgs(optCon); - if (args == NULL && mode != MODE_LISTKEY && mode != MODE_EXPORTKEY) + if (args == NULL && mode != MODE_LISTKEY && mode != MODE_EXPORTKEY && + mode != MODE_REBUILD) argerror(_("no arguments given")); ts = rpmtsCreate(); @@ -183,6 +193,23 @@ int main(int argc, char *argv[]) ec = matchingKeys(ts, args, printKey); break; } + case MODE_REBUILD: + { + + if (backends) { + for (char** strptr = backends; *strptr; strptr++) { + if (!strcmp(*strptr, "rpmdb") && + !strcmp(*strptr, "fs") && + !strcmp(*strptr, "rpmpgp")) { + rpmlog(RPMLOG_ERR, _("unknown backend: %s\n"), *strptr); + ec = -1; + break; + } + } + } + ec = rpmtsRebuildKeystore(ts, backends, rebuildflags); + break; + } default: argerror(_("only one major mode may be specified")); } @@ -192,6 +219,10 @@ int main(int argc, char *argv[]) rpmcliFini(optCon); fflush(stderr); fflush(stdout); + if (backends) + for (char** strptr = backends; *strptr; strptr++) + free(*strptr); + free(backends); if (ferror(stdout) || ferror(stderr)) return 255; /* I/O error */ return ec;