From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 906883858D39 for ; Thu, 9 May 2024 17:56:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 906883858D39 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 906883858D39 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1715277395; cv=none; b=EV6FTHXfrsEq2GLIQt+gmrEe8Z5xFLgIyjZ+bKki4SBoDnyKsRMkjDNcBXX/Z8bFDrUXdvJloLnSvLb1gN6GWFEE/CH/s+DLYdjE2aXY5eM4XItILiVK8EWd3QQuu2w7r+qNzP2KzixWxQG5CbuwgBVOnwyoiO1x4l5VWhk9PtM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1715277395; c=relaxed/simple; bh=Yd7Vc64ajevsyM0+XWsWnquc4r6myY+3P087W1Abxxo=; h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To; b=WIkMzckIzWa2VxiJ/0cJF1owYUKkUucX7j/RhYeZYJL/NBItrVrY8hXUTBSwGA8uuZYO/C2d3X5Gt3fH6325/eZmCcBK575xsi18LGDGM68qKLzKJSciXlHGgaGg6T6nrGlcKUyMpDO2/28FClmz8wRBL3Dzr6NcsxFVFvFsqvc= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1715277389; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=TcCLcG4y1n6duf85w9e0Rnd7i7S1dLl7uLoCHhC05rw=; b=M4lIJOQxxaFbvmdrSARLMaUp/tvUkNbHEja2uCqth+yPuM3gM4JNA9CvxWUTgt4GiONwSE WduEsoN7sLIfxlwptJ8vyX7/rEVGqxz0ybPnPYoKi6xhMti/6S7KwZCoUWG8yngOeSpg67 CvLVEL1ZNwvdk/WsIsVsIvFJz1tJ/vo= Received: from mail-io1-f71.google.com (mail-io1-f71.google.com [209.85.166.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-220-VJw0YF4BM8a1o-jiW2_gkA-1; Thu, 09 May 2024 13:56:25 -0400 X-MC-Unique: VJw0YF4BM8a1o-jiW2_gkA-1 Received: by mail-io1-f71.google.com with SMTP id ca18e2360f4ac-7e182f6cbdbso122376839f.1 for ; Thu, 09 May 2024 10:56:24 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715277384; x=1715882184; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=TcCLcG4y1n6duf85w9e0Rnd7i7S1dLl7uLoCHhC05rw=; b=FfywbvDE3iE28nrfERyPJbouHaNwNT8y1iiztyu+vLLZXHmQ/qbmwZx7/u9MD8JN8t ne2riNEOgr/DJ3w3k5mqZ98GqGaMqDTx6C48h7OezSST7Epwq2F5xhlyCsD34f6JyYn5 UFWVcSxO6smRjfeUfqXtr7BZ6bPdGaxMuzr6oDL7k0tZoeNaH91VL52ULkrcGBR1dQV/ vxAaM6gaKvQXNuJLubudji4SbDRkgLpgUzpmlOQqGNRubqgA6sX0SH+JGKDOw+LS77b3 mfwff39DLY1sHsJg4W4uZTOHO084n/HStOvYRgZdZA6LaqiMI3eUideEh3JYuGEVlI3Z wp/w== X-Forwarded-Encrypted: i=1; AJvYcCU4qQ3DMTCByJx7sgWyyvMXBpn9gBQxGwKdhswniaTDKcv8IIPimX3ALVXNdHtdJ+T7b8DmxXK8EWJWKUvlqS4lo6pOwqAnoSehUZGTWA== X-Gm-Message-State: AOJu0YwrJQZ1voMKXOaRx/Kasi857vekvKFPUAP+FamOlTG9YSiaY7A7 Tk8R/hZOzQDiTbBf8n2cfFAq+72TTleslvIai2DJ3VwwzVE0L4n/Re3L6JBJSlQ3RJre6ys8ZWO IJFgSXknywRwYdWnv1IXPjHYJSKFugY2Z0CMcmu87YaqDXJMR8GbWyVr4eSqfJjqZmgimFGhJcf RsP7Hzg9nowuhrh3RPF1QLHjy0LkxIaQG6rF+7PKifVKBSpcM= X-Received: by 2002:a05:6e02:188f:b0:36c:5efe:9bdf with SMTP id e9e14a558f8ab-36cc14e1ae4mr4154215ab.27.1715277383438; Thu, 09 May 2024 10:56:23 -0700 (PDT) X-Google-Smtp-Source: AGHT+IG3oMTsoLN3g7gJQDxbOHJMl97m/2xLQ5x050CSj6oEcqnLP53M+TkVN8+x91k2ibEepvJXbcFt0NVZN6KBfOg= X-Received: by 2002:a05:6e02:188f:b0:36c:5efe:9bdf with SMTP id e9e14a558f8ab-36cc14e1ae4mr4153715ab.27.1715277382440; Thu, 09 May 2024 10:56:22 -0700 (PDT) MIME-Version: 1.0 References: <437d21e98ce9c83d2c34cb2c1e13c5aa7af98c2e.camel@klomp.org> <20240416221500.GA8994@redhat.com> <20240505013003.GA1291@redhat.com> In-Reply-To: <20240505013003.GA1291@redhat.com> From: Aaron Merey Date: Thu, 9 May 2024 13:56:11 -0400 Message-ID: Subject: Re: [rfc] [patch] PR28204: debuginfod ima signature verification To: "Frank Ch. Eigler" Cc: Mark Wielaard , elfutils-devel@sourceware.org X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: multipart/mixed; boundary="00000000000012a07806180923ad" X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_BADIPHTTP,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP 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: --00000000000012a07806180923ad Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi Frank, I've pointed out a couple nits below, but otherwise the patch LGTM. I've also attached a diff for handling DEBUGINFOD_IMA_CERT_PATH in profile.fish.in that should apply on top of this patch. I know there's already been a lot of discussion re. ima:permissive and I'm weighing in rather late, but FWIW I do support including it. Currently individual ELF sections cannot be downloaded when ima:enforcing is active. With ima:permissive we could support proper section queries while also being able to perform some amount of ima verification. On Tue, Apr 16, 2024 at 6:15=E2=80=AFPM Frank Ch. Eigler = wrote: > > Hi - > > The following is the candidate patch for the basic functionality. > It's been corrected for whitespace & error codes, given more complete > docs and commit message. See also the users/fche/try-bz2824f branch. > > > debuginfod: PR28204 - RPM IMA per-file signature verification > > Recent versions of Fedora/RHEL include per-file cryptographic > signatures in RPMs, not just an overall RPM signature. This work > extends debuginfod client & server to extract, transfer, and verify > those signatures. These allow clients to assure users that the > downloaded files have not been corrupted since their original > packaging. Downloads that fail the test are rejected. > > Clients may select a desired level of enforcement for sets of URLs in > the DEBUGINFOD_URLS by inserting special markers ahead of them: > > ima:ignore pay no attention to absence or presence of signature= s > 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 fo= r > 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=3DPATH 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 extractio= n of the > per-file IMA signature for the queried file and store in http heade= r. > (find_globbed_koji_filepath): New function. > (parse_opt): New flag --koji-sigcache. > * debuginfod/debuginfod-client.c (debuginfod_query_server): Added pol= icy 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 SECU= RITY. > > * 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 > Signed-off-by: Frank Ch. Eigler > > 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)/debugi= nfod/elfutils.urls; \ > fi > + if [ -n "@DEBUGINFOD_IMA_CERT_PATH@" ]; then \ > + echo "@DEBUGINFOD_IMA_CERT_PATH@" > $(DESTDIR)$(sysconfdi= r)/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 variable= s > # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS= . > > +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= . > > +prefix=3D"@prefix@" > if [ -z "$DEBUGINFOD_URLS" ]; then > prefix=3D"@prefix@" This second definition of prefix can be removed. > 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 > 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=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" > + 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= " =3D "xrpmimaevmcrypto"]) > + > dnl The directories with content. > > dnl Documentation. > @@ -881,6 +908,15 @@ AC_ARG_ENABLE(debuginfod-urls, > fi], > [default_debuginfod_urls=3D""]) > AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls) > +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) It might be helpful to add AC_ARG_ENABLE for debuginfod_ima_verification. Then a configure error can alert us to any missing libraries if --enable-debuginfod-ima-verification=3Dyes. > AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fis= h]) > > 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_e= nabled} ${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 +=3D debuginfod-find > endif > > 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 > > 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)/$(= LIBDEBUGINFOD_SONAME) > ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/li= bdebuginfod.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-clien= t.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 > This file is part of elfutils. > > @@ -47,6 +47,17 @@ > #include > #include > > +#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 > > @@ -92,6 +103,7 @@ void debuginfod_end (debuginfod_client *c) { } > #include > #include > #include > +#include > > /* 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. */ > @@ -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 repre= sents > 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 >=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; > + 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 > + { > + 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 getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR); > + if (cert_paths =3D=3D NULL || cert_paths[0] =3D=3D '\0') > + return; > + cert_paths =3D strdup(cert_paths); // Modified during tokenization > + if (cert_paths =3D=3D NULL) > + return; > + > + char* cert_dir_path; > + DIR *dp; > + struct dirent *entry; > + int vfd =3D c->verbose_fd; > + > + 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'; > + > + 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)) > + 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 %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 */ > + > + 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) > { > @@ -861,6 +1066,199 @@ cache_find_section (const char *scn_name, const ch= ar *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 =3D 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 >=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; > + } > + > + 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. Return 0 on ok, -err= no otherwise. */ > +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 -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 alia= sing */ > + > + if (c->verbose_fd >=3D 0) > + dprintf (c->verbose_fd, "Searching for ima keyid %04x\n", ntohl(hdr.= keyid)); > + > + /* 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; // success! > + else if (ret =3D=3D 0) > + ret =3D -EBADMSG; > + err: > + 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; This line can be removed. > + 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 > + 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; > + } > + > + if (!EVP_DigestUpdate(ctx, file_data, n)) > + { > + rc =3D -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 =3D -EBADMSG; > + goto exit_validate; > + } > + > + // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo = dawg > + > + 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; > + > + 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 > @@ -1216,12 +1614,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; > > - 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; > + } > + > /* 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 '/') > @@ -1253,21 +1678,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); > } > > + /* No URLs survived parsing / filtering? Abort abort abort. */ > + if (num_urls =3D=3D 0) > + { > + rc =3D -ENOSYS; > + goto out1; > + } > + > 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) > @@ -1334,7 +1766,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 > > data[i].fd =3D fd; > data[i].target_handle =3D &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 !=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 (enforcing =3D=3D url_ima_policies[committed_to]) > + { > + // All invalid signatures are rejected. > + // Additionally in enforcing mode any non-valid signature is r= ejected, so by reaching > + // this case we do so since we know it is not valid. Note - th= is not just invalid signatures > + // but also signatures that cannot be validated > + if (vfd >=3D 0) dprintf (vfd, "error: invalid or missing signa= ture (%d)\n", result); > + rc =3D result; > + goto out2; > + } > + } > + > /* rename tmp->real */ > rc =3D rename (target_cache_tmppath, target_cache_path); > if (rc < 0) > @@ -1804,6 +2263,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); > > @@ -1837,6 +2297,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); > > 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 !=3D 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 =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. */ > + > /* 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. */ > 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 > + #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 }, > }; > > @@ -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 > > 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 =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,145 @@ handle_buildid_r_match (bool internal_req_p, > return 0; > } > > + // 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, unc= ompressed, rpm/rpmio.h > + { > + if (verbose) obatched(clog) << "There was an error while openi= ng " << b_source0 << endl; > + break; // Exit IMA extraction > + } > + > + Header rpm_hdr; > + if(RPMRC_FAIL =3D=3D rpmReadPackageFile(NULL, rpm_fd, b_source0.c_= str(), &rpm_hdr)) > + { > + if (verbose) obatched(clog) << "There was an error while readi= ng 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 sign= atures (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 checkin= g 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 scann= ed 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 directorie= s 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 t= he > + PACKAGE_KEYID* directories. For simplicity we choose fir= st we > + match against > + > + See: https://pagure.io/koji/issue/3670 > + */ > + > + // Do the mapping from b_source0 to the koji path for the si= gned rpm 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, N= ULL, &pglob))) > + { > + // Break out, but only report real errors > + if (verbose && grc !=3D GLOB_NOMATCH) obatched(clog) << = "There was an error (" << strerror(errno) << ") globbing " << globbed_path = << endl; > + break; // Exit koji sigcache check > + } > + signed_rpm_path =3D pglob.gl_pathv[0]; // See insight 2 abov= e > + 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 =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 /= * Validate magic too */ ); > + if (!sig_hdr || 1 !=3D headerGet(sig_hdr, RPMSIGTAG_FILESIGN= ATURES, &sig_tag_data, HEADERGET_ALLOC)) > + { > + if (verbose) obatched(clog) << "Unable to extract RPMSIG= TAG_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 =3D=3D sig_tag_data.count) > + { > + // In the general case (or a fallback from the koji sigcache m= apping not finding signatures) > + // we can just (try) extract the signatures from the rpm heade= r > + if (1 !=3D headerGet(rpm_hdr, RPMTAG_FILESIGNATURES, &sig_tag_= data, HEADERGET_ALLOC)) > + { > + if (verbose) obatched(clog) << "Unable to extract RPMTAG_F= ILESIGNATURES 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_F= LAGS_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= _fi))); > + 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 signatu= re 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 =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 +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_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 +2364,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" > > /* The libdebuginfod soname. */ > #define DEBUGINFOD_SONAME "@LIBDEBUGINFOD_SONAME@" > diff --git a/doc/debuginfod-client-config.7 b/doc/debuginfod-client-confi= g.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 sepa= rated 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=3D"https://foo.com ima:enforcing https://bar.ca http://l= ocalhost: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 line= s 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 ser= ver. > > .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 styl= e. > 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 ar= e retrieved > +from the Fedora koji sigcache rpm.sig files as opposed to the original R= PM header. > +If a signature cannot be found in the sigcache rpm.sig file, the RPM wil= l 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 HTT= P 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 e= xample: > +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_debugi= nfo.3 > index 0d553665f42b..4e359c8c4bd4 100644 > --- a/doc/debuginfod_find_debuginfo.3 > +++ b/doc/debuginfod_find_debuginfo.3 > @@ -251,13 +251,21 @@ void *debuginfod_so =3D dlopen(DEBUGINFOD_SONAME, R= TLD_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, exc= ept > +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 styl= e. > (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 +=3D run-debuginfod-federation-metrics.sh > endif > +if ENABLE_IMA_VERIFICATION > +TESTS +=3D run-debuginfod-ima-verification.sh > +endif > endif > > if HAVE_CXX11 > @@ -600,6 +603,7 @@ EXTRA_DIST =3D 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 =3D 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.f= c38.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.1= 0-9.fc38.x86_64.rpm.sig b/tests/debuginfod-ima/koji/data/sigcache/keyid/arc= h/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/keyi= d/arch/hello-2.10-9.fc38.x86_64.rpm.sig differ > diff --git a/tests/debuginfod-ima/koji/fedora-38-ima.pem b/tests/debuginf= od-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=3D=3D > +-----END PUBLIC KEY----- > diff --git a/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm b/tests/d= ebuginfod-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-im= a/rhel9/imacert.der > new file mode 100644 > index 000000000000..b0250b6c30d5 > Binary files /dev/null and b/tests/debuginfod-ima/rhel9/imacert.der diffe= r > diff --git a/tests/run-debuginfod-ima-verification.sh b/tests/run-debugin= fod-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 . > + > +. $srcdir/debuginfod-subr.sh > + > +type rpmsign 2>/dev/null || { echo "need rpmsign"; exit 77; } > +cat << EoF > include.c > +#include > +#include > +#include > +#include > +#include > +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)"; exi= t 77; } > + > +set -x > +export DEBUGINFOD_VERBOSE=3D1 > + > +DB=3D${PWD}/.debuginfod_tmp.sqlite > +tempfiles $DB > +export DEBUGINFOD_CACHE_PATH=3D${PWD}/.client_cache > +IMA_POLICY=3D"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-debugi= nfod-*' test > +base=3D14000 > +get_ports > +mkdir R > +env LD_LIBRARY_PATH=3D$ldpath DEBUGINFOD_URLS=3D ${abs_builddir}/../debu= ginfod/debuginfod $VERBOSE -R \ > + -d $DB -p $PORT1 -t0 -g0 R > vlog$PORT1 2>&1 & > +PID1=3D$! > +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=3D460912dbc989106ec7325d243384df20c5ccec0c # /usr/local/bin/= hello > + > +MIN_IMAEVM_MAJ_VERSION=3D3 > +MIN_RPM_MAJ_VERSION=3D4 > +# 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 veri= fication key pair > + openssl genrsa | openssl pkcs8 -topk8 -nocrypt -outform PEM -out sig= ning.pem > + openssl req -x509 -key signing.pem -out imacert.pem -days 365 -keyfo= rm PEM \ > + -subj "/C=3DCA/ST=3DON/L=3DTO/O=3DElfutils/CN=3Dwww.sourceware.o= rg\/elfutils" > + > + tempfiles openpgp-revocs.d/* private-keys-v1.d/* * openpgp-revocs.d = private-keys-v1.d > + > + rpmsign --addsign --signfiles --fskpath=3Dsigning.pem -D "_gpg_name = example@elfutils.org" -D "_gpg_path ${PWD}" signed.rpm > + cp signed.rpm R/signed.rpm > + VERIFICATION_CERT_DIR=3D${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=3D${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=3D"traverse"}' 1 > +wait_ready $PORT1 'thread_work_pending{role=3D"scan"}' 0 > +wait_ready $PORT1 'thread_busy{role=3D"scan"}' 0 > + > +export DEBUGINFOD_URLS=3D"ima:$IMA_POLICY http://127.0.0.1:$PORT1" > + > +echo Test 1: Without a certificate the verification should fail > +export DEBUGINFOD_IMA_CERT_PATH=3D > +RC=3D0 > +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $R= PM_BUILDID || RC=3D1 > +test $RC -ne 0 > + > +echo Test 2: It should pass once the certificate is added to the path > +export DEBUGINFOD_IMA_CERT_PATH=3D$VERIFICATION_CERT_DIR > +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests > +kill -USR1 $PID1 > +wait_ready $PORT1 'thread_work_total{role=3D"traverse"}' 2 > +wait_ready $PORT1 'thread_work_pending{role=3D"scan"}' 0 > +wait_ready $PORT1 'thread_busy{role=3D"scan"}' 0 > +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $R= PM_BUILDID > + > +echo Test 3: Corrupt the data and it should fail > +dd if=3D/dev/zero of=3DR/signed.rpm bs=3D1 count=3D128 seek=3D1024 conv= =3Dnotrunc > +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests > +kill -USR1 $PID1 > +wait_ready $PORT1 'thread_work_total{role=3D"traverse"}' 3 > +wait_ready $PORT1 'thread_work_pending{role=3D"scan"}' 0 > +wait_ready $PORT1 'thread_busy{role=3D"scan"}' 0 > +RC=3D0 > +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_B= UILDID || RC=3D1 > +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=3D"traverse"}' 4 > +wait_ready $PORT1 'thread_work_pending{role=3D"scan"}' 0 > +wait_ready $PORT1 'thread_busy{role=3D"scan"}' 0 > +RC=3D0 > +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_B= UILDID || RC=3D1 > +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_tota= l{extra=3D"ima-sigs-extracted"}' | awk '{print $NF}') -eq 2 ]] > + > +kill $PID1 > +wait $PID1 > +PID1=3D0 > + > +####################################################################### > +# 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=3D$ldpath DEBUGINFOD_URLS=3D ${abs_builddir}/../debu= ginfod/debuginfod $VERBOSE -R \ > + -d $DB -p $PORT2 -t0 -g0 -X /data/ --koji-sigcache R/koji > vlog$POR= T1 2>&1 & > +#reuse PID1 > +PID1=3D$! > +tempfiles vlog$PORT2 > +errfiles vlog$PORT2 > + > +RPM_BUILDID=3Dc592a95e45625d7891b90f6b86e63373d540461d #/usr/bin/hello > +# Note we test with a trailing slash > +VERIFICATION_CERT_DIR=3D/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=3D"traverse"}' 1 > +wait_ready $PORT2 'thread_work_pending{role=3D"scan"}' 0 > +wait_ready $PORT2 'thread_busy{role=3D"scan"}' 0 > + > +echo Test 6: The path should be properly mapped and verified using the a= ctual fedora 38 cert > +export DEBUGINFOD_URLS=3D"ima:$IMA_POLICY http://127.0.0.1:$PORT2" > +export DEBUGINFOD_IMA_CERT_PATH=3D$VERIFICATION_CERT_DIR > +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $R= PM_BUILDID > + > +kill $PID1 > +wait $PID1 > +PID1=3D0 > + > +exit 0 > Aaron --00000000000012a07806180923ad Content-Type: text/x-patch; charset="US-ASCII"; name="fish-ima.diff" Content-Disposition: attachment; filename="fish-ima.diff" Content-Transfer-Encoding: base64 Content-ID: X-Attachment-Id: f_lvzjt2jd0 ZGlmZiAtLWdpdCBhL2NvbmZpZy9wcm9maWxlLmZpc2guaW4gYi9jb25maWcvcHJvZmlsZS5maXNo LmluCmluZGV4IGMwYTIzNGRiLi4xMmI1ZTZmMSAxMDA2NDQKLS0tIGEvY29uZmlnL3Byb2ZpbGUu ZmlzaC5pbgorKysgYi9jb25maWcvcHJvZmlsZS5maXNoLmluCkBAIC00LDEyICs0LDIxIEBACiAj IFNlZSBhbHNvIFttYW4gZGVidWdpbmZvZC1jbGllbnQtY29uZmlnXSBmb3Igb3RoZXIgZW52aXJv bm1lbnQgdmFyaWFibGVzCiAjIHN1Y2ggYXMgJERFQlVHSU5GT0RfTUFYU0laRSwgJERFQlVHSU5G T0RfTUFYVElNRSwgJERFQlVHSU5GT0RfUFJPR1JFU1MuCiAKKyMgVXNlIGxvY2FsIHZhcmlhYmxl cyBzbyB3ZSBkb24ndCBuZWVkIHRvIG1hbnVhbGx5IHVuc2V0IHRoZW0KK3NldCAtLWxvY2FsIHBy ZWZpeCAiQHByZWZpeEAiCisKIGlmIG5vdCBzZXQgLS1xdWVyeSBERUJVR0lORk9EX1VSTFMKLSAg ICAjIFVzZSBsb2NhbCB2YXJpYWJsZXMgc28gd2UgZG9uJ3QgbmVlZCB0byBtYW51YWxseSB1bnNl dCB0aGVtCi0gICAgc2V0IC0tbG9jYWwgcHJlZml4ICJAcHJlZml4QCIKICAgICBzZXQgLS1sb2Nh bCBmaWxlcyAiQHN5c2NvbmZkaXJAL2RlYnVnaW5mb2QvIioudXJscwogICAgIHNldCAtLWxvY2Fs IERFQlVHSU5GT0RfVVJMUyAoY2F0IC9kZXYvbnVsbCAkZmlsZXMgMj4vZGV2L251bGwgfCBzdHJp bmcgcmVwbGFjZSAnXG4nICcgJykKICAgICBpZiB0ZXN0IC1uICIkREVCVUdJTkZPRF9VUkxTIgog ICAgICAgICBzZXQgLS1nbG9iYWwgLS1leHBvcnQgREVCVUdJTkZPRF9VUkxTICIkREVCVUdJTkZP RF9VUkxTIgogICAgIGVuZAogZW5kCisKK2lmIG5vdCBzZXQgLS1xdWVyeSBERUJVR0lORk9EX0lN QV9DRVJUX1BBVEgKKyAgICBzZXQgLS1sb2NhbCBmaWxlcyAiQHN5c2NvbmZkaXJAL2RlYnVnaW5m b2QvIiouY2VydHBhdGgKKyAgICBzZXQgLS1sb2NhbCBERUJVR0lORk9EX0lNQV9DRVJUX1BBVEgg KGNhdCAvZGV2L251bGwgJGZpbGVzIDI+L2Rldi9udWxsIHwgc3RyaW5nIHJlcGxhY2UgJ1xuJyAn OicpCisgICAgaWYgdGVzdCAtbiAiJERFQlVHSU5GT0RfSU1BX0NFUlRfUEFUSCIKKyAgICAgICAg c2V0IC0tZ2xvYmFsIC0tZXhwb3J0IERFQlVHSU5GT0RfSU1BX0NFUlRfUEFUSCAiJERFQlVHSU5G T0RfSU1BX0NFUlRfUEFUSCIKKyAgICBlbmQKK2VuZAo= --00000000000012a07806180923ad--