[RFC][PATCH 12/12] tools/digest-lists: Add rpm digest list generator and parser

From: Roberto Sassu
Date: Fri Jul 21 2023 - 12:37:34 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

Add a generator to generate an rpm digest list from one or multiple RPM
package headers. The digest list contains the RPM magic string, the content
of the RPMTAG_IMMUTABLE section, and the user asymmetric key signature
(module-style) converted from the PGP signature in the RPMTAG_RSAHEADER
section.

This generator has as prerequisite gpg support for a new command
--conv-kernel, which converts the PGP format to a user asymmetric key
signature.

Also add a parser of rpm digest list, to show the content (digest algorithm
and value, and file path), and to add/remove the security.digest_list xattr
to/from each file in the RPM packages.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
tools/digest-lists/.gitignore | 2 +
tools/digest-lists/Makefile | 10 +-
tools/digest-lists/generators/generators.h | 2 +
tools/digest-lists/generators/rpm.c | 257 +++++++++++++++++++++
tools/digest-lists/manage_digest_lists.c | 2 +
tools/digest-lists/parsers/parsers.h | 2 +
tools/digest-lists/parsers/rpm.c | 169 ++++++++++++++
7 files changed, 442 insertions(+), 2 deletions(-)
create mode 100644 tools/digest-lists/generators/rpm.c
create mode 100644 tools/digest-lists/parsers/rpm.c

diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore
index 9a75ae766ff..51ca25f3b50 100644
--- a/tools/digest-lists/.gitignore
+++ b/tools/digest-lists/.gitignore
@@ -3,3 +3,5 @@ manage_digest_lists
manage_digest_lists.1
libgen-tlv-list.so
libparse-tlv-list.so
+libgen-rpm-list.so
+libparse-rpm-list.so
diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile
index 23f9fa3b588..2c8089affb8 100644
--- a/tools/digest-lists/Makefile
+++ b/tools/digest-lists/Makefile
@@ -15,8 +15,8 @@ CFLAGS=-ggdb -Wall

PROGS=manage_digest_lists

-GENERATORS=libgen-tlv-list.so
-PARSERS=libparse-tlv-list.so
+GENERATORS=libgen-tlv-list.so libgen-rpm-list.so
+PARSERS=libparse-tlv-list.so libparse-rpm-list.so

MAN1=manage_digest_lists.1

@@ -31,9 +31,15 @@ manage_digest_lists: manage_digest_lists.c common.c $(GENERATORS) $(PARSERS)
libgen-tlv-list.so: generators/tlv.c common.c
$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-tlv-list.so $^ -o $@

