From 4006d48a5492749cc09fdf42d640a8be59be32eb Mon Sep 17 00:00:00 2001
From: Roberto Sassu <roberto.sassu@huawei.com>
Date: Thu, 12 Mar 2020 17:29:55 +0100
Subject: [PATCH] Generate digest lists

---
 build/files.c           | 296 ++++++++++++++++++-
 build/parsePreamble.c   |   2 +-
 macros.in               |   1 +
 plugins/Makefile.am     |   4 +
 plugins/digest_list.c   | 611 ++++++++++++++++++++++++++++++++++++++++
 rpmio/rpmio_internal.h  |  31 ++
 rpmio/rpmpgp_internal.c |  33 +--
 7 files changed, 938 insertions(+), 40 deletions(-)
 create mode 100644 plugins/digest_list.c

diff --git a/build/files.c b/build/files.c
index 5ae20c6..59c003a 100644
--- a/build/files.c
+++ b/build/files.c
@@ -50,6 +50,8 @@
 #define DEBUG_LIB_PREFIX	"/usr/lib/debug/"
 #define DEBUG_ID_DIR		"/usr/lib/debug/.build-id"
 #define DEBUG_DWZ_DIR 		"/usr/lib/debug/.dwz"
+#define DIGEST_LIST_DIR		"/.digest_lists"
+#define DEST_DIGEST_LIST_DIR	"/etc/ima/digest_lists"
 
 #undef HASHTYPE
 #undef HTKEYTYPE
@@ -129,6 +131,8 @@ typedef struct AttrRec_s {
 
 /* list of files */
 static StringBuf check_fileList = NULL;
+/* list of files per binary package */
+static StringBuf check_fileList_bin_pkg = NULL;
 
 typedef struct FileEntry_s {
     rpmfileAttrs attrFlags;
@@ -193,6 +197,10 @@ typedef struct FileList_s {
     struct FileEntry_s cur;
 } * FileList;
 
+static char *digest_list_dir;
+
+static int genDigestList(Header header, FileList fl, StringBuf fileList);
+
 static void nullAttrRec(AttrRec ar)
 {
     memset(ar, 0, sizeof(*ar));
@@ -984,6 +992,132 @@ static int seenHardLink(FileRecords files, FileListRec flp, rpm_ino_t *fileid)
     return 0;
 }
 
+
+static void genDigestListInput(FileList fl, Package pkg, int isSrc)
+{
+    FileListRec flp;
+    char buf[BUFSIZ];
+    char file_info[BUFSIZ];
+    char file_digest[128 * 2 + 1];
+    int i, gen_digest_lists = 1;
+    uint32_t defaultalgo = PGPHASHALGO_MD5, digestalgo;
+    Header h = pkg->header; /* just a shortcut */
+
+    /*
+     * See if non-md5 file digest algorithm is requested. If not
+     * specified, quietly assume md5. Otherwise check if supported type.
+     */
+    digestalgo = rpmExpandNumeric(isSrc ? "%{_source_filedigest_algorithm}" :
+					  "%{_binary_filedigest_algorithm}");
+    if (digestalgo == 0) {
+	digestalgo = defaultalgo;
+    }
+
+    if (rpmDigestLength(digestalgo) == 0) {
+	rpmlog(RPMLOG_WARNING,
+	       _("Unknown file digest algorithm %u, falling back to MD5\n"),
+	       digestalgo);
+	digestalgo = defaultalgo;
+    }
+
+    /* Sort the big list */
+    if (fl->files.recs) {
+	qsort(fl->files.recs, fl->files.used,
+	      sizeof(*(fl->files.recs)), compareFileListRecs);
+    }
+
+    /* Generate the header. */
+    for (i = 0, flp = fl->files.recs; i < fl->files.used; i++, flp++) {
+	/* Merge duplicate entries. */
+	while (i < (fl->files.used - 1) &&
+	    rstreq(flp->cpioPath, flp[1].cpioPath)) {
+
+	    /* Two entries for the same file found, merge the entries. */
+	    /* Note that an %exclude is a duplication of a file reference */
+
+	    /* file flags */
+	    flp[1].flags |= flp->flags;
+
+	    if (!(flp[1].flags & RPMFILE_EXCLUDE))
+		rpmlog(RPMLOG_WARNING, _("File listed twice: %s\n"),
+			flp->cpioPath);
+
+	    /* file mode */
+	    if (S_ISDIR(flp->fl_mode)) {
+		if ((flp[1].specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE)) <
+		    (flp->specdFlags & (SPECD_DIRMODE | SPECD_DEFDIRMODE)))
+			flp[1].fl_mode = flp->fl_mode;
+	    } else {
+		if ((flp[1].specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE)) <
+		    (flp->specdFlags & (SPECD_FILEMODE | SPECD_DEFFILEMODE)))
+			flp[1].fl_mode = flp->fl_mode;
+	    }
+
+	    /* uid */
+	    if ((flp[1].specdFlags & (SPECD_UID | SPECD_DEFUID)) <
+		(flp->specdFlags & (SPECD_UID | SPECD_DEFUID)))
+	    {
+		flp[1].fl_uid = flp->fl_uid;
+		flp[1].uname = flp->uname;
+	    }
+
+	    /* gid */
+	    if ((flp[1].specdFlags & (SPECD_GID | SPECD_DEFGID)) <
+		(flp->specdFlags & (SPECD_GID | SPECD_DEFGID)))
+	    {
+		flp[1].fl_gid = flp->fl_gid;
+		flp[1].gname = flp->gname;
+	    }
+
+	    /* verify flags */
+	    if ((flp[1].specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY)) <
+		(flp->specdFlags & (SPECD_VERIFY | SPECD_DEFVERIFY)))
+		    flp[1].verifyFlags = flp->verifyFlags;
+
+	    /* XXX to-do: language */
+
+	    flp++; i++;
+	}
+
+	/* Skip files that were marked with %exclude. */
+	if (flp->flags & RPMFILE_EXCLUDE)
+	{
+	    argvAdd(&pkg->fileExcludeList, flp->cpioPath);
+	    continue;
+	}
+
+	buf[0] = '\0';
+	if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST))
+	    (void) rpmDoDigest(digestalgo, flp->diskPath, 1,
+			       (unsigned char *)buf);
+	headerPutString(h, RPMTAG_FILEDIGESTS, buf);
+	snprintf(file_digest, sizeof(file_digest), "%s", buf);
+
+	if (check_fileList_bin_pkg && S_ISREG(flp->fl_mode) &&
+		!(flp->flags & RPMFILE_GHOST)) {
+	    appendStringBuf(check_fileList_bin_pkg, "path=");
+	    appendStringBuf(check_fileList_bin_pkg, flp->diskPath);
+	    snprintf(file_info, sizeof(file_info),
+		"|digestalgopgp=%d|digest=%s|mode=%d"
+		"|uname=%s|gname=%s|caps=%s\n",
+		digestalgo, file_digest, flp->fl_mode,
+		rpmstrPoolStr(fl->pool, flp->uname),
+		rpmstrPoolStr(fl->pool, flp->gname), flp->caps &&
+		strlen(flp->caps) ? flp->caps : "");
+	    appendStringBuf(check_fileList_bin_pkg, file_info);
+	}
+
+	if (S_ISREG(flp->fl_mode) &&
+	    !strncmp(flp->cpioPath, DEST_DIGEST_LIST_DIR,
+		     sizeof(DEST_DIGEST_LIST_DIR) - 1))
+		gen_digest_lists = 0;
+    }
+
+    if (gen_digest_lists &&
+		genDigestList(pkg->header, fl, check_fileList_bin_pkg) > 0)
+	fl->processingFailed = 1;
+}
+
 /**
  * Add file entries to header.
  * @todo Should directories have %doc/%config attributes? (#14531)
@@ -996,6 +1130,8 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc)
 {
     FileListRec flp;
     char buf[BUFSIZ];
+    char file_info[BUFSIZ];
+    char file_digest[128 * 2 + 1];
     int i, npaths = 0;
     int fail_on_dupes = rpmExpandNumeric("%{?_duplicate_files_terminate_build}") > 0;
     uint32_t defaultalgo = RPM_HASH_MD5, digestalgo;
@@ -1003,7 +1139,11 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc)
     Header h = pkg->header; /* just a shortcut */
     time_t source_date_epoch = 0;
     char *srcdate = getenv("SOURCE_DATE_EPOCH");
