summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrank Ch. Eigler <fche@redhat.com>2024-03-30 13:04:14 -0400
committerFrank Ch. Eigler <fche@redhat.com>2024-04-03 16:45:56 -0400
commit56dbea31bd0306d18f813b75beaf592cef029dd4 (patch)
tree0ba73a7082377d8b3aab1c261a2286fb8514c7ee
parent1a1c02ebf257b496becddd9f788660945aba3731 (diff)
PR28204 debuginfod ima verification review changes cont'dupstream/users/fche/try-bz28204e
- Drop use of libimaevm.so. Instead, reimplement the core signature computation functions based on openssl functions directly, using only imaevm.h for struct decls. - Add an --enable-debuginfod-ima-cert-path=PATH configury option to populate /etc/debuginfod/*.imacert files, which profile.d scripts consume
-rw-r--r--config/Makefile.am4
-rw-r--r--config/profile.csh.in10
-rw-r--r--config/profile.sh.in8
-rw-r--r--configure.ac15
-rw-r--r--debuginfod/Makefile.am2
-rw-r--r--debuginfod/debuginfod-client.c510
-rw-r--r--debuginfod/debuginfod.cxx2
-rw-r--r--doc/debuginfod_find_debuginfo.34
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/debuginfod-ima/koji/fedora-38-ima.pem4
-rwxr-xr-xtests/run-debuginfod-ima-verification.sh24
11 files changed, 395 insertions, 189 deletions
diff --git a/config/Makefile.am b/config/Makefile.am
index ae14e625..5a28e66d 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -46,12 +46,16 @@ install-data-local:
if [ -n "@DEBUGINFOD_URLS@" ]; then \
echo "@DEBUGINFOD_URLS@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls; \
fi
+ if [ -n "@DEBUGINFOD_IMA_CERT_PATH@" ]; then \
+ echo "@DEBUGINFOD_IMA_CERT_PATH@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath; \
+ fi
uninstall-local:
rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.sh
rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.csh
rm -f $(DESTDIR)$(datadir)/fish/vendor_conf.d/debuginfod.fish
rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls
+ rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath
-rmdir $(DESTDIR)$(sysconfdir)/debuginfod
endif
diff --git a/config/profile.csh.in b/config/profile.csh.in
index 2a2ecacb..1da9626c 100644
--- a/config/profile.csh.in
+++ b/config/profile.csh.in
@@ -9,12 +9,14 @@ if (! $?DEBUGINFOD_URLS) then
set DEBUGINFOD_URLS=`sh -c 'cat /dev/null "$0"/*.urls 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ' '`
if ( "$DEBUGINFOD_URLS" != "" ) then
setenv DEBUGINFOD_URLS "$DEBUGINFOD_URLS"
- if (! $?DEBUGINFOD_IMA_CERT_PATH) then
- set DEBUGINFOD_IMA_CERT_PATH="@sysconfdir@/debuginfod/ima-certs/:@DEBUGINFOD_IMA_CERT_PATH@"
- setenv DEBUGINFOD_IMA_CERT_PATH "$DEBUGINFOD_IMA_CERT_PATH"
- endif
else
unset DEBUGINFOD_URLS
endif
+ set DEBUGINFOD_IMA_CERT_PATH=`sh -c 'cat /dev/null "$0"/*.certpath 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ':'`
+ if ( "$DEBUGINFOD_IMA_CERT_PATH" != "" ) then
+ setenv DEBUGINFOD_IMA_CERT_PATH "$DEBUGINFOD_IMA_CERT_PATH"
+ else
+ unset DEBUGINFOD_IMA_CERT_PATH
+ endif
endif
unset prefix
diff --git a/config/profile.sh.in b/config/profile.sh.in
index 16926382..7db39996 100644
--- a/config/profile.sh.in
+++ b/config/profile.sh.in
@@ -9,10 +9,10 @@ if [ -z "$DEBUGINFOD_URLS" ]; then
prefix="@prefix@"
DEBUGINFOD_URLS=$(cat /dev/null "@sysconfdir@/debuginfod"/*.urls 2>/dev/null | tr '\n' ' ' || :)
[ -n "$DEBUGINFOD_URLS" ] && export DEBUGINFOD_URLS || unset DEBUGINFOD_URLS
+fi
- if [ -z "$DEBUGINFOD_IMA_CERT_PATH" ]; then
- DEBUGINFOD_IMA_CERT_PATH="@sysconfdir@/debuginfod/ima-certs/:@DEBUGINFOD_IMA_CERT_PATH@"
- export DEBUGINFOD_IMA_CERT_PATH
- fi
+if [ -z "$DEBUGINFOD_IMA_CERT_PATH" ]; then
+ DEBUGINFOD_IMA_CERT_PATH=$(cat "@sysconfdir@/debuginfod"/*.certpath 2>/dev/null | tr '\n' ':' || :)
+ [ -n "$DEBUGINFOD_IMA_CERT_PATH" ] && export DEBUGINFOD_IMA_CERT_PATH || unset DEBUGINFOD_IMA_CERT_PATH
fi
unset prefix
diff --git a/configure.ac b/configure.ac
index 0324c5c0..19ccf107 100644
--- a/configure.ac
+++ b/configure.ac
@@ -677,9 +677,9 @@ AC_CHECK_LIB(rpm, headerGet, [
[], [#include <rpm/rpmlib.h>])
])
-AC_CHECK_LIB(imaevm, imaevm_hash_algo_from_sig, [
+dnl we use only the header, not the code of this library
+AC_CHECK_HEADER(imaevm.h, [
enable_ima_verification=$enable_ima_verification"imaevm"
- AC_SUBST(imaevm_LIBS, '-limaevm')
])
AC_CHECK_LIB(crypto, EVP_MD_CTX_new, [
@@ -910,6 +910,15 @@ AC_ARG_ENABLE(debuginfod-urls,
fi],
[default_debuginfod_urls=""])
AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls)
+AC_ARG_ENABLE(debuginfod-ima-cert-path,
+ [AS_HELP_STRING([--enable-debuginfod-ima-cert-path@<:@=PATH@:>@],[add PATH to profile.d DEBUGINFOD_IMA_CERT_PATH])],
+ [if test "x${enableval}" = "xyes";
+ then AC_MSG_ERROR([PATH required])
+ elif test "x${enableval}" != "xno"; then
+ default_debuginfod_ima_cert_path="${enableval}";
+ fi],
+ [default_debuginfod_ima_cert_path=""])
+AC_SUBST(DEBUGINFOD_IMA_CERT_PATH, $default_debuginfod_ima_cert_path)
AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fish])
AC_OUTPUT
@@ -949,7 +958,7 @@ AC_MSG_NOTICE([
libdebuginfod client support : ${enable_libdebuginfod}
Debuginfod server support : ${enable_debuginfod}
Default DEBUGINFOD_URLS : ${default_debuginfod_urls}
- Debuginfod RPM sig checking : ${debuginfod_ima_verification_enabled}
+ Debuginfod RPM sig checking : ${debuginfod_ima_verification_enabled} ${default_debuginfod_ima_cert_path}
EXTRA TEST FEATURES (used with make check)
have bunzip2 installed (required) : ${HAVE_BUNZIP2}
diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am
index 467f3530..5e4f9669 100644
--- a/debuginfod/Makefile.am
+++ b/debuginfod/Makefile.am
@@ -97,7 +97,7 @@ libdebuginfod_so_LIBS = libdebuginfod_pic.a
if DUMMY_LIBDEBUGINFOD
libdebuginfod_so_LDLIBS =
else
-libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) $(imaevm_LIBS) $(crypto_LIBS)
+libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) $(crypto_LIBS)
endif
$(LIBDEBUGINFOD_SONAME): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
$(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
index 67e5e84b..4618234f 100644
--- a/debuginfod/debuginfod-client.c
+++ b/debuginfod/debuginfod-client.c
@@ -1,5 +1,5 @@
/* Retrieve ELF / DWARF / source files from the debuginfod.
- Copyright (C) 2019-2021 Red Hat, Inc.
+ Copyright (C) 2019-2024 Red Hat, Inc.
Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
This file is part of elfutils.
@@ -47,6 +47,17 @@
#include <stdlib.h>
#include <gelf.h>
+#ifdef ENABLE_IMA_VERIFICATION
+#include <openssl/sha.h>
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/x509v3.h>
+#include <arpa/inet.h>
+#include <imaevm.h>
+#endif
+typedef enum {ignore, enforcing, undefined} ima_policy_t;
+
+
/* We might be building a bootstrap dummy library, which is really simple. */
#ifdef DUMMY_LIBDEBUGINFOD
@@ -115,65 +126,7 @@ void debuginfod_end (debuginfod_client *c) { }
#include <pthread.h>
-typedef enum {ignore, enforcing, undefined} ima_policy_t;
-#ifdef ENABLE_IMA_VERIFICATION
- #include <imaevm.h>
- #include <openssl/pem.h>
- #include <openssl/evp.h>
- #include <openssl/x509v3.h>
- #include <arpa/inet.h>
- static inline unsigned char hex2dec(char c)
- {
- if (c >= '0' && c <= '9') return (c - '0');
- if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
- if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
- return 0;
- }
- static inline ima_policy_t ima_policy_str2enum(const char* ima_pol)
- {
- if (NULL == ima_pol) return undefined;
- if (0 == strcmp(ima_pol, "ignore")) return ignore;
- if (0 == strcmp(ima_pol, "enforcing")) return enforcing;
- return undefined;
- }
-
- static inline const char* ima_policy_enum2str(ima_policy_t ima_pol)
- {
- switch (ima_pol)
- {
- case ignore:
- return "ignore";
- case enforcing:
- return "enforcing";
- case undefined:
- return "undefined";
- }
- return "";
- }
-
- static uint32_t extract_skid(X509* x509)
- {
- if (!x509) return 0;
- uint32_t keyid = 0;
- // Attempt to get the skid from the certificate
- const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509);
- if (skid_asn1_str)
- {
- int skid_len = ASN1_STRING_length(skid_asn1_str);
- memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid));
- }
- else
- {
- // If it is not there fallback to trying to extract it from the
- // public key itself
- EVP_PKEY * pkey = X509_get0_pubkey(x509);
- char name[PATH_MAX];
- calc_keyid_v2(&keyid, name, pkey);
- }
- return ntohl(keyid);
- }
-#endif
static pthread_once_t init_control = PTHREAD_ONCE_INIT;
@@ -183,6 +136,17 @@ libcurl_init(void)
curl_global_init(CURL_GLOBAL_DEFAULT);
}
+
+#ifdef ENABLE_IMA_VERIFICATION
+struct public_key_entry
+{
+ struct public_key_entry *next; /* singly-linked list */
+ uint32_t keyid; /* last 4 bytes of sha1 of public key */
+ EVP_PKEY *key; /* openssl */
+};
+#endif
+
+
struct debuginfod_client
{
/* Progress/interrupt callback function. */
@@ -217,8 +181,14 @@ struct debuginfod_client
handle data, etc. So those don't have to be reparsed and
recreated on each request. */
char * winning_headers;
+
+#ifdef ENABLE_IMA_VERIFICATION
+ /* IMA public keys */
+ struct public_key_entry *ima_public_keys;
+#endif
};
+
/* The cache_clean_interval_s file within the debuginfod cache specifies
how frequently the cache should be cleaned. The file's st_mtime represents
the time of last cleaning. */
@@ -278,6 +248,179 @@ struct handle_data
size_t response_data_size;
};
+
+
+#ifdef ENABLE_IMA_VERIFICATION
+ static inline unsigned char hex2dec(char c)
+ {
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
+ if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
+ return 0;
+ }
+
+ static inline ima_policy_t ima_policy_str2enum(const char* ima_pol)
+ {
+ if (NULL == ima_pol) return undefined;
+ if (0 == strcmp(ima_pol, "ignore")) return ignore;
+ if (0 == strcmp(ima_pol, "enforcing")) return enforcing;
+ return undefined;
+ }
+
+ static inline const char* ima_policy_enum2str(ima_policy_t ima_pol)
+ {
+ switch (ima_pol)
+ {
+ case ignore:
+ return "ignore";
+ case enforcing:
+ return "enforcing";
+ case undefined:
+ return "undefined";
+ }
+ return "";
+ }
+
+
+static uint32_t extract_skid_pk(EVP_PKEY *pkey) // compute keyid by public key hashing
+{
+ if (!pkey) return 0;
+ uint32_t keyid = 0;
+ X509_PUBKEY *pk = NULL;
+ const unsigned char *public_key = NULL;
+ int len;
+ if (X509_PUBKEY_set(&pk, pkey) &&
+ X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk))
+ {
+ uint8_t sha1[SHA_DIGEST_LENGTH];
+ SHA1(public_key, len, sha1);
+ memcpy(&keyid, sha1 + 16, 4);
+ }
+ X509_PUBKEY_free(pk);
+ return ntohl(keyid);
+}
+
+
+static uint32_t extract_skid(X509* x509) // compute keyid from cert or its public key
+ {
+ if (!x509) return 0;
+ uint32_t keyid = 0;
+ // Attempt to get the skid from the certificate
+ const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509);
+ if (skid_asn1_str)
+ {
+ int skid_len = ASN1_STRING_length(skid_asn1_str);
+ memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid));
+ }
+ else // compute keyid ourselves by hashing public key
+ {
+ EVP_PKEY *pkey = X509_get0_pubkey(x509);
+ keyid = htonl(extract_skid_pk(pkey));
+ }
+ return ntohl(keyid);
+ }
+
+
+static void load_ima_public_keys (debuginfod_client *c)
+{
+ /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. */
+ char *cert_paths = strdup (getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR) ?: DEBUGINFOD_IMA_CERT_PATH_DEFAULT);
+ if (!cert_paths)
+ return;
+
+ char* cert_dir_path;
+ DIR *dp;
+ struct dirent *entry;
+ int vfd = c->verbose_fd;
+
+ char *strtok_context = NULL;
+ for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context);
+ cert_dir_path != NULL;
+ cert_dir_path = strtok_r(NULL, ":", &strtok_context))
+ {
+ dp = opendir(cert_dir_path);
+ if(!dp) continue;
+ while((entry = readdir(dp)))
+ {
+ // Only consider regular files with common x509 cert extensions
+ if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer|cert)", entry->d_name, FNM_EXTMATCH)) continue;
+ char certfile[PATH_MAX];
+ strncpy(certfile, cert_dir_path, PATH_MAX - 1);
+ if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/';
+ strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1);
+ certfile[strlen(certfile)] = '\0';
+
+ FILE *cert_fp = fopen(certfile, "r");
+ if(!cert_fp) continue;
+
+ X509 *x509 = NULL;
+ EVP_PKEY *pkey = NULL;
+ char *fmt = "";
+ // Attempt to read the fp as DER
+ if(d2i_X509_fp(cert_fp, &x509))
+ fmt = "der ";
+ // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used
+ // Note we fseek since this is the second time we read from the fp
+ else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL))
+ fmt = "pem "; // PEM with full certificate
+ else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKEY(cert_fp, &pkey, NULL, NULL))
+ fmt = "pem "; // some PEM files have just a PUBLIC KEY in them
+ fclose(cert_fp);
+
+ if (x509)
+ {
+ struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
+ if (ne)
+ {
+ ne->key = X509_extract_key(x509);
+ ne->keyid = extract_skid(x509);
+ ne->next = c->ima_public_keys;
+ c->ima_public_keys = ne;
+ if (vfd >= 0)
+ dprintf(vfd, "Loaded %scertificate %s, keyid = %04x\n", fmt, certfile, ne->keyid);
+ }
+ X509_free (x509);
+ }
+ else if (pkey)
+ {
+ struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
+ if (ne)
+ {
+ ne->key = pkey; // preserve refcount
+ ne->keyid = extract_skid_pk(pkey);
+ ne->next = c->ima_public_keys;
+ c->ima_public_keys = ne;
+ if (vfd >= 0)
+ dprintf(vfd, "Loaded %spubkey %s, keyid = %04x\n", fmt, certfile, ne->keyid);
+ }
+ }
+ else
+ {
+ if (vfd >= 0)
+ dprintf(vfd, "Cannot load certificate %s\n", certfile);
+ }
+ } /* for each file in directory */
+ closedir(dp);
+ } /* for each directory */
+
+ free(cert_paths);
+}
+
+
+static void free_ima_public_keys (debuginfod_client *c)
+{
+ while (c->ima_public_keys)
+ {
+ EVP_PKEY_free (c->ima_public_keys->key);
+ struct public_key_entry *oen = c->ima_public_keys->next;
+ free (c->ima_public_keys);
+ c->ima_public_keys = oen;
+ }
+}
+#endif
+
+
+
static size_t
debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
{
@@ -914,27 +1057,111 @@ cache_find_section (const char *scn_name, const char *target_cache_dir,
return rc;
}
+
+#ifdef ENABLE_IMA_VERIFICATION
+/* Extract the hash algorithm name from the signature header, of which
+ there are several types. The name will be used for openssl hashing
+ of the file content. The header doesn't need to be super carefully
+ parsed, because if any part of it is wrong, be it the hash
+ algorithm number or hash value or whatever, it will fail
+ computation or verification. Return NULL in case of error. */
+static const char*
+get_signature_params(debuginfod_client *c, unsigned char *bin_sig)
+{
+ int hashalgo = 0;
+
+ switch (bin_sig[0])
+ {
+ case EVM_IMA_XATTR_DIGSIG:
+#ifdef IMA_VERITY_DIGSIG /* missing on debian-i386 trybot */
+ case IMA_VERITY_DIGSIG:
+#endif
+ break;
+ default:
+ if (c->verbose_fd >= 0)
+ dprintf (c->verbose_fd, "Unknown ima digsig %d\n", (int)bin_sig[0]);
+ return NULL;
+ }
+
+ switch (bin_sig[1])
+ {
+ case DIGSIG_VERSION_2:
+ struct signature_v2_hdr hdr_v2;
+ memcpy(& hdr_v2, & bin_sig[1], sizeof(struct signature_v2_hdr));
+ hashalgo = hdr_v2.hash_algo;
+ break;
+ default:
+ if (c->verbose_fd >= 0)
+ dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (int)bin_sig[1]);
+ return NULL;
+ }
+
+ switch (hashalgo)
+ {
+ case PKEY_HASH_SHA1: return "sha1";
+ case PKEY_HASH_SHA256: return "sha256";
+ // (could add many others from enum pkey_hash_algo)
+ default:
+ if (c->verbose_fd >= 0)
+ dprintf (c->verbose_fd, "Unknown ima pkey hash %d\n", hashalgo);
+ return NULL;
+ }
+}
+
+
+/* Verify given hash against given signature blob */
+static int
+debuginfod_verify_hash(debuginfod_client *c, const unsigned char *hash, int size,
+ const char *hash_algo, unsigned char *sig, int siglen)
+{
+ int ret = -EINVAL;
+ struct public_key_entry *pkey;
+ struct signature_v2_hdr hdr;
+ EVP_PKEY_CTX *ctx;
+ const EVP_MD *md;
+
+ memcpy(&hdr, sig, sizeof(struct signature_v2_hdr)); /* avoid just aliasing */
+
+ /* Find the matching public key. */
+ for (pkey = c->ima_public_keys; pkey != NULL; pkey = pkey->next)
+ if (pkey->keyid == ntohl(hdr.keyid)) break;
+ if (!pkey)
+ return -ENOKEY;
+
+ if (!(ctx = EVP_PKEY_CTX_new(pkey->key, NULL)))
+ goto err;
+ if (!EVP_PKEY_verify_init(ctx))
+ goto err;
+ if (!(md = EVP_get_digestbyname(hash_algo)))
+ goto err;
+ if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
+ goto err;
+ ret = EVP_PKEY_verify(ctx, sig + sizeof(hdr),
+ siglen - sizeof(hdr), hash, size);
+ if (ret == 1)
+ ret = 0;
+ else if (ret == 0)
+ ret = -EINVAL;
+err:
+ if (ret < 0 || ret > 1)
+ ret = -EINVAL;
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+
+
/* Validate an IMA file signature.
- * returns 0 on signature validity, EINVAL on signature invalidity, ENOSYS on undefined imaevm machinery,
- * ENOKEY on key issues and -errno on error
+ * Returns 0 on signature validity, -EINVAL on signature invalidity, -ENOSYS on undefined imaevm machinery,
+ * -ENOKEY on key issues, or other -errno.
*/
-/* libimaevm is not threadsafe [2023-10], so lock around calls */
-static pthread_mutex_t imaevm_lock = PTHREAD_MUTEX_INITIALIZER;
-
static int
-debuginfod_validate_imasig (debuginfod_client *c, const char* tmp_path, int fd)
+debuginfod_validate_imasig (debuginfod_client *c, int fd)
{
- (void) c;
- (void) tmp_path;
- (void) fd;
int rc = ENOSYS;
- (void) pthread_mutex_lock(& imaevm_lock);
-
- #ifdef ENABLE_IMA_VERIFICATION
- char *cert_paths = NULL; // need to copy because of strtok
- int vfd = c->verbose_fd;
+ // int vfd = c->verbose_fd;
EVP_MD_CTX *ctx = NULL;
if (!c || !c->winning_headers)
{
@@ -962,8 +1189,8 @@ debuginfod_validate_imasig (debuginfod_client *c, const char* tmp_path, int fd)
// Compute the binary digest of the cached file (with file descriptor fd)
ctx = EVP_MD_CTX_new();
- int hash_algo = imaevm_hash_algo_from_sig(bin_sig + 1);
- const EVP_MD *md = EVP_get_digestbyname(imaevm_hash_algo_by_id(hash_algo));
+ const char* sighash_name = get_signature_params(c, bin_sig) ?: "";
+ const EVP_MD *md = EVP_get_digestbyname(sighash_name);
if (!ctx || !md || !EVP_DigestInit(ctx, md))
{
rc = -EBADMSG;
@@ -978,23 +1205,23 @@ debuginfod_validate_imasig (debuginfod_client *c, const char* tmp_path, int fd)
goto exit_validate;
}
- char file_data[DATA_SIZE];
+ char file_data[DATA_SIZE]; // imaevm.h data chunk hash size
ssize_t n;
- for(long k = 0; k < data_len; k += n)
- {
- if (-1 == (n = pread(fd, file_data, DATA_SIZE, k)))
+ for(off_t k = 0; k < data_len; k += n)
{
- rc = -errno;
- goto exit_validate;
- }
-
- if (!EVP_DigestUpdate(ctx, file_data, n))
- {
- rc = -EBADMSG;
- goto exit_validate;
+ if (-1 == (n = pread(fd, file_data, DATA_SIZE, k)))
+ {
+ rc = -errno;
+ goto exit_validate;
+ }
+
+ if (!EVP_DigestUpdate(ctx, file_data, n))
+ {
+ rc = -EBADMSG;
+ goto exit_validate;
+ }
}
- }
-
+
uint8_t bin_dig[MAX_DIGEST_SIZE];
unsigned int bin_dig_len;
if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len))
@@ -1003,89 +1230,24 @@ debuginfod_validate_imasig (debuginfod_client *c, const char* tmp_path, int fd)
goto exit_validate;
}
- /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. Only
- need this done once per process, persistently updating libimaevm's
- public_keys */
- static int certs_loaded_p = 0;
- if (! certs_loaded_p)
- {
- certs_loaded_p = 1;
-
- imaevm_params.verbose = 0;
- cert_paths = strdup (getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR) ?: DEBUGINFOD_IMA_CERT_PATH_DEFAULT);
- rc = ENOKEY; // This is updated iff a good cert is found
- if (!cert_paths)
- goto exit_validate;
-
- char* cert_dir_path;
- DIR *dp;
- struct dirent *entry;
- char *strtok_context = NULL;
- for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context);
- cert_dir_path != NULL;
- cert_dir_path = strtok_r(NULL, ":", &strtok_context))
- {
- dp = opendir(cert_dir_path);
- if(!dp) continue;
- while((entry = readdir(dp)))
- {
- // Only consider regular files with common x509 cert extensions
- if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer)", entry->d_name, FNM_EXTMATCH)) continue;
- char certfile[PATH_MAX];
- strncpy(certfile, cert_dir_path, PATH_MAX - 1);
- if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/';
- strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1);
- certfile[strlen(certfile)] = '\0';
-
- FILE *cert_fp = fopen(certfile, "r");
- if(!cert_fp) continue;
-
- X509 *x509 = NULL;
- // Attempt to read the fp as DER and assuming the key matches that of the signature add this key to be used
- if(d2i_X509_fp(cert_fp, &x509))
- {
- init_public_keys(certfile);
- if (vfd >= 0) dprintf(vfd, "Loaded certificate %s (keyid = %04x, encoding = der)\n", certfile, extract_skid(x509));
- }
- // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used
- // Note we fseek since this is the second time we read from the fp
- else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL))
- {
- /* init_public_keys requires the path to a DER format x509 cert, so when we encounter a PEM
- * format cert we first need to encode x509 to DER and write it to a new temp file.
- * We can then pass this to init_public_keys and remove it afterwards
- */
- char tmp_file[] = "/tmp/debuginfod_tempcert_XXXXXX";
- int tmp_fd = mkstemp(tmp_file);
- if(-1 == tmp_fd) break;
- FILE* der_cert_fp = fopen(tmp_file, "w");
- i2d_X509_fp(der_cert_fp, x509);
- fclose(der_cert_fp);
- init_public_keys(tmp_file);
- if (vfd >= 0) dprintf(vfd, "Loaded certificate %s (keyid = %04x, encoding = pem)\n", certfile, extract_skid(x509));
- unlink(tmp_file);
- close(tmp_fd);
- }
- fclose(cert_fp);
- if(x509) X509_free(x509);
- } /* for each file in directory */
- closedir(dp);
- } /* for each directory */
- free(cert_paths);
- }
-
- int res = ima_verify_signature(tmp_path, bin_sig, bin_sig_len, bin_dig, bin_dig_len);
+ // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo dawg
+
+ int res = debuginfod_verify_hash(c,
+ bin_dig, bin_dig_len,
+ sighash_name,
+ & bin_sig[1], bin_sig_len-1); // skip over first byte of signature
if (c->verbose_fd >= 0)
dprintf (c->verbose_fd, "Computed ima signature verification res=%d\n", res);
- rc = (res == 1) ? EINVAL : res; // We update rc such that res = 1 is mapped to EINVAL, res = 0 is considered valid, res = -1 is an error
+ rc = (res == 1) ? -EINVAL : res;
exit_validate:
free (sig_buf);
EVP_MD_CTX_free(ctx);
- #endif
- (void) pthread_mutex_unlock(& imaevm_lock);
return rc;
}
+#endif /* ENABLE_IMA_VERIFICATION */
+
+
/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
with the specified build-id and type (debuginfo, executable, source or
@@ -1457,7 +1619,10 @@ debuginfod_query_server (debuginfod_client *c,
{
#ifdef ENABLE_IMA_VERIFICATION
ima_policy_t m = ima_policy_str2enum(server_url + strlen("ima:"));
- if(m != undefined) verification_mode = m;
+ if(m != undefined)
+ verification_mode = m;
+ else if (vfd >= 0)
+ dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_url);
#else
if (vfd >= 0)
dprintf(vfd, "IMA signature verification is not enabled, skipping %s\n", server_url);
@@ -2045,7 +2210,11 @@ debuginfod_query_server (debuginfod_client *c,
if(NULL != url_ima_policies && ignore != url_ima_policies[committed_to])
{
- int result = debuginfod_validate_imasig(c, target_cache_tmppath, fd);
+#ifdef ENABLE_IMA_VERIFICATION
+ int result = debuginfod_validate_imasig(c, fd);
+#else
+ int result = -ENOSYS;
+#endif
if(0 == result)
{
if (vfd >= 0) dprintf (vfd, "valid signature\n");
@@ -2057,7 +2226,7 @@ debuginfod_query_server (debuginfod_client *c,
// this case we do so since we know it is not valid. Note - this not just invalid signatures
// but also signatures that cannot be validated
if (vfd >= 0) dprintf (vfd, "error: invalid or missing signature (%d)\n", result);
- rc = -EPERM;
+ rc = -EBADMSG;
goto out2;
}
else
@@ -2189,6 +2358,10 @@ debuginfod_begin (void)
goto out1;
}
+#ifdef ENABLE_IMA_VERIFICATION
+ load_ima_public_keys (client);
+#endif
+
// extra future initialization
goto out;
@@ -2236,6 +2409,9 @@ debuginfod_end (debuginfod_client *client)
curl_slist_free_all (client->headers);
free (client->winning_headers);
free (client->url);
+#ifdef ENABLE_IMA_VERIFICATION
+ free_ima_public_keys (client);
+#endif
free (client);
}
diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx
index 366cea25..30c818dd 100644
--- a/debuginfod/debuginfod.cxx
+++ b/debuginfod/debuginfod.cxx
@@ -506,7 +506,9 @@ static bool scan_source_info = true;
static string tmpdir;
static bool passive_p = false;
static long scan_checkpoint = 256;
+#ifdef ENABLE_IMA_VERIFICATION
static bool requires_koji_sigcache_mapping = false;
+#endif
static void set_metric(const string& key, double value);
static void inc_metric(const string& key);
diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3
index 9be385a1..cb49eb83 100644
--- a/doc/debuginfod_find_debuginfo.3
+++ b/doc/debuginfod_find_debuginfo.3
@@ -333,6 +333,10 @@ Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP and
Query aborted due to the file requested being too big. The
\fB$DEBUGINFOD_MAXSIZE\fP controls this.
+.TP
+.BR EBADMSG
+File content failed IMA verification.
+
.nr zZ 1
.so man7/debuginfod-client-config.7
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f18939f9..c1bd52cf 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -629,6 +629,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
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 \
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/run-debuginfod-ima-verification.sh b/tests/run-debuginfod-ima-verification.sh
index 45f3e7d0..d582af5f 100755
--- a/tests/run-debuginfod-ima-verification.sh
+++ b/tests/run-debuginfod-ima-verification.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
-# Copyright (C) 2019-2023 Red Hat, Inc.
+# 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
@@ -29,6 +29,9 @@ 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
@@ -53,7 +56,8 @@ 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 (command -v openssl &> /dev/null) && \
+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 ] && \
@@ -97,13 +101,13 @@ wait_ready $PORT1 'thread_busy{role="scan"}' 0
export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT1"
-# Test 1: Without a certificate the verification should fail
+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
-# Test 2: It should pass once the certificate is added to the path
+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
@@ -112,7 +116,7 @@ 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
-# Test 3: Corrupt the data and it should fail
+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
@@ -123,7 +127,7 @@ RC=0
testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1
test $RC -ne 0
-# Test 4: A rpm without a signature will fail
+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
@@ -135,7 +139,7 @@ RC=0
testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1
test $RC -ne 0
-# Test 5: Only tests 1,2 will result in extracted signature
+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
@@ -155,8 +159,8 @@ tempfiles vlog$PORT2
errfiles vlog$PORT2
RPM_BUILDID=c592a95e45625d7891b90f6b86e63373d540461d #/usr/bin/hello
-# Note we test with a trailing slash and with the required directory as the third in the PATH
-VERIFICATION_CERT_DIR=/not/a/dir:${abs_srcdir}/debuginfod-ima/rhel9:${abs_srcdir}/../debuginfod/ima-certs/
+# 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
@@ -165,7 +169,7 @@ 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
-# Test 1: The path should be properly mapped and verified using the actual fedora 38 cert
+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