diff options
author | Ryan Goldberg <[email protected]> | 2023-08-14 13:51:00 -0400 |
---|---|---|
committer | Frank Ch. Eigler <[email protected]> | 2024-05-10 12:18:17 -0400 |
commit | 915776dc4ab9308a5c62c42e72b5bd15b7012753 (patch) | |
tree | 8915f5453c03a8ecfc563cfc559e989ea562c385 /tests | |
parent | 1d69b0f46215960bd9487cf68dba92d88573eed2 (diff) |
debuginfod: PR28204 - RPM IMA per-file signature verification
Recent versions of Fedora/RHEL include per-file cryptographic
signatures in RPMs, not just an overall RPM signature. This work
extends debuginfod client & server to extract, transfer, and verify
those signatures. These allow clients to assure users that the
downloaded files have not been corrupted since their original
packaging. Downloads that fail the test are rejected.
Clients may select a desired level of enforcement for sets of URLs in
the DEBUGINFOD_URLS by inserting special markers ahead of them:
ima:ignore pay no attention to absence or presence of signatures
ima:enforcing require every file to be correctly signed
The default is ima:ignore mode. In ima:enforcing mode, section
queries are forced to be entire-file downloads, as it is not
possible to crypto-verify just sections.
IMA signatures are verified against a set of signing certificates.
These are normally published by distributions. The environment
variable $DEBUGINFOD_IMA_CERT_PATH contains a colon-separated path for
finding DER or PEM formatted certificates / public keys. These
certificates are assumed trusted. The profile.d scripts transcribe
/etc/debuginfod/*.certdir files into that variable.
As for implementation:
* configure.ac: Add --enable-debuginfod-ima-verification parameter.
Add --enable-default-ima-cert-path=PATH parameter.
Check for libimaevm (using headers only).
* config/Makefile.am: Install defaults into /etc files.
* config/profile.{csh,sh}.in: Process defaults into env variables.
* config/elfutils.spec.in: Add more buildrequires.
* debuginfod/debuginfod.cxx (handle_buildid_r_match): Added extraction of the
per-file IMA signature for the queried file and store in http header.
(find_globbed_koji_filepath): New function.
(parse_opt): New flag --koji-sigcache.
* debuginfod/debuginfod-client.c (debuginfod_query_server): Added policy for
validating IMA signatures
(debuginfod_validate_imasig): New function, with friends.
* debuginfod/debuginfod.h.in: Added DEBUGINFOD_IMA_CERT_PATH_ENV_VAR.
* debuginfod/Makefile.am: Add linker flags for rpm and crypto.
* doc/debuginfod-client-config.7: Document DEBUGINFOD_IMA_CERT_PATH,
update DEBUGINFOD_URLS.
* doc/debuginfod.8: Document --koji-sigcache.
* doc/debuginfod-find.1, doc/debuginfod_find_debuginfo.3: Update SECURITY.
* tests/run-debuginfod-ima-verification.sh: New test.
* tests/debuginfod-ima: Some new files for the tests.
* tests/Makefile.am: run/distribute them.
Signed-off-by: Ryan Goldberg <[email protected]>
Signed-off-by: Frank Ch. Eigler <[email protected]>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 9 | ||||
-rw-r--r-- | tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm | bin | 0 -> 71621 bytes | |||
-rw-r--r-- | tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig | bin | 0 -> 13000 bytes | |||
-rw-r--r-- | tests/debuginfod-ima/koji/fedora-38-ima.pem | 4 | ||||
-rw-r--r-- | tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm | bin | 0 -> 11702 bytes | |||
-rw-r--r-- | tests/debuginfod-ima/rhel9/imacert.der | bin | 0 -> 913 bytes | |||
-rwxr-xr-x | tests/run-debuginfod-ima-verification.sh | 181 |
7 files changed, 194 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am index 7aae3d8a..db071186 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -278,6 +278,9 @@ if !OLD_LIBMICROHTTPD # Too many open file descriptors confuses libmicrohttpd < 0.9.51 TESTS += run-debuginfod-federation-metrics.sh endif +if ENABLE_IMA_VERIFICATION +TESTS += run-debuginfod-ima-verification.sh +endif endif if HAVE_CXX11 @@ -600,6 +603,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-debuginfod-webapi-concurrency.sh \ run-debuginfod-section.sh \ run-debuginfod-IXr.sh \ + run-debuginfod-ima-verification.sh \ debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm \ debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm \ debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm \ @@ -623,6 +627,11 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm \ debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \ debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \ + debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm \ + debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig \ + debuginfod-ima/koji/fedora-38-ima.pem \ + debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm \ + debuginfod-ima/rhel9/imacert.der \ debuginfod-debs/hithere-dbgsym_1.0-1_amd64.ddeb \ debuginfod-debs/hithere_1.0-1.debian.tar.xz \ debuginfod-debs/hithere_1.0-1.dsc \ diff --git a/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm Binary files differnew file mode 100644 index 00000000..b04ad8c2 --- /dev/null +++ b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm diff --git a/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig Binary files differnew file mode 100644 index 00000000..ee7eb8e4 --- /dev/null +++ b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig diff --git a/tests/debuginfod-ima/koji/fedora-38-ima.pem b/tests/debuginfod-ima/koji/fedora-38-ima.pem new file mode 100644 index 00000000..e323fa24 --- /dev/null +++ b/tests/debuginfod-ima/koji/fedora-38-ima.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj5EVzjUa4PW3I3Y/RTkLgfjP3Elu +4AyKdXXxIldW6VVi3QMEpP5eZ7lZmlB2892QFpbWMLNJ4jXlPehMgqNgvg== +-----END PUBLIC KEY----- diff --git a/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm Binary files differnew file mode 100644 index 00000000..0262ae2f --- /dev/null +++ b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm diff --git a/tests/debuginfod-ima/rhel9/imacert.der b/tests/debuginfod-ima/rhel9/imacert.der Binary files differnew file mode 100644 index 00000000..b0250b6c --- /dev/null +++ b/tests/debuginfod-ima/rhel9/imacert.der diff --git a/tests/run-debuginfod-ima-verification.sh b/tests/run-debuginfod-ima-verification.sh new file mode 100755 index 00000000..d582af5f --- /dev/null +++ b/tests/run-debuginfod-ima-verification.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2023-2024 Red Hat, Inc. +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. $srcdir/debuginfod-subr.sh + +type rpmsign 2>/dev/null || { echo "need rpmsign"; exit 77; } +cat << EoF > include.c +#include <rpm/rpmlib.h> +#include <rpm/rpmfi.h> +#include <rpm/header.h> +#include <imaevm.h> +#include <openssl/evp.h> +EoF +tempfiles include.c +gcc -H -fsyntax-only include.c 2> /dev/null || { echo "one or more devel packages are missing (rpm-devel, ima-evm-utils-devel, openssl-devel)"; exit 77; } + +set -x +export DEBUGINFOD_VERBOSE=1 + +DB=${PWD}/.debuginfod_tmp.sqlite +tempfiles $DB +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache +IMA_POLICY="enforcing" + +# This variable is essential and ensures no time-race for claiming ports occurs +# set base to a unique multiple of 100 not used in any other 'run-debuginfod-*' test +base=14000 +get_ports +mkdir R +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \ + -d $DB -p $PORT1 -t0 -g0 R > vlog$PORT1 2>&1 & +PID1=$! +tempfiles vlog$PORT1 +errfiles vlog$PORT1 + +######################################################################## +cp -pv ${abs_srcdir}/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm signed.rpm +tempfiles signed.rpm +RPM_BUILDID=460912dbc989106ec7325d243384df20c5ccec0c # /usr/local/bin/hello + +MIN_IMAEVM_MAJ_VERSION=3 +MIN_RPM_MAJ_VERSION=4 +# If the correct programs (and versions) exist sign the rpm in the test +if false && \ + (command -v openssl &> /dev/null) && \ + (command -v rpmsign &> /dev/null) && \ + (command -v gpg &> /dev/null) && \ + [ $(ldd `which rpmsign` | grep libimaevm | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_IMAEVM_MAJ_VERSION ] && \ + [ $(rpm --version | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_RPM_MAJ_VERSION ] +then + # SIGN THE RPM + # First remove any old signatures + rpmsign --delsign signed.rpm &> /dev/null + rpmsign --delfilesign signed.rpm &> /dev/null + + # Make a gpg keypair (with $PWD as the homedir) + mkdir -m 700 openpgp-revocs.d private-keys-v1.d + gpg --quick-gen-key --yes --homedir ${PWD} --batch --passphrase '' --no-default-keyring --keyring "${PWD}/pubring.kbx" [email protected] 2> /dev/null + + # Create a private DER signing key and a public X509 DER format verification key pair + openssl genrsa | openssl pkcs8 -topk8 -nocrypt -outform PEM -out signing.pem + openssl req -x509 -key signing.pem -out imacert.pem -days 365 -keyform PEM \ + -subj "/C=CA/ST=ON/L=TO/O=Elfutils/CN=www.sourceware.org\/elfutils" + + tempfiles openpgp-revocs.d/* private-keys-v1.d/* * openpgp-revocs.d private-keys-v1.d + + rpmsign --addsign --signfiles --fskpath=signing.pem -D "_gpg_name [email protected]" -D "_gpg_path ${PWD}" signed.rpm + cp signed.rpm R/signed.rpm + VERIFICATION_CERT_DIR=${PWD} + + # Cleanup + rm -rf openpgp-revocs.d private-keys-v1.d +else + # USE A PRESIGNED RPM + cp signed.rpm R/signed.rpm + # Note we test with no trailing / + VERIFICATION_CERT_DIR=${abs_srcdir}/debuginfod-ima/rhel9 +fi + +######################################################################## +# Server must become ready with R fully scanned and indexed +wait_ready $PORT1 'ready' 1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 1 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 + +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT1" + +echo Test 1: Without a certificate the verification should fail +export DEBUGINFOD_IMA_CERT_PATH= +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 2: It should pass once the certificate is added to the path +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 2 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID + +echo Test 3: Corrupt the data and it should fail +dd if=/dev/zero of=R/signed.rpm bs=1 count=128 seek=1024 conv=notrunc +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 3 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 4: A rpm without a signature will fail +cp signed.rpm R/signed.rpm +rpmsign --delfilesign R/signed.rpm +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 4 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 5: Only tests 1,2 will result in extracted signature +[[ $(curl -s http://127.0.0.1:$PORT1/metrics | grep 'http_responses_total{extra="ima-sigs-extracted"}' | awk '{print $NF}') -eq 2 ]] + +kill $PID1 +wait $PID1 +PID1=0 + +####################################################################### +# We also test the --koji-sigcache +cp -pR ${abs_srcdir}/debuginfod-ima/koji R/koji + +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \ + -d $DB -p $PORT2 -t0 -g0 -X /data/ --koji-sigcache R/koji > vlog$PORT1 2>&1 & +#reuse PID1 +PID1=$! +tempfiles vlog$PORT2 +errfiles vlog$PORT2 + +RPM_BUILDID=c592a95e45625d7891b90f6b86e63373d540461d #/usr/bin/hello +# Note we test with a trailing slash +VERIFICATION_CERT_DIR=/not/a/dir:${abs_srcdir}/debuginfod-ima/koji/ + +######################################################################## +# Server must become ready with koji fully scanned and indexed +wait_ready $PORT2 'ready' 1 +wait_ready $PORT2 'thread_work_total{role="traverse"}' 1 +wait_ready $PORT2 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT2 'thread_busy{role="scan"}' 0 + +echo Test 6: The path should be properly mapped and verified using the actual fedora 38 cert +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT2" +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID + +kill $PID1 +wait $PID1 +PID1=0 + +exit 0 |