+    struct rpmtd_s oldfiledigests;
 
+    headerGet(h, RPMTAG_FILEDIGESTS, &oldfiledigests, HEADERGET_ALLOC);
+    headerDel(h, RPMTAG_FILEDIGESTS);
+    rpmtdInit(&oldfiledigests);
     /* Limit the maximum date to SOURCE_DATE_EPOCH if defined
      * similar to the tar --clamp-mtime option
      * https://reproducible-builds.org/specs/source-date-epoch/
@@ -1200,13 +1340,18 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc)
 	if (fl->haveCaps) {
 	    headerPutString(h, RPMTAG_FILECAPS, flp->caps);
 	}
-	
+
 	buf[0] = '\0';
-	if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST))
-	    (void) rpmDoDigest(digestalgo, flp->diskPath, 1, 
-			       (unsigned char *)buf);
-	headerPutString(h, RPMTAG_FILEDIGESTS, buf);
-	
+	if (strstr(flp->diskPath, DIGEST_LIST_DIR) || !oldfiledigests.count) {
+	    if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST))
+	        (void) rpmDoDigest(digestalgo, flp->diskPath, 1,
+				   (unsigned char *)buf);
+	    headerPutString(h, RPMTAG_FILEDIGESTS, buf);
+	} else {
+	    headerPutString(h, RPMTAG_FILEDIGESTS,
+			    rpmtdNextString(&oldfiledigests));
+	}
+
 	buf[0] = '\0';
 	if (S_ISLNK(flp->fl_mode)) {
 	    ssize_t llen = readlink(flp->diskPath, buf, BUFSIZ-1);
@@ -1247,6 +1392,7 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc)
 
 	headerPutUint32(h, RPMTAG_FILEFLAGS, &(flp->flags) ,1);
     }
+
     pkg->dpaths[npaths] = NULL;
 
     if (totalFileSize < UINT32_MAX) {
@@ -1285,6 +1431,7 @@ static void genCpioListAndHeader(FileList fl, Package pkg, int isSrc)
 	/* Binary packages with dirNames cannot be installed by legacy rpm. */
 	(void) rpmlibNeedsFeature(pkg, "CompressedFileNames", "3.0.4-1");
     }
+    rpmtdFreeData(&oldfiledigests);
 }
 
 static FileRecords FileRecordsFree(FileRecords files)
@@ -1359,8 +1506,8 @@ static int validFilename(const char *fn)
  * @param statp		file stat (possibly NULL)
  * @return		RPMRC_OK on success
  */
