[rfc] PR28204: debuginfod ima signature verification

Message ID Zg3EeNMrJMvefCPY@elastic.org
State New
Headers
Series [rfc] PR28204: debuginfod ima signature verification |

Commit Message

Frank Ch. Eigler April 3, 2024, 9:04 p.m. UTC
  Hi -

The following raw diff reworks this long-blocked patch to overcome
these three objections last fall:

- to drop "permissive" mode
- to stop redistributing published distro ima certificates
- to not use libimaevm.so (due to concurrency / licensing concerns)

This is a raw diff only.  I'll be proposing some changes shortly
downthread.
  

Comments

Mark Wielaard April 9, 2024, 12:31 p.m. UTC | #1
Hi Frank,

On Wed, 2024-04-03 at 17:04 -0400, Frank Ch. Eigler wrote:
> The following raw diff reworks this long-blocked patch to overcome
> these three objections last fall:
> 
> - to drop "permissive" mode

We discussed a bit on irc about "wording". But I think it isn't really
how it is worded, but that there is just different features. What is
called "enforcing" is an authenticity scheme. While "permissive" is
more like an (optional) error-detecting mode. IMHO it makes sense to
simply separate those. That way you don't have a authentication scheme
that is easily defeated (when put in "permissive" mode). And you can
implement a simpler error-detection mode that can work in more cases
(by using the executable .gnu_debuglink CRC)

> - to stop redistributing published distro ima certificates
> - to not use libimaevm.so (due to concurrency / licensing concerns)

Nice, you managed to open code it with just openssl primitives.

> This is a raw diff only.  I'll be proposing some changes shortly
> downthread.

Understood. Just a few quick comments below.

One "big picture" question is whether this should be a per server URL
policy or something that is enabled/disabled for all server URLs?
That makes it less "flexible" but should simplify things a bit for the
user (and the server urls parsing).

Also is this all rpm/koji specific? Or do other distros also use ima
signatures, but encode/store them differently?

> 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

So ima-evm-utils-devel isn't needed here anymore?

>  %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@"
>      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

We now also have a fish profile.

> diff --git a/configure.ac b/configure.ac
> index a279bb5282c9..19ccf107494b 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -667,6 +667,35 @@ 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"
> +  default_ima_cert_path=`eval echo "/etc/keys/ima:/etc/pki/rpm-ima:$sysconfdir/debuginfod/ima-certs"` # expand $prefix too
> +  AC_DEFINE([ENABLE_IMA_VERIFICATION], [1], [Define if the required ima verification libraries are available])
> +  AC_DEFINE_UNQUOTED(DEBUGINFOD_IMA_CERT_PATH_DEFAULT, "$default_ima_cert_path", [Default IMA certificate path])
> +fi
> +AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification" = "xrpmimaevmcrypto"])
> +

Not all these are needed anymore now are they?

>  dnl The directories with content.
>  
>  dnl Documentation.
> @@ -881,6 +910,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)
>  AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fish])
>  
>  AC_OUTPUT
> @@ -920,6 +958,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/ChangeLog b/debuginfod/ChangeLog
> index 0e4810bba501..f4d98c2e93bc 100644
> --- a/debuginfod/ChangeLog
> +++ b/debuginfod/ChangeLog
> @@ -1,3 +1,17 @@
> +2023-08-14  Ryan Goldberg  <rgoldber@redhat.com>
> +
> +	* 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-client.c (debuginfod_query_server): Added policy for
> +	validating IMA signatures
> +	* (debuginfod_validate_imasig): New function.
> +	* debuginfod.h.in: Added DEBUGINFOD_IMA_CERT_PATH_ENV_VAR.
> +	* Makefile.am: Add linker flags for rpm and imaevm and crypto. Also add install/uninstall
> +	ima-certs/ to known location.
> +	* ima-certs/: New directory containing known ima verification certificates.
> +
>  2023-04-21  Frank Ch. Eigler <fche@redhat.com>

Please move this into the commit message.

>  	* debuginfod.cxx (groom): Fix -r / -X logic.
> 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)

Spurious line removal?

> diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
> index 0ee7db3d6638..4618234f0718 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.  */
> @@ -114,6 +126,8 @@ void debuginfod_end (debuginfod_client *c) { }
>  
>  #include <pthread.h>
>  
> +
> +
>  static pthread_once_t init_control = PTHREAD_ONCE_INIT;
> 

Sperious extra blank lines?

