diff --git a/MAINTAINERS b/MAINTAINERS
index 148a2a7957b7..1efc1724376e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5527,6 +5527,7 @@ F:	tools/diglim/common.c
 F:	tools/diglim/common.h
 F:	tools/diglim/compact_gen.c
 F:	tools/diglim/rpm_gen.c
+F:	tools/diglim/rpm_parser.c
 F:	tools/diglim/upload_digest_lists.c
 F:	tools/testing/selftests/diglim/
 
diff --git a/tools/diglim/Makefile b/tools/diglim/Makefile
index a22125ad0281..7019c5b9fad9 100644
--- a/tools/diglim/Makefile
+++ b/tools/diglim/Makefile
@@ -3,7 +3,7 @@
 CC := $(CROSS_COMPILE)gcc
 CFLAGS += -O2 -Wall -g -I./ -I../../usr/include/ -ggdb
 
-PROGS := compact_gen rpm_gen upload_digest_lists
+PROGS := compact_gen rpm_gen upload_digest_lists rpm_parser
 PROGS_EXTENDED := common.o
 
 all: $(PROGS)
@@ -22,3 +22,6 @@ rpm_gen: rpm_gen.c $(PROGS_EXTENDED)
 
 upload_digest_lists: upload_digest_lists.c
 	$(CC) $(CFLAGS) -static $< -o $@ $(LDFLAGS)