-static rpmRC addFile(FileList fl, const char * diskPath,
-		struct stat * statp)
+static rpmRC addFile_common(FileList fl, const char * diskPath,
+		struct stat * statp, int digest_list)
 {
     size_t plen = strlen(diskPath);
     char buf[plen + 1];
@@ -1371,6 +1518,10 @@ static rpmRC addFile(FileList fl, const char * diskPath,
     gid_t fileGid;
     const char *fileUname;
     const char *fileGname;
+    char realPath[PATH_MAX];
+    int digest_list_prefix = 0;
+    struct stat st;
+    int exclude = 0;
     rpmRC rc = RPMRC_FAIL; /* assume failure */
 
     /* Strip trailing slash. The special case of '/' path is handled below. */
@@ -1406,6 +1557,33 @@ static rpmRC addFile(FileList fl, const char * diskPath,
     if (*cpioPath == '\0')
 	cpioPath = "/";
 
+    snprintf(realPath, sizeof(realPath), "%s", diskPath);
+    rpmCleanPath(realPath);
+
+    digest_list_prefix = (!strncmp(realPath, digest_list_dir,
+			  strlen(digest_list_dir)));
+
+    if ((!digest_list && digest_list_prefix) ||
+	(digest_list && !digest_list_prefix)) {
+	    rc = RPMRC_OK;
+	    goto exit;
+    }
+
+    if (digest_list) {
+	if (strncmp(cpioPath, DIGEST_LIST_DIR, sizeof(DIGEST_LIST_DIR) - 1)) {
+	    rc = RPMRC_OK;
+	    goto exit;
+	}
+
+	cpioPath += sizeof(DIGEST_LIST_DIR) - 1;
+
+	snprintf(realPath, sizeof(realPath), "%.*s%s",
+		 (int)(strlen(digest_list_dir) - sizeof(DIGEST_LIST_DIR) + 1),
+		 digest_list_dir, cpioPath);
+	if (!stat(realPath, &st))
+	    exclude = 1;
+    }
+
     /*
      * Unless recursing, we dont have stat() info at hand. Handle the
      * various cases, preserving historical behavior wrt %dev():
@@ -1543,6 +1721,8 @@ static rpmRC addFile(FileList fl, const char * diskPath,
 	}
 
 	flp->flags = fl->cur.attrFlags;
+	if (exclude)
+	    flp->flags |= RPMFILE_EXCLUDE;
 	flp->specdFlags = fl->cur.specdFlags;
 	flp->verifyFlags = fl->cur.verifyFlags;
 
@@ -1563,6 +1743,32 @@ exit:
     return rc;
 }
 
+/**
+ * Add a file to the package manifest.
+ * @param fl		package file tree walk data
+ * @param diskPath	path to file
+ * @param statp		file stat (possibly NULL)
+ * @return		RPMRC_OK on success
+ */
+static rpmRC addFile(FileList fl, const char * diskPath,
+		struct stat * statp)
+{
+	return addFile_common(fl, diskPath, statp, 0);
+}
+
+/**
+ * Add a digest list to the package manifest.
+ * @param fl		package file tree walk data
+ * @param diskPath	path to digest list
+ * @param statp		file stat (possibly NULL)
+ * @return		RPMRC_OK on success
+ */
+static rpmRC addDigestList(FileList fl, const char * diskPath,
+		struct stat * statp)
+{
+	return addFile_common(fl, diskPath, statp, 1);
+}
+
 /**
  * Add directory (and all of its files) to the package manifest.
  * @param fl		package file tree walk data
@@ -2584,6 +2790,58 @@ static void addPackageFileList (struct FileList_s *fl, Package pkg,
     argvFree(fileNames);
 }
 
+/**
+ * Generate digest lists list for current binary package.
+ * @header		package header
+ * @fl			file list
+ * @param fileList	packaged file list
+ * @return		-1 if skipped, 0 on OK, 1 on error
+ */
+static int genDigestList(Header header, FileList fl, StringBuf fileList)
+{
+    const char *errorString;
+    char *binFormat = rpmGetPath("%{_rpmfilename}", NULL);
+    char *binRpm = headerFormat(header, binFormat, &errorString);
+    static char * av_brp[] = { "%{?__brp_digest_list}", DIGEST_LIST_DIR + 1, NULL, NULL };
+    StringBuf sb_stdout = NULL;
+    int rc = -1;
+    char * s = rpmExpand(av_brp[0], NULL);
+
+    if (!(s && *s))
+	goto exit;
+
+    av_brp[2] = strchr(binRpm, '/') + 1;
+    rpmlog(RPMLOG_NOTICE, _("Generating digest list: %s\n"), s);
+
+    rc = rpmfcExec(av_brp, fileList, &sb_stdout, 0, binRpm);
+    if (sb_stdout && getStringBuf(sb_stdout)) {
+	const char * t = getStringBuf(sb_stdout), *ptr;
+	char *digest_list_path;
+
+	while((ptr = strchr(t, '\n'))) {
+	    digest_list_path = strndup(t, ptr - t);
+	    if (!digest_list_path) {
+		rc = -1;
+		goto exit;
+	    }
+	    FileEntryFree(&fl->cur);
+	    resetPackageFilesDefaults(fl, fl->pkgFlags);
+	    rc = addDigestList(fl, digest_list_path, NULL);
+	    free(digest_list_path);
+	    if (rc != RPMRC_OK)
+		break;
+
+	    t = ptr + 1;
+	}
+    }
+exit:
+    free(binFormat);
+    free(binRpm);
+    free(s);
+    freeStringBuf(sb_stdout);
+    return rc;
+}
+
 static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
 				 Package pkg, int didInstall, int test)
 {
@@ -2597,6 +2855,10 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
 	if (readFilesManifest(spec, pkg, *fp))
 	    return RPMRC_FAIL;
     }
+
+    /* Init the buffer containing the list of packaged files */
+    check_fileList_bin_pkg = newStringBuf();
+
     /* Init the file list structure */
     memset(&fl, 0, sizeof(fl));
 
@@ -2652,12 +2914,17 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
     if (checkHardLinks(&fl.files))
 	(void) rpmlibNeedsFeature(pkg, "PartialHardlinkSets", "4.0.4-1");
 
+    genDigestListInput(&fl, pkg, 0);
+    if (fl.processingFailed)
+	goto exit;
+
     genCpioListAndHeader(&fl, pkg, 0);
 
 exit:
     FileListFree(&fl);
     specialDirFree(specialDoc);
     specialDirFree(specialLic);
+    freeStringBuf(check_fileList_bin_pkg);
     return fl.processingFailed ? RPMRC_FAIL : RPMRC_OK;
 }
 
@@ -3126,6 +3393,7 @@ static void addPackageDeps(Package from, Package to, enum rpmTag_e tag)
 rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
 			int didInstall, int test)
 {
+    struct stat st;
     Package pkg;
     rpmRC rc = RPMRC_OK;
     char *buildroot;
@@ -3142,7 +3410,14 @@ rpmRC processBinaryFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags,
     check_fileList = newStringBuf();
     genSourceRpmName(spec);
     buildroot = rpmGenPath(spec->rootDir, spec->buildRoot, NULL);
-    
+
+    digest_list_dir = rpmGenPath(buildroot, DIGEST_LIST_DIR, NULL);
+    if (!digest_list_dir)
+	goto exit;
+
+    if (!stat(digest_list_dir, &st))
+	rpmlog(RPMLOG_NOTICE, _("Ignoring files in: %s\n"), digest_list_dir);
+
     if (rpmExpandNumeric("%{?_debuginfo_subpackages}")) {
 	maindbg = findDebuginfoPackage(spec);
 	if (maindbg) {
@@ -3248,6 +3523,7 @@ exit:
     check_fileList = freeStringBuf(check_fileList);
     _free(buildroot);
     _free(uniquearch);
-    
+    _free(digest_list_dir);
+
     return rc;
 }
diff --git a/build/parsePreamble.c b/build/parsePreamble.c
index 722d0ac..eb043a9 100644
--- a/build/parsePreamble.c
+++ b/build/parsePreamble.c
@@ -801,7 +801,7 @@ static rpmRC handlePreambleTag(rpmSpec spec, Package pkg, rpmTagVal tag,
 	SINGLE_TOKEN_ONLY;
 	if (tag == RPMTAG_RELEASE) {
             char *dist = rpmExpand("%{?dist}",NULL);
-            rasprintf(&field,"%s%s",field,dist);
+            rasprintf(&field,"%s%s",field,(dist && strstr(field, dist)) ? "" : dist);
             free(dist);
         }
 	if (rpmCharCheck(spec, field, ALLOWED_CHARS_VERREL, NULL))
diff --git a/macros.in b/macros.in
index 0f67b07..ae2ad1c 100644
--- a/macros.in
+++ b/macros.in
@@ -1135,6 +1135,7 @@ package or when debugging this package.\
 %__transaction_prioreset	%{__plugindir}/prioreset.so
 %__transaction_audit		%{__plugindir}/audit.so
 %__transaction_dbus_announce	%{__plugindir}/dbus_announce.so
+%__transaction_digest_list	%{__plugindir}/digest_list.so
 
 #------------------------------------------------------------------------------
 # Macros for further automated spec %setup and patch application
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 822c7d2..161fe4c 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -69,3 +69,7 @@ audit_la_sources = audit.c
 audit_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la @WITH_AUDIT_LIB@
 plugins_LTLIBRARIES += audit.la
 endif
+
+digest_list_la_sources = digest_list.c
+digest_list_la_LIBADD = $(top_builddir)/lib/librpm.la $(top_builddir)/rpmio/librpmio.la
+plugins_LTLIBRARIES += digest_list.la
diff --git a/plugins/digest_list.c b/plugins/digest_list.c
new file mode 100644
index 0000000..698c31e
--- /dev/null
+++ b/plugins/digest_list.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2020-2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: digest_list.c
+ *      Plugin to load digest lists in the Linux kernel.
+ */
+
+#include "system.h"
+#include "errno.h"
+
+#include <fcntl.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmts.h>
+#include <rpm/header.h>
+#include <rpmio/rpmpgp_internal.h>
+#include <rpmio/rpmio_internal.h>
+#include <rpm/rpmfileutil.h>
+#include "lib/rpmplugin.h"
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <openssl/sha.h>
+#include <sys/xattr.h>
+#include <sys/capability.h>
+#include <linux/xattr.h>
+#include <asm/byteorder.h>
+#include <sys/wait.h>
+
+#include "debug.h"
+
+#define IMA_DIR "/sys/kernel/security/ima"
+#define DIGEST_LIST_DATA_PATH IMA_DIR "/digest_list_data"
+#define DIGEST_LIST_DATA_DEL_PATH IMA_DIR "/digest_list_data_del"
+#define DIGEST_LIST_COUNT IMA_DIR "/digests_count"
+#define DIGEST_LIST_DEFAULT_PATH "/etc/ima/digest_lists"
+#define RPM_PARSER "/usr/libexec/rpm_parser"
+
+enum hash_algo {
+	HASH_ALGO_MD4,
+	HASH_ALGO_MD5,
+	HASH_ALGO_SHA1,
+	HASH_ALGO_RIPE_MD_160,
+	HASH_ALGO_SHA256,
+	HASH_ALGO_SHA384,
+	HASH_ALGO_SHA512,
+	HASH_ALGO_SHA224,
+	HASH_ALGO_RIPE_MD_128,
+	HASH_ALGO_RIPE_MD_256,
+	HASH_ALGO_RIPE_MD_320,
+	HASH_ALGO_WP_256,
+	HASH_ALGO_WP_384,
+	HASH_ALGO_WP_512,
+	HASH_ALGO_TGR_128,
+	HASH_ALGO_TGR_160,
+	HASH_ALGO_TGR_192,
+	HASH_ALGO_SM3_256,
+	HASH_ALGO__LAST
+};
+
+#define PGPHASHALGO__LAST PGPHASHALGO_SHA224 + 1
+enum hash_algo pgp_algo_mapping[PGPHASHALGO__LAST] = {
+	[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,
+};
+
+/* from integrity.h */
+enum evm_ima_xattr_type {
+	IMA_XATTR_DIGEST = 0x01,
+	EVM_XATTR_HMAC,
+	EVM_IMA_XATTR_DIGSIG,
+	IMA_XATTR_DIGEST_NG,
+	EVM_XATTR_PORTABLE_DIGSIG,
+	EVM_IMA_XATTR_DIGEST_LIST,
+	IMA_XATTR_LAST
+};
+
+struct evm_ima_xattr_data {
+	uint8_t type;
+	uint8_t digest[SHA512_DIGEST_LENGTH + 1];
+} __attribute__((packed));
+
+struct signature_v2_hdr {
+	uint8_t type;		/* xattr type */
+	uint8_t version;	/* signature format version */
+	uint8_t hash_algo;	/* Digest algorithm [enum hash_algo] */
+	__be32 keyid;		/* IMA key identifier - not X509/PGP specific */
+	__be16 sig_size;	/* signature size */
+	uint8_t sig[0];		/* signature payload */
+} __attribute__((packed));
+
+static int upload_digest_list(char *path, int type, int digest_list_signed)
+{
+	size_t size;
+	char buf[21];
+	const char *ima_path = DIGEST_LIST_DATA_PATH;
+	struct stat st;
+	pid_t pid;
+	int ret = 0, fd;
+
+	if (type == TR_REMOVED)
+		ima_path = DIGEST_LIST_DATA_DEL_PATH;
+
+	if (stat(ima_path, &st) == -1)
+		return 0;
+
+	/* First determine if kernel interface can accept new digest lists */
+	fd = open(DIGEST_LIST_COUNT, O_RDONLY);
+	if (fd < 0) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not open IMA interface "
+		       "'%s': %s\n", DIGEST_LIST_COUNT, strerror(errno));
+		return -EACCES;
+	}
+
+	ret = read(fd, buf, sizeof(buf));
+	close(fd);
+
+	if (ret <= 0) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not read from IMA "
+		       "interface '%s': %s\n", DIGEST_LIST_COUNT,
+		       strerror(errno));
+		return -EACCES;
+	}
+
+	/* Last character is newline */
+	buf[ret - 1] = '\0';
+
+	rpmlog(RPMLOG_DEBUG, "digest_list: digests count %s\n", buf);
+
+	if (*buf == '0') {
+		rpmlog(RPMLOG_DEBUG, "digest_list: not uploading '%s' to IMA "
+		       "interface '%s'\n", path, ima_path);
+		return RPMRC_OK;
+	}
+
+	/* If the digest list is not signed, execute the RPM parser */
+	if (!digest_list_signed) {
+		if (stat(RPM_PARSER, &st) == -1) {
+			rpmlog(RPMLOG_DEBUG, "digest_list: %s not found, "
+			       "not uploading digest list\n", RPM_PARSER);
+			return 0;
+		}
+
+		if ((pid = fork()) == 0) {
+			execlp(RPM_PARSER, RPM_PARSER, (type == TR_ADDED) ?
+			       "add" : "del", path, NULL);
+			_exit(EXIT_FAILURE);
+		}
+
+		waitpid(pid, &ret, 0);
+		if (ret != 0)
+			rpmlog(RPMLOG_ERR, "digest_list: %s returned %d\n",
+			       RPM_PARSER, ret);
+		return 0;
+	}
+
+	fd = open(ima_path, O_WRONLY);
+	if (fd < 0) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not open IMA interface "
+		       "'%s': %s\n", ima_path, strerror(errno));
+		return -EACCES;
+	}
+
+	/* Write the path of the digest list to securityfs */
+	size = write(fd, path, strlen(path));
+	if (size != strlen(path)) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not write '%s' to IMA "
+		       "interface '%s': %s\n", path, ima_path, strerror(errno));
+		ret = -EIO;
+		goto out;
+	}
+
+	rpmlog(RPMLOG_DEBUG, "digest_list: written '%s' to '%s'\n", path,
+	       ima_path);
+out:
+	close(fd);
+	return ret;
+}
+
+static int write_rpm_digest_list(rpmte te, char *path)
+{
+	FD_t fd;
+	ssize_t written;
+	Header rpm = rpmteHeader(te);
+	rpmtd immutable;
+	int ret = 0;
+
+	immutable = rpmtdNew();
+	headerGet(rpm, RPMTAG_HEADERIMMUTABLE, immutable, 0);
+
+	fd = Fopen(path, "w.ufdio");
+	if (fd == NULL || Ferror(fd)) {
+		ret = -EACCES;
+		goto out;
+	}
+
+	written = Fwrite(rpm_header_magic, sizeof(uint8_t),
+			 sizeof(rpm_header_magic), fd);
+
+	if (written != sizeof(rpm_header_magic)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	written = Fwrite(immutable->data, sizeof(uint8_t),
+			 immutable->count, fd);
+	if (written != immutable->count || Ferror(fd))
+		ret = -EIO;
+out:
+	Fclose(fd);
+	rpmtdFree(immutable);
+	return ret;
+}
+
+static int write_rpm_digest_list_ima_xattr(rpmte te, char *path)
+{
+	rpmtd signature;
+	ssize_t written;
+	uint8_t sig[2048] = { 0 };
+	pgpDigParams sigp = NULL;
+	struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig;
+	Header rpm = rpmteHeader(te);
+	FD_t fd;
+	int ret = 0, sig_size, sig_size_rounded;
+
+	signature = rpmtdNew();
+	headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+	ret = pgpPrtParams(signature->data, signature->count,
+			   PGPTAG_SIGNATURE, &sigp);
+
+	if (ret) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	fd = Fopen(path, "a.ufdio");
+	if (fd == NULL || Ferror(fd)) {
+		ret = -EACCES;
+		goto out;
+	}
+
+	written = Fwrite(sigp->hash, sizeof(uint8_t),
+			 sigp->hashlen, fd);
+	if (written != sigp->hashlen || Ferror(fd)) {
+		ret = -EIO;
+		goto out;
+	}
+
+	if (sigp->version == 4) {
+		/* V4 trailer is six octets long (rfc4880) */
+		uint8_t trailer[6];
+		uint32_t nb = sigp->hashlen;
+		nb = htonl(nb);
+		trailer[0] = sigp->version;
+		trailer[1] = 0xff;
+		memcpy(trailer+2, &nb, 4);
+
+		written = Fwrite(trailer, sizeof(uint8_t), sizeof(trailer), fd);
+		if (written != sizeof(trailer) || Ferror(fd)) {
+			ret = -EIO;
+			goto out;
+		}
+	}
+
+	Fclose(fd);
+
+	sig_hdr->type = EVM_IMA_XATTR_DIGSIG;
+	sig_hdr->version = 2;
+	sig_hdr->hash_algo = pgp_algo_mapping[sigp->hash_algo];
+	memcpy((void *)&sig_hdr->keyid, sigp->signid + sizeof(uint32_t),
+	       sizeof(uint32_t));
+
+	sig_size = (pgpMpiBits(sigp->data) + 7) >> 3;
+	if (sizeof(sig_hdr) + sig_size > sizeof(sig)) {
+		rpmlog(RPMLOG_ERR,
+		       "digest_list: signature in %s too big\n", path);
+		ret = -E2BIG;
+		goto out;
+	}
+
+	sig_size_rounded = ((sig_size + 7) >> 3) * 8;
+	sig_hdr->sig_size = __cpu_to_be16(sig_size_rounded);
+
+	memcpy(sig_hdr->sig + sig_size_rounded - sig_size,
+	       (uint8_t *)sigp->data + 2, sig_size);
+
+	ret = lsetxattr(path, XATTR_NAME_IMA,
+			sig, sizeof(*sig_hdr) + sig_size_rounded, 0);
+	if (ret < 0)
+		rpmlog(RPMLOG_ERR, "digest_list: could not apply security.ima "
+		       "on '%s': %s\n", path, strerror(errno));
+	else
+		rpmlog(RPMLOG_DEBUG, "digest_list: security.ima successfully "
+		       "applied on '%s'\n", path);
+out:
+	pgpDigParamsFree(sigp);
+	rpmtdFree(signature);
+	return ret;
+}
+
+static int write_digest_list_ima_xattr(rpmte te, char *path, char *path_sig)
+{
+	rpmtd signature;
+	uint8_t sig[2048] = { 0 };
+	pgpDigParams sigp = NULL;
+	struct signature_v2_hdr *sig_hdr = (struct signature_v2_hdr *)sig;
+	Header rpm = rpmteHeader(te);
+	FD_t fd;
+	struct stat st;
+	int ret = 0, sig_size;
+
+	signature = rpmtdNew();
+	headerGet(rpm, RPMTAG_RSAHEADER, signature, 0);
+	ret = pgpPrtParams(signature->data, signature->count,
+			   PGPTAG_SIGNATURE, &sigp);
+
+	if (ret) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	sig_hdr->type = EVM_IMA_XATTR_DIGSIG;
+	sig_hdr->version = 2;
+	sig_hdr->hash_algo = HASH_ALGO_SHA256;
+	memcpy((void *)&sig_hdr->keyid, sigp->signid + sizeof(uint32_t),
+	       sizeof(uint32_t));
+
+	if (stat(path_sig, &st) == -1) {
+		ret = -EACCES;
+		goto out;
+	}
+
+	if (sizeof(sig_hdr) + st.st_size > sizeof(sig)) {
+		rpmlog(RPMLOG_ERR, "digest_list: signature in %s too big\n",
+		       path);
+		ret = -E2BIG;
+		goto out;
+	}
+
+	fd = Fopen(path_sig, "r.ufdio");
+	if (fd < 0) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not open '%s': %s\n",
+		       path_sig, strerror(errno));
+		ret = -EACCES;
+		goto out;
+	}
+
+	sig_size = Fread(sig_hdr->sig, sizeof(uint8_t), st.st_size, fd);
+	if (sig_size != st.st_size || Ferror(fd)) {
+		rpmlog(RPMLOG_ERR, "digest_list: could not read '%s': %s\n",
+		       path_sig, strerror(errno));
+		Fclose(fd);
+		ret = -EIO;
+		goto out;
+	}
+
+	sig_hdr->sig_size = __cpu_to_be16(sig_size);
+
+	rpmlog(RPMLOG_DEBUG,
+	       "digest_list: read signature of %d bytes from '%s'\n",
+	       sig_size, path_sig);
+
+	ret = lsetxattr(path, XATTR_NAME_IMA,
+			sig, sizeof(*sig_hdr) + sig_size, 0);
+	if (ret < 0)
+		rpmlog(RPMLOG_ERR, "digest_list: could not apply security.ima "
+		       "on '%s': %s\n", path, strerror(errno));
+	else
+		rpmlog(RPMLOG_DEBUG, "digest_list: security.ima successfully "
+		       "applied on '%s'\n", path);
+out:
+	pgpDigParamsFree(sigp);
+	rpmtdFree(signature);
+	return ret;
+}
+
+static int process_digest_list(rpmte te, int parser, int pre)
+{
+	char *path = NULL, *path_sig = NULL;
+	int digest_list_signed = 0;
+	struct stat st;
+	ssize_t size;
+	int type = rpmteType(te);
+	struct __user_cap_header_struct cap_header_data;
+	cap_user_header_t cap_header = &cap_header_data;
+	struct __user_cap_data_struct cap_data_data;
+	cap_user_data_t cap_data = &cap_data_data;
+	rpmRC ret = RPMRC_OK;
+
+	path = malloc(PATH_MAX);
+	if (!path) {
+		ret = RPMRC_FAIL;
+		goto out;
+	}
+
+	path_sig = malloc(PATH_MAX);
+	if (!path_sig) {
+		ret = RPMRC_FAIL;
+		goto out;
+	}
+
+	if (parser)
+		snprintf(path_sig, PATH_MAX,
+			 "%s.sig/0-parser_list-compact-libexec.sig",
+			 DIGEST_LIST_DEFAULT_PATH);
+	else
+		snprintf(path_sig, PATH_MAX,
+			"%s.sig/0-metadata_list-compact-%s-%s-%s.%s.sig",
+			 DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+			 rpmteR(te), rpmteA(te));
+
+	if (!stat(path_sig, &st))
+		digest_list_signed = 1;
+
+	if (parser && !digest_list_signed)
+		goto out;
+
+	if (parser)
+		snprintf(path, PATH_MAX, "%s/0-parser_list-compact-libexec",
+			 DIGEST_LIST_DEFAULT_PATH);
+	else
+		snprintf(path, PATH_MAX,
+			 "%s/0-metadata_list-compact-%s-%s-%s.%s",
+			 DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+			 rpmteR(te), rpmteA(te));
+
+	if (stat(path, &st) == -1)
+		goto out;
+
+	if (!parser && !digest_list_signed)
+		snprintf(path, PATH_MAX, "%s/0-metadata_list-rpm-%s-%s-%s.%s",
+			 DIGEST_LIST_DEFAULT_PATH, rpmteN(te), rpmteV(te),
+			 rpmteR(te), rpmteA(te));
+
+	size = lgetxattr(path, XATTR_NAME_IMA, NULL, 0);
+
+	if (type == TR_ADDED && !pre && size < 0) {
+		if (!digest_list_signed) {
+			/* Write RPM header to the disk */
+			ret = write_rpm_digest_list(te, path);
+			if (ret < 0) {
+				ret = RPMRC_FAIL;
+				goto out;
+			}
+		}
+
+		/* don't call lsetxattr without CAP_SYS_ADMIN */
+		cap_header->pid = getpid();
+		cap_header->version = _LINUX_CAPABILITY_VERSION_1;
+		if (capget(cap_header, cap_data) < 0) {
+			ret = -ENOENT;
+			goto out;
+		}
+		if (!(cap_data->effective & CAP_TO_MASK(CAP_SYS_ADMIN))) {
+			ret = -EPERM;
+			goto out;
+		}
+
+		if (!digest_list_signed) {
+			/* Write RPM header sig to security.ima */
+			ret = write_rpm_digest_list_ima_xattr(te, path);
+		} else {
+			ret = write_digest_list_ima_xattr(te, path, path_sig);
+		}
+
+		if (ret < 0) {
+			ret = RPMRC_FAIL;
+			goto out;
+		}
+	} else if (type == TR_ADDED && pre) {
+		if (size < 0)
+			goto out;
+
+		/* rpm is overwriting the digest list, remove from the kernel */
+		type = TR_REMOVED;
+	}
+
+	/* Upload digest list to securityfs */
+	upload_digest_list(path, type, digest_list_signed);
+
+	if (type == TR_REMOVED) {
+		if (!digest_list_signed) {
+			unlink(path);
+			goto out;
+		}
+
+		ret = lremovexattr(path, XATTR_NAME_IMA);
+		if (ret < 0)
+			rpmlog(RPMLOG_ERR, "digest_list: cannot remove "
+			       "security.ima from '%s'\n", path);
+		else
+			rpmlog(RPMLOG_DEBUG, "digest_list: security.ima "
+			       "successfully removed from '%s'\n", path);
+	}
+out:
+	free(path);
+	free(path_sig);
+	return ret;
+}
+
+rpmte cur_te;
+int digest_list_counter;
+
+static rpmRC digest_list_psm_pre(rpmPlugin plugin, rpmte te)
+{
+	Header rpm = rpmteHeader(te);
+	rpmtd dirnames, dirindexes;
+	int i = -1;
+
+	digest_list_counter = 0;
+
+	dirnames = rpmtdNew();
+	headerGet(rpm, RPMTAG_DIRNAMES, dirnames, 0);
+
+	while ((i = rpmtdNext(dirnames)) >= 0) {
+		char *dirname = (char *) rpmtdGetString(dirnames);
+
+		if (!strncmp(dirname, DIGEST_LIST_DEFAULT_PATH,
+			     sizeof(DIGEST_LIST_DEFAULT_PATH) - 1) &&
+		    dirname[sizeof(DIGEST_LIST_DEFAULT_PATH) - 1] == '/')
+			break;
+	}
+
+	rpmtdFree(dirnames);
+
+	if (i == -1)
+		return RPMRC_OK;
+
+	dirindexes = rpmtdNew();
+	headerGet(rpm, RPMTAG_DIRINDEXES, dirindexes, 0);
+	while (rpmtdNext(dirindexes) >= 0)
+		if (rpmtdGetNumber(dirindexes) == i)
+			digest_list_counter++;
+
+	rpmtdFree(dirindexes);
+
+	cur_te = te;
+	return RPMRC_OK;
+}
+
+static rpmRC digest_list_file_common(rpmPlugin plugin, rpmfi fi,
+				     const char* path, mode_t file_mode,
+				     rpmFsmOp op, int pre, int res)
+{
+	rpmFileAction action = XFO_ACTION(op);
+
+	if (!digest_list_counter)
+		return RPMRC_OK;
+
+	if (!cur_te)
+		return RPMRC_OK;
+
+	if (!pre && res != RPMRC_OK)
+		return res;
+
+	if (!pre && rpmteType(cur_te) != TR_ADDED)
+		return RPMRC_OK;
+
+	if (pre && action == FA_SKIP)
+		return RPMRC_OK;
+
+	if (strncmp(path, DIGEST_LIST_DEFAULT_PATH,
+		    sizeof(DIGEST_LIST_DEFAULT_PATH) - 1) ||
+	    path[sizeof(DIGEST_LIST_DEFAULT_PATH) - 1] != '/')
+		return RPMRC_OK;
+
+	if (!pre && --digest_list_counter)
+		return RPMRC_OK;
+
+	rpmlog(RPMLOG_DEBUG, "process ima digest, pre: %d, action: %d, teType: %d\n",
+		pre, action, rpmteType(cur_te));
+	process_digest_list(cur_te, 0, pre);
+	if (!strcmp(rpmteN(cur_te), "digest-list-tools")) {
+		if (pre && rpmteType(cur_te) == TR_REMOVED)
+			return RPMRC_OK;
+
+		rpmlog(RPMLOG_DEBUG, "process parser digest\n");
+		process_digest_list(cur_te, 1, pre);
+	}
+
+	return RPMRC_OK;
+}
+
+static rpmRC digest_list_file_pre(rpmPlugin plugin, rpmfi fi,
+				  const char* path, mode_t file_mode,
+				  rpmFsmOp op)
+{
+	return digest_list_file_common(plugin, fi, path, file_mode, op, 1, 0);
+}
+
+static rpmRC digest_list_file_post(rpmPlugin plugin, rpmfi fi,
+				  const char* path, mode_t file_mode,
+				  rpmFsmOp op, int res)
+{
+	return digest_list_file_common(plugin, fi, path, file_mode, op, 0, res);
+}
+
+struct rpmPluginHooks_s digest_list_hooks = {
+	.psm_pre = digest_list_psm_pre,
+	.fsm_file_pre = digest_list_file_pre,
+	.fsm_file_post = digest_list_file_post,
+};
diff --git a/rpmio/rpmio_internal.h b/rpmio/rpmio_internal.h
index fb82b04..afb3a06 100644
--- a/rpmio/rpmio_internal.h
+++ b/rpmio/rpmio_internal.h
@@ -7,11 +7,42 @@
 
 #include <rpm/rpmio.h>
 #include <rpm/rpmpgp.h>
