[RFC][PATCH 10/12] tools: Add tool to manage digest lists

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


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

Add a tool to generate and manage the digest lists. Digest lists can be
generated from a directory, an individual file, or from a list.

Once generated, digest list content can be showed (digest algorithm and
value, file path). Also, the tool can add/remove the security.digest_list
xattr to/from each file in the generated digest lists.

To select the proper generator and parser, each digest list file name must
start with 'format-'.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
MAINTAINERS | 1 +
tools/Makefile | 16 +-
tools/digest-lists/.gitignore | 3 +
tools/digest-lists/Makefile | 48 +++
tools/digest-lists/common.c | 163 ++++++++++
tools/digest-lists/common.h | 90 ++++++
tools/digest-lists/manage_digest_lists.c | 342 +++++++++++++++++++++
tools/digest-lists/manage_digest_lists.txt | 82 +++++
8 files changed, 739 insertions(+), 6 deletions(-)
create mode 100644 tools/digest-lists/.gitignore
create mode 100644 tools/digest-lists/Makefile
create mode 100644 tools/digest-lists/common.c
create mode 100644 tools/digest-lists/common.h
create mode 100644 tools/digest-lists/manage_digest_lists.c
create mode 100644 tools/digest-lists/manage_digest_lists.txt

diff --git a/MAINTAINERS b/MAINTAINERS
index d3af1e179b0..6ee11828f2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10294,6 +10294,7 @@ S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git
F: security/integrity/
F: security/integrity/ima/
+F: tools/digest-lists/

INTEL 810/815 FRAMEBUFFER DRIVER
M: Antonino Daplas <adaplas@xxxxxxxxx>
diff --git a/tools/Makefile b/tools/Makefile
index 37e9f680483..3789b5f292e 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -15,6 +15,7 @@ help:
@echo ' counter - counter tools'
@echo ' cpupower - a tool for all things x86 CPU power'
@echo ' debugging - tools for debugging'
+ @echo ' digest-lists - tools for managing digest lists'
@echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer'
@echo ' firmware - Firmware tools'
@echo ' freefall - laptop accelerometer program for disk protection'
@@ -69,7 +70,7 @@ acpi: FORCE
cpupower: FORCE
$(call descend,power/$@)

-cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing: FORCE
+cgroup counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi pci firmware debugging tracing digest-lists: FORCE
$(call descend,$@)

bpf/%: FORCE
@@ -120,7 +121,8 @@ all: acpi cgroup counter cpupower gpio hv firewire \
perf selftests bootconfig spi turbostat usb \
virtio mm bpf x86_energy_perf_policy \
tmon freefall iio objtool kvm_stat wmi \
- pci debugging tracing thermal thermometer thermal-engine
+ pci debugging tracing thermal thermometer thermal-engine \
+ digest-lists

acpi_install:
$(call descend,power/$(@:_install=),install)
@@ -128,7 +130,7 @@ acpi_install:
cpupower_install:
$(call descend,power/$(@:_install=),install)

-cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install:
+cgroup_install counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install pci_install debugging_install tracing_install digest-lists_install:
$(call descend,$(@:_install=),install)

selftests_install:
@@ -161,7 +163,8 @@ install: acpi_install cgroup_install counter_install cpupower_install gpio_insta
virtio_install mm_install bpf_install x86_energy_perf_policy_install \
tmon_install freefall_install objtool_install kvm_stat_install \
wmi_install pci_install debugging_install intel-speed-select_install \
- tracing_install thermometer_install thermal-engine_install
+ tracing_install thermometer_install thermal-engine_install \
+ digest-lists_install

acpi_clean:
$(call descend,power/acpi,clean)
@@ -169,7 +172,7 @@ acpi_clean:
cpupower_clean:
$(call descend,power/cpupower,clean)

-cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean:
+cgroup_clean counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean tracing_clean digest-lists_clean:
$(call descend,$(@:_clean=),clean)

libapi_clean:
@@ -214,6 +217,7 @@ clean: acpi_clean cgroup_clean counter_clean cpupower_clean hv_clean firewire_cl
mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean \
gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
- intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean
+ intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean \
+ digest-lists_clean

