From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gnu.wildebeest.org (gnu.wildebeest.org [45.83.234.184]) by sourceware.org (Postfix) with ESMTPS id EC22E3858C39 for ; Tue, 9 Apr 2024 12:31:47 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org EC22E3858C39 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=klomp.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=klomp.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org EC22E3858C39 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=45.83.234.184 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712665914; cv=none; b=vyD6WZPpwiOVimuAyEhVfBC2ertqSunncXlva1ShRe/qYtNGtyqsBWEEVVnOh27yTZweo/0myYM1KsoXHjiHoh6IAiiKeCgYto0F7NyynJJLC2UyUJZCowppWaei+3PnePaOGnjrtznwtwGvD+EgrkfmxgviNzdmi2kpFBbTkeo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712665914; c=relaxed/simple; bh=SlWrCrGHWYxvwSxnb3o2Fp/Kx/Bhm4VNcvu3eWB6wDY=; h=Message-ID:Subject:From:To:Date:MIME-Version; b=bH1GdHpHoK0eBtVVJGakpTOKO3V71XXagPXka/LlinEehf6pTtCwW//pfnuyul/Bm/uHL4ZB8bb3P+g+siBBavAPt3d78eqckTfnjzFcoJLFoaJlSr6xKHoPOAip6QpHbzPRCMRaEYFxGIhd6jNtsmKpbHtLSECBjVFZhvVICuA= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from r6.localdomain (82-217-174-174.cable.dynamic.v4.ziggo.nl [82.217.174.174]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by gnu.wildebeest.org (Postfix) with ESMTPSA id CD5093000595; Tue, 9 Apr 2024 14:31:44 +0200 (CEST) Received: by r6.localdomain (Postfix, from userid 1000) id 4BFD7340513; Tue, 9 Apr 2024 14:31:44 +0200 (CEST) Message-ID: <437d21e98ce9c83d2c34cb2c1e13c5aa7af98c2e.camel@klomp.org> Subject: Re: [rfc] [patch] PR28204: debuginfod ima signature verification From: Mark Wielaard To: "Frank Ch. Eigler" , elfutils-devel@sourceware.org Date: Tue, 09 Apr 2024 14:31:44 +0200 In-Reply-To: References: Autocrypt: addr=mark@klomp.org; prefer-encrypt=mutual; keydata=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 QifSU1tvZxQ== Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.50.4 (3.50.4-1.fc39) MIME-Version: 1.0 X-Spam-Status: No, score=-7.9 required=5.0 tests=BAYES_00,GIT_PATCH_0,JMQ_SPF_NEUTRAL,KAM_DMARC_STATUS,RCVD_IN_BARRACUDACENTRAL,SPF_HELO_NONE,SPF_PASS,TXREP,URIBL_SBL_A autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: 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: >=20 > - 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 +=3D 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 > =20 > 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 > =20 > 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 > =20 > +# 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- > =20 > 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 variable= s > # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS= . > =20 > +set prefix=3D"@prefix@" > if (! $?DEBUGINFOD_URLS) then > - set prefix=3D"@prefix@" > set DEBUGINFOD_URLS=3D`sh -c 'cat /dev/null "$0"/*.urls 2>/dev/null;= :' "@sysconfdir@/debuginfod" | tr '\n' ' '` > if ( "$DEBUGINFOD_URLS" !=3D "" ) then > setenv DEBUGINFOD_URLS "$DEBUGINFOD_URLS" > else > unset DEBUGINFOD_URLS > endif > - unset prefix > + set DEBUGINFOD_IMA_CERT_PATH=3D`sh -c 'cat /dev/null "$0"/*.certpath= 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ':'` > + if ( "$DEBUGINFOD_IMA_CERT_PATH" !=3D "" ) 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 variable= s > # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS= . > =20 > +prefix=3D"@prefix@" > if [ -z "$DEBUGINFOD_URLS" ]; then > prefix=3D"@prefix@" > DEBUGINFOD_URLS=3D$(cat /dev/null "@sysconfdir@/debuginfod"/*.urls 2= >/dev/null | tr '\n' ' ' || :) > [ -n "$DEBUGINFOD_URLS" ] && export DEBUGINFOD_URLS || unset DEBUGIN= FOD_URLS > - unset prefix > fi > + > +if [ -z "$DEBUGINFOD_IMA_CERT_PATH" ]; then > + DEBUGINFOD_IMA_CERT_PATH=3D$(cat "@sysconfdir@/debuginfod"/*.certpat= h 2>/dev/null | tr '\n' ':' || :) > + [ -n "$DEBUGINFOD_IMA_CERT_PATH" ] && export DEBUGINFOD_IMA_CERT_PAT= H || 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]) > =20 > +enable_ima_verification=3D"x" > +AC_CHECK_LIB(rpm, headerGet, [ > + AC_CHECK_DECL(RPMSIGTAG_FILESIGNATURES, > + [ > + enable_ima_verification=3D$enable_ima_verification"rpm" > + AC_SUBST(rpm_LIBS, '-lrpm -lrpmio') > + ], > + [], [#include ]) > +]) > + > +dnl we use only the header, not the code of this library > +AC_CHECK_HEADER(imaevm.h, [ > + enable_ima_verification=3D$enable_ima_verification"imaevm" > +]) > + > +AC_CHECK_LIB(crypto, EVP_MD_CTX_new, [ > + enable_ima_verification=3D$enable_ima_verification"crypto" > + AC_SUBST(crypto_LIBS, '-lcrypto') > +]) > + > +debuginfod_ima_verification_enabled=3D"no" > +if test "$enable_ima_verification" =3D "xrpmimaevmcrypto"; then > + debuginfod_ima_verification_enabled=3D"yes" > + default_ima_cert_path=3D`eval echo "/etc/keys/ima:/etc/pki/rpm-ima:$sy= sconfdir/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_cer= t_path", [Default IMA certificate path]) > +fi > +AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification= " =3D "xrpmimaevmcrypto"]) > + Not all these are needed anymore now are they? > dnl The directories with content. > =20 > dnl Documentation. > @@ -881,6 +910,15 @@ AC_ARG_ENABLE(debuginfod-urls, > fi], > [default_debuginfod_urls=3D""]) > AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls) =20 > +AC_ARG_ENABLE(debuginfod-ima-cert-path, > + [AS_HELP_STRING([--enable-debuginfod-ima-cert-path@<:@=3DPAT= H@:>@],[add PATH to profile.d DEBUGINFOD_IMA_CERT_PATH])], > + [if test "x${enableval}" =3D "xyes"; > + then AC_MSG_ERROR([PATH required]) > + elif test "x${enableval}" !=3D "xno"; then > + default_debuginfod_ima_cert_path=3D"${enableval}"; > + fi], > + [default_debuginfod_ima_cert_path=3D""]) > +AC_SUBST(DEBUGINFOD_IMA_CERT_PATH, $default_debuginfod_ima_cert_path) > AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fis= h]) > =20 > 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_e= nabled} ${default_debuginfod_ima_cert_path} > =20 > 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 > + > + * 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 certifica= tes. > + > 2023-04-21 Frank Ch. Eigler 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 +=3D debuginfod-find > endif > =20 > debuginfod_SOURCES =3D debuginfod.cxx > -debuginfod_LDADD =3D $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp= _LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS= ) -lpthread -ldl > +debuginfod_LDADD =3D $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp= _LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS= ) $(rpm_LIBS) -lpthread -ldl > =20 > debuginfod_find_SOURCES =3D debuginfod-find.c > debuginfod_find_LDADD =3D $(libdw) $(libelf) $(libeu) $(libdebuginfod) $= (argp_LDADD) $(fts_LIBS) > @@ -97,7 +97,7 @@ libdebuginfod_so_LIBS =3D libdebuginfod_pic.a > if DUMMY_LIBDEBUGINFOD > libdebuginfod_so_LDLIBS =3D > else > -libdebuginfod_so_LDLIBS =3D -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libe= lf) > +libdebuginfod_so_LDLIBS =3D -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libe= lf) $(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)/$(LIBDEB= UGINFOD_SONAME) > ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebug= infod.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-clien= t.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 > This file is part of elfutils. > =20 > @@ -47,6 +47,17 @@ > #include > #include > =20 > +#ifdef ENABLE_IMA_VERIFICATION > +#include > +#include > +#include > +#include > +#include > +#include > +#endif > +typedef enum {ignore, enforcing, undefined} ima_policy_t; > + > + > /* We might be building a bootstrap dummy library, which is really simpl= e. */ > #ifdef DUMMY_LIBDEBUGINFOD > =20 > @@ -92,6 +103,7 @@ void debuginfod_end (debuginfod_client *c) { } > #include > #include > #include > +#include > =20 > /* 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 manuall= y. */ > @@ -114,6 +126,8 @@ void debuginfod_end (debuginfod_client *c) { } > =20 > #include > =20 > + > + > static pthread_once_t init_control =3D PTHREAD_ONCE_INIT; >=20 Sperious extra blank lines? > =20 > static void > @@ -122,6 +136,17 @@ libcurl_init(void) > curl_global_init(CURL_GLOBAL_DEFAULT); > } > =20 > + > +#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 > }; > =20 > + > /* The cache_clean_interval_s file within the debuginfod cache specifies > how frequently the cache should be cleaned. The file's st_mtime repre= sents > the time of last cleaning. */ > @@ -217,6 +248,179 @@ struct handle_data > size_t response_data_size; > }; > =20 > + > + > +#ifdef ENABLE_IMA_VERIFICATION > + static inline unsigned char hex2dec(char c) > + { > + if (c >=3D '0' && c <=3D '9') return (c - '0'); > + if (c >=3D 'a' && c <=3D 'f') return (c - 'a') + 10; > + if (c >=3D 'A' && c <=3D 'F') return (c - 'A') + 10; > + return 0; > + } > + > + static inline ima_policy_t ima_policy_str2enum(const char* ima_pol) > + { > + if (NULL =3D=3D ima_pol) return undefined; > + if (0 =3D=3D strcmp(ima_pol, "ignore")) return ignore; > + if (0 =3D=3D 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 publ= ic key hashing > +{ > + if (!pkey) return 0; > + uint32_t keyid =3D 0; > + X509_PUBKEY *pk =3D NULL; > + const unsigned char *public_key =3D NULL; = =20 > + 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 i= ts public key=20 > + { > + if (!x509) return 0; > + uint32_t keyid =3D 0; > + // Attempt to get the skid from the certificate > + const ASN1_OCTET_STRING *skid_asn1_str =3D X509_get0_subject_key_id(= x509); > + if (skid_asn1_str) > + { > + int skid_len =3D 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 =3D X509_get0_pubkey(x509); > + keyid =3D 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 =3D strdup (getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR) = ?: DEBUGINFOD_IMA_CERT_PATH_DEFAULT); > + if (!cert_paths) > + return; > + =20 > + char* cert_dir_path; > + DIR *dp; > + struct dirent *entry; > + int vfd =3D c->verbose_fd; > + =20 > + char *strtok_context =3D NULL; > + for(cert_dir_path =3D strtok_r(cert_paths, ":", &strtok_context); > + cert_dir_path !=3D NULL; > + cert_dir_path =3D strtok_r(NULL, ":", &strtok_context)) > + { > + dp =3D opendir(cert_dir_path); > + if(!dp) continue; > + while((entry =3D readdir(dp))) > + { > + // Only consider regular files with common x509 cert extension= s > + if(entry->d_type !=3D DT_REG || 0 !=3D fnmatch("*.@(der|pem|cr= t|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] !=3D '/') certfile[strlen(cert= file)] =3D '/'; > + strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) -= 1); > + certfile[strlen(certfile)] =3D '\0'; > + =20 > + FILE *cert_fp =3D fopen(certfile, "r"); > + if(!cert_fp) continue; > + > + X509 *x509 =3D NULL; > + EVP_PKEY *pkey =3D NULL; > + char *fmt =3D ""; > + // Attempt to read the fp as DER > + if(d2i_X509_fp(cert_fp, &x509)) > + fmt =3D "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 th= e fp > + else if(0 =3D=3D fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(= cert_fp, &x509, NULL, NULL)) > + fmt =3D "pem "; // PEM with full certificate > + else if(0 =3D=3D fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKE= Y(cert_fp, &pkey, NULL, NULL))=20 > + fmt =3D "pem "; // some PEM files have just a PUBLIC KEY in = them > + fclose(cert_fp); > + > + if (x509) > + { > + struct public_key_entry *ne =3D calloc(1, sizeof(struct pu= blic_key_entry)); > + if (ne) > + { > + ne->key =3D X509_extract_key(x509); > + ne->keyid =3D extract_skid(x509); > + ne->next =3D c->ima_public_keys; > + c->ima_public_keys =3D ne; > + if (vfd >=3D 0) > + dprintf(vfd, "Loaded %scertificate %s, keyid =3D %04= x\n", fmt, certfile, ne->keyid); > + } > + X509_free (x509); > + } > + else if (pkey) > + { > + struct public_key_entry *ne =3D calloc(1, sizeof(struct pu= blic_key_entry)); > + if (ne) > + { > + ne->key =3D pkey; // preserve refcount > + ne->keyid =3D extract_skid_pk(pkey); > + ne->next =3D c->ima_public_keys; > + c->ima_public_keys =3D ne; > + if (vfd >=3D 0) > + dprintf(vfd, "Loaded %spubkey %s, keyid =3D %04x\n",= fmt, certfile, ne->keyid); > + } > + } > + else > + { > + if (vfd >=3D 0) > + dprintf(vfd, "Cannot load certificate %s\n", certfile); > + } > + } /* for each file in directory */ > + closedir(dp); > + } /* for each directory */ > + =20 > + 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 =3D c->ima_public_keys->next; > + free (c->ima_public_keys); > + c->ima_public_keys =3D oen; > + } > +} > +#endif > + > + > + > static size_t > debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *d= ata) > { > @@ -853,6 +1057,198 @@ cache_find_section (const char *scn_name, const ch= ar *target_cache_dir, > return rc; > } > =20 > + > +#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 =3D 0; > + =20 > + 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 >=3D 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 =3D hdr_v2.hash_algo; > + break; > + default: > + if (c->verbose_fd >=3D 0) > + dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (i= nt)bin_sig[1]); > + return NULL; > + } > + =20 > + 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 >=3D 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 si= glen) > +{ > + int ret =3D -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 jus= t aliasing */ > + =20 > + /* Find the matching public key. */ > + for (pkey =3D c->ima_public_keys; pkey !=3D NULL; pkey =3D pkey-= >next) > + if (pkey->keyid =3D=3D ntohl(hdr.keyid)) break; > + if (!pkey) > + return -ENOKEY; > + > + if (!(ctx =3D EVP_PKEY_CTX_new(pkey->key, NULL))) > + goto err; > + if (!EVP_PKEY_verify_init(ctx)) > + goto err; > + if (!(md =3D EVP_get_digestbyname(hash_algo))) > + goto err; > + if (!EVP_PKEY_CTX_set_signature_md(ctx, md)) > + goto err; > + ret =3D EVP_PKEY_verify(ctx, sig + sizeof(hdr), > + siglen - sizeof(hdr), hash, size); > + if (ret =3D=3D 1) > + ret =3D 0; > + else if (ret =3D=3D 0) > + ret =3D -EINVAL; > +err: > + if (ret < 0 || ret > 1) > + ret =3D -EINVAL; > + EVP_PKEY_CTX_free(ctx); > + return ret; > +} > + > + > + > +/* Validate an IMA file signature. > + * Returns 0 on signature validity, -EINVAL on signature invalidity, -EN= OSYS on undefined imaevm machinery, > + * -ENOKEY on key issues, or other -errno. > + */ > + > +static int > +debuginfod_validate_imasig (debuginfod_client *c, int fd) > +{ > + int rc =3D ENOSYS; > + > + // int vfd =3D c->verbose_fd; > + EVP_MD_CTX *ctx =3D NULL; > + if (!c || !c->winning_headers) > + { > + rc =3D -ENODATA; > + goto exit_validate; > + } > + // Extract the HEX IMA-signature from the header > + char* sig_buf =3D NULL; > + char* hdr_ima_sig =3D strcasestr(c->winning_headers, "x-debuginfod-i= masignature"); > + if (!hdr_ima_sig || 1 !=3D sscanf(hdr_ima_sig + strlen("x-debuginfod= -imasignature:"), "%ms", &sig_buf)) > + { > + rc =3D -ENODATA; > + goto exit_validate; > + } > + if (strlen(sig_buf) > MAX_SIGNATURE_SIZE) // reject if too long > + { > + rc =3D -EBADMSG; > + goto exit_validate; > + } > + // Convert the hex signature to bin > + size_t bin_sig_len =3D strlen(sig_buf)/2; > + unsigned char bin_sig[MAX_SIGNATURE_SIZE/2]; > + for (size_t b =3D 0; b < bin_sig_len; b++) > + bin_sig[b] =3D (hex2dec(sig_buf[2*b]) << 4) | hex2dec(sig_buf[2*b+= 1]); > + > + // Compute the binary digest of the cached file (with file descripto= r fd) > + ctx =3D EVP_MD_CTX_new(); > + const char* sighash_name =3D get_signature_params(c, bin_sig) ?: ""; > + const EVP_MD *md =3D EVP_get_digestbyname(sighash_name); > + if (!ctx || !md || !EVP_DigestInit(ctx, md)) > + { > + rc =3D -EBADMSG; > + goto exit_validate; > + } > + > + long data_len; > + char* hdr_data_len =3D strcasestr(c->winning_headers, "x-debuginfod-= size"); > + if (!hdr_data_len || 1 !=3D sscanf(hdr_data_len + strlen("x-debuginf= od-size:") , "%ld", &data_len)) > + { > + rc =3D -ENODATA; > + goto exit_validate; > + } > + > + char file_data[DATA_SIZE]; // imaevm.h data chunk hash size=20 > + ssize_t n; > + for(off_t k =3D 0; k < data_len; k +=3D n) > + { > + if (-1 =3D=3D (n =3D pread(fd, file_data, DATA_SIZE, k))) > + { > + rc =3D -errno; > + goto exit_validate; > + } > + =20 > + if (!EVP_DigestUpdate(ctx, file_data, n)) > + { > + rc =3D -EBADMSG; > + goto exit_validate; > + } > + } > + =20 > + uint8_t bin_dig[MAX_DIGEST_SIZE]; > + unsigned int bin_dig_len; > + if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len)) > + { > + rc =3D -EBADMSG; > + goto exit_validate; > + } > + > + // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo = dawg > + =20 > + int res =3D debuginfod_verify_hash(c, > + bin_dig, bin_dig_len, > + sighash_name, > + & bin_sig[1], bin_sig_len-1); // sk= ip over first byte of signature > + if (c->verbose_fd >=3D 0) > + dprintf (c->verbose_fd, "Computed ima signature verification res= =3D%d\n", res); > + rc =3D (res =3D=3D 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 o= r > 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 =3D NULL; > - char *server_url =3D strtok_r(server_urls, url_delim, &strtok_saveptr)= ; > + ima_policy_t* url_ima_policies =3D NULL; > + char* server_url; > /* Count number of URLs. */ > int num_urls =3D 0; > =20 > - while (server_url !=3D NULL) > + ima_policy_t verification_mode =3D ignore; // The default mode > + for(server_url =3D strtok_r(server_urls, url_delim, &strtok_saveptr); > + server_url !=3D NULL; server_url =3D strtok_r(NULL, url_delim, &st= rtok_saveptr)) > { > + // When we encounted a (well-formed) token off the form ima:foo, w= e 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 =3D ima_policy_str2enum(server_url + strlen("ima:= ")); > + if(m !=3D undefined) > + verification_mode =3D m; > + else if (vfd >=3D 0) > + dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_= url); > +#else > + if (vfd >=3D 0) > + dprintf(vfd, "IMA signature verification is not enabled, ski= pping %s\n", server_url); > +#endif > + continue; // Not a url, just a mode change so keep going > + } > + > + if (verification_mode=3D=3Denforcing && 0=3D=3Dstrcmp(type,"sectio= n")) > + { > + if (vfd >=3D 0) > + dprintf(vfd, "skipping server %s section query in IMA enforc= ing mode\n", server_url); > + continue; > + } >=20 Aha, nice. That then works with the fallthrough later... > =20 > /* 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] =3D= =3D '/') > @@ -1245,21 +1668,28 @@ debuginfod_query_server (debuginfod_client *c, > else > { > num_urls++; > - char ** realloc_ptr; > - realloc_ptr =3D reallocarray(server_url_list, num_urls, > - sizeof(char*)); > - if (realloc_ptr =3D=3D NULL) > + if (NULL =3D=3D (server_url_list =3D reallocarray(server_url_= list, num_urls, sizeof(char*))) > +#ifdef ENABLE_IMA_VERIFICATION > + || NULL =3D=3D (url_ima_policies =3D reallocarray(url_ima_poli= cies, num_urls, sizeof(ima_policy_t))) > +#endif > + ) > { > free (tmp_url); > rc =3D -ENOMEM; > goto out1; > } > - server_url_list =3D realloc_ptr; > server_url_list[num_urls-1] =3D tmp_url; > + if(NULL !=3D url_ima_policies) url_ima_policies[num_urls-1] = =3D verification_mode; > } > - server_url =3D strtok_r(NULL, url_delim, &strtok_saveptr); > } > =20 > + /* No URLs survived parsing / filtering? Abort abort abort. */ > + if (num_urls =3D=3D 0) > + { > + rc =3D -ENOSYS; > + goto out1; > + } > + =20 > int retry_limit =3D default_retry_limit; > const char* retry_limit_envvar =3D getenv(DEBUGINFOD_RETRY_LIMIT_ENV_V= AR); > if (retry_limit_envvar !=3D NULL) > @@ -1326,7 +1756,11 @@ debuginfod_query_server (debuginfod_client *c, > if ((server_url =3D server_url_list[i]) =3D=3D NULL) > break; > if (vfd >=3D 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 > =20 > data[i].fd =3D fd; > data[i].target_handle =3D &target_handle; > @@ -1774,6 +2208,33 @@ debuginfod_query_server (debuginfod_client *c, > /* PR31248: lseek back to beginning */ > (void) lseek(fd, 0, SEEK_SET); > =20 > + if(NULL !=3D url_ima_policies && ignore !=3D url_ima_policies[committe= d_to]) > + { > +#ifdef ENABLE_IMA_VERIFICATION > + int result =3D debuginfod_validate_imasig(c, fd); > +#else > + int result =3D -ENOSYS; > +#endif > + if(0 =3D=3D result) > + { > + if (vfd >=3D 0) dprintf (vfd, "valid signature\n"); > + } > + else if(EINVAL =3D=3D result || enforcing =3D=3D url_ima_policies[co= mmitted_to]) > + { > + // All invalid signatures are rejected. > + // Additionally in enforcing mode any non-valid signature is rejec= ted, so by reaching > + // this case we do so since we know it is not valid. Note - this n= ot just invalid signatures > + // but also signatures that cannot be validated > + if (vfd >=3D 0) dprintf (vfd, "error: invalid or missing signature= (%d)\n", result); > + rc =3D -EBADMSG; > + goto out2; > + } > + else > + { > + // NOTREACHED > + } > + } > + > /* rename tmp->real */ > rc =3D rename (target_cache_tmppath, target_cache_path); > if (rc < 0) > @@ -1794,6 +2255,7 @@ debuginfod_query_server (debuginfod_client *c, > for (int i =3D 0; i < num_urls; ++i) > free(server_url_list[i]); > free(server_url_list); > + free(url_ima_policies); > free (data); > free (server_urls); > =20 > @@ -1827,6 +2289,7 @@ debuginfod_query_server (debuginfod_client *c, > for (int i =3D 0; i < num_urls; ++i) > free(server_url_list[i]); > free(server_url_list); > + free(url_ima_policies); > =20 > 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 !=3D NULL) > + (void)unlink (target_cache_tmppath); > free (target_cache_tmppath); > + > + =20 > return rc; > } > =20 > @@ -1891,6 +2358,10 @@ debuginfod_begin (void) > goto out1; > } > =20 > +#ifdef ENABLE_IMA_VERIFICATION > + load_ima_public_keys (client); > +#endif > + > // extra future initialization > =20 > 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); > } > =20 > @@ -1977,9 +2451,11 @@ debuginfod_find_section (debuginfod_client *client= , > { > int rc =3D debuginfod_query_server(client, build_id, build_id_len, > "section", section, path); > - if (rc !=3D -EINVAL) > + if (rc !=3D -EINVAL && rc !=3D -ENOSYS) > return rc; > - > + /* NB: we fall through in case of ima:enforcing-filtered DEBUGINFOD_UR= LS servers, > + so we can download the entire file, verify it locally, then slice i= t. */ > + =20 > /* The servers may have lacked support for section queries. Attempt t= o > download the debuginfo or executable containing the section in orde= r > 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 > =20 > +#ifdef ENABLE_IMA_VERIFICATION > + #include > + #include > + #include > + #include > +#endif > + > #include > #include > #include > @@ -443,6 +450,10 @@ static const struct argp_option options[] =3D > { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do n= ot 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 specif= ic mapping of rpm paths to get IMA signatures.", 0 }, > +#endif > { NULL, 0, NULL, 0, NULL, 0 }, > }; > =20 > @@ -495,6 +506,9 @@ static bool scan_source_info =3D true; > static string tmpdir; > static bool passive_p =3D false; > static long scan_checkpoint =3D 256; > +#ifdef ENABLE_IMA_VERIFICATION > +static bool requires_koji_sigcache_mapping =3D false; > +#endif > =20 > 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"); =20 > break; > +#ifdef ENABLE_IMA_VERIFICATION > + case ARGP_KEY_KOJI_SIGCACHE: > + requires_koji_sigcache_mapping =3D true; > + break; > +#endif > // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_H= ELP_EXIT_OK); > default: return ARGP_ERR_UNKNOWN; > } > @@ -1959,6 +1978,146 @@ handle_buildid_r_match (bool internal_req_p, > return 0; > } > =20 > + // Extract the IMA per-file signature (if it exists) > + string ima_sig =3D ""; > + #ifdef ENABLE_IMA_VERIFICATION > + do > + { > + FD_t rpm_fd; > + if(!(rpm_fd =3D Fopen(b_source0.c_str(), "r.ufdio"))) // read, uncom= pressed, 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 =3D=3D rpmReadPackageFile(NULL, rpm_fd, b_source0.c_st= r(), &rpm_hdr)) > + { > + if (verbose) obatched(clog) << "There was an error while reading t= he 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 signat= ures (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 follow= ing > + - 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 > + =20 > + We therefore need to do a mapping: > + =20 > + 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 =20 > + =20 > + 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 > + =20 > + 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 > + =20 > + See: https://pagure.io/koji/issue/3670 > + */ > + > + // Do the mapping from b_source0 to the koji path for the signed r= pm header > + string signed_rpm_path =3D b_source0; > + size_t insert_pos =3D string::npos; > + for(int i =3D 0; i < 2; i++) insert_pos =3D signed_rpm_path.rfind(= "/", insert_pos) - 1; > + string globbed_path =3D signed_rpm_path.insert(insert_pos + 1, "/= data/sigcache/*").append(".sig"); // The globbed path we're seeking > + glob_t pglob; > + int grc; > + if(0 !=3D (grc =3D glob(globbed_path.c_str(), GLOB_NOSORT, NULL, &= pglob))) > + { > + // Break out, but only report real errors > + if (verbose && grc !=3D GLOB_NOMATCH) obatched(clog) << "There w= as an error (" << strerror(errno) << ") globbing " << globbed_path << endl; > + break; // Exit koji sigcache check > + } > + signed_rpm_path =3D pglob.gl_pathv[0]; // See insight 2 above > + globfree(&pglob); > + > + if (verbose > 2) obatched(clog) << "attempting IMA signature extra= ction from koji header " << signed_rpm_path << endl; > + > + FD_t sig_rpm_fd; > + if(NULL =3D=3D (sig_rpm_fd =3D 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 =3D headerRead(sig_rpm_fd, HEADER_MAGIC_YES /* Vali= date magic too */ ); > + if (!sig_hdr || 1 !=3D headerGet(sig_hdr, RPMSIGTAG_FILESIGNATURES= , &sig_tag_data, HEADERGET_ALLOC)) > + { > + if (verbose) obatched(clog) << "Unable to extract RPMSIGTAG_FILE= SIGNATURES 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 =3D=3D sig_tag_data.count) > + { > + // In the general case (or a fallback from the koji sigcache mappi= ng not finding signatures) > + // we can just (try) extract the signatures from the rpm header > + if (1 !=3D headerGet(rpm_hdr, RPMTAG_FILESIGNATURES, &sig_tag_data= , HEADERGET_ALLOC)) > + { > + if (verbose) obatched(clog) << "Unable to extract RPMTAG_FILESIG= NATURES from " << b_source0 << endl; > + } > + } > + // Search the array for the signature coresponding to b_source1 > + int idx =3D -1; > + char *sig =3D NULL; > + rpmfi hdr_fi =3D rpmfiNew(NULL, rpm_hdr, RPMTAG_BASENAMES, RPMFI_FLA= GS_QUERY); > + do > + { > + sig =3D (char*)rpmtdNextString(&sig_tag_data); > + idx =3D rpmfiNext(hdr_fi); > + } > + while (idx !=3D -1 && 0 !=3D strcmp(b_source1.c_str(), rpmfiFN(hdr_f= i))); > + rpmfiFree(hdr_fi); > + > + if(sig && 0 !=3D strlen(sig) && idx !=3D -1) > + { > + if (verbose > 2) obatched(clog) << "Found IMA signature for " << b= _source1 << ":\n" << sig << endl; > + ima_sig =3D sig; > + inc_metric("http_responses_total","extra","ima-sigs-extracted"); > + } > + else > + { > + if (verbose > 2) obatched(clog) << "Could not find IMA signature f= or " << 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 =3D fdcache.lookup(b_source0, b_source1); > while (fd >=3D 0) // got one!; NB: this is really an if() with a possi= ble 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_st= r()); > add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str()= ); > + if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMAS= IGNATURE", 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=3D" << section << endl; > + << " section=3D" << section > + << " IMA signature=3D" << ima_sig << endl; > /* libmicrohttpd will close it. */ > if (result_fd) > *result_fd =3D fd; > @@ -2204,11 +2365,13 @@ handle_buildid_r_match (bool internal_req_p, > to_string(archive_entry_size(e)).c_st= r()); > add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.= c_str()); > add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_s= tr()); > + 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=3D" << section << endl; > + << " section=3D" << section > + << " IMA signature=3D" << ima_sig << endl; > /* libmicrohttpd will close it. */ > if (result_fd) > *result_fd =3D 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" > =20 > /* 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 > + > + * 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