summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorRyan Goldberg <[email protected]>2023-08-14 13:51:00 -0400
committerFrank Ch. Eigler <[email protected]>2024-05-10 12:18:17 -0400
commit915776dc4ab9308a5c62c42e72b5bd15b7012753 (patch)
tree8915f5453c03a8ecfc563cfc559e989ea562c385 /tests
parent1d69b0f46215960bd9487cf68dba92d88573eed2 (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.am9
-rw-r--r--tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpmbin0 -> 71621 bytes
-rw-r--r--tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sigbin0 -> 13000 bytes
-rw-r--r--tests/debuginfod-ima/koji/fedora-38-ima.pem4
-rw-r--r--tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpmbin0 -> 11702 bytes
-rw-r--r--tests/debuginfod-ima/rhel9/imacert.derbin0 -> 913 bytes
-rwxr-xr-xtests/run-debuginfod-ima-verification.sh181
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
new file mode 100644
index 00000000..b04ad8c2
--- /dev/null
+++ b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm
Binary files differ
diff --git a/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig
new file mode 100644
index 00000000..ee7eb8e4
--- /dev/null
+++ b/tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig
Binary files differ
diff --git a/tests/debuginfod-ima/koji/fedora-38-ima.pem b/tests/debuginfod-ima/koji/fedora-38-ima.pem
new file mode 100644
index 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
new file mode 100644
index 00000000..0262ae2f
--- /dev/null
+++ b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm
Binary files differ
diff --git a/tests/debuginfod-ima/rhel9/imacert.der b/tests/debuginfod-ima/rhel9/imacert.der
new file mode 100644
index 00000000..b0250b6c
--- /dev/null
+++ b/tests/debuginfod-ima/rhel9/imacert.der
Binary files differ
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