.PHONY: FORCE
diff --git a/tools/digest-lists/.gitignore b/tools/digest-lists/.gitignore
new file mode 100644
index 00000000000..1b8a7b9c205
--- /dev/null
+++ b/tools/digest-lists/.gitignore
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+manage_digest_lists
+manage_digest_lists.1
diff --git a/tools/digest-lists/Makefile b/tools/digest-lists/Makefile
new file mode 100644
index 00000000000..05af3a91c06
--- /dev/null
+++ b/tools/digest-lists/Makefile
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../scripts/Makefile.include
+include ../scripts/utilities.mak
+BINDIR=usr/bin
+MANDIR=usr/share/man
+MAN1DIR=$(MANDIR)/man1
+CFLAGS=-ggdb -Wall
+
+PROGS=manage_digest_lists
+
+MAN1=manage_digest_lists.1
+
+A2X=a2x
+a2x_path := $(call get-executable,$(A2X))
+
+all: man $(PROGS)
+
+manage_digest_lists: manage_digest_lists.c common.c
+ $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) -lcrypto
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ ifneq ($(V),1)
+ QUIET_A2X = @echo ' A2X '$@;
+ endif
+endif
+
+%.1: %.txt
+ifeq ($(a2x_path),)
+ $(error "You need to install asciidoc for man pages")
+else
+ $(QUIET_A2X)$(A2X) --doctype manpage --format manpage $<
+endif
+
+clean:
+ rm -f $(MAN1) $(PROGS)
+
+man: $(MAN1)
+
+install-man: man
+ install -d -m 755 $(INSTALL_ROOT)/$(MAN1DIR)
+ install -m 644 $(MAN1) $(INSTALL_ROOT)/$(MAN1DIR)
+
+install-tools: $(PROGS)
+ install -d -m 755 $(INSTALL_ROOT)/$(BINDIR)
+ install -m 755 -p $(PROGS) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+
+install: install-tools install-man
+.PHONY: all clean man install-tools install-man install
diff --git a/tools/digest-lists/common.c b/tools/digest-lists/common.c
new file mode 100644
index 00000000000..5378e677c09
--- /dev/null
+++ b/tools/digest-lists/common.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Common functions and data.
+ */
+
+#include <sys/mman.h>
+#include <sys/random.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#include <asm/byteorder.h>
+
+#include "common.h"
+
+const char *const hash_algo_name[HASH_ALGO__LAST] = {
+ [HASH_ALGO_MD4] = "md4",
+ [HASH_ALGO_MD5] = "md5",
+ [HASH_ALGO_SHA1] = "sha1",
+ [HASH_ALGO_RIPE_MD_160] = "rmd160",
+ [HASH_ALGO_SHA256] = "sha256",
+ [HASH_ALGO_SHA384] = "sha384",
+ [HASH_ALGO_SHA512] = "sha512",
+ [HASH_ALGO_SHA224] = "sha224",
+ [HASH_ALGO_RIPE_MD_128] = "rmd128",
+ [HASH_ALGO_RIPE_MD_256] = "rmd256",
+ [HASH_ALGO_RIPE_MD_320] = "rmd320",
+ [HASH_ALGO_WP_256] = "wp256",
+ [HASH_ALGO_WP_384] = "wp384",
+ [HASH_ALGO_WP_512] = "wp512",
+ [HASH_ALGO_TGR_128] = "tgr128",
+ [HASH_ALGO_TGR_160] = "tgr160",
+ [HASH_ALGO_TGR_192] = "tgr192",
+ [HASH_ALGO_SM3_256] = "sm3",
+ [HASH_ALGO_STREEBOG_256] = "streebog256",
+ [HASH_ALGO_STREEBOG_512] = "streebog512",
+};
+
+const int hash_digest_size[HASH_ALGO__LAST] = {
+ [HASH_ALGO_MD4] = MD5_DIGEST_SIZE,
+ [HASH_ALGO_MD5] = MD5_DIGEST_SIZE,
+ [HASH_ALGO_SHA1] = SHA1_DIGEST_SIZE,
+ [HASH_ALGO_RIPE_MD_160] = RMD160_DIGEST_SIZE,
+ [HASH_ALGO_SHA256] = SHA256_DIGEST_SIZE,
+ [HASH_ALGO_SHA384] = SHA384_DIGEST_SIZE,
+ [HASH_ALGO_SHA512] = SHA512_DIGEST_SIZE,
+ [HASH_ALGO_SHA224] = SHA224_DIGEST_SIZE,
+ [HASH_ALGO_RIPE_MD_128] = RMD128_DIGEST_SIZE,
+ [HASH_ALGO_RIPE_MD_256] = RMD256_DIGEST_SIZE,
+ [HASH_ALGO_RIPE_MD_320] = RMD320_DIGEST_SIZE,
+ [HASH_ALGO_WP_256] = WP256_DIGEST_SIZE,
+ [HASH_ALGO_WP_384] = WP384_DIGEST_SIZE,
+ [HASH_ALGO_WP_512] = WP512_DIGEST_SIZE,
+ [HASH_ALGO_TGR_128] = TGR128_DIGEST_SIZE,
+ [HASH_ALGO_TGR_160] = TGR160_DIGEST_SIZE,
+ [HASH_ALGO_TGR_192] = TGR192_DIGEST_SIZE,
+ [HASH_ALGO_SM3_256] = SM3256_DIGEST_SIZE,
+ [HASH_ALGO_STREEBOG_256] = STREEBOG256_DIGEST_SIZE,
+ [HASH_ALGO_STREEBOG_512] = STREEBOG512_DIGEST_SIZE,
+};
+
+int read_file(const char *path, size_t *len, unsigned char **data)
+{
+ struct stat st;
+ int rc = 0, fd;
+
+ if (stat(path, &st) == -1)
+ return -ENOENT;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -EACCES;
+
+ *len = st.st_size;
+
+ *data = mmap(NULL, *len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (*data == MAP_FAILED)
+ rc = -ENOMEM;
+
+ close(fd);
+ return rc;
+}
+
+int calc_digest(__u8 *digest, void *data, __u64 len, enum hash_algo algo)
+{
+ EVP_MD_CTX *mdctx;
+ const EVP_MD *md;
+ int ret = -EINVAL;
+
+ OpenSSL_add_all_algorithms();
+
+ md = EVP_get_digestbyname(hash_algo_name[algo]);
+ if (!md)
+ goto out;
+
+ mdctx = EVP_MD_CTX_create();
+ if (!mdctx)
+ goto out;
+
+ if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
+ goto out_mdctx;
+
+ if (EVP_DigestUpdate(mdctx, data, len) != 1)
+ goto out_mdctx;
+
+ if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1)
+ goto out_mdctx;
+
+ ret = 0;
+out_mdctx:
+ EVP_MD_CTX_destroy(mdctx);
+out:
+ EVP_cleanup();
+ return ret;
+}
+
+int calc_file_digest(__u8 *digest, const char *path, enum hash_algo algo)
+{
+ unsigned char *data;
+ size_t len;
+ int ret;
+
+ ret = read_file(path, &len, &data);
+ if (ret < 0)
+ return ret;
+
+ ret = calc_digest(digest, data, len, algo);
+
+ munmap(data, len);
+ return ret;
+}
+
+ssize_t _write(int fd, void *buf, size_t buf_len)
+{
+ ssize_t len;
+ loff_t offset = 0;
+
+ while (offset < buf_len) {
+ len = write(fd, buf + offset, buf_len - offset);
+ if (len < 0)
+ return -errno;
+
+ offset += len;
+ }
+
+ return buf_len;
+}
diff --git a/tools/digest-lists/common.h b/tools/digest-lists/common.h
new file mode 100644
index 00000000000..d65168e2932
--- /dev/null
+++ b/tools/digest-lists/common.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2005,2006,2007,2008 IBM Corporation
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Header of common.c
+ */
+
+#include <stdint.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/hash_info.h>
+
+#define MD5_DIGEST_SIZE 16
+#define SHA1_DIGEST_SIZE 20
+#define RMD160_DIGEST_SIZE 20
+#define SHA256_DIGEST_SIZE 32
+#define SHA384_DIGEST_SIZE 48
+#define SHA512_DIGEST_SIZE 64
+#define SHA224_DIGEST_SIZE 28
+#define RMD128_DIGEST_SIZE 16
+#define RMD256_DIGEST_SIZE 32
+#define RMD320_DIGEST_SIZE 40
+#define WP256_DIGEST_SIZE 32
+#define WP384_DIGEST_SIZE 48
+#define WP512_DIGEST_SIZE 64
+#define TGR128_DIGEST_SIZE 16
+#define TGR160_DIGEST_SIZE 20
+#define TGR192_DIGEST_SIZE 24
+#define SM3256_DIGEST_SIZE 32
+#define STREEBOG256_DIGEST_SIZE 32
+#define STREEBOG512_DIGEST_SIZE 64
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define DIGEST_LIST_SIZE_MAX (64 * 1024 * 1024 - 1)
+
+/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
+#define MODULE_SIG_STRING "~Module signature appended~\n"
+
+enum pkey_id_type {
+ PKEY_ID_PGP, /* OpenPGP generated key ID */
+ PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */
+ PKEY_ID_PKCS7, /* Signature in PKCS#7 message */
+};
+
+/*
+ * Module signature information block.
+ *
+ * The constituents of the signature section are, in order:
+ *
+ * - Signer's name
+ * - Key identifier
+ * - Signature data
+ * - Information block
+ */
+struct module_signature {
+ __u8 algo; /* Public-key crypto algorithm [0] */
+ __u8 hash; /* Digest algorithm [0] */
+ __u8 id_type; /* Key identifier type [PKEY_ID_PKCS7] */
+ __u8 signer_len; /* Length of signer's name [0] */
+ __u8 key_id_len; /* Length of key identifier [0] */
+ __u8 __pad[3];
+ __be32 sig_len; /* Length of signature data */
+};
+
+enum ops { OP_GEN, OP_SHOW, OP_ADD_XATTR, OP_RM_XATTR, OP__LAST };
+
+struct generator {
+ const char *name;
+ void *(*new)(int dirfd, char *input, enum hash_algo algo);
+ int (*add)(int dirfd, void *ptr, char *input);
+ void (*close)(void *ptr);
+};
+
+struct parser {
+ const char *name;
+ int (*parse)(const char *digest_list_path, enum ops op);
+};
+
+extern const char *ops_str[OP__LAST];
+extern const char *const hash_algo_name[HASH_ALGO__LAST];
+extern const int hash_digest_size[HASH_ALGO__LAST];
+
+int read_file(const char *path, size_t *len, unsigned char **data);
+int calc_digest(__u8 *digest, void *data, __u64 len, enum hash_algo algo);
+int calc_file_digest(__u8 *digest, const char *path, enum hash_algo algo);
+ssize_t _write(int fd, void *buf, size_t buf_len);
diff --git a/tools/digest-lists/manage_digest_lists.c b/tools/digest-lists/manage_digest_lists.c
new file mode 100644
index 00000000000..9da62bd3570
--- /dev/null
+++ b/tools/digest-lists/manage_digest_lists.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Implement a tool to manage digest lists..
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <linux/hash_info.h>
+#include <linux/xattr.h>
+#include <fts.h>
+
+#include "common.h"
+
+const char *ops_str[OP__LAST] = {
+ [OP_GEN] = "gen",
+ [OP_SHOW] = "show",
+ [OP_ADD_XATTR] = "add-xattr",
+ [OP_RM_XATTR] = "rm-xattr",
+};
+
+struct generator generators[] = {
+};
+
+struct parser parsers[] = {
+};
+
+static int generator_add(struct generator *generator, int dirfd,
+ void *ptr, char *input)
+{
+ char *full_path = input;
+ int ret;
+
+ if (!generator->add)
+ return -ENOENT;
+
+ if (strncmp(input, "rpmdb", 5)) {
+ full_path = realpath(input, NULL);
+ if (!full_path) {
+ printf("Error generating full path of %s\n", full_path);
+ return -ENOMEM;
+ }
+ }
+
+ ret = generator->add(dirfd, ptr, full_path);
+
+ if (full_path != input)
+ free(full_path);
+
+ return ret;
+}
+
+static int gen_digest_list(char *digest_list_format, char *digest_list_dir,
+ char *input, int input_is_list, __u8 algo)
+{
+ struct generator *generator;
+ void *ptr;
+ FTS *fts = NULL;
+ FTSENT *ftsent;
+ FILE *fp;
+ int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+ char *paths[2] = { input, NULL };
+ char line[1024], *p;
+ int ret, i, dirfd;
+
+ for (i = 0; i < ARRAY_SIZE(generators); i++)
+ if (!strcmp(generators[i].name, digest_list_format))
+ break;
+
+ if (i == ARRAY_SIZE(generators)) {
+ printf("Cannot find generator for %s\n", digest_list_format);
+ return -ENOENT;
+ }
+
+ generator = &generators[i];
+
+ dirfd = open(digest_list_dir, O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0) {
+ printf("Unable to open %s, ret: %d\n", digest_list_dir, -errno);
+ return -errno;
+ }
+
+ if (generator->new) {
+ ptr = generator->new(dirfd, input, algo);
+ if (!ptr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+
+ if (input_is_list) {
+ fp = fopen(input, "r");
+ if (!fp) {
+ ret = -EACCES;
+ goto out_close;
+ }
+
+ while ((fgets(line, sizeof(line), fp))) {
+ p = strrchr(line, '\n');
+ *p = '\0';
+
+ ret = generator_add(generator, dirfd, ptr, line);
+ if (ret < 0) {
+ printf("Error generating entry for %s, ret: %d\n",
+ line, ret);
+ fclose(fp);
+ goto out_close;
+ }
+ }
+
+ fclose(fp);
+ goto out_close;
+ } else if (!strncmp(input, "rpmdb", 5)) {
+ ret = generator_add(generator, dirfd, ptr, input);
+ if (ret < 0) {
+ printf("Error generating entry for %s, ret: %d\n",
+ input, ret);
+ goto out_close;
+ }
+ }
+
+ fts = fts_open(paths, fts_flags, NULL);
+ if (!fts) {
+ printf("Unable to open %s\n", input);
+ ret = -EACCES;
+ goto out_close;
+ }
+
+ while ((ftsent = fts_read(fts)) != NULL) {
+ switch (ftsent->fts_info) {
+ case FTS_F:
+ ret = generator_add(generator, dirfd, ptr,
+ ftsent->fts_path);
+ if (ret < 0) {
+ printf("Error generating entry for %s, ret: %d\n",
+ ftsent->fts_path, ret);
+ goto out_fts_close;
+ }
+ default:
+ break;
+ }
+ }
+
+out_fts_close:
+ fts_close(fts);
+out_close:
+ if (generator->close)
+ generator->close(ptr);
+out:
+ close(dirfd);
+ return ret;
+}
+
+static struct parser *get_parser(const char *filename)
+{
+ const char *separator;
+ int i;
+
+ separator = strchr(filename, '-');
+ if (!separator)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(parsers); i++)
+ if (!strncmp(parsers[i].name, filename, separator - filename))
+ break;
+
+ if (i == ARRAY_SIZE(parsers)) {
+ printf("Cannot find parser for %s\n", filename);
+ return NULL;
+ }
+
+ return &parsers[i];
+}
+
+static int parse_digest_list(char *digest_list_format, char *digest_list_path,
+ enum ops op)
+{
+ struct parser *parser;
+ FTS *fts = NULL;
+ FTSENT *ftsent;
+ int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+ char *paths[2] = { NULL, NULL };
+ char *full_path = NULL;
+ int ret;
+
+ full_path = realpath(digest_list_path, NULL);
+ if (!full_path)
+ return -ENOMEM;
+
+ paths[0] = full_path;
+
+ fts = fts_open(paths, fts_flags, NULL);
+ if (!fts) {
+ printf("Unable to open %s\n", digest_list_path);
+ free(full_path);
+ return -EACCES;
+ }
+
+ while ((ftsent = fts_read(fts)) != NULL) {
+ switch (ftsent->fts_info) {
+ case FTS_F:
+ parser = get_parser(ftsent->fts_name);
+ if (!parser)
+ continue;
+
+ ret = parser->parse(ftsent->fts_path, op);
+ if (ret < 0) {
+ printf("Error parsing entry %s, ret: %d\n",
+ ftsent->fts_path, ret);
+ goto out_fts_close;
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+out_fts_close:
+ fts_close(fts);
+ free(full_path);
+ return ret;
+}
+
+static void usage(char *progname)
+{
+ printf("Usage: %s <options>\n", progname);
+ printf("Options:\n");
+ printf("\t-d <directory>: directory digest lists are written to\n"
+ "\t-i <input>: input digest list for an operation"
+ "\t-L: input is a list of files/directories\n"
+ "\t-a <algo>: digest list algorithm\n"
+ "\t-f <format>: digest list format\n"
+ "\t-o <operation>: operation to perform\n"
+ "\t\tgen: generate a digest list\n"
+ "\t\tshow: show the content of a digest list\n"
+ "\t\tadd-xattr: set the " XATTR_NAME_DIGEST_LIST " xattr to the digest list path\n"
+ "\t\trm-xattr: remove the " XATTR_NAME_DIGEST_LIST " xattr\n"
+ "\t-h: display help\n");
+}
+
+int main(int argc, char *argv[])
+{
+ char *digest_list_dir = NULL, *digest_list_format = NULL, *input = NULL;
+ enum hash_algo algo = HASH_ALGO_SHA256;
+ enum ops op = OP__LAST;
+ struct stat st;
+ int c, i;
+ int ret, input_is_list = 0;
+
+ while ((c = getopt(argc, argv, "d:i:La:f:o:h")) != -1) {
+ switch (c) {
+ case 'd':
+ digest_list_dir = optarg;
+ break;
+ case 'i':
+ input = optarg;
+ break;
+ case 'L':
+ input_is_list = 1;
+ break;
+ case 'a':
+ for (i = 0; i < HASH_ALGO__LAST; i++)
+ if (!strcmp(hash_algo_name[i], optarg))
+ break;
+ if (i == HASH_ALGO__LAST) {
+ printf("Invalid algo %s\n", optarg);
+ exit(1);
+ }
+ algo = i;
+ break;
+ case 'f':
+ digest_list_format = optarg;
+ break;
+ case 'o':
+ for (op = 0; op < OP__LAST; op++)
+ if (!strcmp(ops_str[op], optarg))
+ break;
+ if (op == OP__LAST) {
+ printf("Invalid op %s\n", optarg);
+ exit(1);
+ }
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ default:
+ printf("Invalid option %c\n", c);
+ exit(1);
+ }
+ }
+
+ if (op == OP__LAST) {
+ printf("Operation not specified\n");
+ exit(1);
+ }
+
+ switch (op) {
+ case OP_GEN:
+ if (!digest_list_format || !input || !digest_list_dir) {
+ printf("Missing format/input/digest list directory\n");
+ exit(1);
+ }
+
+ if (stat(digest_list_dir, &st) == -1) {
+ ret = mkdir(digest_list_dir, 0755);
+ if (ret < 0) {
+ printf("Unable to create %s, ret: %d\n",
+ digest_list_dir, -errno);
+ return(-errno);
+ }
+ }
+
+ ret = gen_digest_list(digest_list_format, digest_list_dir,
+ input, input_is_list, algo);
+ break;
+ case OP_SHOW:
+ case OP_ADD_XATTR:
+ case OP_RM_XATTR:
+ if (!input) {
+ printf("Missing input\n");
+ exit(1);
+ }
+
+ ret = parse_digest_list(digest_list_format, input, op);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
diff --git a/tools/digest-lists/manage_digest_lists.txt b/tools/digest-lists/manage_digest_lists.txt
new file mode 100644
index 00000000000..62d655516e8
--- /dev/null
+++ b/tools/digest-lists/manage_digest_lists.txt
@@ -0,0 +1,82 @@
+manage_digest_lists(1)
+======================
+
+NAME
+----
+manage_digest_lists - manage digest lists lifecycle
+
+
+SYNOPSIS
+--------
+manage_digest_lists [options]
+
+
+DESCRIPTION
+------------
+manage_digest_lists can be used to manage the lifecycle of digest lists (e.g. generate, show).
+
+
+OPTIONS
+-------
+-d <directory>::
+ directory digest lists are written to
+
+-i <input>::
+ input digest list for an operation
+
+-L::
+ input is a list of files/directories
+
+-a <algo>::
+ digest list algorithm
+
+-f <format>::
+ digest list format
+
+-o <operation>::
+ operation to perform:::
+ gen::::
+ generate a digest list
+ show::::
+ show the content of a digest list
+ add-xattr::::
+ set the security.digest_list xattr to the digest list path
+ rm-xattr::::
+ remove the security.digest_list xattr
+
+-h::
+ display help
+
+
+EXAMPLES
+--------
+Generate digest lists from the RPM database:
+
+# manage_digest_lists -d /etc/digest_lists -i rpmdb -o gen -f rpm
+
+
+Generate digest lists for the kernel modules (for custom kernels):
+
+# manage_digest_lists -d /etc/digest_lists -i /lib/modules/`uname -r` -o gen -f tlv
+
+
+Show digest lists content in /etc/digest_lists
+
+# manage_digest_lists -i /etc/digest_lists -o show
+
+
+Add security.digest_list xattr for digest lists in /etc/digest_lists
+
+# manage_digest_lists -i /etc/digest_lists -o add-xattr
+
+
+AUTHOR
+------
+Written by Roberto Sassu, <roberto.sassu at huawei.com>.
+
+
+COPYING
+-------
+Copyright (C) 2017-2023 Huawei Technologies Duesseldorf GmbH. Free use of
+this software is granted under the terms of the GNU Public License 2.0
+(GPLv2).
--
2.34.1