+
+rpm_parser: rpm_parser.c $(PROGS_EXTENDED)
+	$(CC) $(CFLAGS) -static $< $(PROGS_EXTENDED) -o $@ $(LDFLAGS)
diff --git a/tools/diglim/rpm_parser.c b/tools/diglim/rpm_parser.c
new file mode 100644
index 000000000000..f8a4b63b2fa8
--- /dev/null
+++ b/tools/diglim/rpm_parser.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Parse RPM header and upload digest list to the kernel.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <fts.h>
+#include <rpm/rpmtag.h>
+#include <rpm/rpmpgp.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/xattr.h>
+#include <sys/mount.h>
+#include <linux/diglim.h>
+#include <linux/hash_info.h>
+#include <bits/endianness.h>
+
+#include "common.h"
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#include <linux/byteorder/big_endian.h>
+#else
+#include <linux/byteorder/little_endian.h>
+#endif
+
+#define SYSFS_MNTPOINT "/sys"
+#define SECURITYFS_MNTPOINT SYSFS_MNTPOINT "/kernel/security"
+#define DIGLIM_DIR SECURITYFS_MNTPOINT "/integrity/diglim"
+#define DIGEST_LIST_ADD DIGLIM_DIR "/digest_list_add"
+#define DIGEST_LIST_DEL DIGLIM_DIR "/digest_list_del"
+#define DIGEST_LIST_LABEL DIGLIM_DIR "/digest_list_label"
+#define DIGEST_LIST_DIR "/etc/digest_lists"
+
+#define MOUNT_FLAGS (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME)
+
+enum hash_algo pgp_algo_mapping[PGPHASHALGO_SHA224 + 1] = {
+	[PGPHASHALGO_MD5] = HASH_ALGO_MD5,
+	[PGPHASHALGO_SHA1] = HASH_ALGO_SHA1,
+	[PGPHASHALGO_SHA224] = HASH_ALGO_SHA224,
+	[PGPHASHALGO_SHA256] = HASH_ALGO_SHA256,
+	[PGPHASHALGO_SHA384] = HASH_ALGO_SHA384,
+	[PGPHASHALGO_SHA512] = HASH_ALGO_SHA512,
+};
+
+struct rpm_hdr {
+	int32_t magic;
+	int32_t reserved;
+	int32_t tags;
+	int32_t datasize;
+} __attribute__((packed));
+
+struct rpm_entryinfo {
+	int32_t tag;
+	int32_t type;
+	int32_t offset;
+	int32_t count;
+} __attribute__((packed));
+
+/* from lib/hexdump.c (Linux kernel) */
+static int hex_to_bin(char ch)
+{
+	if ((ch >= '0') && (ch <= '9'))
+		return ch - '0';
+	ch = tolower(ch);
+	if ((ch >= 'a') && (ch <= 'f'))
+		return ch - 'a' + 10;
+	return -1;
+}
+
+int _hex2bin(unsigned char *dst, const char *src, size_t count)
+{
+	while (count--) {
+		int hi = hex_to_bin(*src++);
+		int lo = hex_to_bin(*src++);
+
+		if ((hi < 0) || (lo < 0))
+			return -1;
+
+		*dst++ = (hi << 4) | lo;
+	}
+	return 0;
+}
+
+static u8 *new_digest_list(enum hash_algo algo, enum compact_types type,
+			   u16 modifiers, u32 count)
+{
+	u8 *digest_list;
+	struct compact_list_hdr *hdr;
+
+	digest_list = calloc(1, sizeof(struct compact_list_hdr) +
+			     count * hash_digest_size[algo]);
+	if (!digest_list)
+		return NULL;
+
+	hdr = (struct compact_list_hdr *)digest_list;
+	hdr->version = 1;
+	hdr->type = __cpu_to_le16(type);
+	hdr->modifiers = __cpu_to_le16(modifiers);
+	hdr->algo = __cpu_to_le16(algo);
+	return digest_list;
+}
+
+static int upload_digest_list(int add_del_fd, int label_fd, u8 *digest_list,
+			      char *label)
+{
+	struct compact_list_hdr *hdr;
+	u32 datalen;
+	ssize_t ret;
+
+	hdr = (struct compact_list_hdr *)digest_list;
+	if (!hdr->count)
+		return 0;
+
+	datalen = hdr->datalen;
+	hdr->count = __cpu_to_le32(hdr->count);
+	hdr->datalen = __cpu_to_le32(hdr->datalen);
+
+	ret = write(label_fd, label, strlen(label) + 1);
+	if (ret < 0 || ret != strlen(label) + 1)
+		return -EIO;
+
+	ret = write(add_del_fd, digest_list, sizeof(*hdr) + datalen);
+	if (ret < 0) {
+		if (errno == EEXIST || errno == ENOENT)
+			return 0;
+
+		return ret;
+	} else if (ret != sizeof(*hdr) + datalen) {
+		return -EIO;
+	}
+
+	return ret;
+}
+
+static int parse_rpm(int add_del_fd, int label_fd, char *path,
+		     struct stat *st, bool only_immutable)
+{
+	void *buf, *bufp, *bufendp, *datap;
+	struct rpm_hdr *hdr;
+	int32_t tags;
+	struct rpm_entryinfo *entry;
+	void *digests = NULL, *algo_buf = NULL, *modes = NULL, *sizes = NULL;
+	void *dirnames = NULL, *basenames = NULL, *dirindexes = NULL;
+	char **dirnames_ptr = NULL;
+	u8 *digest_list = NULL, *digest_list_immutable = NULL;
+	u32 digests_count = 0, dirnames_count = 0;
+	u16 algo = HASH_ALGO_MD5;
+	char *label = strrchr(path, '/') + 1;
+	int ret = 0, fd_rpm, i, dirname_len;
+
+	const unsigned char rpm_header_magic[8] = {
+		0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+	};
+
+	if (st->st_size < sizeof(*hdr)) {
+		printf("Missing RPM header\n");
+		return -EINVAL;
+	}
+
+	fd_rpm = open(path, O_RDONLY);
+	if (fd_rpm < 0) {
+		printf("Cannot access %s (%d)\n", path, -errno);
+		return -EACCES;
+	}
+
+	buf = bufp = mmap(NULL, st->st_size, PROT_READ | PROT_WRITE,
+			  MAP_PRIVATE, fd_rpm, 0);
+
+	close(fd_rpm);
+
+	if (bufp == MAP_FAILED)
+		return -ENOMEM;
+
+	if (memcmp(bufp, rpm_header_magic, sizeof(rpm_header_magic))) {
+		printf("Invalid RPM header\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	hdr = (struct rpm_hdr *)bufp;
+	tags = __be32_to_cpu(hdr->tags);
+	datap = bufp + sizeof(*hdr) + tags * sizeof(struct rpm_entryinfo);
+	bufendp = bufp + st->st_size;
+	bufp += sizeof(*hdr);
+
+	for (i = 0; i < tags && (bufp + sizeof(*entry)) <= bufendp;
+	     i++, bufp += sizeof(*entry)) {
+		entry = bufp;
+
+		switch (__be32_to_cpu(entry->tag)) {
+		case RPMTAG_FILEDIGESTS:
+			digests = datap + __be32_to_cpu(entry->offset);
+			digests_count = __be32_to_cpu(entry->count);
+			break;
+		case RPMTAG_FILEDIGESTALGO:
+			algo_buf = datap + __be32_to_cpu(entry->offset);
+			break;
+		case RPMTAG_DIRNAMES:
+			dirnames = datap + __be32_to_cpu(entry->offset);
+			dirnames_count = __be32_to_cpu(entry->count);
+			break;
+		case RPMTAG_BASENAMES:
+			basenames = datap + __be32_to_cpu(entry->offset);
+			break;
+		case RPMTAG_DIRINDEXES:
+			dirindexes = datap + __be32_to_cpu(entry->offset);
+			break;
+		case RPMTAG_FILEMODES:
+			modes = datap + __be32_to_cpu(entry->offset);
+			break;
+		case RPMTAG_FILESIZES:
+			sizes = datap + __be32_to_cpu(entry->offset);
+			break;
+
+		if (digests && algo_buf && modes && dirnames && basenames &&
+		    dirindexes)
+			break;
+		}
+	}
+
+	if (!digests || !modes || !sizes || !dirnames || !basenames ||
+	    !dirindexes) {
+		ret = 0;
+		goto out;
+	}
+
+	if (algo_buf && algo_buf + sizeof(u32) <= bufendp)
+		algo = pgp_algo_mapping[__be32_to_cpu(*(u32 *)algo_buf)];
+
+	digest_list = new_digest_list(algo, COMPACT_FILE, 0, digests_count);
+	if (!digest_list) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	digest_list_immutable = new_digest_list(algo, COMPACT_FILE,
+						(1 << COMPACT_MOD_IMMUTABLE),
+						digests_count);
+	if (!digest_list_immutable) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	dirnames_ptr = calloc(dirnames_count, sizeof(*dirnames_ptr));
+	if (!dirnames_ptr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	for (i = 0; i < dirnames_count; i++) {
+		dirname_len = strlen(dirnames) + 1;
+
+		if (dirnames + dirname_len > bufendp) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		dirnames_ptr[i] = dirnames;
+		dirnames += dirname_len;
+	}
+
+	for (i = 0; i < digests_count; i++) {
+		int digest_str_len = strlen(digests);
+		int basename_str_len = strlen(basenames);
+		u8 *digest_list_ptr = digest_list;
+		struct compact_list_hdr *cur_hdr;
+		u32 dirindex;
+		u32 size;
+		u16 mode;
+
+		if (digests + digest_str_len * 2 + 1 > bufendp) {
+			printf("RPM header read at invalid offset\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (basenames + basename_str_len + 1 > bufendp) {
+			printf("RPM header read at invalid offset\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (dirindexes + sizeof(u32) > bufendp) {
+			printf("RPM header read at invalid offset\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (sizes + sizeof(u32) > bufendp) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (modes + sizeof(u16) > bufendp) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (!digest_str_len) {
+			digests += digest_str_len + 1;
+			basenames += basename_str_len + 1;
+			dirindexes += sizeof(u32);
+			sizes += sizeof(u32);
+			modes += sizeof(u16);
+			continue;
+		}
+
+		dirindex = __be32_to_cpu(*(u32 *)dirindexes);
+		size = __be32_to_cpu(*(u32 *)sizes);
+		mode = __be16_to_cpu(*(u16 *)modes);
+
+		if (((mode & 0111) || !(mode & 0222)) && size)
+			digest_list_ptr = digest_list_immutable;
+
+		if ((strstr(dirnames_ptr[dirindex], "/lib/modules") &&
+		    strncmp(basenames, "modules.", 8)) ||
+		    strstr(dirnames_ptr[dirindex], "/lib/firmware") ||
+		    strstr(dirnames_ptr[dirindex], "/usr/libexec/sudo"))
+			digest_list_ptr = digest_list_immutable;
+
+		if (only_immutable && digest_list_ptr != digest_list_immutable)
+			continue;
+
+		cur_hdr = (struct compact_list_hdr *)digest_list_ptr;
+
+		ret = _hex2bin(digest_list_ptr + sizeof(*cur_hdr) +
+			       cur_hdr->count * hash_digest_size[algo],
+			       digests, digest_str_len / 2);
+		if (ret < 0)
+			goto out;
+
+		digests += digest_str_len + 1;
+		basenames += basename_str_len + 1;
+		dirindexes += sizeof(u32);
+		sizes += sizeof(u32);
+		modes += sizeof(u16);
+
+		cur_hdr->count++;
+		cur_hdr->datalen += hash_digest_size[algo];
+	}
+
+	ret = upload_digest_list(add_del_fd, label_fd, digest_list, label);
+	if (ret < 0) {
+		printf("Failed to upload digest list\n");
+		goto out;
+	}
+
+	ret = upload_digest_list(add_del_fd, label_fd, digest_list_immutable,
+				 label);
+	if (ret < 0)
+		printf("Failed to upload digest list\n");
+out:
+	munmap(buf, st->st_size);
+	free(digest_list);
+	free(digest_list_immutable);
+	free(dirnames_ptr);
+	return ret;
+}
+
+int parse_digest_list(char *name)
+{
+	char *type_start, *format_start, *format_end;
+
+	type_start = strchr(name, '-');
+	if (!type_start++)
+		return 0;
+
+	format_start = strchr(type_start, '-');
+	if (!format_start++)
+		return 0;
+
+	format_end = strchr(format_start, '-');
+	if (!format_end)
+		return 0;
+
+	if (format_end - format_start != 3 ||
+	    strncmp(format_start, "rpm", 3))
+		return 0;
+
+	return 1;
+}
+
+int main(int argc, char *argv[])
+{
+	FTS *fts = NULL;
+	FTSENT *ftsent;
+	int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV);
+	char *paths[2] = { NULL, NULL };
+	char *add_del_path = DIGEST_LIST_ADD;
+	char path[PATH_MAX], *path_ptr = path;
+	bool only_immutable = false;
+	struct stat st;
+	int ret, add_del_fd = -1, label_fd = -1;
+
+	if (argc < 3) {
+		printf("Usage: %s add|del <digest list path>\n",
+		       argv[0]);
+		return -EINVAL;
+	}
+
+	if (stat(argv[2], &st) == -1)
+		return -ENOENT;
+
+	strncpy(path, argv[2], sizeof(path) - 1);
+
+	if (!S_ISDIR(st.st_mode)) {
+		path_ptr = strrchr(path, '/');
+		if (!path_ptr)
+			path_ptr = path;
+		else
+			path_ptr++;
+	} else {
+		path_ptr = path + strlen(path);
+	}
+
+	strncpy(path_ptr, "/.immutable", sizeof(path) - (path_ptr - path));
+	if (!stat(path, &st))
+		only_immutable = true;
+
+	paths[0] = argv[2];
+
+	if (!strcmp(argv[1], "del"))
+		add_del_path = DIGEST_LIST_DEL;
+
+	add_del_fd = open(add_del_path, O_WRONLY);
+	if (add_del_fd < 0) {
+		printf("Unable to open %s\n", add_del_path);
+		return -EACCES;
+	}
+
+	label_fd = open(DIGEST_LIST_LABEL, O_WRONLY);
+	if (label_fd < 0) {
+		printf("Unable to open %s\n", DIGEST_LIST_LABEL);
+		ret = -EACCES;
+		goto out;
+	}
+
+	fts = fts_open(paths, fts_flags, NULL);
+	if (!fts) {
+		printf("Unable to open %s\n", argv[2]);
+		ret = -EACCES;
+		goto out;
+	}
+
+	while ((ftsent = fts_read(fts)) != NULL) {
+		switch (ftsent->fts_info) {
+		case FTS_F:
+			if (!parse_digest_list(ftsent->fts_name))
+				break;
+
+			ret = parse_rpm(add_del_fd, label_fd,
+					ftsent->fts_path, ftsent->fts_statp,
+					only_immutable);
+			if (ret < 0)
+				printf("Cannot parse %s\n", ftsent->fts_path);
+			break;
+		default:
+			break;
+		}
+	}
+
+out:
+	if (add_del_fd >= 0)
+		close(add_del_fd);
+	if (label_fd >= 0)
+		close(label_fd);
+	if (fts)
+		fts_close(fts);
+
+	return 0;
+}