+#include <rpmio/rpmpgp_internal.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/** \ingroup rpmio
+ * Values parsed from OpenPGP signature/pubkey packet(s).
+ */
+struct pgpDigParams_s {
+    char * userid;
+    uint8_t * hash;
+    uint8_t tag;
+    const uint8_t * data;
+
+    uint8_t key_flags;          /*!< key usage flags */
+    uint8_t version;            /*!< version number. */
+    uint32_t time;              /*!< key/signature creation time. */
+    uint8_t pubkey_algo;                /*!< public key algorithm. */
+
+    uint8_t hash_algo;
+    uint8_t sigtype;
+    uint32_t hashlen;
+    uint8_t signhash16[2];
+    pgpKeyID_t signid;
+    uint8_t saved;              /*!< Various flags.  `PGPDIG_SAVED_*` are never reset.
+                                 * `PGPDIG_SIG_HAS_*` are reset for each signature. */
+#define	PGPDIG_SAVED_TIME	(1 << 0)
+#define	PGPDIG_SAVED_ID		(1 << 1)
+#define	PGPDIG_SIG_HAS_CREATION_TIME	(1 << 2)
+#define	PGPDIG_SIG_HAS_KEY_FLAGS	(1 << 3)
+
+    pgpDigAlg alg;
+};
+
+
 void fdSetBundle(FD_t fd, rpmDigestBundle bundle);
 rpmDigestBundle fdGetBundle(FD_t fd, int create);
 
