From: Aaron Merey <amerey@redhat.com>
To: "Frank Ch. Eigler" <fche@redhat.com>
Cc: Mark Wielaard <mark@klomp.org>, elfutils-devel@sourceware.org
Subject: Re: [rfc] [patch] PR28204: debuginfod ima signature verification
Date: Thu, 9 May 2024 13:56:11 -0400 [thread overview]
Message-ID: <CAJDtP-Sq=m0nUwy4B-UsN-mAEp72Hv55HT3vM7VBiJafS4DK7A@mail.gmail.com> (raw)
In-Reply-To: <20240505013003.GA1291@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 64457 bytes --]
Hi Frank,
I've pointed out a couple nits below, but otherwise the patch LGTM.
I've also attached a diff for handling DEBUGINFOD_IMA_CERT_PATH in
profile.fish.in that should apply on top of this patch.
I know there's already been a lot of discussion re. ima:permissive and
I'm weighing in rather late, but FWIW I do support including it.
Currently individual ELF sections cannot be downloaded when
ima:enforcing is active. With ima:permissive we could support proper
section queries while also being able to perform some amount of
ima verification.
On Tue, Apr 16, 2024 at 6:15 PM Frank Ch. Eigler <fche@redhat.com> wrote:
>
> Hi -
>
> The following is the candidate patch for the basic functionality.
> It's been corrected for whitespace & error codes, given more complete
> docs and commit message. See also the users/fche/try-bz2824f branch.
>
>
> debuginfod: PR28204 - RPM IMA per-file signature verification
>
> Recent versions of Fedora/RHEL include per-file cryptographic
> signatures in RPMs, not just an overall RPM signature. This work
> extends debuginfod client & server to extract, transfer, and verify
> those signatures. These allow clients to assure users that the
> downloaded files have not been corrupted since their original
> packaging. Downloads that fail the test are rejected.
>
> Clients may select a desired level of enforcement for sets of URLs in
> the DEBUGINFOD_URLS by inserting special markers ahead of them:
>
> ima:ignore pay no attention to absence or presence of signatures
> ima:enforcing require every file to be correctly signed
>
> The default is ima:ignore mode. In ima:enforcing mode, section
> queries are forced to be entire-file downloads, as it is not
> possible to crypto-verify just sections.
>
> IMA signatures are verified against a set of signing certificates.
> These are normally published by distributions. The environment
> variable $DEBUGINFOD_IMA_CERT_PATH contains a colon-separated path for
> finding DER or PEM formatted certificates / public keys. These
> certificates are assumed trusted. The profile.d scripts transcribe
> /etc/debuginfod/*.certdir files into that variable.
>
> As for implementation:
>
> * configure.ac: Add --enable-default-ima-cert-path=PATH parameter.
> Check for libimaevm (using headers only).
>
> * config/Makefile.am: Install defaults into /etc files.
> * config/profile.{csh,sh}.in: Process defaults into env variables.
> * config/elfutils.spec.in: Add more buildrequires.
>
> * debuginfod/debuginfod.cxx (handle_buildid_r_match): Added extraction of the
> per-file IMA signature for the queried file and store in http header.
> (find_globbed_koji_filepath): New function.
> (parse_opt): New flag --koji-sigcache.
> * debuginfod/debuginfod-client.c (debuginfod_query_server): Added policy for
> validating IMA signatures
> (debuginfod_validate_imasig): New function, with friends.
> * debuginfod/debuginfod.h.in: Added DEBUGINFOD_IMA_CERT_PATH_ENV_VAR.
> * debuginfod/Makefile.am: Add linker flags for rpm and crypto.
>
> * doc/debuginfod-client-config.7: Document DEBUGINFOD_IMA_CERT_PATH,
> update DEBUGINFOD_URLS.
> * doc/debuginfod.8: Document --koji-sigcache.
> * doc/debuginfod-find.1, doc/debuginfod_find_debuginfo.3: Update SECURITY.
>
> * tests/run-debuginfod-ima-verification.sh: New test.
> * tests/debuginfod-ima: Some new files for the tests.
> * tests/Makefile.am: run/distribute them.
>
> Signed-off-by: Ryan Goldberg <rgoldber@redhat.com>
> Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
>
> diff --git a/config/Makefile.am b/config/Makefile.am
> index ae14e625b726..5a28e66d4408 100644
> --- a/config/Makefile.am
> +++ b/config/Makefile.am
> @@ -46,12 +46,16 @@ pkgconfig_DATA += libdebuginfod.pc
> if [ -n "@DEBUGINFOD_URLS@" ]; then \
> echo "@DEBUGINFOD_URLS@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls; \
> fi
> + if [ -n "@DEBUGINFOD_IMA_CERT_PATH@" ]; then \
> + echo "@DEBUGINFOD_IMA_CERT_PATH@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath; \
> + fi
>
> uninstall-local:
> rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.sh
> rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.csh
> rm -f $(DESTDIR)$(datadir)/fish/vendor_conf.d/debuginfod.fish
> rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls
> + rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath
> -rmdir $(DESTDIR)$(sysconfdir)/debuginfod
> endif
>
> diff --git a/config/elfutils.spec.in b/config/elfutils.spec.in
> index 4d802a25ad5f..460729972420 100644
> --- a/config/elfutils.spec.in
> +++ b/config/elfutils.spec.in
> @@ -43,6 +43,12 @@ BuildRequires: curl
> # For run-debuginfod-response-headers.sh test case
> BuildRequires: socat
>
> +# For debuginfod rpm IMA verification
> +BuildRequires: rpm-devel
> +BuildRequires: ima-evm-utils-devel
> +BuildRequires: openssl-devel
> +BuildRequires: rpm-sign
> +
> %define _gnu %{nil}
> %define _programprefix eu-
>
> diff --git a/config/profile.csh.in b/config/profile.csh.in
> index d962d969c05b..1da9626c711b 100644
> --- a/config/profile.csh.in
> +++ b/config/profile.csh.in
> @@ -4,13 +4,19 @@
> # See also [man debuginfod-client-config] for other environment variables
> # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS.
>
> +set prefix="@prefix@"
> if (! $?DEBUGINFOD_URLS) then
> - set prefix="@prefix@"
> set DEBUGINFOD_URLS=`sh -c 'cat /dev/null "$0"/*.urls 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ' '`
> if ( "$DEBUGINFOD_URLS" != "" ) then
> setenv DEBUGINFOD_URLS "$DEBUGINFOD_URLS"
> else
> unset DEBUGINFOD_URLS
> endif
> - unset prefix
> + set DEBUGINFOD_IMA_CERT_PATH=`sh -c 'cat /dev/null "$0"/*.certpath 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ':'`
> + if ( "$DEBUGINFOD_IMA_CERT_PATH" != "" ) then
> + setenv DEBUGINFOD_IMA_CERT_PATH "$DEBUGINFOD_IMA_CERT_PATH"
> + else
> + unset DEBUGINFOD_IMA_CERT_PATH
> + endif
> endif
> +unset prefix
> diff --git a/config/profile.sh.in b/config/profile.sh.in
> index 84d3260ddcfc..7db399960915 100644
> --- a/config/profile.sh.in
> +++ b/config/profile.sh.in
> @@ -4,9 +4,15 @@
> # See also [man debuginfod-client-config] for other environment variables
> # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS.
>
> +prefix="@prefix@"
> if [ -z "$DEBUGINFOD_URLS" ]; then
> prefix="@prefix@"
This second definition of prefix can be removed.
> DEBUGINFOD_URLS=$(cat /dev/null "@sysconfdir@/debuginfod"/*.urls 2>/dev/null | tr '\n' ' ' || :)
> [ -n "$DEBUGINFOD_URLS" ] && export DEBUGINFOD_URLS || unset DEBUGINFOD_URLS
> - unset prefix
> fi
> +
> +if [ -z "$DEBUGINFOD_IMA_CERT_PATH" ]; then
> + DEBUGINFOD_IMA_CERT_PATH=$(cat "@sysconfdir@/debuginfod"/*.certpath 2>/dev/null | tr '\n' ':' || :)
> + [ -n "$DEBUGINFOD_IMA_CERT_PATH" ] && export DEBUGINFOD_IMA_CERT_PATH || unset DEBUGINFOD_IMA_CERT_PATH
> +fi
> +unset prefix
> diff --git a/configure.ac b/configure.ac
> index a279bb5282c9..d75d9ba02e79 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -667,6 +667,33 @@ case "$ac_cv_search__obstack_free" in
> esac
> AC_SUBST([obstack_LIBS])
>
> +enable_ima_verification="x"
> +AC_CHECK_LIB(rpm, headerGet, [
> + AC_CHECK_DECL(RPMSIGTAG_FILESIGNATURES,
> + [
> + enable_ima_verification=$enable_ima_verification"rpm"
> + AC_SUBST(rpm_LIBS, '-lrpm -lrpmio')
> + ],
> + [], [#include <rpm/rpmlib.h>])
> +])
> +
> +dnl we use only the header, not the code of this library
> +AC_CHECK_HEADER(imaevm.h, [
> + enable_ima_verification=$enable_ima_verification"imaevm"
> +])
> +
> +AC_CHECK_LIB(crypto, EVP_MD_CTX_new, [
> + enable_ima_verification=$enable_ima_verification"crypto"
> + AC_SUBST(crypto_LIBS, '-lcrypto')
> +])
> +
> +debuginfod_ima_verification_enabled="no"
> +if test "$enable_ima_verification" = "xrpmimaevmcrypto"; then
> + debuginfod_ima_verification_enabled="yes"
> + AC_DEFINE([ENABLE_IMA_VERIFICATION], [1], [Define if the required ima verification libraries are available])
> +fi
> +AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification" = "xrpmimaevmcrypto"])
> +
> dnl The directories with content.
>
> dnl Documentation.
> @@ -881,6 +908,15 @@ AC_ARG_ENABLE(debuginfod-urls,
> fi],
> [default_debuginfod_urls=""])
> AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls)
> +AC_ARG_ENABLE(debuginfod-ima-cert-path,
> + [AS_HELP_STRING([--enable-debuginfod-ima-cert-path@<:@=PATH@:>@],[add PATH to profile.d DEBUGINFOD_IMA_CERT_PATH])],
> + [if test "x${enableval}" = "xyes";
> + then AC_MSG_ERROR([PATH required])
> + elif test "x${enableval}" != "xno"; then
> + default_debuginfod_ima_cert_path="${enableval}";
> + fi],
> + [default_debuginfod_ima_cert_path=""])
> +AC_SUBST(DEBUGINFOD_IMA_CERT_PATH, $default_debuginfod_ima_cert_path)
It might be helpful to add AC_ARG_ENABLE for debuginfod_ima_verification.
Then a configure error can alert us to any missing libraries if
--enable-debuginfod-ima-verification=yes.
> AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fish])
>
> AC_OUTPUT
> @@ -920,6 +956,7 @@ AC_MSG_NOTICE([
> libdebuginfod client support : ${enable_libdebuginfod}
> Debuginfod server support : ${enable_debuginfod}
> Default DEBUGINFOD_URLS : ${default_debuginfod_urls}
> + Debuginfod RPM sig checking : ${debuginfod_ima_verification_enabled} ${default_debuginfod_ima_cert_path}
>
> EXTRA TEST FEATURES (used with make check)
> have bunzip2 installed (required) : ${HAVE_BUNZIP2}
> diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am
> index 125be97bbfcc..5e4f9669d7c1 100644
> --- a/debuginfod/Makefile.am
> +++ b/debuginfod/Makefile.am
> @@ -70,7 +70,7 @@ bin_PROGRAMS += debuginfod-find
> endif
>
> debuginfod_SOURCES = debuginfod.cxx
> -debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) -lpthread -ldl
> +debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) $(rpm_LIBS) -lpthread -ldl
>
> debuginfod_find_SOURCES = debuginfod-find.c
> debuginfod_find_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS)
> @@ -97,7 +97,7 @@ libdebuginfod_so_LIBS = libdebuginfod_pic.a
> if DUMMY_LIBDEBUGINFOD
> libdebuginfod_so_LDLIBS =
> else
> -libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf)
> +libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) $(crypto_LIBS)
> endif
> $(LIBDEBUGINFOD_SONAME): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
> $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
> @@ -117,7 +117,6 @@ install: install-am libdebuginfod.so
> $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
> ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/$(LIBDEBUGINFOD_SONAME)
> ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so
> -
> uninstall: uninstall-am
> rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
> rm -f $(DESTDIR)$(libdir)/$(LIBDEBUGINFOD_SONAME)
> diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
> index 4e7a8a2ad9ff..4dc6b4411eb2 100644
> --- a/debuginfod/debuginfod-client.c
> +++ b/debuginfod/debuginfod-client.c
> @@ -1,5 +1,5 @@
> /* Retrieve ELF / DWARF / source files from the debuginfod.
> - Copyright (C) 2019-2021 Red Hat, Inc.
> + Copyright (C) 2019-2024 Red Hat, Inc.
> Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
> This file is part of elfutils.
>
> @@ -47,6 +47,17 @@
> #include <stdlib.h>
> #include <gelf.h>
>
> +#ifdef ENABLE_IMA_VERIFICATION
> +#include <openssl/sha.h>
> +#include <openssl/pem.h>
> +#include <openssl/evp.h>
> +#include <openssl/x509v3.h>
> +#include <arpa/inet.h>
> +#include <imaevm.h>
> +#endif
> +typedef enum {ignore, enforcing, undefined} ima_policy_t;
> +
> +
> /* We might be building a bootstrap dummy library, which is really simple. */
> #ifdef DUMMY_LIBDEBUGINFOD
>
> @@ -92,6 +103,7 @@ void debuginfod_end (debuginfod_client *c) { }
> #include <sys/stat.h>
> #include <sys/utsname.h>
> #include <curl/curl.h>
> +#include <fnmatch.h>
>
> /* If fts.h is included before config.h, its indirect inclusions may not
> give us the right LFS aliases of these functions, so map them manually. */
> @@ -130,6 +142,17 @@ libcurl_init(void)
> }
> }
>
> +
> +#ifdef ENABLE_IMA_VERIFICATION
> +struct public_key_entry
> +{
> + struct public_key_entry *next; /* singly-linked list */
> + uint32_t keyid; /* last 4 bytes of sha1 of public key */
> + EVP_PKEY *key; /* openssl */
> +};
> +#endif
> +
> +
> struct debuginfod_client
> {
> /* Progress/interrupt callback function. */
> @@ -164,8 +187,14 @@ struct debuginfod_client
> handle data, etc. So those don't have to be reparsed and
> recreated on each request. */
> char * winning_headers;
> +
> +#ifdef ENABLE_IMA_VERIFICATION
> + /* IMA public keys */
> + struct public_key_entry *ima_public_keys;
> +#endif
> };
>
> +
> /* The cache_clean_interval_s file within the debuginfod cache specifies
> how frequently the cache should be cleaned. The file's st_mtime represents
> the time of last cleaning. */
> @@ -225,6 +254,182 @@ struct handle_data
> size_t response_data_size;
> };
>
> +
> +
> +#ifdef ENABLE_IMA_VERIFICATION
> + static inline unsigned char hex2dec(char c)
> + {
> + if (c >= '0' && c <= '9') return (c - '0');
> + if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
> + if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
> + return 0;
> + }
> +
> + static inline ima_policy_t ima_policy_str2enum(const char* ima_pol)
> + {
> + if (NULL == ima_pol) return undefined;
> + if (0 == strcmp(ima_pol, "ignore")) return ignore;
> + if (0 == strcmp(ima_pol, "enforcing")) return enforcing;
> + return undefined;
> + }
> +
> + static inline const char* ima_policy_enum2str(ima_policy_t ima_pol)
> + {
> + switch (ima_pol)
> + {
> + case ignore:
> + return "ignore";
> + case enforcing:
> + return "enforcing";
> + case undefined:
> + return "undefined";
> + }
> + return "";
> + }
> +
> +
> +static uint32_t extract_skid_pk(EVP_PKEY *pkey) // compute keyid by public key hashing
> +{
> + if (!pkey) return 0;
> + uint32_t keyid = 0;
> + X509_PUBKEY *pk = NULL;
> + const unsigned char *public_key = NULL;
> + int len;
> + if (X509_PUBKEY_set(&pk, pkey) &&
> + X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk))
> + {
> + uint8_t sha1[SHA_DIGEST_LENGTH];
> + SHA1(public_key, len, sha1);
> + memcpy(&keyid, sha1 + 16, 4);
> + }
> + X509_PUBKEY_free(pk);
> + return ntohl(keyid);
> +}
> +
> +
> +static uint32_t extract_skid(X509* x509) // compute keyid from cert or its public key
> + {
> + if (!x509) return 0;
> + uint32_t keyid = 0;
> + // Attempt to get the skid from the certificate
> + const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509);
> + if (skid_asn1_str)
> + {
> + int skid_len = ASN1_STRING_length(skid_asn1_str);
> + memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid));
> + }
> + else // compute keyid ourselves by hashing public key
> + {
> + EVP_PKEY *pkey = X509_get0_pubkey(x509);
> + keyid = htonl(extract_skid_pk(pkey));
> + }
> + return ntohl(keyid);
> + }
> +
> +
> +static void load_ima_public_keys (debuginfod_client *c)
> +{
> + /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. */
> + char *cert_paths = getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR);
> + if (cert_paths == NULL || cert_paths[0] == '\0')
> + return;
> + cert_paths = strdup(cert_paths); // Modified during tokenization
> + if (cert_paths == NULL)
> + return;
> +
> + char* cert_dir_path;
> + DIR *dp;
> + struct dirent *entry;
> + int vfd = c->verbose_fd;
> +
> + char *strtok_context = NULL;
> + for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context);
> + cert_dir_path != NULL;
> + cert_dir_path = strtok_r(NULL, ":", &strtok_context))
> + {
> + dp = opendir(cert_dir_path);
> + if(!dp) continue;
> + while((entry = readdir(dp)))
> + {
> + // Only consider regular files with common x509 cert extensions
> + if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer|cert)", entry->d_name, FNM_EXTMATCH)) continue;
> + char certfile[PATH_MAX];
> + strncpy(certfile, cert_dir_path, PATH_MAX - 1);
> + if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/';
> + strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1);
> + certfile[strlen(certfile)] = '\0';
> +
> + FILE *cert_fp = fopen(certfile, "r");
> + if(!cert_fp) continue;
> +
> + X509 *x509 = NULL;
> + EVP_PKEY *pkey = NULL;
> + char *fmt = "";
> + // Attempt to read the fp as DER
> + if(d2i_X509_fp(cert_fp, &x509))
> + fmt = "der ";
> + // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used
> + // Note we fseek since this is the second time we read from the fp
> + else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL))
> + fmt = "pem "; // PEM with full certificate
> + else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKEY(cert_fp, &pkey, NULL, NULL))
> + fmt = "pem "; // some PEM files have just a PUBLIC KEY in them
> + fclose(cert_fp);
> +
> + if (x509)
> + {
> + struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
> + if (ne)
> + {
> + ne->key = X509_extract_key(x509);
> + ne->keyid = extract_skid(x509);
> + ne->next = c->ima_public_keys;
> + c->ima_public_keys = ne;
> + if (vfd >= 0)
> + dprintf(vfd, "Loaded %scertificate %s, keyid = %04x\n", fmt, certfile, ne->keyid);
> + }
> + X509_free (x509);
> + }
> + else if (pkey)
> + {
> + struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
> + if (ne)
> + {
> + ne->key = pkey; // preserve refcount
> + ne->keyid = extract_skid_pk(pkey);
> + ne->next = c->ima_public_keys;
> + c->ima_public_keys = ne;
> + if (vfd >= 0)
> + dprintf(vfd, "Loaded %spubkey %s, keyid %04x\n", fmt, certfile, ne->keyid);
> + }
> + }
> + else
> + {
> + if (vfd >= 0)
> + dprintf(vfd, "Cannot load certificate %s\n", certfile);
> + }
> + } /* for each file in directory */
> + closedir(dp);
> + } /* for each directory */
> +
> + free(cert_paths);
> +}
> +
> +
> +static void free_ima_public_keys (debuginfod_client *c)
> +{
> + while (c->ima_public_keys)
> + {
> + EVP_PKEY_free (c->ima_public_keys->key);
> + struct public_key_entry *oen = c->ima_public_keys->next;
> + free (c->ima_public_keys);
> + c->ima_public_keys = oen;
> + }
> +}
> +#endif
> +
> +
> +
> static size_t
> debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
> {
> @@ -861,6 +1066,199 @@ cache_find_section (const char *scn_name, const char *target_cache_dir,
> return rc;
> }
>
> +
> +#ifdef ENABLE_IMA_VERIFICATION
> +/* Extract the hash algorithm name from the signature header, of which
> + there are several types. The name will be used for openssl hashing
> + of the file content. The header doesn't need to be super carefully
> + parsed, because if any part of it is wrong, be it the hash
> + algorithm number or hash value or whatever, it will fail
> + computation or verification. Return NULL in case of error. */
> +static const char*
> +get_signature_params(debuginfod_client *c, unsigned char *bin_sig)
> +{
> + int hashalgo = 0;
> +
> + switch (bin_sig[0])
> + {
> + case EVM_IMA_XATTR_DIGSIG:
> +#ifdef IMA_VERITY_DIGSIG /* missing on debian-i386 trybot */
> + case IMA_VERITY_DIGSIG:
> +#endif
> + break;
> + default:
> + if (c->verbose_fd >= 0)
> + dprintf (c->verbose_fd, "Unknown ima digsig %d\n", (int)bin_sig[0]);
> + return NULL;
> + }
> +
> + switch (bin_sig[1])
> + {
> + case DIGSIG_VERSION_2:
> + struct signature_v2_hdr hdr_v2;
> + memcpy(& hdr_v2, & bin_sig[1], sizeof(struct signature_v2_hdr));
> + hashalgo = hdr_v2.hash_algo;
> + break;
> + default:
> + if (c->verbose_fd >= 0)
> + dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (int)bin_sig[1]);
> + return NULL;
> + }
> +
> + switch (hashalgo)
> + {
> + case PKEY_HASH_SHA1: return "sha1";
> + case PKEY_HASH_SHA256: return "sha256";
> + // (could add many others from enum pkey_hash_algo)
> + default:
> + if (c->verbose_fd >= 0)
> + dprintf (c->verbose_fd, "Unknown ima pkey hash %d\n", hashalgo);
> + return NULL;
> + }
> +}
> +
> +
> +/* Verify given hash against given signature blob. Return 0 on ok, -errno otherwise. */
> +static int
> +debuginfod_verify_hash(debuginfod_client *c, const unsigned char *hash, int size,
> + const char *hash_algo, unsigned char *sig, int siglen)
> +{
> + int ret = -EBADMSG;
> + struct public_key_entry *pkey;
> + struct signature_v2_hdr hdr;
> + EVP_PKEY_CTX *ctx;
> + const EVP_MD *md;
> +
> + memcpy(&hdr, sig, sizeof(struct signature_v2_hdr)); /* avoid just aliasing */
> +
> + if (c->verbose_fd >= 0)
> + dprintf (c->verbose_fd, "Searching for ima keyid %04x\n", ntohl(hdr.keyid));
> +
> + /* Find the matching public key. */
> + for (pkey = c->ima_public_keys; pkey != NULL; pkey = pkey->next)
> + if (pkey->keyid == ntohl(hdr.keyid)) break;
> + if (!pkey)
> + return -ENOKEY;
> +
> + if (!(ctx = EVP_PKEY_CTX_new(pkey->key, NULL)))
> + goto err;
> + if (!EVP_PKEY_verify_init(ctx))
> + goto err;
> + if (!(md = EVP_get_digestbyname(hash_algo)))
> + goto err;
> + if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
> + goto err;
> + ret = EVP_PKEY_verify(ctx, sig + sizeof(hdr),
> + siglen - sizeof(hdr), hash, size);
> + if (ret == 1)
> + ret = 0; // success!
> + else if (ret == 0)
> + ret = -EBADMSG;
> + err:
> + EVP_PKEY_CTX_free(ctx);
> + return ret;
> +}
> +
> +
> +
> +/* Validate an IMA file signature.
> + * Returns 0 on signature validity, -EINVAL on signature invalidity, -ENOSYS on undefined imaevm machinery,
> + * -ENOKEY on key issues, or other -errno.
> + */
> +
> +static int
> +debuginfod_validate_imasig (debuginfod_client *c, int fd)
> +{
> + int rc = ENOSYS;
> +
> + // int vfd = c->verbose_fd;
This line can be removed.
> + EVP_MD_CTX *ctx = NULL;
> + if (!c || !c->winning_headers)
> + {
> + rc = -ENODATA;
> + goto exit_validate;
> + }
> + // Extract the HEX IMA-signature from the header
> + char* sig_buf = NULL;
> + char* hdr_ima_sig = strcasestr(c->winning_headers, "x-debuginfod-imasignature");
> + if (!hdr_ima_sig || 1 != sscanf(hdr_ima_sig + strlen("x-debuginfod-imasignature:"), "%ms", &sig_buf))
> + {
> + rc = -ENODATA;
> + goto exit_validate;
> + }
> + if (strlen(sig_buf) > MAX_SIGNATURE_SIZE) // reject if too long
> + {
> + rc = -EBADMSG;
> + goto exit_validate;
> + }
> + // Convert the hex signature to bin
> + size_t bin_sig_len = strlen(sig_buf)/2;
> + unsigned char bin_sig[MAX_SIGNATURE_SIZE/2];
> + for (size_t b = 0; b < bin_sig_len; b++)
> + bin_sig[b] = (hex2dec(sig_buf[2*b]) << 4) | hex2dec(sig_buf[2*b+1]);
> +
> + // Compute the binary digest of the cached file (with file descriptor fd)
> + ctx = EVP_MD_CTX_new();
> + const char* sighash_name = get_signature_params(c, bin_sig) ?: "";
> + const EVP_MD *md = EVP_get_digestbyname(sighash_name);
> + if (!ctx || !md || !EVP_DigestInit(ctx, md))
> + {
> + rc = -EBADMSG;
> + goto exit_validate;
> + }
> +
> + long data_len;
> + char* hdr_data_len = strcasestr(c->winning_headers, "x-debuginfod-size");
> + if (!hdr_data_len || 1 != sscanf(hdr_data_len + strlen("x-debuginfod-size:") , "%ld", &data_len))
> + {
> + rc = -ENODATA;
> + goto exit_validate;
> + }
> +
> + char file_data[DATA_SIZE]; // imaevm.h data chunk hash size
> + ssize_t n;
> + for(off_t k = 0; k < data_len; k += n)
> + {
> + if (-1 == (n = pread(fd, file_data, DATA_SIZE, k)))
> + {
> + rc = -errno;
> + goto exit_validate;
> + }
> +
> + if (!EVP_DigestUpdate(ctx, file_data, n))
> + {
> + rc = -EBADMSG;
> + goto exit_validate;
> + }
> + }
> +
> + uint8_t bin_dig[MAX_DIGEST_SIZE];
> + unsigned int bin_dig_len;
> + if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len))
> + {
> + rc = -EBADMSG;
> + goto exit_validate;
> + }
> +
> + // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo dawg
> +
> + int res = debuginfod_verify_hash(c,
> + bin_dig, bin_dig_len,
> + sighash_name,
> + & bin_sig[1], bin_sig_len-1); // skip over first byte of signature
> + if (c->verbose_fd >= 0)
> + dprintf (c->verbose_fd, "Computed ima signature verification res=%d\n", res);
> + rc = res;
> +
> + exit_validate:
> + free (sig_buf);
> + EVP_MD_CTX_free(ctx);
> + return rc;
> +}
> +#endif /* ENABLE_IMA_VERIFICATION */
> +
> +
> +
> /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
> with the specified build-id and type (debuginfo, executable, source or
> section). If type is source, then type_arg should be a filename. If
> @@ -1216,12 +1614,39 @@ debuginfod_query_server (debuginfod_client *c,
> /* Initialize the memory to zero */
> char *strtok_saveptr;
> char **server_url_list = NULL;
> - char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
> + ima_policy_t* url_ima_policies = NULL;
> + char* server_url;
> /* Count number of URLs. */
> int num_urls = 0;
>
> - while (server_url != NULL)
> + ima_policy_t verification_mode = ignore; // The default mode
> + for(server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
> + server_url != NULL; server_url = strtok_r(NULL, url_delim, &strtok_saveptr))
> {
> + // When we encounted a (well-formed) token off the form ima:foo, we update the policy
> + // under which results from that server will be ima verified
> + if(startswith(server_url, "ima:"))
> + {
> +#ifdef ENABLE_IMA_VERIFICATION
> + ima_policy_t m = ima_policy_str2enum(server_url + strlen("ima:"));
> + if(m != undefined)
> + verification_mode = m;
> + else if (vfd >= 0)
> + dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_url);
> +#else
> + if (vfd >= 0)
> + dprintf(vfd, "IMA signature verification is not enabled, skipping %s\n", server_url);
> +#endif
> + continue; // Not a url, just a mode change so keep going
> + }
> +
> + if (verification_mode==enforcing && 0==strcmp(type,"section"))
> + {
> + if (vfd >= 0)
> + dprintf(vfd, "skipping server %s section query in IMA enforcing mode\n", server_url);
> + continue;
> + }
> +
> /* PR 27983: If the url is already set to be used use, skip it */
> char *slashbuildid;
> if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
> @@ -1253,21 +1678,28 @@ debuginfod_query_server (debuginfod_client *c,
> else
> {
> num_urls++;
> - char ** realloc_ptr;
> - realloc_ptr = reallocarray(server_url_list, num_urls,
> - sizeof(char*));
> - if (realloc_ptr == NULL)
> + if (NULL == (server_url_list = reallocarray(server_url_list, num_urls, sizeof(char*)))
> +#ifdef ENABLE_IMA_VERIFICATION
> + || NULL == (url_ima_policies = reallocarray(url_ima_policies, num_urls, sizeof(ima_policy_t)))
> +#endif
> + )
> {
> free (tmp_url);
> rc = -ENOMEM;
> goto out1;
> }
> - server_url_list = realloc_ptr;
> server_url_list[num_urls-1] = tmp_url;
> + if(NULL != url_ima_policies) url_ima_policies[num_urls-1] = verification_mode;
> }
> - server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
> }
>
> + /* No URLs survived parsing / filtering? Abort abort abort. */
> + if (num_urls == 0)
> + {
> + rc = -ENOSYS;
> + goto out1;
> + }
> +
> int retry_limit = default_retry_limit;
> const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR);
> if (retry_limit_envvar != NULL)
> @@ -1334,7 +1766,11 @@ debuginfod_query_server (debuginfod_client *c,
> if ((server_url = server_url_list[i]) == NULL)
> break;
> if (vfd >= 0)
> - dprintf (vfd, "init server %d %s\n", i, server_url);
> +#ifdef ENABLE_IMA_VERIFICATION
> + dprintf (vfd, "init server %d %s [IMA verification policy: %s]\n", i, server_url, ima_policy_enum2str(url_ima_policies[i]));
> +#else
> + dprintf (vfd, "init server %d %s\n", i, server_url);
> +#endif
>
> data[i].fd = fd;
> data[i].target_handle = &target_handle;
> @@ -1784,6 +2220,29 @@ debuginfod_query_server (debuginfod_client *c,
> /* PR31248: lseek back to beginning */
> (void) lseek(fd, 0, SEEK_SET);
>
> + if(NULL != url_ima_policies && ignore != url_ima_policies[committed_to])
> + {
> +#ifdef ENABLE_IMA_VERIFICATION
> + int result = debuginfod_validate_imasig(c, fd);
> +#else
> + int result = -ENOSYS;
> +#endif
> + if(0 == result)
> + {
> + if (vfd >= 0) dprintf (vfd, "valid signature\n");
> + }
> + else if (enforcing == url_ima_policies[committed_to])
> + {
> + // All invalid signatures are rejected.
> + // Additionally in enforcing mode any non-valid signature is rejected, so by reaching
> + // this case we do so since we know it is not valid. Note - this not just invalid signatures
> + // but also signatures that cannot be validated
> + if (vfd >= 0) dprintf (vfd, "error: invalid or missing signature (%d)\n", result);
> + rc = result;
> + goto out2;
> + }
> + }
> +
> /* rename tmp->real */
> rc = rename (target_cache_tmppath, target_cache_path);
> if (rc < 0)
> @@ -1804,6 +2263,7 @@ debuginfod_query_server (debuginfod_client *c,
> for (int i = 0; i < num_urls; ++i)
> free(server_url_list[i]);
> free(server_url_list);
> + free(url_ima_policies);
> free (data);
> free (server_urls);
>
> @@ -1837,6 +2297,7 @@ debuginfod_query_server (debuginfod_client *c,
> for (int i = 0; i < num_urls; ++i)
> free(server_url_list[i]);
> free(server_url_list);
> + free(url_ima_policies);
>
> out0:
> free (server_urls);
> @@ -1869,7 +2330,11 @@ debuginfod_query_server (debuginfod_client *c,
> free (cache_miss_path);
> free (target_cache_dir);
> free (target_cache_path);
> + if (rc < 0 && target_cache_tmppath != NULL)
> + (void)unlink (target_cache_tmppath);
> free (target_cache_tmppath);
> +
> +
> return rc;
> }
>
> @@ -1901,6 +2366,10 @@ debuginfod_begin (void)
> goto out1;
> }
>
> +#ifdef ENABLE_IMA_VERIFICATION
> + load_ima_public_keys (client);
> +#endif
> +
> // extra future initialization
>
> goto out;
> @@ -1948,6 +2417,9 @@ debuginfod_end (debuginfod_client *client)
> curl_slist_free_all (client->headers);
> free (client->winning_headers);
> free (client->url);
> +#ifdef ENABLE_IMA_VERIFICATION
> + free_ima_public_keys (client);
> +#endif
> free (client);
> }
>
> @@ -1987,9 +2459,11 @@ debuginfod_find_section (debuginfod_client *client,
> {
> int rc = debuginfod_query_server(client, build_id, build_id_len,
> "section", section, path);
> - if (rc != -EINVAL)
> + if (rc != -EINVAL && rc != -ENOSYS)
> return rc;
> -
> + /* NB: we fall through in case of ima:enforcing-filtered DEBUGINFOD_URLS servers,
> + so we can download the entire file, verify it locally, then slice it. */
> +
> /* The servers may have lacked support for section queries. Attempt to
> download the debuginfo or executable containing the section in order
> to extract it. */
> diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
> index ece5031f02f9..d9259ad26bb8 100644
> --- a/debuginfod/debuginfod.cxx
> +++ b/debuginfod/debuginfod.cxx
> @@ -122,6 +122,13 @@ using namespace std;
> #define MHD_RESULT int
> #endif
>
> +#ifdef ENABLE_IMA_VERIFICATION
> + #include <rpm/rpmlib.h>
> + #include <rpm/rpmfi.h>
> + #include <rpm/header.h>
> + #include <glob.h>
> +#endif
> +
> #include <curl/curl.h>
> #include <archive.h>
> #include <archive_entry.h>
> @@ -443,6 +450,10 @@ static const struct argp_option options[] =
> { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do not scan dwarf source info.", 0 },
> #define ARGP_SCAN_CHECKPOINT 0x100A
> { "scan-checkpoint", ARGP_SCAN_CHECKPOINT, "NUM", 0, "Number of files scanned before a WAL checkpoint.", 0 },
> +#ifdef ENABLE_IMA_VERIFICATION
> +#define ARGP_KEY_KOJI_SIGCACHE 0x100B
> + { "koji-sigcache", ARGP_KEY_KOJI_SIGCACHE, NULL, 0, "Do a koji specific mapping of rpm paths to get IMA signatures.", 0 },
> +#endif
> { NULL, 0, NULL, 0, NULL, 0 },
> };
>
> @@ -495,6 +506,9 @@ static bool scan_source_info = true;
> static string tmpdir;
> static bool passive_p = false;
> static long scan_checkpoint = 256;
> +#ifdef ENABLE_IMA_VERIFICATION
> +static bool requires_koji_sigcache_mapping = false;
> +#endif
>
> static void set_metric(const string& key, double value);
> static void inc_metric(const string& key);
> @@ -699,6 +713,11 @@ parse_opt (int key, char *arg,
> if (scan_checkpoint < 0)
> argp_failure(state, 1, EINVAL, "scan checkpoint");
> break;
> +#ifdef ENABLE_IMA_VERIFICATION
> + case ARGP_KEY_KOJI_SIGCACHE:
> + requires_koji_sigcache_mapping = true;
> + break;
> +#endif
> // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
> default: return ARGP_ERR_UNKNOWN;
> }
> @@ -1959,6 +1978,145 @@ handle_buildid_r_match (bool internal_req_p,
> return 0;
> }
>
> + // Extract the IMA per-file signature (if it exists)
> + string ima_sig = "";
> + #ifdef ENABLE_IMA_VERIFICATION
> + do
> + {
> + FD_t rpm_fd;
> + if(!(rpm_fd = Fopen(b_source0.c_str(), "r.ufdio"))) // read, uncompressed, rpm/rpmio.h
> + {
> + if (verbose) obatched(clog) << "There was an error while opening " << b_source0 << endl;
> + break; // Exit IMA extraction
> + }
> +
> + Header rpm_hdr;
> + if(RPMRC_FAIL == rpmReadPackageFile(NULL, rpm_fd, b_source0.c_str(), &rpm_hdr))
> + {
> + if (verbose) obatched(clog) << "There was an error while reading the header of " << b_source0 << endl;
> + Fclose(rpm_fd);
> + break; // Exit IMA extraction
> + }
> +
> + // Fill sig_tag_data with an alloc'd copy of the array of IMA signatures (if they exist)
> + struct rpmtd_s sig_tag_data;
> + rpmtdReset(&sig_tag_data);
> + do{ /* A do-while so we can break out of the koji sigcache checking on failure */
> + if(requires_koji_sigcache_mapping)
> + {
> + /* NB: Koji builds result in a directory structure like the following
> + - PACKAGE/VERSION/RELEASE
> + - ARCH1
> + - foo.rpm // The rpm known by debuginfod
> + - ...
> + - ARCHN
> + - data
> + - signed // Periodically purged (and not scanned by debuginfod)
> + - sigcache
> + - ARCH1
> + - foo.rpm.sig // An empty rpm header
> + - ...
> + - ARCHN
> + - PACKAGE_KEYID1
> + - ARCH1
> + - foo.rpm.sig // The header of the signed rpm. This is the file we need to extract the IMA signatures
> + - ...
> + - ARCHN
> + - ...
> + - PACKAGE_KEYIDn
> +
> + We therefore need to do a mapping:
> +
> + P/V/R/A/N-V-R.A.rpm ->
> + P/V/R/data/sigcache/KEYID/A/N-V-R.A.rpm.sig
> +
> + There are 2 key insights here
> +
> + 1. We need to go 2 directories down from sigcache to get to the
> + rpm header. So to distinguish ARCH1/foo.rpm.sig and
> + PACKAGE_KEYID1/ARCH1/foo.rpm.sig we can look 2 directories down
> +
> + 2. It's safe to assume that the user will have all of the
> + required verification certs. So we can pick from any of the
> + PACKAGE_KEYID* directories. For simplicity we choose first we
> + match against
> +
> + See: https://pagure.io/koji/issue/3670
> + */
> +
> + // Do the mapping from b_source0 to the koji path for the signed rpm header
> + string signed_rpm_path = b_source0;
> + size_t insert_pos = string::npos;
> + for(int i = 0; i < 2; i++) insert_pos = signed_rpm_path.rfind("/", insert_pos) - 1;
> + string globbed_path = signed_rpm_path.insert(insert_pos + 1, "/data/sigcache/*").append(".sig"); // The globbed path we're seeking
> + glob_t pglob;
> + int grc;
> + if(0 != (grc = glob(globbed_path.c_str(), GLOB_NOSORT, NULL, &pglob)))
> + {
> + // Break out, but only report real errors
> + if (verbose && grc != GLOB_NOMATCH) obatched(clog) << "There was an error (" << strerror(errno) << ") globbing " << globbed_path << endl;
> + break; // Exit koji sigcache check
> + }
> + signed_rpm_path = pglob.gl_pathv[0]; // See insight 2 above
> + globfree(&pglob);
> +
> + if (verbose > 2) obatched(clog) << "attempting IMA signature extraction from koji header " << signed_rpm_path << endl;
> +
> + FD_t sig_rpm_fd;
> + if(NULL == (sig_rpm_fd = Fopen(signed_rpm_path.c_str(), "r")))
> + {
> + if (verbose) obatched(clog) << "There was an error while opening " << signed_rpm_path << endl;
> + break; // Exit koji sigcache check
> + }
> +
> + Header sig_hdr = headerRead(sig_rpm_fd, HEADER_MAGIC_YES /* Validate magic too */ );
> + if (!sig_hdr || 1 != headerGet(sig_hdr, RPMSIGTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC))
> + {
> + if (verbose) obatched(clog) << "Unable to extract RPMSIGTAG_FILESIGNATURES from " << signed_rpm_path << endl;
> + }
> + headerFree(sig_hdr); // We can free here since sig_tag_data has an alloc'd copy of the data
> + Fclose(sig_rpm_fd);
> + }
> + }while(false);
> +
> + if(0 == sig_tag_data.count)
> + {
> + // In the general case (or a fallback from the koji sigcache mapping not finding signatures)
> + // we can just (try) extract the signatures from the rpm header
> + if (1 != headerGet(rpm_hdr, RPMTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC))
> + {
> + if (verbose) obatched(clog) << "Unable to extract RPMTAG_FILESIGNATURES from " << b_source0 << endl;
> + }
> + }
> + // Search the array for the signature coresponding to b_source1
> + int idx = -1;
> + char *sig = NULL;
> + rpmfi hdr_fi = rpmfiNew(NULL, rpm_hdr, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY);
> + do
> + {
> + sig = (char*)rpmtdNextString(&sig_tag_data);
> + idx = rpmfiNext(hdr_fi);
> + }
> + while (idx != -1 && 0 != strcmp(b_source1.c_str(), rpmfiFN(hdr_fi)));
> + rpmfiFree(hdr_fi);
> +
> + if(sig && 0 != strlen(sig) && idx != -1)
> + {
> + if (verbose > 2) obatched(clog) << "Found IMA signature for " << b_source1 << ":\n" << sig << endl;
> + ima_sig = sig;
> + inc_metric("http_responses_total","extra","ima-sigs-extracted");
> + }
> + else
> + {
> + if (verbose > 2) obatched(clog) << "Could not find IMA signature for " << b_source1 << endl;
> + }
> +
> + rpmtdFreeData (&sig_tag_data);
> + headerFree(rpm_hdr);
> + Fclose(rpm_fd);
> + } while(false);
> + #endif
> +
> // check for a match in the fdcache first
> int fd = fdcache.lookup(b_source0, b_source1);
> while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end
> @@ -2016,11 +2174,13 @@ handle_buildid_r_match (bool internal_req_p,
> to_string(fs.st_size).c_str());
> add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str());
> add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
> + if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMASIGNATURE", ima_sig.c_str());
> add_mhd_last_modified (r, fs.st_mtime);
> if (verbose > 1)
> obatched(clog) << "serving fdcache archive " << b_source0
> << " file " << b_source1
> - << " section=" << section << endl;
> + << " section=" << section
> + << " IMA signature=" << ima_sig << endl;
> /* libmicrohttpd will close it. */
> if (result_fd)
> *result_fd = fd;
> @@ -2204,11 +2364,13 @@ handle_buildid_r_match (bool internal_req_p,
> to_string(archive_entry_size(e)).c_str());
> add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str());
> add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
> + if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMASIGNATURE", ima_sig.c_str());
> add_mhd_last_modified (r, archive_entry_mtime(e));
> if (verbose > 1)
> obatched(clog) << "serving archive " << b_source0
> << " file " << b_source1
> - << " section=" << section << endl;
> + << " section=" << section
> + << " IMA signature=" << ima_sig << endl;
> /* libmicrohttpd will close it. */
> if (result_fd)
> *result_fd = fd;
> diff --git a/debuginfod/debuginfod.h.in b/debuginfod/debuginfod.h.in
> index 4a256ba9af1f..73f633f0b8e9 100644
> --- a/debuginfod/debuginfod.h.in
> +++ b/debuginfod/debuginfod.h.in
> @@ -39,6 +39,7 @@
> #define DEBUGINFOD_MAXSIZE_ENV_VAR "DEBUGINFOD_MAXSIZE"
> #define DEBUGINFOD_MAXTIME_ENV_VAR "DEBUGINFOD_MAXTIME"
> #define DEBUGINFOD_HEADERS_FILE_ENV_VAR "DEBUGINFOD_HEADERS_FILE"
> +#define DEBUGINFOD_IMA_CERT_PATH_ENV_VAR "DEBUGINFOD_IMA_CERT_PATH"
>
> /* The libdebuginfod soname. */
> #define DEBUGINFOD_SONAME "@LIBDEBUGINFOD_SONAME@"
> diff --git a/doc/debuginfod-client-config.7 b/doc/debuginfod-client-config.7
> index 53d82806d395..f16612084e9b 100644
> --- a/doc/debuginfod-client-config.7
> +++ b/doc/debuginfod-client-config.7
> @@ -27,6 +27,33 @@ debuginfod instances. Alternate URL prefixes are separated by space.
> This environment variable may be set by /etc/profile.d scripts
> reading /etc/debuginfod/*.urls files.
>
> +This environment variable can also contain policy defining tags which
> +dictate the response policy for verifying per-file IMA signatures in
> +RPMs. As the space seperated list is read left to right, upon
> +encountering a tag, subsequent URLs up to the next tag will be handled
> +using that specified policy. All URLs before the first tag will use
> +the default policy, \fIima:ignore\fP. For example:
> +
> +.in +4n
> +.EX
> +DEBUGINFOD_URLS="https://foo.com ima:enforcing https://bar.ca http://localhost:8002/ ima:ignore https://baz.org"
> +.EE
> +.in
> +
> +Where foo.com and baz.org use the default \fIignore\fP policy and
> +bar.ca and localhost use an \fIenforcing\fP policy. The policy tag
> +may be one of the following:
> +.IP
> +\fIima:enforcing\fP Every downloaded file requires a valid signature,
> +fully protecting integrity.
> +.IP
> +\fIima:ignore\fP Skips verification altogether, providing no
> +protection.
> +.IP
> +
> +Alerts of validation failure will be directed as specified
> +in $DEBUGINFOD_VERBOSE.
> +
> .TP
> .B $DEBUGINFOD_CACHE_PATH
> This environment variable governs the location of the cache where
> @@ -82,6 +109,12 @@ outbound HTTP requests, one per line. The header lines shouldn't end with
> CRLF, unless that's the system newline convention. Whitespace-only lines
> are skipped.
>
> +.TP
> +.B $DEBUGINFOD_IMA_CERT_PATH
> +This environment variable contains a list of absolute directory paths
> +holding X.509 certificates for RPM per-file IMA-verification.
> +Alternate paths are separated by colons.
> +
> .SH CACHE
>
> Before each query, the debuginfod client library checks for a need to
> diff --git a/doc/debuginfod-find.1 b/doc/debuginfod-find.1
> index 7d577babeb89..d7db1bfdd838 100644
> --- a/doc/debuginfod-find.1
> +++ b/doc/debuginfod-find.1
> @@ -129,10 +129,18 @@ and printing the http response headers from the server.
>
> .SH "SECURITY"
>
> -debuginfod-find \fBdoes not\fP include any particular security
> -features. It trusts that the binaries returned by the debuginfod(s)
> -are accurate. Therefore, the list of servers should include only
> -trustworthy ones. If accessed across HTTP rather than HTTPS, the
> +If IMA signature(s) are available from the RPMs that contain
> +requested files, then
> +.BR debuginfod
> +will extract those signatures into response headers, and
> +.BR debuginfod-find
> +will perform verification upon the files.
> +Validation policy is controlled via tags inserted into
> +$DEBUGINFOD_URLS. By default,
> +.BR debuginfod-find
> +acts in ignore mode.
> +
> +If accessed across HTTP rather than HTTPS, the
> network should be trustworthy. Authentication information through
> the internal \fIlibcurl\fP library is not currently enabled, except
> for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style.
> diff --git a/doc/debuginfod.8 b/doc/debuginfod.8
> index 42e0fc9fbb34..577f58b6ee2e 100644
> --- a/doc/debuginfod.8
> +++ b/doc/debuginfod.8
> @@ -285,6 +285,14 @@ completed archive or file scans. This may slow down parallel scanning
> phase somewhat, but generate much smaller "-wal" temporary files on
> busy servers. The default is 256. Disabled if 0.
>
> +.TP
> +.B "\-\-koji\-sigcache"
> +Enable an additional step of RPM path mapping when extracting signatures for use
> +in RPM per-file IMA verification on koji repositories. The signatures are retrieved
> +from the Fedora koji sigcache rpm.sig files as opposed to the original RPM header.
> +If a signature cannot be found in the sigcache rpm.sig file, the RPM will be
> +tried as a fallback.
> +
> .TP
> .B "\-v"
> Increase verbosity of logging to the standard error file descriptor.
> @@ -300,8 +308,15 @@ Unknown buildid / request combinations result in HTTP error codes.
> This file service resemblance is intentional, so that an installation
> can take advantage of standard HTTP management infrastructure.
>
> -For most queries, some custom http headers are added to the response,
> -providing additional metadata about the buildid-related response. For example:
> +Upon finding a file in an archive or simply in the database, some
> +custom http headers are added to the response. For files in the
> +database X-DEBUGINFOD-FILE and X-DEBUGINFOD-SIZE are added.
> +X-DEBUGINFOD-FILE is simply the unescaped filename and
> +X-DEBUGINFOD-SIZE is the size of the file. For files found in archives,
> +in addition to X-DEBUGINFOD-FILE and X-DEBUGINFOD-SIZE,
> +X-DEBUGINFOD-ARCHIVE is added. X-DEBUGINFOD-ARCHIVE is the name of the
> +archive the file was found in. X-DEBUGINFOD-IMA-SIGNATURE contains the
> +per-file IMA signature as a hexadecimal blob.
>
> .SAMPLE
> % debuginfod-find -v debuginfo /bin/ls |& grep -i x-debuginfo
> diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3
> index 0d553665f42b..4e359c8c4bd4 100644
> --- a/doc/debuginfod_find_debuginfo.3
> +++ b/doc/debuginfod_find_debuginfo.3
> @@ -251,13 +251,21 @@ void *debuginfod_so = dlopen(DEBUGINFOD_SONAME, RTLD_LAZY);
> .in
>
> .SH "SECURITY"
> +
> +If IMA signature(s) are available from the RPMs that contain
> +requested files, then
> +.BR debuginfod
> +will extract those signatures into response headers, and
> +.BR debuginfod_find_* ()
> +will perform verification upon the files.
> +Validation policy is controlled via tags inserted into
> +$DEBUGINFOD_URLS. By default,
> .BR debuginfod_find_* ()
> -functions \fBdo not\fP include any particular security
> -features. They trust that the binaries returned by the debuginfod(s)
> -are accurate. Therefore, the list of servers should include only
> -trustworthy ones. If accessed across HTTP rather than HTTPS, the
> -network should be trustworthy. Passing user authentication information
> -through the internal \fIlibcurl\fP library is not currently enabled, except
> +acts in ignore mode.
> +
> +If accessed across HTTP rather than HTTPS, the
> +network should be trustworthy. Authentication information through
> +the internal \fIlibcurl\fP library is not currently enabled, except
> for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style.
> (The debuginfod server does not perform authentication, but a front-end
> proxy server could.)
> @@ -325,6 +333,18 @@ Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP and
> Query aborted due to the file requested being too big. The
> \fB$DEBUGINFOD_MAXSIZE\fP controls this.
>
> +.TP
> +.BR EBADMSG
> +File content failed IMA verification against a known signer certificate.
> +
> +.TP
> +.BR ENOKEY
> +File content failed IMA verification due to missing signer certificate.
> +
> +.TP
> +.BR ENODATA
> +File content failed IMA verification because of a missing signature.
> +
> .nr zZ 1
> .so man7/debuginfod-client-config.7
>
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index 7aae3d8aa0e5..db071186c533 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -278,6 +278,9 @@ if !OLD_LIBMICROHTTPD
> # Too many open file descriptors confuses libmicrohttpd < 0.9.51
> TESTS += run-debuginfod-federation-metrics.sh
> endif
> +if ENABLE_IMA_VERIFICATION
> +TESTS += run-debuginfod-ima-verification.sh
> +endif
> endif
>
> if HAVE_CXX11
> @@ -600,6 +603,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
> run-debuginfod-webapi-concurrency.sh \
> run-debuginfod-section.sh \
> run-debuginfod-IXr.sh \
> + run-debuginfod-ima-verification.sh \
> debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm \
> debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm \
> debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm \
> @@ -623,6 +627,11 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
> debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm \
> debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \
> debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \
> + debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm \
> + debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig \
> + debuginfod-ima/koji/fedora-38-ima.pem \
> + debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm \
> + debuginfod-ima/rhel9/imacert.der \
> debuginfod-debs/hithere-dbgsym_1.0-1_amd64.ddeb \
> debuginfod-debs/hithere_1.0-1.debian.tar.xz \
> debuginfod-debs/hithere_1.0-1.dsc \
> diff --git a/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm
> new file mode 100644
> index 000000000000..b04ad8c2af39
> Binary files /dev/null and b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm differ
> diff --git a/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig
> new file mode 100644
> index 000000000000..ee7eb8e467b4
> Binary files /dev/null and b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig differ
> diff --git a/tests/debuginfod-ima/koji/fedora-38-ima.pem b/tests/debuginfod-ima/koji/fedora-38-ima.pem
> new file mode 100644
> index 000000000000..e323fa24a6fd
> --- /dev/null
> +++ b/tests/debuginfod-ima/koji/fedora-38-ima.pem
> @@ -0,0 +1,4 @@
> +-----BEGIN PUBLIC KEY-----
> +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj5EVzjUa4PW3I3Y/RTkLgfjP3Elu
> +4AyKdXXxIldW6VVi3QMEpP5eZ7lZmlB2892QFpbWMLNJ4jXlPehMgqNgvg==
> +-----END PUBLIC KEY-----
> diff --git a/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm
> new file mode 100644
> index 000000000000..0262ae2f0c4c
> Binary files /dev/null and b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm differ
> diff --git a/tests/debuginfod-ima/rhel9/imacert.der b/tests/debuginfod-ima/rhel9/imacert.der
> new file mode 100644
> index 000000000000..b0250b6c30d5
> Binary files /dev/null and b/tests/debuginfod-ima/rhel9/imacert.der differ
> diff --git a/tests/run-debuginfod-ima-verification.sh b/tests/run-debuginfod-ima-verification.sh
> new file mode 100755
> index 000000000000..d582af5f6a9d
> --- /dev/null
> +++ b/tests/run-debuginfod-ima-verification.sh
> @@ -0,0 +1,181 @@
> +#!/usr/bin/env bash
> +#
> +# Copyright (C) 2023-2024 Red Hat, Inc.
> +# This file is part of elfutils.
> +#
> +# This file 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; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# elfutils is distributed in the hope that it will be useful, but
> +# WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
> +
> +. $srcdir/debuginfod-subr.sh
> +
> +type rpmsign 2>/dev/null || { echo "need rpmsign"; exit 77; }
> +cat << EoF > include.c
> +#include <rpm/rpmlib.h>
> +#include <rpm/rpmfi.h>
> +#include <rpm/header.h>
> +#include <imaevm.h>
> +#include <openssl/evp.h>
> +EoF
> +tempfiles include.c
> +gcc -H -fsyntax-only include.c 2> /dev/null || { echo "one or more devel packages are missing (rpm-devel, ima-evm-utils-devel, openssl-devel)"; exit 77; }
> +
> +set -x
> +export DEBUGINFOD_VERBOSE=1
> +
> +DB=${PWD}/.debuginfod_tmp.sqlite
> +tempfiles $DB
> +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache
> +IMA_POLICY="enforcing"
> +
> +# This variable is essential and ensures no time-race for claiming ports occurs
> +# set base to a unique multiple of 100 not used in any other 'run-debuginfod-*' test
> +base=14000
> +get_ports
> +mkdir R
> +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \
> + -d $DB -p $PORT1 -t0 -g0 R > vlog$PORT1 2>&1 &
> +PID1=$!
> +tempfiles vlog$PORT1
> +errfiles vlog$PORT1
> +
> +########################################################################
> +cp -pv ${abs_srcdir}/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm signed.rpm
> +tempfiles signed.rpm
> +RPM_BUILDID=460912dbc989106ec7325d243384df20c5ccec0c # /usr/local/bin/hello
> +
> +MIN_IMAEVM_MAJ_VERSION=3
> +MIN_RPM_MAJ_VERSION=4
> +# If the correct programs (and versions) exist sign the rpm in the test
> +if false && \
> + (command -v openssl &> /dev/null) && \
> + (command -v rpmsign &> /dev/null) && \
> + (command -v gpg &> /dev/null) && \
> + [ $(ldd `which rpmsign` | grep libimaevm | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_IMAEVM_MAJ_VERSION ] && \
> + [ $(rpm --version | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_RPM_MAJ_VERSION ]
> +then
> + # SIGN THE RPM
> + # First remove any old signatures
> + rpmsign --delsign signed.rpm &> /dev/null
> + rpmsign --delfilesign signed.rpm &> /dev/null
> +
> + # Make a gpg keypair (with $PWD as the homedir)
> + mkdir -m 700 openpgp-revocs.d private-keys-v1.d
> + gpg --quick-gen-key --yes --homedir ${PWD} --batch --passphrase '' --no-default-keyring --keyring "${PWD}/pubring.kbx" example@elfutils.org 2> /dev/null
> +
> + # Create a private DER signing key and a public X509 DER format verification key pair
> + openssl genrsa | openssl pkcs8 -topk8 -nocrypt -outform PEM -out signing.pem
> + openssl req -x509 -key signing.pem -out imacert.pem -days 365 -keyform PEM \
> + -subj "/C=CA/ST=ON/L=TO/O=Elfutils/CN=www.sourceware.org\/elfutils"
> +
> + tempfiles openpgp-revocs.d/* private-keys-v1.d/* * openpgp-revocs.d private-keys-v1.d
> +
> + rpmsign --addsign --signfiles --fskpath=signing.pem -D "_gpg_name example@elfutils.org" -D "_gpg_path ${PWD}" signed.rpm
> + cp signed.rpm R/signed.rpm
> + VERIFICATION_CERT_DIR=${PWD}
> +
> + # Cleanup
> + rm -rf openpgp-revocs.d private-keys-v1.d
> +else
> + # USE A PRESIGNED RPM
> + cp signed.rpm R/signed.rpm
> + # Note we test with no trailing /
> + VERIFICATION_CERT_DIR=${abs_srcdir}/debuginfod-ima/rhel9
> +fi
> +
> +########################################################################
> +# Server must become ready with R fully scanned and indexed
> +wait_ready $PORT1 'ready' 1
> +wait_ready $PORT1 'thread_work_total{role="traverse"}' 1
> +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0
> +wait_ready $PORT1 'thread_busy{role="scan"}' 0
> +
> +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT1"
> +
> +echo Test 1: Without a certificate the verification should fail
> +export DEBUGINFOD_IMA_CERT_PATH=
> +RC=0
> +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID || RC=1
> +test $RC -ne 0
> +
> +echo Test 2: It should pass once the certificate is added to the path
> +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR
> +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests
> +kill -USR1 $PID1
> +wait_ready $PORT1 'thread_work_total{role="traverse"}' 2
> +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0
> +wait_ready $PORT1 'thread_busy{role="scan"}' 0
> +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID
> +
> +echo Test 3: Corrupt the data and it should fail
> +dd if=/dev/zero of=R/signed.rpm bs=1 count=128 seek=1024 conv=notrunc
> +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests
> +kill -USR1 $PID1
> +wait_ready $PORT1 'thread_work_total{role="traverse"}' 3
> +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0
> +wait_ready $PORT1 'thread_busy{role="scan"}' 0
> +RC=0
> +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1
> +test $RC -ne 0
> +
> +echo Test 4: A rpm without a signature will fail
> +cp signed.rpm R/signed.rpm
> +rpmsign --delfilesign R/signed.rpm
> +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests
> +kill -USR1 $PID1
> +wait_ready $PORT1 'thread_work_total{role="traverse"}' 4
> +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0
> +wait_ready $PORT1 'thread_busy{role="scan"}' 0
> +RC=0
> +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1
> +test $RC -ne 0
> +
> +echo Test 5: Only tests 1,2 will result in extracted signature
> +[[ $(curl -s http://127.0.0.1:$PORT1/metrics | grep 'http_responses_total{extra="ima-sigs-extracted"}' | awk '{print $NF}') -eq 2 ]]
> +
> +kill $PID1
> +wait $PID1
> +PID1=0
> +
> +#######################################################################
> +# We also test the --koji-sigcache
> +cp -pR ${abs_srcdir}/debuginfod-ima/koji R/koji
> +
> +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests
> +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \
> + -d $DB -p $PORT2 -t0 -g0 -X /data/ --koji-sigcache R/koji > vlog$PORT1 2>&1 &
> +#reuse PID1
> +PID1=$!
> +tempfiles vlog$PORT2
> +errfiles vlog$PORT2
> +
> +RPM_BUILDID=c592a95e45625d7891b90f6b86e63373d540461d #/usr/bin/hello
> +# Note we test with a trailing slash
> +VERIFICATION_CERT_DIR=/not/a/dir:${abs_srcdir}/debuginfod-ima/koji/
> +
> +########################################################################
> +# Server must become ready with koji fully scanned and indexed
> +wait_ready $PORT2 'ready' 1
> +wait_ready $PORT2 'thread_work_total{role="traverse"}' 1
> +wait_ready $PORT2 'thread_work_pending{role="scan"}' 0
> +wait_ready $PORT2 'thread_busy{role="scan"}' 0
> +
> +echo Test 6: The path should be properly mapped and verified using the actual fedora 38 cert
> +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT2"
> +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR
> +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID
> +
> +kill $PID1
> +wait $PID1
> +PID1=0
> +
> +exit 0
>
Aaron
[-- Attachment #2: fish-ima.diff --]
[-- Type: text/x-patch, Size: 1172 bytes --]
diff --git a/config/profile.fish.in b/config/profile.fish.in
index c0a234db..12b5e6f1 100644
--- a/config/profile.fish.in
+++ b/config/profile.fish.in
@@ -4,12 +4,21 @@
# See also [man debuginfod-client-config] for other environment variables
# such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS.
+# Use local variables so we don't need to manually unset them
+set --local prefix "@prefix@"
+
if not set --query DEBUGINFOD_URLS
- # Use local variables so we don't need to manually unset them
- set --local prefix "@prefix@"
set --local files "@sysconfdir@/debuginfod/"*.urls
set --local DEBUGINFOD_URLS (cat /dev/null $files 2>/dev/null | string replace '\n' ' ')
if test -n "$DEBUGINFOD_URLS"
set --global --export DEBUGINFOD_URLS "$DEBUGINFOD_URLS"
end
end
+
+if not set --query DEBUGINFOD_IMA_CERT_PATH
+ set --local files "@sysconfdir@/debuginfod/"*.certpath
+ set --local DEBUGINFOD_IMA_CERT_PATH (cat /dev/null $files 2>/dev/null | string replace '\n' ':')
+ if test -n "$DEBUGINFOD_IMA_CERT_PATH"
+ set --global --export DEBUGINFOD_IMA_CERT_PATH "$DEBUGINFOD_IMA_CERT_PATH"
+ end
+end
next prev parent reply other threads:[~2024-05-09 17:56 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-03 21:04 Frank Ch. Eigler
2024-04-09 12:31 ` Mark Wielaard
2024-04-10 21:01 ` Frank Ch. Eigler
2024-04-11 10:14 ` Mark Wielaard
2024-04-11 14:09 ` Frank Ch. Eigler
2024-04-16 22:15 ` Frank Ch. Eigler
2024-05-05 1:30 ` Frank Ch. Eigler
2024-05-09 17:56 ` Aaron Merey [this message]
2024-05-14 15:18 ` Mark Wielaard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAJDtP-Sq=m0nUwy4B-UsN-mAEp72Hv55HT3vM7VBiJafS4DK7A@mail.gmail.com' \
--to=amerey@redhat.com \
--cc=elfutils-devel@sourceware.org \
--cc=fche@redhat.com \
--cc=mark@klomp.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).