+libgen-rpm-list.so: generators/rpm.c common.c
+ $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libgen-rpm-list.so $^ -o $@ -lrpm -lrpmio
+
libparse-tlv-list.so: parsers/tlv.c common.c ../../lib/tlv_parser.c
$(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-tlv-list.so $^ -o $@ -I parsers

+libparse-rpm-list.so: parsers/rpm.c common.c
+ $(CC) $(CFLAGS) -fPIC --shared -Wl,-soname,libparse-rpm-list.so $^ -o $@ -I parsers -lrpm -lrpmio
+
ifneq ($(findstring $(MAKEFLAGS),s),s)
ifneq ($(V),1)
QUIET_A2X = @echo ' A2X '$@;
diff --git a/tools/digest-lists/generators/generators.h b/tools/digest-lists/generators/generators.h
index 9830b791667..ff3ed6ac8d4 100644
--- a/tools/digest-lists/generators/generators.h
+++ b/tools/digest-lists/generators/generators.h
@@ -14,3 +14,5 @@
void *tlv_list_gen_new(int dirfd, char *input, enum hash_algo algo);
int tlv_list_gen_add(int dirfd, void *ptr, char *input);
void tlv_list_gen_close(void *ptr);
+
+int rpm_list_gen_add(int dirfd, void *ptr, char *input);
diff --git a/tools/digest-lists/generators/rpm.c b/tools/digest-lists/generators/rpm.c
new file mode 100644
index 00000000000..29e7a6eb0ca
--- /dev/null
+++ b/tools/digest-lists/generators/rpm.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Generate rpm digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <asm/byteorder.h>
+
+#include "../common.h"
+
+static int gen_filename(Header rpm, char *filename, int filename_len)
+{
+ char *_filename = headerFormat(rpm, "rpm-%{nvra}", NULL);
+
+ if (!_filename)
+ return -ENOMEM;
+
+ strncpy(filename, _filename, filename_len);
+ free(_filename);
+ return 0;
+}
+
+static int write_rpm_header(Header rpm, int dirfd, char *filename)
+{
+ rpmtd immutable;
+ ssize_t ret;
+ int fd;
+
+ fd = openat(dirfd, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ return -EACCES;
+
+ ret = _write(fd, (void *)rpm_header_magic, sizeof(rpm_header_magic));
+ if (ret != sizeof(rpm_header_magic)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ immutable = rpmtdNew();
+ headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0);
+ ret = _write(fd, immutable->data, immutable->count);
+ if (ret != immutable->count) {
+ ret = -EIO;
+ goto out;
+ }
+
+ rpmtdFree(immutable);
+out:
+ close(fd);
+
+ if (ret < 0)
+ unlinkat(dirfd, filename, 0);
+
+ return ret;
+}
+
+static int write_rpm_header_signature(Header rpm, int dirfd, char *filename)
+{
+ char sig_to_convert[] = "/tmp/sig_to_convert_XXXXXX";
+ char uasym_sig[] = "/tmp/uasym_sig_XXXXXX";
+ struct module_signature modsig = { 0 };
+ rpmtd signature = rpmtdNew();
+ __u8 buf[1024];
+ struct stat st;
+ int ret, n_read, status, fd, fd_sig_to_convert, fd_uasym_sig;
+
+ fd_sig_to_convert = mkstemp(sig_to_convert);
+ if (fd_sig_to_convert == -1)
+ return -errno;
+
+ fd_uasym_sig = mkstemp(uasym_sig);
+ if (fd_uasym_sig == -1) {
+ ret = -errno;
+ goto out;
+ }
+
+ headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+ if (!signature->count) {
+ printf("Warning: no RPM signature for %s\n", filename);
+ ret = 0;
+ goto out_get;
+ }
+
+ ret = _write(fd_sig_to_convert, signature->data, signature->count);
+ if (ret != signature->count)
+ goto out_get;
+
+ close(fd_sig_to_convert);
+ fd_sig_to_convert = -1;
+
+ if (fork() == 0)
+ return execlp("gpg", "gpg", "--no-keyring", "--conv-kernel",
+ "-o", uasym_sig, sig_to_convert, NULL);
+
+ wait(&status);
+ if (WEXITSTATUS(status)) {
+ ret = WEXITSTATUS(status);
+ goto out_get;
+ }
+
+ if (stat(uasym_sig, &st) == -1)
+ goto out_get;
+
+ fd = openat(dirfd, filename, O_WRONLY | O_APPEND);
+ if (fd < 0) {
+ ret = -errno;
+ goto out_get;
+ }
+
+ modsig.id_type = PKEY_ID_PGP;
+ modsig.sig_len = st.st_size;
+ modsig.sig_len = __cpu_to_be32(modsig.sig_len);
+
+ while ((n_read = read(fd_uasym_sig, buf, sizeof(buf))) > 0) {
+ ret = _write(fd, buf, n_read);
+ if (ret != n_read)
+ goto out_fd;
+ }
+
+ ret = _write(fd, &modsig, sizeof(modsig));
+ if (ret != sizeof(modsig))
+ goto out_fd;
+
+ ret = _write(fd, MODULE_SIG_STRING, sizeof(MODULE_SIG_STRING) - 1);
+ if (ret != sizeof(MODULE_SIG_STRING) - 1)
+ goto out_fd;
+
+ ret = 0;
+out_fd:
+ close(fd);
+
+ if (ret < 0)
+ unlinkat(dirfd, filename, 0);
+out_get:
+ rpmtdFree(signature);
+out:
+ close(fd_sig_to_convert);
+ unlink(sig_to_convert);
+ close(fd_uasym_sig);
+ unlink(uasym_sig);
+
+ return ret;
+}
+
+static void write_rpm_digest_list(Header rpm, int dirfd, char *filename)
+{
+ int ret;
+
+ ret = write_rpm_header(rpm, dirfd, filename);
+ if (ret < 0) {
+ printf("Cannot dump RPM header of %s\n", filename);
+ return;
+ }
+
+ ret = write_rpm_header_signature(rpm, dirfd, filename);
+ if (ret < 0)
+ printf("Cannot add signature to %s\n", filename);
+}
+
+int rpm_list_gen_add(int dirfd, void *ptr, char *input)
+{
+ char filename[NAME_MAX + 1], *selection;
+ rpmts ts = NULL;
+ Header hdr;
+ FD_t fd;
+ rpmdbMatchIterator mi;
+ rpmVSFlags vsflags = 0;
+ int ret;
+
+ ts = rpmtsCreate();
+ if (!ts) {
+ rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ ret = rpmReadConfigFiles(NULL, NULL);
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n");
+ ret = -EACCES;
+ goto out_ts;
+ }
+
+ if (strncmp(input, "rpmdb", 5)) {
+ vsflags |= _RPMVSF_NODIGESTS;
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ rpmtsSetVSFlags(ts, vsflags);
+
+ fd = Fopen(input, "r.ufdio");
+ if (!fd || Ferror(fd)) {
+ rpmlog(RPMLOG_NOTICE,
+ "Failed to open package file %s, %s\n", input,
+ Fstrerror(fd));
+ ret = -EACCES;
+ goto out_rpm;
+ }
+
+ ret = rpmReadPackageFile(ts, fd, "rpm", &hdr);
+ Fclose(fd);
+
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE,
+ "Could not read package file %s\n", input);
+ goto out_rpm;
+ }
+
+ gen_filename(hdr, filename, sizeof(filename));
+
+ write_rpm_digest_list(hdr, dirfd, filename);
+ headerFree(hdr);
+ goto out_rpm;
+ }
+
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+ while ((hdr = rpmdbNextIterator(mi)) != NULL) {
+ gen_filename(hdr, filename, sizeof(filename));
+
+ /* Skip rpm- */
+ if (strstr(filename + 4, "gpg-pubkey"))
+ continue;
+
+ selection = strchr(input, ':');
+ if (selection && !strstr(filename + 4, selection + 1))
+ continue;
+
+ write_rpm_digest_list(hdr, dirfd, filename);
+ }
+
+ rpmdbFreeIterator(mi);
+out_rpm:
+ rpmFreeRpmrc();
+ rpmFreeCrypto();
+ rpmFreeMacros(NULL);
+out_ts:
+ rpmtsFree(ts);
+out:
+ return ret;
+}
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
index db5680506a8..75ddb542062 100644
--- a/tools/digest-lists/manage_digest_lists.c
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -33,10 +33,12 @@ const char *ops_str[OP__LAST] = {
struct generator generators[] = {
{ .name = "tlv", .new = tlv_list_gen_new, .add = tlv_list_gen_add,
.close = tlv_list_gen_close },
+ { .name = "rpm", .add = rpm_list_gen_add },
};

struct parser parsers[] = {
{ .name = "tlv", .parse = tlv_list_parse },
+ { .name = "rpm", .parse = rpm_list_gen_parse },
};

static int generator_add(struct generator *generator, int dirfd,
diff --git a/tools/digest-lists/parsers/parsers.h b/tools/digest-lists/parsers/parsers.h
index 708da7eac3b..ecefb2ec79b 100644
--- a/tools/digest-lists/parsers/parsers.h
+++ b/tools/digest-lists/parsers/parsers.h
@@ -12,3 +12,5 @@
#include <errno.h>

int tlv_list_parse(const char *digest_list_path, enum ops op);
+
+int rpm_list_gen_parse(const char *digest_list_path, enum ops op);
diff --git a/tools/digest-lists/parsers/rpm.c b/tools/digest-lists/parsers/rpm.c
new file mode 100644
index 00000000000..7dd063b64ac
--- /dev/null
+++ b/tools/digest-lists/parsers/rpm.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Parse rpm digest lists.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <asm/byteorder.h>
+
+#include "../common.h"
+
+static const enum hash_algo pgp_hash_algorithms[PGPHASHALGO_SHA224 + 1] = {
+ [PGPHASHALGO_MD5] = HASH_ALGO_MD5,
+ [PGPHASHALGO_SHA1] = HASH_ALGO_SHA1,
+ [PGPHASHALGO_RIPEMD160] = HASH_ALGO_RIPE_MD_160,
+ [PGPHASHALGO_SHA256] = HASH_ALGO_SHA256,
+ [PGPHASHALGO_SHA384] = HASH_ALGO_SHA384,
+ [PGPHASHALGO_SHA512] = HASH_ALGO_SHA512,
+ [PGPHASHALGO_SHA224] = HASH_ALGO_SHA224,
+};
+
+int rpm_list_gen_parse(const char *digest_list_path, enum ops op)
+{
+ rpmtd filedigestalgo, filedigests, basenames, dirnames, dirindexes;
+ rpmts ts = NULL;
+ Header hdr;
+ FD_t fd;
+ rpmVSFlags vsflags = 0;
+ char file_path[PATH_MAX];
+ enum hash_algo algo = HASH_ALGO_MD5;
+ const char *digest_str, *basename, *dirname;
+ __u32 dirindex, *pgp_algo_ptr;
+ size_t digest_list_path_len = strlen(digest_list_path);
+ int ret;
+
+ ts = rpmtsCreate();
+ if (!ts) {
+ rpmlog(RPMLOG_NOTICE, "rpmtsCreate() error..\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ ret = rpmReadConfigFiles(NULL, NULL);
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE, "Unable to read RPM configuration.\n");
+ ret = -EACCES;
+ goto out_ts;
+ }
+
+ vsflags |= _RPMVSF_NODIGESTS;
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ rpmtsSetVSFlags(ts, vsflags);
+
+ fd = Fopen(digest_list_path, "r.ufdio");
+ if (!fd || Ferror(fd)) {
+ rpmlog(RPMLOG_NOTICE, "Failed to open package file %s, %s\n",
+ digest_list_path, Fstrerror(fd));
+ ret = -EACCES;
+ goto out_rpm;
+ }
+
+ ret = rpmReadHeader(ts, fd, &hdr, NULL);
+ Fclose(fd);
+
+ if (ret != RPMRC_OK) {
+ rpmlog(RPMLOG_NOTICE, "Could not read package file %s\n",
+ digest_list_path);
+ goto out_rpm;
+ }
+
+ filedigestalgo = rpmtdNew();
+ filedigests = rpmtdNew();
+ basenames = rpmtdNew();
+ dirnames = rpmtdNew();
+ dirindexes = rpmtdNew();
+
+ headerGet(hdr, RPMTAG_FILEDIGESTALGO, filedigestalgo, 0);
+ headerGet(hdr, RPMTAG_FILEDIGESTS, filedigests, 0);
+ headerGet(hdr, RPMTAG_BASENAMES, basenames, 0);
+ headerGet(hdr, RPMTAG_DIRNAMES, dirnames, 0);
+ headerGet(hdr, RPMTAG_DIRINDEXES, dirindexes, 0);
+
+ pgp_algo_ptr = rpmtdGetUint32(filedigestalgo);
+ if (pgp_algo_ptr && *pgp_algo_ptr <= PGPHASHALGO_SHA224)
+ algo = pgp_hash_algorithms[*pgp_algo_ptr];
+
+ while ((digest_str = rpmtdNextString(filedigests))) {
+ basename = rpmtdNextString(basenames);
+ dirindex = *rpmtdNextUint32(dirindexes);
+
+ rpmtdSetIndex(dirnames, dirindex);
+ dirname = rpmtdGetString(dirnames);
+
+ snprintf(file_path, sizeof(file_path), "%s%s", dirname,
+ basename);
+
+ if (!strlen(digest_str))
+ continue;
+
+ switch (op) {
+ case OP_SHOW:
+ printf("%s:%s %s\n", hash_algo_name[algo], digest_str,
+ file_path);
+ ret = 0;
+ break;
+ case OP_ADD_XATTR:
+ ret = lsetxattr(file_path, XATTR_NAME_DIGEST_LIST,
+ digest_list_path,
+ digest_list_path_len, 0);
+ if (ret < 0 && errno == ENODATA)
+ ret = 0;
+
+ if (ret < 0)
+ printf("Error setting %s on %s, %s\n",
+ XATTR_NAME_DIGEST_LIST, file_path,
+ strerror(errno));
+ break;
+ case OP_RM_XATTR:
+ ret = lremovexattr(file_path, XATTR_NAME_DIGEST_LIST);
+ if (ret < 0 && errno == ENODATA)
+ ret = 0;
+
+ if (ret < 0)
+ printf("Error removing %s from %s, %s\n",
+ XATTR_NAME_DIGEST_LIST, file_path,
+ strerror(errno));
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (ret < 0)
+ break;
+ }
+
+ rpmtdFree(filedigestalgo);
+ rpmtdFree(filedigests);
+ rpmtdFree(basenames);
+ rpmtdFree(dirnames);
+ rpmtdFree(dirindexes);
+ headerFree(hdr);
+out_rpm:
+ rpmFreeRpmrc();
+ rpmFreeCrypto();
+ rpmFreeMacros(NULL);
+out_ts:
+ rpmtsFree(ts);
+out:
+ return ret;
+}
--
2.34.1