>  
>  static void
> @@ -122,6 +136,17 @@ libcurl_init(void)
>    curl_global_init(CURL_GLOBAL_DEFAULT);
>  }
>  
> +
> +#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. */
> @@ -156,8 +181,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.  */
> @@ -217,6 +248,179 @@ 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 = strdup (getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR) ?: DEBUGINFOD_IMA_CERT_PATH_DEFAULT);
> +  if (!cert_paths)
> +    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)
>  {
> @@ -853,6 +1057,198 @@ 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 */
> +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 = -EINVAL;
> +        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 */
> +        
> +        /* 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;
> +	else if (ret == 0)
> +		ret = -EINVAL;
> +err:
> +	if (ret < 0 || ret > 1)
> +		ret = -EINVAL;
> +	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;
> +    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 == 1) ? -EINVAL : 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
> @@ -1208,12 +1604,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;
> +        }
> 

Aha, nice. That then works with the fallthrough later...

>    
>        /* 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] == '/')
> @@ -1245,21 +1668,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)
> @@ -1326,7 +1756,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;
> @@ -1774,6 +2208,33 @@ 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(EINVAL == result || 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 = -EBADMSG;
> +      goto out2;
> +    }
> +    else
> +    {
> +      // NOTREACHED
> +    }
> +  }
> +
>    /* rename tmp->real */
>    rc = rename (target_cache_tmppath, target_cache_path);
>    if (rc < 0)
> @@ -1794,6 +2255,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);
>  
> @@ -1827,6 +2289,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);
> @@ -1859,7 +2322,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;
>  }
>  
> @@ -1891,6 +2358,10 @@ debuginfod_begin (void)
>  	goto out1;
>      }
>  
> +#ifdef ENABLE_IMA_VERIFICATION
> +  load_ima_public_keys (client);
> +#endif
> +
>    // extra future initialization
>    
>    goto out;
> @@ -1938,6 +2409,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);
>  }
>  
> @@ -1977,9 +2451,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.  */

... here. Cool.

> diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
> index ece5031f02f9..30c818dd24bf 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,146 @@ 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 +2175,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 +2365,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/ChangeLog b/doc/ChangeLog
> index 7f2d6ff4fd31..914f8f649511 100644
> --- a/doc/ChangeLog
> +++ b/doc/ChangeLog
> @@ -1,3 +1,10 @@
> +2023-08-14  Ryan Goldberg  <rgoldber@redhat.com>
> +
> +	* debuginfod-client-config.7: Document DEBUGINFOD_IMA_CERT_PATH,
> +	update DEBUGINFOD_URLS.
> +	* debuginfod.8: Document --koji-sigcache
> +	* debuginfod-find.1, debuginfod_find_debuginfo.3: Update SECURITY
> +

More ChangeLog that could move into the commit message.

Cheers,

Mar
  
Frank Ch. Eigler April 10, 2024, 9:01 p.m. UTC | #2
Hi, Mark -


> > - to drop "permissive" mode
> 
> We discussed a bit on irc about "wording". But I think it isn't really
> how it is worded, but that there is just different features. What is
> called "enforcing" is an authenticity scheme. While "permissive" is
> more like an (optional) error-detecting mode. IMHO it makes sense to
> simply separate those.

For the record, on IRC, I presented these two additional arguments:


    in the case of federated debuginfod servers, such as
    debuginfod.elfutils.org, a user is stuck with "ignore" mode
    operation, because not all upstream servers will have ima
    signatures to pass.  this sacrifices all possible assurance that
    could come from the federated server relaying ima signatures from
    those servers that have them

    even in non-federated cases such as fedoraproject.org, the ideal
    case for "enforcing" mode as a default, it will fail the moment
    the user happens to try to debug some pre-fedora-38 binary that
    may be sitting on the system --- because those rpms just don't
    have signatures available at all

    for both these scenarios, the original "permissive" mode would be
    suitable


IOW, without a "permissive" mode being available at all, we could not
ask users to enable this new code at all for our own federated
servers, nor even for fedora.  That's because no server can guarantee
the availability of signatures for all content they can serve.


> That way you don't have a authentication scheme that is easily
> defeated (when put in "permissive" mode).

This "easily defeated" theory needs a threat model.  It sounds like
you are thinking of
- an evil debuginfod instance that
- you trust enough to list in DEBUGINFOD_URLS
- but not enough to label it with "ima:enforcing"
- which may strip the X-DEBUGINFOD-FILE-SIGNATURE header" en route
then yeah, but sounds far-fetched rather than easy.


> And you can implement a simpler error-detection mode that can work
> in more cases (by using the executable .gnu_debuglink CRC)

