Skip to content

Commit

Permalink
Implement openpgp.cert.d based keystore
Browse files Browse the repository at this point in the history
Refactor code from the fs backend into shared helper functions

This does implement the layout on the file system and the write lock of
the openpgp.cert.d proposal according to
https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html
but not the Trust root, Petname mapping or Trusted introducers.

This still is a mess of C and C++ style strings that we want to clean
up later by adding C++ string based path handling and may be using the
filesystem C++ library.

Resolves:  rpm-software-management#3341
  • Loading branch information
ffesti committed Nov 8, 2024
1 parent 117e6b5 commit 38b2ef5
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 36 deletions.
178 changes: 142 additions & 36 deletions lib/keystore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <string>

#include <fcntl.h>
#include <unistd.h>

#include <rpm/header.h>
#include <rpm/rpmbase64.h>
Expand All @@ -24,11 +25,11 @@ using namespace rpm;

static int makePubkeyHeader(rpmts ts, rpmPubkey key, Header * hdrp);

rpmRC keystore_fs::load_keys(rpmtxn txn, rpmKeyring keyring)
static rpmRC load_keys_from_glob(rpmtxn txn, rpmKeyring keyring, string glob)
{
ARGV_t files = NULL;
/* XXX TODO: deal with chroot path issues */
char *pkpath = rpmGetPath(rpmtxnRootDir(txn), "%{_keyringpath}/*.key", NULL);
char *pkpath = rpmGetPath(rpmtxnRootDir(txn), glob.c_str(), NULL);

rpmlog(RPMLOG_DEBUG, "loading keyring from pubkeys in %s\n", pkpath);
if (rpmGlob(pkpath, NULL, &files)) {
Expand All @@ -55,6 +56,53 @@ rpmRC keystore_fs::load_keys(rpmtxn txn, rpmKeyring keyring)
return RPMRC_OK;
}

static rpmRC write_key_to_disk(rpmPubkey key, string & dir, string & filename, int replace, rpmFlags flags)
{
rpmRC rc = RPMRC_FAIL;
char *keyval = rpmPubkeyArmorWrap(key);
string tmppath = "";
string path = dir + "/" + filename;

if (replace) {
tmppath = dir + "/" + filename + ".new";
unlink(tmppath.c_str());
}

if (rpmMkdirs(NULL, dir.c_str())) {
rpmlog(RPMLOG_ERR, _("failed to create keyring directory %s: %s\n"),
path.c_str(), strerror(errno));
goto exit;
}

if (FD_t fd = Fopen(replace ? tmppath.c_str() : path.c_str(), "wx")) {
size_t keylen = strlen(keyval);
if (Fwrite(keyval, 1, keylen, fd) == keylen)
rc = RPMRC_OK;
Fclose(fd);
}
if (!rc && replace && rename(tmppath.c_str(), path.c_str()) != 0)
rc = RPMRC_FAIL;

if (rc) {
rpmlog(RPMLOG_ERR, _("failed to import key: %s: %s\n"),
replace ? tmppath.c_str() : path.c_str(), strerror(errno));
if (replace)
unlink(tmppath.c_str());
}

exit:
free(keyval);
return rc;
}


/*****************************************************************************/

rpmRC keystore_fs::load_keys(rpmtxn txn, rpmKeyring keyring)
{
return load_keys_from_glob(txn, keyring, "%{_keyringpath}/*.key");
}

rpmRC keystore_fs::delete_key(rpmtxn txn, const string & keyid, const string & newname)
{
rpmRC rc = RPMRC_NOTFOUND;
Expand Down Expand Up @@ -82,55 +130,113 @@ rpmRC keystore_fs::delete_key(rpmtxn txn, rpmPubkey key)
rpmRC keystore_fs::import_key(rpmtxn txn, rpmPubkey key, int replace, rpmFlags flags)
{
rpmRC rc = RPMRC_FAIL;
const char *fp = rpmPubkeyFingerprintAsHex(key);
char *keyfmt = rstrscat(NULL, "gpg-pubkey-", fp, ".key", NULL);
char *keyval = rpmPubkeyArmorWrap(key);
char *path = rpmGenPath(rpmtxnRootDir(txn), "%{_keyringpath}/", keyfmt);
char *tmppath = NULL;
string fp = rpmPubkeyFingerprintAsHex(key);
string keyfmt = "gpg-pubkey-" + fp + ".key";
char *dir = rpmGenPath(rpmtxnRootDir(txn), "%{_keyringpath}/", NULL);
string dirstr = dir;

if (replace) {
rasprintf(&tmppath, "%s.new", path);
unlink(tmppath);
rc = write_key_to_disk(key, dirstr, keyfmt, replace, flags);

if (!rc && replace) {
/* find and delete the old pubkey entry */
if (delete_key(txn, fp, keyfmt) == RPMRC_NOTFOUND) {
/* make sure an old, short keyid version gets removed */
delete_key(txn, fp.substr(32), keyfmt);
}
}

if (rpmMkdirs(rpmtxnRootDir(txn), "%{_keyringpath}")) {
free(dir);
return rc;
}

/*****************************************************************************/

static rpmRC acquire_write_lock(rpmtxn txn)
{
char * keyringpath = rpmGenPath(rpmtxnRootDir(txn), "%{_keyringpath}/", NULL);
char * lockpath = rpmGenPath(rpmtxnRootDir(txn), "%{_keyringpath}/", "writelock");
FILE* fp = NULL;
rpmRC rc = RPMRC_FAIL;

if (rpmMkdirs(NULL, keyringpath)) {
rpmlog(RPMLOG_ERR, _("failed to create keyring directory %s: %s\n"),
path, strerror(errno));
keyringpath, strerror(errno));
goto exit;
}

if (FD_t fd = Fopen(tmppath ? tmppath : path, "wx")) {
size_t keylen = strlen(keyval);
if (Fwrite(keyval, 1, keylen, fd) == keylen)
rc = RPMRC_OK;
Fclose(fd);
}
if (!rc && tmppath && rename(tmppath, path) != 0)
rc = RPMRC_FAIL;
fp = std::fopen(lockpath, "wx");

if (rc) {
rpmlog(RPMLOG_ERR, _("failed to import key: %s: %s\n"),
tmppath ? tmppath : path, strerror(errno));
if (tmppath)
unlink(tmppath);
if (!fp) {
rpmlog(RPMLOG_ERR, _("Can't acquire writelock for keyring at %s\n"), keyringpath);
} else {
rc = RPMRC_OK;
std::fclose(fp);
}

if (!rc && replace) {
/* find and delete the old pubkey entry */
if (delete_key(txn, fp, keyfmt) == RPMRC_NOTFOUND) {
/* make sure an old, short keyid version gets removed */
delete_key(txn, fp+32, keyfmt);
}
}
exit:
free(keyringpath);
free(lockpath);
return rc;
}

exit:
static void free_write_lock(rpmtxn txn)
{
char * path = rpmGenPath(rpmtxnRootDir(txn), "%{_keyringpath}/", "writelock");
unlink(path);
free(path);
free(keyval);
free(keyfmt);
free(tmppath);
}

rpmRC keystore_openpgp_cert_d::load_keys(rpmtxn txn, rpmKeyring keyring)
{
return load_keys_from_glob(txn, keyring, "%{_keyringpath}/*/*");
}

rpmRC keystore_openpgp_cert_d::delete_key(rpmtxn txn, rpmPubkey key)
{
rpmRC rc = RPMRC_NOTFOUND;

if (acquire_write_lock(txn) != RPMRC_OK)
return RPMRC_FAIL;

string fp = rpmPubkeyFingerprintAsHex(key);
string dir = fp.substr(0, 2);
string filename = fp.substr(2);
char * filepath = rpmGetPath(rpmtxnRootDir(txn), "%{_keyringpath}/", dir.c_str(), "/", filename.c_str(), NULL);
char * dirpath = rpmGetPath(rpmtxnRootDir(txn), "%{_keyringpath}/", dir.c_str(), NULL);

if (!access(filepath, F_OK))
rc = unlink(filepath) ? RPMRC_FAIL : RPMRC_OK;
/* unlink directory if empty */
unlink(dirpath);

free(filepath);
free(dirpath);
free_write_lock(txn);
return rc;
}

rpmRC keystore_openpgp_cert_d::import_key(rpmtxn txn, rpmPubkey key, int replace, rpmFlags flags)
{
rpmRC rc = RPMRC_NOTFOUND;

if ((rc = acquire_write_lock(txn)) == RPMRC_OK) {
string fp = rpmPubkeyFingerprintAsHex(key);
string dir = fp.substr(0, 2);
string filename = fp.substr(2);
char *dirpath = rpmGetPath(rpmtxnRootDir(txn), "%{_keyringpath}/", dir.c_str(), NULL);
string dirstr = dirpath;

rc = write_key_to_disk(key, dirstr, filename, replace, flags);

free_write_lock(txn);
free(dirpath);
}

return rc;
}

/*****************************************************************************/

rpmRC keystore_rpmdb::load_keys(rpmtxn txn, rpmKeyring keyring)
{
Header h;
Expand Down
7 changes: 7 additions & 0 deletions lib/keystore.hh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ private:
rpmRC delete_key(rpmtxn txn, const std::string & keyid, unsigned int newinstance = 0);
};

class keystore_openpgp_cert_d : public keystore {
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);
};

}; /* namespace */

#endif /* _KEYSTORE_H */
2 changes: 2 additions & 0 deletions lib/rpmts.cc
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ static keystore *getKeystore(rpmts ts)
ts->keystore = new keystore_fs();
} else if (rstreq(krtype, "rpmdb")) {
ts->keystore = new keystore_rpmdb();
} else if (rstreq(krtype, "openpgp")) {
ts->keystore = new keystore_openpgp_cert_d();
} else {
/* Fall back to using rpmdb if unknown, for now at least */
rpmlog(RPMLOG_WARNING,
Expand Down
89 changes: 89 additions & 0 deletions tests/rpmsigdig.at
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,95 @@ runroot rpmkeys -Kv /data/RPMS/hello-2.0-1.x86_64.rpm /data/RPMS/hello-1.0-1.i38
RPMTEST_CLEANUP


AT_SETUP([rpmkeys key update (openpgp)])
AT_KEYWORDS([rpmkeys signature])
RPMDB_INIT
# root's .rpmmacros used to keep this build prefix independent
echo "%_keyring openpgp" >> "${RPMTEST}"/root/.rpmmacros
RPMTEST_CHECK([
runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-test.pub
runroot rpmkeys -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm
],
[1],
[/data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm:
Header V4 EdDSA/SHA512 signature, key ID 6323c42711450b6c: NOKEY
Header SHA256 digest: OK
Payload SHA256 digest: OK
],
[])

RPMTEST_CHECK([
runroot_other touch /usr/lib/sysimage/rpm/pubkeys/writelock
runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-add-subkey.asc
runroot_other rm /usr/lib/sysimage/rpm/pubkeys/writelock
],
[0],
[],
[error: Can't acquire writelock for keyring at /usr/lib/sysimage/rpm/pubkeys
error: /data/keys/rpm.org-rsa-2048-add-subkey.asc: key 1 import failed.
])


RPMTEST_CHECK([
runroot rpmkeys --list | wc -l
runroot rpmkeys --import /data/keys/rpm.org-rsa-2048-add-subkey.asc
runroot rpmkeys --list | wc -l
],
[0],
[1
1
],
[])

RPMTEST_CHECK([
runroot rpmkeys -Kv /data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm
],
[0],
[/data/RPMS/hello-2.0-1.x86_64-signed-with-new-subkey.rpm:
Header V4 EdDSA/SHA512 signature, key fingerprint: 771b18d3d7baa28734333c424344591e1964c5fc: OK
Header SHA256 digest: OK
Payload SHA256 digest: OK
],
[])

RPMTEST_CHECK([
runroot rpmkeys --delete abcd gimmekey 1111aaaa2222bbbb
],
[1],
[],
[error: invalid key id: abcd
error: invalid key id: gimmekey
error: key not found: 1111aaaa2222bbbb
])

RPMTEST_CHECK([
runroot rpmkeys --delete 1964c5fc
],
[0],
[],
[])

RPMTEST_CHECK([
runroot rpmkeys --list | wc -l
],
[0],
[0
],
[])
RPMTEST_CLEANUP
# ------------------------------
# Test rpmkeys write errors
AT_SETUP([[rpmkeys -K no space left on stdout]])
AT_KEYWORDS([rpmkeys digest])
RPMTEST_CHECK([
RPMDB_INIT[

runroot rpmkeys -Kv /data/RPMS/hello-2.0-1.x86_64.rpm /data/RPMS/hello-1.0-1.i386.rpm >/dev/full
]],255,,[[Error writing to log: No space left on device
]])
RPMTEST_CLEANUP


AT_SETUP([rpmkeys -Kv <reconstructed> 1])
AT_KEYWORDS([rpmkeys digest])
RPMDB_INIT
Expand Down

0 comments on commit 38b2ef5

Please sign in to comment.