From 3c28f57ac3d35074cb17a771f705028e4ff4352a Mon Sep 17 00:00:00 2001 From: Samir MOUHOUNE Date: Tue, 3 Mar 2026 10:56:38 +0100 Subject: [PATCH 1/3] crapi: add OpenSSL EVP digest backend Extend the crapi layer with a third cryptographic backend, selectable at build time with -DWITH_CRYPTO=openssl. This gives users and distribution maintainers an additional choice of crypto provider alongside the existing gcrypt and NSS backends. One concrete motivation is deployments that restrict cryptographic operations to a FIPS 140-3 validated library: on those systems OpenSSL may be the only permitted provider, making this backend necessary to run the filehash probes. The implementation uses the OpenSSL EVP high-level digest API, which provides a stable, uniform interface across OpenSSL 1.0.x, 1.1.x, and 3.x. A compile-time compatibility shim aliases EVP_MD_CTX_new() and EVP_MD_CTX_free() to their pre-1.1.0 equivalents (EVP_MD_CTX_create() and EVP_MD_CTX_destroy()) for builds against older OpenSSL releases. Because OpenSSL is already an unconditional build dependency of OpenSCAP (required by xmlsec), selecting this backend introduces no new external dependency. The supported digest algorithms are SHA-224, SHA-256, SHA-384, SHA-512, and optionally MD5 and SHA-1 (governed by the existing OPENSCAP_ENABLE_MD5 and OPENSCAP_ENABLE_SHA1 flags). RIPEMD-160, which is gcrypt-specific and not part of the OVAL specification, is intentionally not included: its status in OpenSSL 3.x is deprecated and requires loading the legacy provider explicitly. --- CMakeLists.txt | 10 ++- config.h.in | 5 ++ src/CMakeLists.txt | 2 +- src/OVAL/probes/crapi/crapi.c | 11 +++ src/OVAL/probes/crapi/digest.c | 146 ++++++++++++++++++++++++++++++++- 5 files changed, 170 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca43d1c00..f222cfd1c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,15 +176,21 @@ check_library_exists(pthread pthread_setname_np "" HAVE_PTHREAD_SETNAME_NP) check_library_exists(pthread pthread_getname_np "" HAVE_PTHREAD_GETNAME_NP) # WITH_CRYPTO -set(WITH_CRYPTO "gcrypt" CACHE STRING "gcrypt|nss") +set(WITH_CRYPTO "gcrypt" CACHE STRING "gcrypt|nss|openssl") +set_property(CACHE WITH_CRYPTO PROPERTY STRINGS "gcrypt" "nss" "openssl") if(${WITH_CRYPTO} STREQUAL "nss") message("-- Using NSS") find_package(NSS) +elseif(${WITH_CRYPTO} STREQUAL "openssl") + message("-- Using OpenSSL for crypto backend") + # OpenSSL is already a required dependency (see find_package(OpenSSL REQUIRED) above); + # this flag tells the crapi layer to use it as the digest backend. + set(OPENSCAP_CRYPTO_OPENSSL TRUE) else() message("-- Using GCrypt") find_package(GCrypt) endif() -if(GCRYPT_FOUND OR NSS_FOUND) +if(GCRYPT_FOUND OR NSS_FOUND OR OPENSCAP_CRYPTO_OPENSSL) set(CRYPTO_FOUND TRUE) endif() diff --git a/config.h.in b/config.h.in index 305bd231cf..093d805a4f 100644 --- a/config.h.in +++ b/config.h.in @@ -24,6 +24,11 @@ #define HAVE_NSS3 #endif +#cmakedefine OPENSCAP_CRYPTO_OPENSSL +#if defined(OPENSCAP_CRYPTO_OPENSSL) +#define HAVE_OPENSSL_CRYPTO +#endif + #define OSCAP_DEFAULT_SCHEMA_PATH "@OSCAP_DEFAULT_SCHEMA_PATH@" #define OSCAP_DEFAULT_XSLT_PATH "@OSCAP_DEFAULT_XSLT_PATH@" #define OSCAP_DEFAULT_CPE_PATH "@OSCAP_DEFAULT_CPE_PATH@" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d59bf3f03..7f23365670 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,7 +59,7 @@ if (ENABLE_PROBES) ) endif() endif() -if (HAVE_MMAN_H AND (GCRYPT_FOUND OR NSS_FOUND)) +if (HAVE_MMAN_H AND (GCRYPT_FOUND OR NSS_FOUND OR OPENSCAP_CRYPTO_OPENSSL)) list(APPEND OBJECTS_TO_LINK_AGAINST $ ) diff --git a/src/OVAL/probes/crapi/crapi.c b/src/OVAL/probes/crapi/crapi.c index 8648978f37..b51c1b8070 100644 --- a/src/OVAL/probes/crapi/crapi.c +++ b/src/OVAL/probes/crapi/crapi.c @@ -47,6 +47,17 @@ int crapi_init (void *unused) return(0); } +#elif defined(HAVE_OPENSSL_CRYPTO) +#include +#include +int crapi_init (void *) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + /* OpenSSL < 1.1.0 requires explicit digest algorithm registration. */ + OpenSSL_add_all_digests(); +#endif + return 0; +} #else int crapi_init (void *unused) { diff --git a/src/OVAL/probes/crapi/digest.c b/src/OVAL/probes/crapi/digest.c index 2fc1c6998e..28d417601f 100644 --- a/src/OVAL/probes/crapi/digest.c +++ b/src/OVAL/probes/crapi/digest.c @@ -41,10 +41,23 @@ #include #elif defined(HAVE_GCRYPT) #include +#elif defined(HAVE_OPENSSL_CRYPTO) +#include +#include #else #error "No crypto library available!" #endif +/* + * Compatibility shim for OpenSSL < 1.1.0, which used EVP_MD_CTX_create() and + * EVP_MD_CTX_destroy() instead of the EVP_MD_CTX_new() / EVP_MD_CTX_free() + * names introduced in 1.1.0. + */ +#if defined(HAVE_OPENSSL_CRYPTO) && OPENSSL_VERSION_NUMBER < 0x10100000L +# define EVP_MD_CTX_new() EVP_MD_CTX_create() +# define EVP_MD_CTX_free(c) EVP_MD_CTX_destroy(c) +#endif + #if defined(HAVE_NSS3) static int crapi_alg_t_to_lib_arg(crapi_alg_t alg) { @@ -95,8 +108,72 @@ static int crapi_alg_t_to_lib_arg(crapi_alg_t alg) return -1; } } +#elif defined(HAVE_OPENSSL_CRYPTO) +static const EVP_MD *crapi_alg_t_to_evp_md(crapi_alg_t alg) +{ + switch (alg) { +#ifdef OPENSCAP_ENABLE_MD5 + case CRAPI_DIGEST_MD5: + return EVP_md5(); +#endif +#ifdef OPENSCAP_ENABLE_SHA1 + case CRAPI_DIGEST_SHA1: + return EVP_sha1(); +#endif + case CRAPI_DIGEST_SHA224: + return EVP_sha224(); + case CRAPI_DIGEST_SHA256: + return EVP_sha256(); + case CRAPI_DIGEST_SHA384: + return EVP_sha384(); + case CRAPI_DIGEST_SHA512: + return EVP_sha512(); + default: + return NULL; + } +} #endif +#if defined(HAVE_OPENSSL_CRYPTO) +/* Read fd and compute its digest using the streaming EVP API. */ +static int crapi_digest_fd_stream(int fd, const EVP_MD *evp_md, void *dst, size_t *size) +{ + uint8_t buf[CRAPI_IO_BUFSZ]; + ssize_t ret; + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL) + return -1; + if (EVP_DigestInit_ex(ctx, evp_md, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return -1; + } + while ((ret = read(fd, buf, sizeof buf)) == sizeof buf) + EVP_DigestUpdate(ctx, (const void *)buf, sizeof buf); + switch (ret) { + case 0: + break; + case -1: + EVP_MD_CTX_free(ctx); + return -1; + default: + if (ret <= 0) { + EVP_MD_CTX_free(ctx); + return -1; + } + EVP_DigestUpdate(ctx, (const void *)buf, (size_t)ret); + } + unsigned int md_len = (unsigned int)*size; + if (EVP_DigestFinal_ex(ctx, (unsigned char *)dst, &md_len) != 1) { + EVP_MD_CTX_free(ctx); + return -1; + } + *size = (size_t)md_len; + EVP_MD_CTX_free(ctx); + return 0; +} +#endif /* HAVE_OPENSSL_CRYPTO */ + int crapi_digest_fd(int fd, crapi_alg_t alg, void *dst, size_t *size) { struct stat st; @@ -107,6 +184,22 @@ int crapi_digest_fd(int fd, crapi_alg_t alg, void *dst, size_t *size) errno = EFAULT; return -1; } + + /* + * Resolve the algorithm and verify the output buffer is large enough + * to hold the digest. + */ +#if defined(HAVE_OPENSSL_CRYPTO) + const EVP_MD *evp_md = crapi_alg_t_to_evp_md(alg); + if (evp_md == NULL) { + errno = EINVAL; + return -1; + } + if (*size < (size_t)EVP_MD_size(evp_md)) { + errno = ENOBUFS; + return -1; + } +#else int lib_alg = crapi_alg_t_to_lib_arg(alg); #if defined(HAVE_NSS3) if (*size < HASH_ResultLen(lib_alg)) { @@ -116,6 +209,7 @@ int crapi_digest_fd(int fd, crapi_alg_t alg, void *dst, size_t *size) errno = ENOBUFS; return -1; } +#endif /* HAVE_OPENSSL_CRYPTO */ if (fstat (fd, &st) != 0) return (-1); @@ -129,6 +223,10 @@ int crapi_digest_fd(int fd, crapi_alg_t alg, void *dst, size_t *size) # endif if (buffer == NULL) { #endif /* _FILE_OFFSET_BITS == 32 */ +#if defined(HAVE_OPENSSL_CRYPTO) + if (crapi_digest_fd_stream(fd, evp_md, dst, size) != 0) + return -1; +#else uint8_t _buffer[CRAPI_IO_BUFSZ]; ssize_t ret; @@ -181,10 +279,18 @@ int crapi_digest_fd(int fd, crapi_alg_t alg, void *dst, size_t *size) memcpy (dst, buffer, gcry_md_get_algo_dlen(lib_alg)); gcry_md_close (hd); #endif +#endif /* !HAVE_OPENSSL_CRYPTO */ #if _FILE_OFFSET_BITS == 32 } else { -#if defined(HAVE_NSS3) +#if defined(HAVE_OPENSSL_CRYPTO) + unsigned int md_len = (unsigned int)*size; + if (EVP_Digest(buffer, buflen, (unsigned char *)dst, &md_len, evp_md, NULL) != 1) { + munmap(buffer, buflen); + return -1; + } + *size = (size_t)md_len; +#elif defined(HAVE_NSS3) HASH_HashBuf(lib_alg, (unsigned char *)dst, (unsigned char *)buffer, (unsigned int)buflen); #elif defined(HAVE_GCRYPT) gcry_md_hash_buffer(lib_alg, dst, (const void *)buffer, buflen); @@ -201,6 +307,8 @@ struct crapi_digest_ctx { HASHContext *ctx; #elif defined(HAVE_GCRYPT) gcry_md_hd_t ctx; +#elif defined(HAVE_OPENSSL_CRYPTO) + EVP_MD_CTX *ctx; #endif void *dst; size_t *size; @@ -215,7 +323,9 @@ static void *crapi_digest_init (void *dst, void *size, crapi_alg_t alg) { struct crapi_digest_ctx *ctx = malloc(sizeof(struct crapi_digest_ctx)); +#if defined(HAVE_NSS3) || defined(HAVE_GCRYPT) int lib_alg = crapi_alg_t_to_lib_arg(alg); +#endif #if defined(HAVE_NSS3) ctx->ctx = HASH_Create(lib_alg); #elif defined(HAVE_GCRYPT) @@ -223,6 +333,25 @@ static void *crapi_digest_init (void *dst, void *size, crapi_alg_t alg) free(ctx); return NULL; } +#elif defined(HAVE_OPENSSL_CRYPTO) + if (ctx == NULL) + return NULL; + const EVP_MD *evp_md = crapi_alg_t_to_evp_md(alg); + if (evp_md == NULL) { + free(ctx); + errno = EINVAL; + return NULL; + } + ctx->ctx = EVP_MD_CTX_new(); + if (ctx->ctx == NULL) { + free(ctx); + return NULL; + } + if (EVP_DigestInit_ex(ctx->ctx, evp_md, NULL) != 1) { + EVP_MD_CTX_free(ctx->ctx); + free(ctx); + return NULL; + } #endif ctx->dst = dst; ctx->size = size; @@ -245,6 +374,9 @@ static int crapi_digest_update(struct crapi_digest_ctx *ctx, void *bptr, size_t HASH_Update(ctx->ctx, (const unsigned char *)bptr, (unsigned int)blen); #elif defined(HAVE_GCRYPT) gcry_md_write(ctx->ctx, (const void *)bptr, blen); +#elif defined(HAVE_OPENSSL_CRYPTO) + if (EVP_DigestUpdate(ctx->ctx, (const void *)bptr, blen) != 1) + return -1; #endif return (0); } @@ -264,6 +396,15 @@ static int crapi_digest_fini(struct crapi_digest_ctx *ctx, crapi_alg_t alg) buffer = (void *)gcry_md_read(ctx->ctx, lib_alg); memcpy(ctx->dst, buffer, gcry_md_get_algo_dlen(lib_alg)); gcry_md_close(ctx->ctx); +#elif defined(HAVE_OPENSSL_CRYPTO) + unsigned int md_len = (unsigned int)*ctx->size; + if (EVP_DigestFinal_ex(ctx->ctx, (unsigned char *)ctx->dst, &md_len) != 1) { + EVP_MD_CTX_free(ctx->ctx); + free(ctx); + return -1; + } + *ctx->size = (size_t)md_len; + EVP_MD_CTX_free(ctx->ctx); #endif free (ctx); @@ -275,6 +416,9 @@ static void crapi_digest_free(struct crapi_digest_ctx *ctx) #if defined(HAVE_NSS3) HASH_Destroy(ctx->ctx); free(ctx); +#elif defined(HAVE_OPENSSL_CRYPTO) + EVP_MD_CTX_free(ctx->ctx); + free(ctx); #endif return; } From b0d1241e58044c0d9791070bb34cf0fdf9a9064e Mon Sep 17 00:00:00 2001 From: Smouhoune Date: Mon, 9 Mar 2026 21:47:30 +0100 Subject: [PATCH 2/3] Update src/OVAL/probes/crapi/crapi.c Co-authored-by: Matthew Burket --- src/OVAL/probes/crapi/crapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OVAL/probes/crapi/crapi.c b/src/OVAL/probes/crapi/crapi.c index b51c1b8070..f445f9a500 100644 --- a/src/OVAL/probes/crapi/crapi.c +++ b/src/OVAL/probes/crapi/crapi.c @@ -50,7 +50,7 @@ int crapi_init (void *unused) #elif defined(HAVE_OPENSSL_CRYPTO) #include #include -int crapi_init (void *) +int crapi_init (void *unused) { #if OPENSSL_VERSION_NUMBER < 0x10100000L /* OpenSSL < 1.1.0 requires explicit digest algorithm registration. */ From 39b54b7fe1e93e63ebc03f48e2710451d9149f7c Mon Sep 17 00:00:00 2001 From: Samir MOUHOUNE Date: Tue, 10 Mar 2026 08:39:11 +0100 Subject: [PATCH 3/3] crapi: check EVP_DigestUpdate return value --- src/OVAL/probes/crapi/digest.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/OVAL/probes/crapi/digest.c b/src/OVAL/probes/crapi/digest.c index 28d417601f..5b476f5617 100644 --- a/src/OVAL/probes/crapi/digest.c +++ b/src/OVAL/probes/crapi/digest.c @@ -148,8 +148,12 @@ static int crapi_digest_fd_stream(int fd, const EVP_MD *evp_md, void *dst, size_ EVP_MD_CTX_free(ctx); return -1; } - while ((ret = read(fd, buf, sizeof buf)) == sizeof buf) - EVP_DigestUpdate(ctx, (const void *)buf, sizeof buf); + while ((ret = read(fd, buf, sizeof buf)) == sizeof buf) { + if (EVP_DigestUpdate(ctx, (const void *)buf, sizeof buf) != 1) { + EVP_MD_CTX_free(ctx); + return -1; + } + } switch (ret) { case 0: break; @@ -161,7 +165,10 @@ static int crapi_digest_fd_stream(int fd, const EVP_MD *evp_md, void *dst, size_ EVP_MD_CTX_free(ctx); return -1; } - EVP_DigestUpdate(ctx, (const void *)buf, (size_t)ret); + if (EVP_DigestUpdate(ctx, (const void *)buf, (size_t)ret) != 1) { + EVP_MD_CTX_free(ctx); + return -1; + } } unsigned int md_len = (unsigned int)*size; if (EVP_DigestFinal_ex(ctx, (unsigned char *)dst, &md_len) != 1) {