No, we already know that this is incapable of checking e.g. source
files.  And there is no separate CRC for executables vs. debuginfo
files.  And note that this provides zero protection in the same threat
model above (as the CRC field could be MITM'd).


> [...]
> One "big picture" question is whether this should be a per server URL
> policy or something that is enabled/disabled for all server URLs?
> That makes it less "flexible" but should simplify things a bit for the
> user (and the server urls parsing).

Blame some guy named Mark for getting Ryan to build that out last year. :-)

https://sourceware.org/pipermail/elfutils-devel/2023q3/006299.html

> Also is this all rpm/koji specific? Or do other distros also use ima
> signatures, but encode/store them differently?

As far as I know, Fedora/RHEL are the first.  Yeah it's all
package/style specific.


> [...]
> We now also have a fish profile.

I'm afraid I don't speak fish. :-)


> > +debuginfod_ima_verification_enabled="no"
> > +if test "$enable_ima_verification" = "xrpmimaevmcrypto"; then
> > +  debuginfod_ima_verification_enabled="yes"
> > +  default_ima_cert_path=`eval echo "/etc/keys/ima:/etc/pki/rpm-ima:$sysconfdir/debuginfod/ima-certs"` # expand $prefix too
> > +  AC_DEFINE([ENABLE_IMA_VERIFICATION], [1], [Define if the required ima verification libraries are available])
> > +  AC_DEFINE_UNQUOTED(DEBUGINFOD_IMA_CERT_PATH_DEFAULT, "$default_ima_cert_path", [Default IMA certificate path])
> > +fi
> > +AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification" = "xrpmimaevmcrypto"])
> > +
> 
> Not all these are needed anymore now are they?
> [...]

The elaborate default_ima_cert_path?  Yeah probably not, just force
Fedora etc. to use the configure parameter.  The AM_CONDITIONAL
"xrpmimaevmcrypto" part?  Yeah we need those.  (The iamevm part
is just for the headers.)

At some point, I'd like to reformat the debuginfod code base, it's
become a bit of a mishmash.  Separate commit later, I guess?


- FChE
  
Mark Wielaard April 11, 2024, 10:14 a.m. UTC | #3
Hi Frank,

On Wed, Apr 10, 2024 at 05:01:36PM -0400, Frank Ch. Eigler wrote:
> > > - to drop "permissive" mode
> > 
> > We discussed a bit on irc about "wording". But I think it isn't really
> > how it is worded, but that there is just different features. What is
> > called "enforcing" is an authenticity scheme. While "permissive" is
> > more like an (optional) error-detecting mode. IMHO it makes sense to
> > simply separate those.
> 
> For the record, on IRC, I presented these two additional arguments:
> 
> 
>     in the case of federated debuginfod servers, such as
>     debuginfod.elfutils.org, a user is stuck with "ignore" mode
>     operation, because not all upstream servers will have ima
>     signatures to pass.  this sacrifices all possible assurance that
>     could come from the federated server relaying ima signatures from
>     those servers that have them
> 
>     even in non-federated cases such as fedoraproject.org, the ideal
>     case for "enforcing" mode as a default, it will fail the moment
>     the user happens to try to debug some pre-fedora-38 binary that
>     may be sitting on the system --- because those rpms just don't
>     have signatures available at all
> 
>     for both these scenarios, the original "permissive" mode would be
>     suitable
> 
> 
> IOW, without a "permissive" mode being available at all, we could not
> ask users to enable this new code at all for our own federated
> servers, nor even for fedora.  That's because no server can guarantee
> the availability of signatures for all content they can serve.

I don't understand why you say we cannot ask users to enable the ima
checking code. Isn't the goal to make sure that the user only gets
ima-signed/verified files for the distro they are debugging/analyzing
on? Especially if a server can also provide non-verifiable files, then
you would want to have strict checking to make sure.

> > That way you don't have a authentication scheme that is easily
> > defeated (when put in "permissive" mode).
> 
> This "easily defeated" theory needs a threat model.  It sounds like
> you are thinking of
> - an evil debuginfod instance that
> - you trust enough to list in DEBUGINFOD_URLS
> - but not enough to label it with "ima:enforcing"
> - which may strip the X-DEBUGINFOD-FILE-SIGNATURE header" en route
> then yeah, but sounds far-fetched rather than easy.

I think I simply don't understand what the thread model is that
ima:permissive guards against. It seems not to protect against the
main thing you want to do ima checking for. You only want
debuginfod-client to provide files that are signed and can be
verified.