diff --git a/rpmio/rpmpgp_internal.c b/rpmio/rpmpgp_internal.c
index 19947be..efc206c 100644
--- a/rpmio/rpmpgp_internal.c
+++ b/rpmio/rpmpgp_internal.c
@@ -19,34 +19,6 @@
 
 static int _print = 0;
 
-/** \ingroup rpmio
- * Values parsed from OpenPGP signature/pubkey packet(s).
- */
-struct pgpDigParams_s {
-    char * userid;
-    uint8_t * hash;
-    uint8_t tag;
-
-    uint8_t key_flags;		/*!< key usage flags */
-    uint8_t version;		/*!< version number. */
-    uint32_t time;		/*!< key/signature creation time. */
-    uint8_t pubkey_algo;		/*!< public key algorithm. */
-
-    uint8_t hash_algo;
-    uint8_t sigtype;
-    uint32_t hashlen;
-    uint8_t signhash16[2];
-    pgpKeyID_t signid;
-    uint8_t saved;		/*!< Various flags.  `PGPDIG_SAVED_*` are never reset.
-				 * `PGPDIG_SIG_HAS_*` are reset for each signature. */
-#define	PGPDIG_SAVED_TIME	(1 << 0)
-#define	PGPDIG_SAVED_ID		(1 << 1)
-#define	PGPDIG_SIG_HAS_CREATION_TIME	(1 << 2)
-#define	PGPDIG_SIG_HAS_KEY_FLAGS	(1 << 3)
-
-    pgpDigAlg alg;
-};
-
 /** \ingroup rpmio
  * Container for values parsed from an OpenPGP signature and public key.
  */
@@ -484,6 +456,7 @@ static int pgpPrtSig(pgpTag tag, const uint8_t *h, size_t hlen,
 	}
 
 	p = ((uint8_t *)v) + sizeof(*v);
+	_digp->data = p;
 	rc = tag ? pgpPrtSigParams(tag, v->pubkey_algo, p, h, hlen, _digp) : 0;
     }	break;
     case 4:
@@ -545,7 +518,8 @@ static int pgpPrtSig(pgpTag tag, const uint8_t *h, size_t hlen,
 	p += 2;
 	if (p > hend)
 	    return 1;
-
+	
+	_digp->data = p;
 	rc = tag ? pgpPrtSigParams(tag, v->pubkey_algo, p, h, hlen, _digp) : 0;
     }	break;
     default:
@@ -636,6 +610,7 @@ static int pgpPrtKey(pgpTag tag, const uint8_t *h, size_t hlen,
 	    }
 
 	    p = ((uint8_t *)v) + sizeof(*v);
+	    _digp->data = p;
 	    rc = pgpPrtPubkeyParams(v->pubkey_algo, p, h, hlen, _digp);
 	}
     }	break;
-- 
2.26.2