> > And you can implement a simpler error-detection mode that can work
> > in more cases (by using the executable .gnu_debuglink CRC)
> 
> No, we already know that this is incapable of checking e.g. source
> files.  And there is no separate CRC for executables vs. debuginfo
> files.  And note that this provides zero protection in the same threat
> model above (as the CRC field could be MITM'd).

Sure. I guess if you don't enforce ima checking you can just rely on
https. And it is kind of the point that this is similar to the threat
model that "permissive" guards against (random, bit flipping,
accidential replacement of files). Relying on https and CRC checking
seems simpler to understand for that. But you are right that there
isn't really anyhing for the main executables. For the source files
you could check the lenght and time stamps (or the MD5 sums if added
by the DWARF producer).

> > [...]
> > One "big picture" question is whether this should be a per server URL
> > policy or something that is enabled/disabled for all server URLs?
> > That makes it less "flexible" but should simplify things a bit for the
> > user (and the server urls parsing).
> 
> Blame some guy named Mark for getting Ryan to build that out last year. :-)
> 
> https://sourceware.org/pipermail/elfutils-devel/2023q3/006299.html

Sorry. I see back then I also didn't get use case for the "permissive"
policy. But yes, it might have been a mistake to suggest different
policies per URL/server.

Cheers,

Mark
  
Frank Ch. Eigler April 11, 2024, 2:09 p.m. UTC | #4
Hi -

> > IOW, without a "permissive" mode being available at all, we could not
> > ask users to enable this new code at all for our own federated
> > servers, nor even for fedora.  That's because no server can guarantee
> > the availability of signatures for all content they can serve.
> 
> I don't understand why you say we cannot ask users to enable the ima
> checking code. Isn't the goal to make sure that the user only gets
> ima-signed/verified files for the distro they are debugging/analyzing
> on? Especially if a server can also provide non-verifiable files, then
> you would want to have strict checking to make sure.

The goal of dealing with 100% verified files is nice in theory, but
the question is what to do with the exceptions.  We know there will be
exceptions (when debugging older binaries, or when the server has a
hickup, or when debugging other distro's binaries from a container, or
...).

In the absence of a "permissive" type mode, those exceptions result in
a failure.  Curing those requires the user to turn off signature
checking entirely and restart whatever servers/clients are affected.


> [...]  I think I simply don't understand what the thread model is
> that ima:permissive guards against.

In the absence of deliberately hostile servers that would strip
X-DEBUGINFOD-FILE-SIGNATURE, this mode provides ima:enforcing level
security for those servers & files that have verifiable info and
ima:ignore level (non) security for those that don't.

It's as if

DEBUGINFOD_URLS="ima:enforcing HTTP:FOO ima:ignore HTTP:FOO"

except that cannot work with the current client code (because of
parallelism tie-breaking & dupe elimination).


> It seems not to protect against the main thing you want to do ima
> checking for. You only want debuginfod-client to provide files that
> are signed and can be verified.

No, I am not so absolutist with the goal.  "Best effort" would be my
default.  If you wish to use only ima:enforcing, you'd be welcome to.


> > > And you can implement a simpler error-detection mode that can work
> > > in more cases (by using the executable .gnu_debuglink CRC)
> > 
> > No, we already know that this is incapable of checking e.g. source
> > files.  And there is no separate CRC for executables vs. debuginfo
> > files.  And note that this provides zero protection in the same threat
> > model above (as the CRC field could be MITM'd).
> 
> Sure. I guess if you don't enforce ima checking you can just rely on
> https. And it is kind of the point that this is similar to the threat
> model that "permissive" guards against (random, bit flipping,
> accidential replacement of files). [...]

It is "similar" but strictly less protective.


> > > [...]
> > > One "big picture" question is whether this should be a per server URL
> > > policy or something that is enabled/disabled for all server URLs?
> > > That makes it less "flexible" but should simplify things a bit for the
> > > user (and the server urls parsing).
> > 
> > Blame some guy named Mark for getting Ryan to build that out last year. :-)
> > 
> > https://sourceware.org/pipermail/elfutils-devel/2023q3/006299.html
> 
> Sorry. I see back then I also didn't get use case for the "permissive"
> policy. But yes, it might have been a mistake to suggest different
> policies per URL/server.

A single global policy setting would be even worse, without an
available "permissive" setting.  It would not be possible to handle
exceptions without a reconfiguration/restart.


- FChE
  
Frank Ch. Eigler April 16, 2024, 10:15 p.m. UTC | #5
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@"
     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)
 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;
+    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
  

Patch

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@"
     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..19ccf107494b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -667,6 +667,35 @@  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"
+  default_ima_cert_path=`eval echo "/etc/keys/ima:/etc/pki/rpm-ima:$sysconfdir/debuginfod/ima-certs"` # expand $prefix too
+  AC_DEFINE([ENABLE_IMA_VERIFICATION], [1], [Define if the required ima verification libraries are available])
+  AC_DEFINE_UNQUOTED(DEBUGINFOD_IMA_CERT_PATH_DEFAULT, "$default_ima_cert_path", [Default IMA certificate path])
+fi
+AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification" = "xrpmimaevmcrypto"])
+
 dnl The directories with content.
 
 dnl Documentation.
@@ -881,6 +910,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)
 AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fish])
 
 AC_OUTPUT
@@ -920,6 +958,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/ChangeLog b/debuginfod/ChangeLog
index 0e4810bba501..f4d98c2e93bc 100644
--- a/debuginfod/ChangeLog
+++ b/debuginfod/ChangeLog
@@ -1,3 +1,17 @@ 
+2023-08-14  Ryan Goldberg  <rgoldber@redhat.com>
+
+	* 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-client.c (debuginfod_query_server): Added policy for
+	validating IMA signatures
+	* (debuginfod_validate_imasig): New function.
+	* debuginfod.h.in: Added DEBUGINFOD_IMA_CERT_PATH_ENV_VAR.
+	* Makefile.am: Add linker flags for rpm and imaevm and crypto. Also add install/uninstall
+	ima-certs/ to known location.
+	* ima-certs/: New directory containing known ima verification certificates.
+
 2023-04-21  Frank Ch. Eigler <fche@redhat.com>
 
 	* debuginfod.cxx (groom): Fix -r / -X logic.
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 0ee7db3d6638..4618234f0718 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.  */
@@ -114,6 +126,8 @@  void debuginfod_end (debuginfod_client *c) { }
 
 #include <pthread.h>
 
+
+
 static pthread_once_t init_control = PTHREAD_ONCE_INIT;
 
 static void
@@ -122,6 +136,17 @@  libcurl_init(void)
   curl_global_init(CURL_GLOBAL_DEFAULT);
 }
 
+
+#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. */
@@ -156,8 +181,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.  */
@@ -217,6 +248,179 @@  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 = strdup (getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR) ?: DEBUGINFOD_IMA_CERT_PATH_DEFAULT);
+  if (!cert_paths)
+    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)
 {
@@ -853,6 +1057,198 @@  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 */
+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 = -EINVAL;
+        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 */
+        
+        /* 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;
+	else if (ret == 0)
+		ret = -EINVAL;
+err:
+	if (ret < 0 || ret > 1)
+		ret = -EINVAL;
+	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;
+    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 == 1) ? -EINVAL : 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
@@ -1208,12 +1604,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] == '/')
@@ -1245,21 +1668,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)
@@ -1326,7 +1756,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;
@@ -1774,6 +2208,33 @@  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(EINVAL == result || 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 = -EBADMSG;
+      goto out2;
+    }
+    else
+    {
+      // NOTREACHED
+    }
+  }
+
   /* rename tmp->real */
   rc = rename (target_cache_tmppath, target_cache_path);
   if (rc < 0)
@@ -1794,6 +2255,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);
 
@@ -1827,6 +2289,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);
@@ -1859,7 +2322,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;
 }
 
@@ -1891,6 +2358,10 @@  debuginfod_begin (void)
 	goto out1;
     }
 
+#ifdef ENABLE_IMA_VERIFICATION
+  load_ima_public_keys (client);
+#endif
+
   // extra future initialization
   
   goto out;
@@ -1938,6 +2409,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);
 }
 
@@ -1977,9 +2451,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..30c818dd24bf 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,146 @@  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 +2175,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 +2365,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/ChangeLog b/doc/ChangeLog
index 7f2d6ff4fd31..914f8f649511 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,3 +1,10 @@ 
+2023-08-14  Ryan Goldberg  <rgoldber@redhat.com>
+
+	* debuginfod-client-config.7: Document DEBUGINFOD_IMA_CERT_PATH,
+	update DEBUGINFOD_URLS.
+	* debuginfod.8: Document --koji-sigcache
+	* debuginfod-find.1, debuginfod_find_debuginfo.3: Update SECURITY
+
 2023-02-14  Mark Wielaard  <mark@klomp.org>
 
 	* debuginfod.8: Add .TP before -g.
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..cb49eb83d779 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,10 @@  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.
+
 .nr zZ 1
 .so man7/debuginfod-client-config.7
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 40e0eaa5a368..c1bd52cf4fe8 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