From d233429a0a8465f92b665ee930d8f2d367eb1bd8 Mon Sep 17 00:00:00 2001 From: Mark Carey Date: Mon, 19 Aug 2024 14:30:22 -0400 Subject: [PATCH 1/3] - Add external FIPS 140-2 / 140-3 compliant signatures for the Clam Databases. - Added tools to extract the public exponent and modulus of generated keys so that they internal defines holding the hard coded key validation can be updated by the signing authority. - Added the script sigext/cvd_hash_sha256_sign.sh which uses a private RSA key and openssl to sign ClamAV Databases and produces an external signature file which can then be used to validate the contents of the ClamAV database updates. --- libclamav/crypto.c | 252 ++++++++++++++++++++++++++- libclamav/crypto.h | 37 ++++ libclamav/cvd.c | 11 ++ libclamav/dsig.c | 217 +++++++++++++++++++++++ libclamav/dsig.h | 1 + libclamav/libclamav.map | 3 + libfreshclam/libfreshclam_internal.c | 158 +++++++++++++++++ sigext/cvd_hash_sha256_sign.sh | 32 ++++ sigext/extract_mod_and_exp.sh | 34 ++++ 9 files changed, 738 insertions(+), 7 deletions(-) create mode 100644 libclamav/crypto.h create mode 100644 sigext/cvd_hash_sha256_sign.sh create mode 100644 sigext/extract_mod_and_exp.sh diff --git a/libclamav/crypto.c b/libclamav/crypto.c index e4824fdb79..b8895a71f2 100644 --- a/libclamav/crypto.c +++ b/libclamav/crypto.c @@ -52,6 +52,17 @@ #include #include +#include +#include +#include +#include +#include + +// These are the public key components for the external signature +// Ideally these should be packaged in a way that allows individual entites +// to use their own keys if they should so desire. +#define CLI_NSTR_EXT_SIG "E32B3AC1D501EE975296A45BA65DD699100DADD340FF3BBD1F1030C66D6BB16DBFBD53DF4D97BBD42EF8FC777E7C114A6074A87DD8095A5C08B3DD7B85817713047647EF396C58358C5C22B5C3ADF85CE8D0ABC429F89E936EC917B64DD00E02A712E6666FAE1A71591092BCEE59E3141758C4719B4B08589117B0FF7CDBDBB261F8486A193E2E720AE0B16D40DD5E56E97346CBD8010DC81B35332F41C9E93E61490802DDCDFC823D581BA6888588968C68A3C95B93949AF411682E73323F7469473F668B0958F6966849FF03BDE808866D127A2C058B16F17C741A9EE50812A5C7841224E55BF7ADDB5AEAE8EB5476F9BC8740178AB35926D5DC375583C641" +#define CLI_ESTR_EXT_SIG "010001" #if OPENSSL_VERSION_NUMBER < 0x10100000L #define X509_CRL_get0_nextUpdate X509_CRL_get_nextUpdate @@ -125,6 +136,74 @@ time_t timegm(struct tm *t) } #endif +/** + * This variable determines if we are operating in FIPS mode, default to no. + */ +int cli_fips_mode = 0; + +/** + * @brief This function determines if we are running in FIPS mode and sets the cl_fips_mode variable + * + * This function is called by cl_init() and does not need to be called by the user + * + * @return int Returns 1 if we are running in FIPS mode, 0 otherwise + * + */ + +void cli_setup_fips_configuration(void) +{ + #if OPENSSL_VERSION_MAJOR == 1 + // OpenSSL 1.x (1.0 or 1.1) + #ifdef OPENSSL_FIPS + if (FIPS_mode()) { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); + cl_fips_mode = 1; + } else { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); + cl_fips_mode = 0; + } + #else + cl_fips_mode = 0; + #endif + + #elif OPENSSL_VERSION_MAJOR == 3 + // OpenSSL 3.0.x + OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new(); + if (libctx == NULL) { + cli_warnmsg("cl_setup_fips_configuration: Failed to create libctx.\n"); + cli_fips_mode = 0; + return; + } + + OSSL_PROVIDER *fips = OSSL_PROVIDER_load(libctx, "fips"); + if (fips != NULL) { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); + cli_fips_mode = 1; + OSSL_PROVIDER_unload(fips); + OSSL_LIB_CTX_free(libctx); + } else { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); + cli_fips_mode = 0; + OSSL_LIB_CTX_free(libctx); + } + #else + #error "Unsupported OpenSSL version" + #endif +} + +/** + * @brief Return the status of our FIPS condition. + * + * This function allows users of the library to determine if the library is running in FIPS mode. + * + * @return int Returns 1 if we are running in FIPS mode, 0 otherwise + */ + +int cli_get_fips_mode(void) +{ + return cli_fips_mode; +} + /** * @brief This function initializes the openssl crypto system * @@ -145,6 +224,8 @@ int cl_initialize_crypto(void) ERR_load_crypto_strings(); #endif + cli_setup_fips_configuration(); + return 0; } @@ -170,7 +251,12 @@ unsigned char *cl_hash_data(const char *alg, const void *buf, size_t len, unsign size_t cur; int winres = 0; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) return NULL; @@ -260,7 +346,12 @@ unsigned char *cl_hash_file_fd(int fd, const char *alg, unsigned int *olen) const EVP_MD *md; unsigned char *res; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) return NULL; @@ -392,7 +483,12 @@ int cl_verify_signature_hash(EVP_PKEY *pkey, const char *alg, unsigned char *sig const EVP_MD *md; size_t mdsz; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) return -1; @@ -437,7 +533,12 @@ int cl_verify_signature_fd(EVP_PKEY *pkey, const char *alg, unsigned char *sig, if (!(digest)) return -1; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) { free(digest); return -1; @@ -506,7 +607,12 @@ int cl_verify_signature(EVP_PKEY *pkey, const char *alg, unsigned char *sig, uns return -1; } - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) { free(digest); if (decode) @@ -725,7 +831,12 @@ unsigned char *cl_sign_data(EVP_PKEY *pkey, const char *alg, unsigned char *hash unsigned int siglen; unsigned char *sig; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) return NULL; @@ -1148,7 +1259,12 @@ void *cl_hash_init(const char *alg) EVP_MD_CTX *ctx; const EVP_MD *md; - md = EVP_get_digestbyname(alg); + // If OpenSSL is running in FIPS mode, we need to use the FIPS-compliant md5 lookup of the algorithm + if (cli_fips_mode && !strncasecmp(alg, "md5", 3)) + md = EVP_MD_fetch(NULL, alg, "-fips"); + else + md = EVP_get_digestbyname(alg); + if (!(md)) return NULL; @@ -1210,3 +1326,125 @@ void cl_hash_destroy(void *ctx) EVP_MD_CTX_destroy((EVP_MD_CTX *)ctx); } + +#if OPENSSL_VERSION_MAJOR == 1 +RSA *cli_build_ext_signing_key(void) +{ + RSA *rsa = RSA_new(); + BIGNUM *n = BN_new(); + BIGNUM *e = BN_new(); + + if (!rsa || !n || !e) { + RSA_free(rsa); + BN_free(n); + BN_free(e); + return NULL; + } + + if (!BN_hex2bn(&n, CLI_NSTR_EXT_SIG) || !BN_hex2bn(&e, CLI_ESTR_EXT_SIG)) { + RSA_free(rsa); + BN_free(n); + BN_free(e); + return NULL; + } + rsa->n = n; + rsa->e = e; + + return rsa; +} +#elif OPENSSL_VERSION_MAJOR == 3 +// Do this the OpenSSL 3 way, avoiding deprecation warnings +EVP_PKEY *cli_build_ext_signing_key(void) +{ + EVP_PKEY *pkey = EVP_PKEY_new(); + BIGNUM *n = BN_new(); + BIGNUM *e = BN_new(); + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM *params = NULL; + int result = 0; + + // Check bld and params + if (!pkey || !n || !e || !bld) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + // Set the public key components + if (!BN_hex2bn(&n, CLI_NSTR_EXT_SIG) || !BN_hex2bn(&e, CLI_ESTR_EXT_SIG)) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + result = OSSL_PARAM_BLD_push_BN(bld, "n", n); + if (!result) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + result = OSSL_PARAM_BLD_push_BN(bld, "e", e); + if (!result) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + result = OSSL_PARAM_BLD_push_BN(bld, "d", NULL); + if (!result) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + params = OSSL_PARAM_BLD_to_param(bld); + if (!params) { + EVP_PKEY_free(pkey); + BN_free(n); + BN_free(e); + OSSL_PARAM_BLD_free(bld); + return NULL; + } + + OSSL_PARAM_BLD_free(bld); + BN_free(n); + BN_free(e); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + if (!ctx) { + EVP_PKEY_free(pkey); + return NULL; + } + + if (EVP_PKEY_fromdata_init(ctx) <= 0) { + EVP_PKEY_free(pkey); + return NULL; + } + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) { + EVP_PKEY_free(pkey); + return NULL; + } + + if (params) + OSSL_PARAM_free(params); + + if (ctx) + EVP_PKEY_CTX_free(ctx); + + return pkey; +} +#else +#error "Unsupported OpenSSL version" +#endif diff --git a/libclamav/crypto.h b/libclamav/crypto.h new file mode 100644 index 0000000000..76f0c5b015 --- /dev/null +++ b/libclamav/crypto.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013-2024 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * Copyright (C) 2011-2013 Sourcefire, Inc. + * + * Authors: aCaB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef __CRYPTO_H +#define __CRYPTO_H +#include + +void cli_setup_fips_configuration(void); +int cli_get_fips_mode(void); + +#if OPENSSL_VERSION_MAJOR == 1 +RSA *cli_build_ext_signing_key(void); +#elif OPENSSL_VERSION_MAJOR == 3 +EVP_PKEY *cli_build_ext_signing_key(void); +#else +#error "Unsupported OpenSSL version" +#endif + +#endif diff --git a/libclamav/cvd.c b/libclamav/cvd.c index 6d54dc7998..790e38f5a3 100644 --- a/libclamav/cvd.c +++ b/libclamav/cvd.c @@ -46,10 +46,13 @@ #include "others.h" #include "dsig.h" #include "str.h" +#include "crypto.h" #include "cvd.h" #include "readdb.h" #include "default.h" +#include + #define TAR_BLOCKSIZE 512 static void cli_untgz_cleanup(char *path, gzFile infile, FILE *outfile, int fdd) @@ -627,6 +630,14 @@ cl_error_t cli_cvdload(FILE *fs, struct cl_engine *engine, unsigned int *signo, cli_dbgmsg("in cli_cvdload()\n"); + // FIPS verification MUST preclude other verification if in FIPS mode. + if (cli_get_fips_mode()) { + ret = cli_sigver_external(filename); + if (ret != CL_SUCCESS) { + return ret; + } + } + /* verify */ if ((ret = cli_cvdverify(fs, &cvd, dbtype))) return ret; diff --git a/libclamav/dsig.c b/libclamav/dsig.c index b0cf212f07..204f55cb33 100644 --- a/libclamav/dsig.c +++ b/libclamav/dsig.c @@ -34,6 +34,7 @@ #include "clamav.h" #include "others.h" +#include "crypto.h" #include "dsig.h" #include "str.h" @@ -424,3 +425,219 @@ int cli_versig2(const unsigned char *sha256, const char *dsig_str, const char *n BN_free(e); return ret; } + +int cli_hex2bin(const char *hex, unsigned char *bin, int len) +{ + // Use tricks to do this fast and without memory violations + unsigned char *in = (unsigned char *)hex; + unsigned char *out = bin; + int retlen = len/2; + + while (len--) { + *out = 0; + if (*in >= '0' && *in <= '9') + *out = *in - '0'; + else if (*in >= 'A' && *in <= 'F') + *out = *in - 'A' + 10; + else if (*in >= 'a' && *in <= 'f') + *out = *in - 'a' + 10; + else + return -1; + in++; + *out <<= 4; + if (*in >= '0' && *in <= '9') + *out |= *in - '0'; + else if (*in >= 'A' && *in <= 'F') + *out |= *in - 'A' + 10; + else if (*in >= 'a' && *in <= 'f') + *out |= *in - 'a' + 10; + else + return -1; + in++; + out++; + } + return retlen; +} + +cl_error_t cli_sigver_external(const char *file) +{ + cl_error_t result = CL_ERROR; + unsigned char sha256_bin[SHA256_DIGEST_LENGTH]; + char *sha256 = NULL; + unsigned char *sig_bin = NULL; + + // Use the built-in method to hash the CVD file. + FILE *fs = fopen(file, "rb"); + if (fs == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't open file %s\n", file); + return CL_EOPEN; + } + fseek(fs, 512, SEEK_SET); + sha256 = cli_hashstream(fs, NULL, 3); + fclose(fs); + if (sha256 == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't generate SHA256 hash\n"); + result = CL_EMEM; + goto done; + } + cli_dbgmsg("SHA256(.tar.gz) = %s\n", sha256); + + // Build the RSA key from the exponent and modulus +#if OPENSSL_VERSION_MAJOR == 1 + RSA *rsa = cli_build_ext_signing_key(); + if (rsa == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't create RSA key from public key\n"); + result = CL_EVERIFY; + goto done; + } +#elif OPENSSL_VERSION_MAJOR == 3 + EVP_PKEY *rsa = cli_build_ext_signing_key(); + if (rsa == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't create RSA key from public key\n"); + result = CL_EVERIFY; + goto done; + } +#else + #error "Unsupported OpenSSL version" +#endif + + // Convert the sha256 hash to binary + if (cli_hex2bin(sha256, sha256_bin, SHA256_DIGEST_LENGTH) == -1) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't convert sha256 hash to binary\n"); + result = CL_EVERIFY; + goto done; + } + + // + // External Signature processing + // + + // Load the external signature file + char *sigfile = strdup(file); + if (sigfile == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature file name\n"); + result = CL_EMEM; + goto done; + } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-truncation" + strncpy(sigfile + strlen(sigfile) - 4, ".sig", 4); + #pragma GCC diagnostic pop + fs = fopen(sigfile, "rb"); + if (fs == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't open signature file %s\n", sigfile); + result = CL_EOPEN; + goto done; + } + + // Read the signature file + fseek(fs, 0, SEEK_END); + size_t siglen = (size_t) ftell(fs); + fseek(fs, 0, SEEK_SET); + char *sig = (char *)malloc(siglen+1); + if (sig == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature\n"); + fclose(fs); + result = CL_EMEM; + goto done; + } + + if (fread(sig, 1, siglen, fs) != siglen) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't read signature from file\n"); + fclose(fs); + result = CL_EVERIFY; + goto done; + } + sig[siglen] = 0; + fclose(fs); + + // Extract the parts of the signature file + // it's kept in a format like: "SHA256 hash hex:RSA signature hex" + char *sig_seperator = strchr(sig, ':'); + if (sig_seperator == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't find signature in external database signature file\n"); + result = CL_EVERIFY; + goto done; + } + *sig_seperator = 0; + sig_seperator++; + siglen = strlen(sig_seperator)/2; + sig_bin = (unsigned char *)malloc(siglen); + if (sig_bin == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature binary\n"); + result = CL_EMEM; + goto done; + } + + // convert the signature to binary + if (cli_hex2bin(sig_seperator, sig_bin, siglen) == -1) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't convert signature to binary\n"); + result = CL_EVERIFY; + } + + // If we are using a verson of openssl less than 3.0.0, we need to use the RSA_verify function +#if OPENSSL_VERSION_MAJOR == 1 + // verify the signature + //int sig_verify = RSA_verify(NID_sha256, sha256, strlen(sha256), sig_bin, siglen, rsa); + int sig_verify = RSA_verify(NID_sha256, sha256_bin, SHA256_DIGEST_LENGTH, sig_bin, siglen, rsa); + if (sig_verify != 1) { + cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); + result = CL_EVERIFY; + goto done; + } + else { + cli_dbgmsg("cli_cvd_ext_sig_verify: RSA signature verification successful for external database signature\n"); + result = CL_SUCCESS; + } +#elif OPENSSL_VERSION_MAJOR == 3 + // verify the signature + EVP_PKEY_CTX *pctx = NULL; + + pctx = EVP_PKEY_CTX_new(rsa, NULL); + if (pctx == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't create EVP_PKEY_CTX\n"); + result = CL_EVERIFY; + goto done; + } + + if (EVP_PKEY_verify_init(pctx) != 1) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't initialize EVP_PKEY_verify_init\n"); + result = CL_EVERIFY; + goto done; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha256()) != 1) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't set signature MD\n"); + result = CL_EVERIFY; + goto done; + } + + if (EVP_PKEY_verify(pctx, sig_bin, siglen, sha256_bin, SHA256_DIGEST_LENGTH) != 1) { + cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); + result = CL_EVERIFY; + goto done; + } + else { + cli_dbgmsg("cli_cvd_ext_sig_verify: RSA signature verification successful for external database signature\n"); + result = CL_SUCCESS; + } + + if(pctx) EVP_PKEY_CTX_free(pctx); +#else + #error "Unsupported OpenSSL version" +#endif + +done: + // Clean up + if (sig) free(sig); + if (sigfile) free(sigfile); + if (sha256) free(sha256); + if (sig_bin) free(sig_bin); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (rsa) RSA_free(rsa); +#else + if (rsa) EVP_PKEY_free(rsa); +#endif + + return result; +} \ No newline at end of file diff --git a/libclamav/dsig.h b/libclamav/dsig.h index 61666010c4..aa569b7623 100644 --- a/libclamav/dsig.h +++ b/libclamav/dsig.h @@ -31,6 +31,7 @@ cl_error_t cli_versig(const char *md5, const char *dsig); int cli_versig2(const unsigned char *sha256, const char *dsig_str, const char *n_str, const char *e_str); +cl_error_t cli_sigver_external(const char *file); /** * @brief Connect to a signing server, send the data to be signed, and return the digital signature. diff --git a/libclamav/libclamav.map b/libclamav/libclamav.map index a34a05dbb6..ccf5ae716f 100644 --- a/libclamav/libclamav.map +++ b/libclamav/libclamav.map @@ -302,6 +302,9 @@ CLAMAV_PRIVATE { cli_magic_scan_buff; cli_checklimits; cli_matchmeta; + cli_setup_fips_configuration; + cli_fips_mode; + cli_get_fips_mode; __cli_strcasestr; __cli_strndup; diff --git a/libfreshclam/libfreshclam_internal.c b/libfreshclam/libfreshclam_internal.c index 5afde46710..85b8d53d49 100644 --- a/libfreshclam/libfreshclam_internal.c +++ b/libfreshclam/libfreshclam_internal.c @@ -74,6 +74,7 @@ #include "clamav.h" #include "others.h" #include "str.h" +#include "crypto.h" #include "cvd.h" #include "regex_list.h" @@ -88,6 +89,7 @@ #include "libfreshclam.h" #include "libfreshclam_internal.h" #include "dns.h" +#include "clamav.h" #define DB_FILENAME_MAX 60 #define CVD_HEADER_SIZE 512 @@ -1478,6 +1480,67 @@ static fc_error_t getcvd( goto done; } + // + // If we are in FIPS mode, download the external signature file + // + char *sigfile = NULL; + char *extSigTmpFile = NULL; + char *extSigUrl = NULL; + if (cli_get_fips_mode()) + { + + // The external signature file is the same as the CVD file, but with a different extension. + sigfile = strdup(cvdfile); + if (!sigfile) { + logg(LOGG_ERROR, "Can't allocate memory for external signature file name!\n"); + status = FC_EMEM; + goto done; + } + + // Change the extension to .sig + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-truncation" + strncpy(sigfile + strlen(sigfile) - 4, ".sig", 4); + #pragma GCC diagnostic pop + + urlLen = strlen(server) + strlen("/") + strlen(sigfile); + extSigUrl = malloc(urlLen + 1); + if (!extSigUrl) { + logg(LOGG_ERROR, "Can't allocate memory for external signature file URL!\n"); + status = FC_EMEM; + goto done; + } + + // Create a temporary file for the external signature (same name as tmpFile but with an .sig extension instead of .tmp) + extSigTmpFile = strdup(tmpfile); + if (!extSigTmpFile) { + logg(LOGG_ERROR, "Can't allocate memory for external signature temp file name!\n"); + status = FC_EMEM; + goto done; + } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-truncation" + strncpy(extSigTmpFile + strlen(extSigTmpFile) - 4, ".sig", 4); + #pragma GCC diagnostic pop + + // Construct the URL + snprintf(extSigUrl, urlLen + 1, "%s/%s", server, sigfile); + + printf("Downloading external signature file %s from %s\n", sigfile, extSigUrl); + + // Download the external signature file + ret = downloadFile(extSigUrl, extSigTmpFile, 1, logerr, ifModifiedSince); + if (ret == FC_UPTODATE) { + logg(LOGG_INFO, "%s is up-to-date.\n", sigfile); + status = ret; + goto done; + } else if (ret > FC_UPTODATE) { + logg(logerr ? LOGG_ERROR : LOGG_WARNING, "Can't download %s from %s\n", sigfile, extSigUrl); + status = ret; + goto done; + } + } + /* Temporarily rename file to correct extension for verification. */ tmpfile_with_extension = strdup(tmpfile); if (!tmpfile_with_extension) { @@ -1490,6 +1553,28 @@ static fc_error_t getcvd( logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno)); status = FC_EDBDIRACCESS; goto done; + } + + // If we're in FIPS mode, temporarily rename the external signature file to the expected path: + // The database file will be something like: /share/SAP/clam.d/tmp.b2b103a70a/clamav-3bbac78e36cbf974e1060de6dbbbfea3.tmp-daily.cld + // The expected external signature will be: /share/SAP/clam.d/tmp.b2b103a70a/clamav-3bbac78e36cbf974e1060de6dbbbfea3.tmp-daily.sig + char *extSigTmpFileWithExtension = NULL; + if (cli_get_fips_mode()) + { + // Temprorarily rename the the sig file to the expected path + extSigTmpFileWithExtension = strdup(tmpfile_with_extension); + if (!extSigTmpFileWithExtension) { + logg(LOGG_ERROR, "Can't allocate memory for external signature temp file with extension!\n"); + status = FC_EMEM; + goto done; + } + strncpy(extSigTmpFileWithExtension + strlen(extSigTmpFileWithExtension) - 4, sigfile + strlen(sigfile) - 4, 4); + printf("RENAMING %s to %s\n", extSigTmpFile, extSigTmpFileWithExtension); + if (rename(extSigTmpFile, extSigTmpFileWithExtension) == -1) { + logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", extSigTmpFile, extSigTmpFileWithExtension, strerror(errno)); + status = FC_EDBDIRACCESS; + goto done; + } } if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile_with_extension))) { @@ -1511,6 +1596,17 @@ static fc_error_t getcvd( goto done; } + // Rename the .sig file if we are in FIPS mode + if (cli_get_fips_mode()) + { + if (rename(extSigTmpFileWithExtension, extSigTmpFile) == -1) { + logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", extSigTmpFileWithExtension, extSigTmpFile, strerror(errno)); + status = FC_EDBDIRACCESS; + goto done; + } + + } + if (cvd->version < remoteVersion) { logg(LOGG_DEBUG, "The %s database downloaded from %s is older than the version advertised in the DNS TXT record.\n", cvdfile, @@ -1522,6 +1618,22 @@ static fc_error_t getcvd( status = FC_SUCCESS; done: + if (cli_get_fips_mode()) + { + if (NULL != extSigTmpFileWithExtension) { + free(extSigTmpFileWithExtension); + } + if (NULL != extSigTmpFile) { + free(extSigTmpFile); + } + if (NULL != sigfile) { + free(sigfile); + } + if (NULL != extSigUrl) { + free(extSigUrl); + } + } + if (NULL != cvd) { cl_cvdfree(cvd); } @@ -2475,6 +2587,29 @@ fc_error_t updatedb( status = FC_EDBDIRACCESS; goto done; } + + // // If we are running in FIPS mode, we need to move the .sig file in, as well. + if (cli_get_fips_mode()) { + // just copy tmpfile into tmpsigfile and replace the .tmp with .sig + char *tmpsigfile = strdup(tmpfile); + strcpy(tmpsigfile + strlen(tmpsigfile) - 4, ".sig"); + + // do the same for tmpfile_with_extension + char *tmpsigfile_with_extension = strdup(tmpfile_with_extension); + strcpy(tmpsigfile_with_extension + strlen(tmpsigfile_with_extension) - 4, ".sig"); + + // do the rename + if (rename(tmpsigfile, tmpsigfile_with_extension) == -1) { + logg(LOGG_ERROR, "(line %d) updatedb: Can't rename %s to %s: %s\n", __LINE__, tmpsigfile, tmpsigfile_with_extension, strerror(errno)); + status = FC_EDBDIRACCESS; + free(tmpsigfile); + free(tmpsigfile_with_extension); + goto done; + } + free(tmpsigfile); + free(tmpsigfile_with_extension); + } + free(tmpfile); tmpfile = tmpfile_with_extension; tmpfile_with_extension = NULL; @@ -2504,6 +2639,29 @@ fc_error_t updatedb( goto done; } + // If we are running in FIPS mode, we need to move the .sig file in, as well. + if (cli_get_fips_mode()) + { + // just duplicate the newLocalFileName buffer and replace the .cld with .sig + char *newLocalSigFilename = strdup(newLocalFilename); + strcpy(newLocalSigFilename + strlen(newLocalSigFilename) - 4, ".sig"); + + // do the same for tmpfile + char *tmpsigfile = strdup(tmpfile); + strcpy(tmpsigfile + strlen(tmpsigfile) - 4, ".sig"); + + // do the rename + if (rename(tmpsigfile, newLocalSigFilename) == -1) { + logg(LOGG_ERROR, "(line %d) updatedb: Can't rename %s to %s: %s\n", __LINE__, tmpsigfile, newLocalSigFilename, strerror(errno)); + status = FC_EDBDIRACCESS; + free(tmpsigfile); + free(newLocalSigFilename); + goto done; + } + free(tmpsigfile); + free(newLocalSigFilename); + } + /* If we just updated from a CVD to a CLD, delete the old CVD */ if ((NULL != localFilename) && !access(localFilename, R_OK) && strcmp(newLocalFilename, localFilename)) if (unlink(localFilename)) diff --git a/sigext/cvd_hash_sha256_sign.sh b/sigext/cvd_hash_sha256_sign.sh new file mode 100644 index 0000000000..68aedc7360 --- /dev/null +++ b/sigext/cvd_hash_sha256_sign.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Create the keypair with: +# openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 +# openssl rsa -pubout -in private_key.pem -out public_key.pem + +# The public exponent and modulus can be extracted from the public key with: +# openssl rsa -pubin -in public_key.pem -text -noout + +# The public exponent and modulus can be used to create a public key in C with: +#define CLI_NSTR_EXT_SIG "E32B3AC1D501EE975296A45BA65DD699100DADD340FF3BBD1F1030C66D6BB16DBFBD53DF4D97BBD42EF8FC777E7C114A6074A87DD8095A5C08B3DD7B85817713047647EF396C58358C5C22B5C3ADF85CE8D0ABC429F89E936EC917B64DD00E02A712E6666FAE1A71591092BCEE59E3141758C4719B4B08589117B0FF7CDBDBB261F8486A193E2E720AE0B16D40DD5E56E97346CBD8010DC81B35332F41C9E93E61490802DDCDFC823D581BA6888588968C68A3C95B93949AF411682E73323F7469473F668B0958F6966849FF03BDE808866D127A2C058B16F17C741A9EE50812A5C7841224E55BF7ADDB5AEAE8EB5476F9BC8740178AB35926D5DC375583C641" +#define CLI_ESTR_EXT_SIG "010001" + + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +file_path="$1" +private_key="$2" +offset=512 # 0x200 in decimal + +# Generate the hexadecimal SHA-256 hash +sha256_hash_hex=$(tail -c +$((offset + 1)) "$file_path" | sha256sum | awk '{print $1}') + +# Sign the binary hash (using the binary data corresponding to the hex hash) and output the signature in base64 +signature_hex=$(echo -n "$sha256_hash_hex" | xxd -r -p | openssl pkeyutl -sign -inkey "$private_key" -pkeyopt digest:sha256 | xxd -p -c 1024) + +# Output the hexadecimal hash and the base64-encoded signature +echo "$sha256_hash_hex:$signature_hex" + diff --git a/sigext/extract_mod_and_exp.sh b/sigext/extract_mod_and_exp.sh new file mode 100644 index 0000000000..f75552178a --- /dev/null +++ b/sigext/extract_mod_and_exp.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# This produces the defines needed to use a generated signing key with the ClamAV Database External signature +# system. You must replace the define values for CLI_NSTR_EXT_SIG and CLI_ESTR_EXT_SIG with the values produced +# by this script in order to change the public signing key that CalmAV uses to validate the external signature +# files when in FIPS mode. + +# Useage: +# $ ./extract_mod_and_exp.sh public_key.pem CLI_NSTR_EXT_SIG CLI_ESTR_EXT_SIG +# #define CLI_NSTR_EXT_SIG "E32B3AC1D501EE975296A45BA65DD699100DADD340FF3BBD1F1030C66D6BB16DBFBD53DF4D97BBD42EF8FC777E7C114A6074A87DD8095A5C08B3DD7B85817713047647EF396C58358C5C22B5C3ADF85CE8D0ABC429F89E936EC917B64DD00E02A712E6666FAE1A71591092BCEE59E3141758C4719B4B08589117B0FF7CDBDBB261F8486A193E2E720AE0B16D40DD5E56E97346CBD8010DC81B35332F41C9E93E61490802DDCDFC823D581BA6888588968C68A3C95B93949AF411682E73323F7469473F668B0958F6966849FF03BDE808866D127A2C058B16F17C741A9EE50812A5C7841224E55BF7ADDB5AEAE8EB5476F9BC8740178AB35926D5DC375583C641" +# #define CLI_ESTR_EXT_SIG 65537 + +# Check for correct number of arguments +if [ "$#" -ne 3 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Assign command line arguments to variables +public_key_file=$1 +modulus_name=$2 +exponent_name=$3 + +# Extract the modulus and exponent +modulus=$(openssl rsa -in "$public_key_file" -pubin -noout -modulus | sed 's/Modulus=//') +exponent=$(openssl rsa -in "$public_key_file" -pubin -noout -text | grep 'Exponent' | awk '{print $2}') + +# Format the modulus as a single line string +formatted_modulus=$(echo $modulus | tr -d '\n') + +# Create the #define strings +echo "#define $modulus_name \"$formatted_modulus\"" +echo "#define $exponent_name $exponent" + From d64161d451155ef598b14e547d6f4fe5d8307369 Mon Sep 17 00:00:00 2001 From: Mark Carey Date: Tue, 20 Aug 2024 14:53:03 -0400 Subject: [PATCH 2/3] - Updated with clang-format to clean up pipeline issues. --- libclamav/crypto.c | 98 ++++++++++++++-------------- libclamav/crypto.h | 1 + libclamav/dsig.c | 46 +++++++------ libfreshclam/libfreshclam_internal.c | 38 +++++------ 4 files changed, 88 insertions(+), 95 deletions(-) diff --git a/libclamav/crypto.c b/libclamav/crypto.c index b8895a71f2..3bb911dd44 100644 --- a/libclamav/crypto.c +++ b/libclamav/crypto.c @@ -138,64 +138,64 @@ time_t timegm(struct tm *t) /** * This variable determines if we are operating in FIPS mode, default to no. - */ + */ int cli_fips_mode = 0; /** * @brief This function determines if we are running in FIPS mode and sets the cl_fips_mode variable - * + * * This function is called by cl_init() and does not need to be called by the user - * + * * @return int Returns 1 if we are running in FIPS mode, 0 otherwise - * + * */ void cli_setup_fips_configuration(void) { - #if OPENSSL_VERSION_MAJOR == 1 - // OpenSSL 1.x (1.0 or 1.1) - #ifdef OPENSSL_FIPS - if (FIPS_mode()) { - cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); - cl_fips_mode = 1; - } else { - cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); - cl_fips_mode = 0; - } - #else +#if OPENSSL_VERSION_MAJOR == 1 +// OpenSSL 1.x (1.0 or 1.1) +#ifdef OPENSSL_FIPS + if (FIPS_mode()) { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); + cl_fips_mode = 1; + } else { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); cl_fips_mode = 0; - #endif - - #elif OPENSSL_VERSION_MAJOR == 3 - // OpenSSL 3.0.x - OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new(); - if (libctx == NULL) { - cli_warnmsg("cl_setup_fips_configuration: Failed to create libctx.\n"); - cli_fips_mode = 0; - return; - } + } +#else + cl_fips_mode = 0; +#endif - OSSL_PROVIDER *fips = OSSL_PROVIDER_load(libctx, "fips"); - if (fips != NULL) { - cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); - cli_fips_mode = 1; - OSSL_PROVIDER_unload(fips); - OSSL_LIB_CTX_free(libctx); - } else { - cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); - cli_fips_mode = 0; - OSSL_LIB_CTX_free(libctx); - } - #else - #error "Unsupported OpenSSL version" - #endif +#elif OPENSSL_VERSION_MAJOR == 3 + // OpenSSL 3.0.x + OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new(); + if (libctx == NULL) { + cli_warnmsg("cl_setup_fips_configuration: Failed to create libctx.\n"); + cli_fips_mode = 0; + return; + } + + OSSL_PROVIDER *fips = OSSL_PROVIDER_load(libctx, "fips"); + if (fips != NULL) { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider found.\n"); + cli_fips_mode = 1; + OSSL_PROVIDER_unload(fips); + OSSL_LIB_CTX_free(libctx); + } else { + cli_infomsg_simple("cl_setup_fips_configuration: FIPS mode provider was not found.\n"); + cli_fips_mode = 0; + OSSL_LIB_CTX_free(libctx); + } +#else +#error "Unsupported OpenSSL version" +#endif } /** - * @brief Return the status of our FIPS condition. - * + * @brief Return the status of our FIPS condition. + * * This function allows users of the library to determine if the library is running in FIPS mode. - * + * * @return int Returns 1 if we are running in FIPS mode, 0 otherwise */ @@ -1330,7 +1330,7 @@ void cl_hash_destroy(void *ctx) #if OPENSSL_VERSION_MAJOR == 1 RSA *cli_build_ext_signing_key(void) { - RSA *rsa = RSA_new(); + RSA *rsa = RSA_new(); BIGNUM *n = BN_new(); BIGNUM *e = BN_new(); @@ -1356,12 +1356,12 @@ RSA *cli_build_ext_signing_key(void) // Do this the OpenSSL 3 way, avoiding deprecation warnings EVP_PKEY *cli_build_ext_signing_key(void) { - EVP_PKEY *pkey = EVP_PKEY_new(); - BIGNUM *n = BN_new(); - BIGNUM *e = BN_new(); + EVP_PKEY *pkey = EVP_PKEY_new(); + BIGNUM *n = BN_new(); + BIGNUM *e = BN_new(); OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); - OSSL_PARAM *params = NULL; - int result = 0; + OSSL_PARAM *params = NULL; + int result = 0; // Check bld and params if (!pkey || !n || !e || !bld) { @@ -1439,7 +1439,7 @@ EVP_PKEY *cli_build_ext_signing_key(void) if (params) OSSL_PARAM_free(params); - + if (ctx) EVP_PKEY_CTX_free(ctx); diff --git a/libclamav/crypto.h b/libclamav/crypto.h index 76f0c5b015..961cd93934 100644 --- a/libclamav/crypto.h +++ b/libclamav/crypto.h @@ -22,6 +22,7 @@ #ifndef __CRYPTO_H #define __CRYPTO_H #include +#include void cli_setup_fips_configuration(void); int cli_get_fips_mode(void); diff --git a/libclamav/dsig.c b/libclamav/dsig.c index 204f55cb33..b8b54f483f 100644 --- a/libclamav/dsig.c +++ b/libclamav/dsig.c @@ -429,9 +429,9 @@ int cli_versig2(const unsigned char *sha256, const char *dsig_str, const char *n int cli_hex2bin(const char *hex, unsigned char *bin, int len) { // Use tricks to do this fast and without memory violations - unsigned char *in = (unsigned char *)hex; + unsigned char *in = (unsigned char *)hex; unsigned char *out = bin; - int retlen = len/2; + int retlen = len / 2; while (len--) { *out = 0; @@ -463,7 +463,7 @@ cl_error_t cli_sigver_external(const char *file) { cl_error_t result = CL_ERROR; unsigned char sha256_bin[SHA256_DIGEST_LENGTH]; - char *sha256 = NULL; + char *sha256 = NULL; unsigned char *sig_bin = NULL; // Use the built-in method to hash the CVD file. @@ -498,7 +498,7 @@ cl_error_t cli_sigver_external(const char *file) goto done; } #else - #error "Unsupported OpenSSL version" +#error "Unsupported OpenSSL version" #endif // Convert the sha256 hash to binary @@ -519,10 +519,10 @@ cl_error_t cli_sigver_external(const char *file) result = CL_EMEM; goto done; } - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wstringop-truncation" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" strncpy(sigfile + strlen(sigfile) - 4, ".sig", 4); - #pragma GCC diagnostic pop +#pragma GCC diagnostic pop fs = fopen(sigfile, "rb"); if (fs == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't open signature file %s\n", sigfile); @@ -532,9 +532,9 @@ cl_error_t cli_sigver_external(const char *file) // Read the signature file fseek(fs, 0, SEEK_END); - size_t siglen = (size_t) ftell(fs); + size_t siglen = (size_t)ftell(fs); fseek(fs, 0, SEEK_SET); - char *sig = (char *)malloc(siglen+1); + char *sig = (char *)malloc(siglen + 1); if (sig == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature\n"); fclose(fs); @@ -561,7 +561,7 @@ cl_error_t cli_sigver_external(const char *file) } *sig_seperator = 0; sig_seperator++; - siglen = strlen(sig_seperator)/2; + siglen = strlen(sig_seperator) / 2; sig_bin = (unsigned char *)malloc(siglen); if (sig_bin == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature binary\n"); @@ -578,21 +578,20 @@ cl_error_t cli_sigver_external(const char *file) // If we are using a verson of openssl less than 3.0.0, we need to use the RSA_verify function #if OPENSSL_VERSION_MAJOR == 1 // verify the signature - //int sig_verify = RSA_verify(NID_sha256, sha256, strlen(sha256), sig_bin, siglen, rsa); + // int sig_verify = RSA_verify(NID_sha256, sha256, strlen(sha256), sig_bin, siglen, rsa); int sig_verify = RSA_verify(NID_sha256, sha256_bin, SHA256_DIGEST_LENGTH, sig_bin, siglen, rsa); if (sig_verify != 1) { cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); result = CL_EVERIFY; goto done; - } - else { + } else { cli_dbgmsg("cli_cvd_ext_sig_verify: RSA signature verification successful for external database signature\n"); result = CL_SUCCESS; } #elif OPENSSL_VERSION_MAJOR == 3 // verify the signature EVP_PKEY_CTX *pctx = NULL; - + pctx = EVP_PKEY_CTX_new(rsa, NULL); if (pctx == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't create EVP_PKEY_CTX\n"); @@ -616,27 +615,26 @@ cl_error_t cli_sigver_external(const char *file) cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); result = CL_EVERIFY; goto done; - } - else { + } else { cli_dbgmsg("cli_cvd_ext_sig_verify: RSA signature verification successful for external database signature\n"); result = CL_SUCCESS; } - if(pctx) EVP_PKEY_CTX_free(pctx); + if (pctx) EVP_PKEY_CTX_free(pctx); #else - #error "Unsupported OpenSSL version" +#error "Unsupported OpenSSL version" #endif done: // Clean up - if (sig) free(sig); - if (sigfile) free(sigfile); - if (sha256) free(sha256); - if (sig_bin) free(sig_bin); + if (sig) free(sig); + if (sigfile) free(sigfile); + if (sha256) free(sha256); + if (sig_bin) free(sig_bin); #if OPENSSL_VERSION_NUMBER < 0x30000000L - if (rsa) RSA_free(rsa); + if (rsa) RSA_free(rsa); #else - if (rsa) EVP_PKEY_free(rsa); + if (rsa) EVP_PKEY_free(rsa); #endif return result; diff --git a/libfreshclam/libfreshclam_internal.c b/libfreshclam/libfreshclam_internal.c index 85b8d53d49..94a226342a 100644 --- a/libfreshclam/libfreshclam_internal.c +++ b/libfreshclam/libfreshclam_internal.c @@ -1483,11 +1483,10 @@ static fc_error_t getcvd( // // If we are in FIPS mode, download the external signature file // - char *sigfile = NULL; + char *sigfile = NULL; char *extSigTmpFile = NULL; - char *extSigUrl = NULL; - if (cli_get_fips_mode()) - { + char *extSigUrl = NULL; + if (cli_get_fips_mode()) { // The external signature file is the same as the CVD file, but with a different extension. sigfile = strdup(cvdfile); @@ -1497,13 +1496,13 @@ static fc_error_t getcvd( goto done; } - // Change the extension to .sig - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wstringop-truncation" +// Change the extension to .sig +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" strncpy(sigfile + strlen(sigfile) - 4, ".sig", 4); - #pragma GCC diagnostic pop +#pragma GCC diagnostic pop - urlLen = strlen(server) + strlen("/") + strlen(sigfile); + urlLen = strlen(server) + strlen("/") + strlen(sigfile); extSigUrl = malloc(urlLen + 1); if (!extSigUrl) { logg(LOGG_ERROR, "Can't allocate memory for external signature file URL!\n"); @@ -1518,10 +1517,10 @@ static fc_error_t getcvd( status = FC_EMEM; goto done; } - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wstringop-truncation" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-truncation" strncpy(extSigTmpFile + strlen(extSigTmpFile) - 4, ".sig", 4); - #pragma GCC diagnostic pop +#pragma GCC diagnostic pop // Construct the URL snprintf(extSigUrl, urlLen + 1, "%s/%s", server, sigfile); @@ -1553,14 +1552,13 @@ static fc_error_t getcvd( logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", tmpfile, tmpfile_with_extension, strerror(errno)); status = FC_EDBDIRACCESS; goto done; - } + } // If we're in FIPS mode, temporarily rename the external signature file to the expected path: // The database file will be something like: /share/SAP/clam.d/tmp.b2b103a70a/clamav-3bbac78e36cbf974e1060de6dbbbfea3.tmp-daily.cld // The expected external signature will be: /share/SAP/clam.d/tmp.b2b103a70a/clamav-3bbac78e36cbf974e1060de6dbbbfea3.tmp-daily.sig char *extSigTmpFileWithExtension = NULL; - if (cli_get_fips_mode()) - { + if (cli_get_fips_mode()) { // Temprorarily rename the the sig file to the expected path extSigTmpFileWithExtension = strdup(tmpfile_with_extension); if (!extSigTmpFileWithExtension) { @@ -1597,14 +1595,12 @@ static fc_error_t getcvd( } // Rename the .sig file if we are in FIPS mode - if (cli_get_fips_mode()) - { + if (cli_get_fips_mode()) { if (rename(extSigTmpFileWithExtension, extSigTmpFile) == -1) { logg(LOGG_ERROR, "Can't rename %s to %s: %s\n", extSigTmpFileWithExtension, extSigTmpFile, strerror(errno)); status = FC_EDBDIRACCESS; goto done; } - } if (cvd->version < remoteVersion) { @@ -1618,8 +1614,7 @@ static fc_error_t getcvd( status = FC_SUCCESS; done: - if (cli_get_fips_mode()) - { + if (cli_get_fips_mode()) { if (NULL != extSigTmpFileWithExtension) { free(extSigTmpFileWithExtension); } @@ -2640,8 +2635,7 @@ fc_error_t updatedb( } // If we are running in FIPS mode, we need to move the .sig file in, as well. - if (cli_get_fips_mode()) - { + if (cli_get_fips_mode()) { // just duplicate the newLocalFileName buffer and replace the .cld with .sig char *newLocalSigFilename = strdup(newLocalFilename); strcpy(newLocalSigFilename + strlen(newLocalSigFilename) - 4, ".sig"); From e6d93eff5dc05ecaa598af7399b0f67f69767114 Mon Sep 17 00:00:00 2001 From: Mark Carey Date: Wed, 21 Aug 2024 20:40:11 -0400 Subject: [PATCH 3/3] Added cryptographic method flexibility to external signatures. This includes a demo of post-quantum encryption using the open-quantum-safe project providers for OpenSSL v3 and later. You must have liboqs (https://github.com/open-quantum-safe/liboqs) installed, and the oqsprovider for OpenSSL v3 and later (https://github.com/open-quantum-safe/oqs-provider). Please note that you will also need to have altered the openssl.cnf file to include the oqsprovider in your configuration. Instructions are on the oqsprovider page. I have tested dilithium2 quantum resistant signatures and have included the source in a way that it will not cause faults if the provider is not installed. It should be noted that at the present time NIST has NOT approved dilithium2 for use in FIPS systems. I have put hard blockers on MD5 and SHA1 when running in FIPS mode to prevent mishaps. The signing bash script will now hash and sign files in a flexible manner. Algorithms support for SHA256 and SHA3-256 have been added to the existing hashing functions of the library. I have validated functionality and ran valgrind against this commit. I can find no bugs, but would always love to have a second or third set of eyes to help spot what I missed. Shell script name changed to be more generic in the spirit of supporting arbitrary hashing and signing algorithms. Script is now called: clamav/sigext/cvd_ext_sigh.sh If needed, I can add Elliptical Curve keys and signatures, as well, but it should be fairly elementary for anyone to do so now. --- libclamav/crypto.c | 110 +++++++++++++- libclamav/crypto.h | 4 +- libclamav/dsig.c | 257 +++++++++++++++++++++++---------- libclamav/others.c | 45 +++++- libclamav/others.h | 1 + sigext/cvd_ext_sign.sh | 64 ++++++++ sigext/cvd_hash_sha256_sign.sh | 32 ---- sigext/extract_di2_key.sh | 18 +++ 8 files changed, 409 insertions(+), 122 deletions(-) create mode 100644 sigext/cvd_ext_sign.sh delete mode 100644 sigext/cvd_hash_sha256_sign.sh create mode 100644 sigext/extract_di2_key.sh diff --git a/libclamav/crypto.c b/libclamav/crypto.c index 3bb911dd44..d22c297b80 100644 --- a/libclamav/crypto.c +++ b/libclamav/crypto.c @@ -64,6 +64,8 @@ #define CLI_NSTR_EXT_SIG "E32B3AC1D501EE975296A45BA65DD699100DADD340FF3BBD1F1030C66D6BB16DBFBD53DF4D97BBD42EF8FC777E7C114A6074A87DD8095A5C08B3DD7B85817713047647EF396C58358C5C22B5C3ADF85CE8D0ABC429F89E936EC917B64DD00E02A712E6666FAE1A71591092BCEE59E3141758C4719B4B08589117B0FF7CDBDBB261F8486A193E2E720AE0B16D40DD5E56E97346CBD8010DC81B35332F41C9E93E61490802DDCDFC823D581BA6888588968C68A3C95B93949AF411682E73323F7469473F668B0958F6966849FF03BDE808866D127A2C058B16F17C741A9EE50812A5C7841224E55BF7ADDB5AEAE8EB5476F9BC8740178AB35926D5DC375583C641" #define CLI_ESTR_EXT_SIG "010001" +#define CLI_DI2_EXT_SIG "43b6cb0fc62b2d4e03432505300f7258cdd0be4b2de837cb8f0c394359b0b6d2810056525f178a623fbe5e6eb19eef42ae806583a7ae7bdfaa57d3aaf3d88311bf72538ae4b6843f6a10005bfada7c92b57fb6725d3ccbc9c7de0e91f89920a0eb1f8e039641730c8e7d8450d3a62d349624cec8dd5a4199a2ce6942c058fdc6ac8b5e566067c5f0a5667c86dd1e832327e4de6f8f169e6e20a0f5e08496897bd90797506d9669477eaa13b02862078d0c2cf9db1c70408f4f31eeabcd9603386450fa1781d4a3eaa9163fe5aae9d7c4791153a504334c8840ab8b1f85b3f5e62912c3e2c0a7bb74a7801edb21b30921cc422bd1aa15fa4a6d56fdf05703c156c1937930b86767ad4fd5cbbfc579650bba2e94971f885f020da8fc4a71241526c2a99615aff786184583a7d189c582dc3073dacf6e84eaf9c14f19315c08ad3812cc5acbfb8f42fe0e6b127b5b8df75078231620052c16076e6964fda5471c139d0573637065f8489ff1582c0bef13e5cc46421bc18eeca91fb88cd95e84548bf1b00257b626d9f99e2a3f0887e181ec7f41e1c01d1e55c440dd3f07fdc77005f5669a9475278cd0e9c04e953f538d0101dd14ea37c9d76a3d38bcda2659137f6540b4d5ca43dc3cbfe327b079d1dc449f99f68bdecb54548243165363443c9ec3c28b7e58ee1105d066d1f6fb35e86904592008102ef2045a33defefa8c7ad217c1786fa34dd8315d31032328e36ddcfe4542a655f8b5c099695890159ae00bb6bcfcc8c8c4a2a8b85ba41aa9ae1d067caaeac8b27dcb3c0bc490dff360d7d07b42d0372ed8fee4aac3ecf9bcbcb1c6d137b00fd13385af2dcd936debdd8760922c1bd5e6af5893d0f6a51529d1af42bc05266632c70dae56b3c800163b25ad12e53d20723f3517c3394790c49f8e48fb75310174e0448aa628b68a3f0d0a18756575d211b0551740d29ba7db06fb7d6b6ff0200531bc7a879d705e485470f45cdc78588883132211aae5506bfb523451e177896a84018fd996bc26cf845501d3f19fd948fd5ad7d9d13db578bee40f317092b96aa07acaada37ba2ba79d156bdd7f68f61e8cca0a357a054683df31961ce9913cec9a92de0135c63fa574111aab5baf093b121afb92511bd4b8e0b3fce609d27891b69466246b9cf6eced970142009b172d8c73d2ae25f8c86b3579b75fc7754932afc7aefc0c0255be40c1b67e2d94fe9ae103f807f37a05ab3ee1ff141d221c5942df34f61c9c7287fbc173b6caa1c19523cdf09d22c0b9b3fca5ef49144718c3d79ee9657ec69d5dc78fbde1cce4a5395c96af2f9172fe6c884bfc5335b89b192147c6ed6d9587db80c3c3d373447012eee4fc389b34807d068512815e1d45f7f26294fe6b4e3e52b282c4b57fa7ca39f9d27362925120bd9ec3e2f03e879405f1ba2dc9378244782ef384f244526eb8f81f91f3810a0bb55d228ae54f4a646f56e27cdd8bcb4eba316c7023e66b1cd37c11a7d793e88e93d274ed972a79e220b741ada013067e157211d403a09e7ed8136326ffec72a6638e6d12a64f45277375e620792e5ecfbb10251826251d0cbfca7fb48f0279a3699a0605b813f58f037d18e6aec14e460571fd01acacf65e27b384b2c3aac05fd82065fd287b61e3e4b8f84aa326dd7f6b86eab04352e02719e34655360786bc4236e5d962961e37c523cc03def7ad71e7bb3c2b5aca307559833571b87b486de9d8c3a4aac5ee9435643c62b1d80f41c10a1a08260dca5cbf309ce3eb5b680b7a0b867d6be0963923be21d068d5de36e42d3d86cf4dc8ee0ec48895a8b2f8597ada44e9f01da75864dc4feda2cca1e2ed5ac75" + #if OPENSSL_VERSION_NUMBER < 0x10100000L #define X509_CRL_get0_nextUpdate X509_CRL_get_nextUpdate #endif @@ -1354,7 +1356,7 @@ RSA *cli_build_ext_signing_key(void) } #elif OPENSSL_VERSION_MAJOR == 3 // Do this the OpenSSL 3 way, avoiding deprecation warnings -EVP_PKEY *cli_build_ext_signing_key(void) +EVP_PKEY *cli_build_ext_signing_key_RSA(void) { EVP_PKEY *pkey = EVP_PKEY_new(); BIGNUM *n = BN_new(); @@ -1445,6 +1447,112 @@ EVP_PKEY *cli_build_ext_signing_key(void) return pkey; } + +EVP_PKEY *cli_build_ext_signing_key_Dilithium2(void) +{ + EVP_PKEY *pkey = EVP_PKEY_new(); + int result = 0; + OSSL_PARAM params[2]; + + // Check bld and params + if (!pkey) { + EVP_PKEY_free(pkey); + return NULL; + } + + // Create a context for the public key + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "dilithium2", NULL); + if (!ctx) { + EVP_PKEY_free(pkey); + return NULL; + } + + // Initialize key generation from data + if (EVP_PKEY_fromdata_init(ctx) <= 0) { + EVP_PKEY_free(pkey); + return NULL; + } + + // Setup for binary conversion of the public key string + unsigned char *pubkey = NULL; + size_t key_hex_strlen = strlen(CLI_DI2_EXT_SIG); + size_t pubkey_len = key_hex_strlen / 2; + + pubkey = malloc(pubkey_len); + if (!pubkey) { + EVP_PKEY_free(pkey); + return NULL; + } + + // Convert the hex string to binary + result = OPENSSL_hexstr2buf_ex(pubkey, pubkey_len, NULL, CLI_DI2_EXT_SIG, 0); + if (!result) { + free(pubkey); + EVP_PKEY_free(pkey); + return NULL; + } + + // Build the parameters + params[0] = OSSL_PARAM_construct_octet_string("pub", pubkey, pubkey_len); + params[1] = OSSL_PARAM_construct_end(); + + if (!params[0].data) { + free(pubkey); + EVP_PKEY_free(pkey); + return NULL; + } + + // Construct the public key + int ret_code = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params); + if (ret_code <= 0) { + // Actaully dump the errors here, as post quantum stuff is likely to change + // and knowing why it failed will be helpful to whomever has to debug this later. + cli_errmsg("cli_build_ext_signing_key_Dilithium2: failed to construct public key.\n"); + // Dump all the specific information OpenSSL has about this error. + const char *file; + int line; + const char *function; + const char *data; + int flags; + unsigned long error_code = ERR_peek_last_error_all(&file, &line, &function, &data, &flags); + cli_errmsg("cli_build_ext_signing_key_Dilithium2: OpenSSL error code: %lu (file: %s, line: %d, function: %s, data: %s, flags: %d)\n", error_code, file, line, function, data, flags); + + free(pubkey); + EVP_PKEY_free(pkey); + return NULL; + } + + // Cleanup + // DO NOT free params[0].data, it poitns to pubkey. + // OSSL_PARAM_free(params[0].data); + + // Free the public key + free(pubkey); + + // Free the context + if (ctx) + EVP_PKEY_CTX_free(ctx); + + return pkey; +} + +EVP_PKEY *cli_build_ext_signing_key(unsigned int keytype) +{ + switch (keytype) { + case 1: + cli_dbgmsg("cli_build_ext_signing_key: building RSA external signing key\n"); + return cli_build_ext_signing_key_RSA(); + break; + case 2: + cli_dbgmsg("cli_build_ext_signing_key: building Dilithium2 external signing key\n"); + return cli_build_ext_signing_key_Dilithium2(); + break; + default: + return NULL; + break; + } +} + #else #error "Unsupported OpenSSL version" #endif diff --git a/libclamav/crypto.h b/libclamav/crypto.h index 961cd93934..fb6ba8a26e 100644 --- a/libclamav/crypto.h +++ b/libclamav/crypto.h @@ -28,9 +28,9 @@ void cli_setup_fips_configuration(void); int cli_get_fips_mode(void); #if OPENSSL_VERSION_MAJOR == 1 -RSA *cli_build_ext_signing_key(void); +RSA *cli_build_ext_signing_key(unsigned int keytype); #elif OPENSSL_VERSION_MAJOR == 3 -EVP_PKEY *cli_build_ext_signing_key(void); +EVP_PKEY *cli_build_ext_signing_key(unsigned int keytype); #else #error "Unsupported OpenSSL version" #endif diff --git a/libclamav/dsig.c b/libclamav/dsig.c index b8b54f483f..1fb495da22 100644 --- a/libclamav/dsig.c +++ b/libclamav/dsig.c @@ -426,7 +426,7 @@ int cli_versig2(const unsigned char *sha256, const char *dsig_str, const char *n return ret; } -int cli_hex2bin(const char *hex, unsigned char *bin, int len) +int cli_hex2bin(const char *hex, unsigned char *bin, unsigned int len) { // Use tricks to do this fast and without memory violations unsigned char *in = (unsigned char *)hex; @@ -461,108 +461,100 @@ int cli_hex2bin(const char *hex, unsigned char *bin, int len) cl_error_t cli_sigver_external(const char *file) { - cl_error_t result = CL_ERROR; - unsigned char sha256_bin[SHA256_DIGEST_LENGTH]; - char *sha256 = NULL; + cl_error_t result = CL_ERROR; + unsigned char *hash = NULL; + unsigned int hash_len = 0; unsigned char *sig_bin = NULL; - // Use the built-in method to hash the CVD file. - FILE *fs = fopen(file, "rb"); - if (fs == NULL) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't open file %s\n", file); - return CL_EOPEN; - } - fseek(fs, 512, SEEK_SET); - sha256 = cli_hashstream(fs, NULL, 3); - fclose(fs); - if (sha256 == NULL) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't generate SHA256 hash\n"); - result = CL_EMEM; - goto done; - } - cli_dbgmsg("SHA256(.tar.gz) = %s\n", sha256); - - // Build the RSA key from the exponent and modulus -#if OPENSSL_VERSION_MAJOR == 1 - RSA *rsa = cli_build_ext_signing_key(); - if (rsa == NULL) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't create RSA key from public key\n"); - result = CL_EVERIFY; - goto done; - } -#elif OPENSSL_VERSION_MAJOR == 3 - EVP_PKEY *rsa = cli_build_ext_signing_key(); - if (rsa == NULL) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't create RSA key from public key\n"); - result = CL_EVERIFY; - goto done; - } -#else -#error "Unsupported OpenSSL version" -#endif - - // Convert the sha256 hash to binary - if (cli_hex2bin(sha256, sha256_bin, SHA256_DIGEST_LENGTH) == -1) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't convert sha256 hash to binary\n"); - result = CL_EVERIFY; - goto done; - } - // // External Signature processing // // Load the external signature file - char *sigfile = strdup(file); - if (sigfile == NULL) { + char *sig_filename = strdup(file); + if (sig_filename == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature file name\n"); result = CL_EMEM; goto done; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-truncation" - strncpy(sigfile + strlen(sigfile) - 4, ".sig", 4); + strncpy(sig_filename + strlen(sig_filename) - 4, ".sig", 4); #pragma GCC diagnostic pop - fs = fopen(sigfile, "rb"); + FILE *fs = fopen(sig_filename, "rb"); if (fs == NULL) { - cli_errmsg("cli_cvd_ext_sig_verify: Can't open signature file %s\n", sigfile); + cli_errmsg("cli_cvd_ext_sig_verify: Can't open signature file %s\n", sig_filename); result = CL_EOPEN; goto done; } // Read the signature file fseek(fs, 0, SEEK_END); - size_t siglen = (size_t)ftell(fs); + size_t sigfile_len = (size_t)ftell(fs); fseek(fs, 0, SEEK_SET); - char *sig = (char *)malloc(siglen + 1); - if (sig == NULL) { + char *sig_file = (char *)malloc(sigfile_len + 1); + if (sig_file == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature\n"); fclose(fs); result = CL_EMEM; goto done; } - if (fread(sig, 1, siglen, fs) != siglen) { + if (fread(sig_file, 1, sigfile_len, fs) != sigfile_len) { cli_errmsg("cli_cvd_ext_sig_verify: Can't read signature from file\n"); fclose(fs); result = CL_EVERIFY; goto done; } - sig[siglen] = 0; + sig_file[sigfile_len] = 0; fclose(fs); - // Extract the parts of the signature file - // it's kept in a format like: "SHA256 hash hex:RSA signature hex" - char *sig_seperator = strchr(sig, ':'); - if (sig_seperator == NULL) { + // + // Parse the signature file + // + + // Use strtok to extract the parts of the signature file + // it's kept in a format like: "hash algorithm:signature algorithm:SHA256 hash hex:RSA signature hex" + char *hash_alg = strtok(sig_file, ":"); + if (hash_alg == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't find hash algorithm in external database signature file\n"); + result = CL_EVERIFY; + goto done; + } + + // Parse the integer into our hash algorithm selector + int hash_algorithm = atoi(hash_alg); + + // Store the signature algorithm + char *sig_alg = strtok(NULL, ":"); + if (sig_alg == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't find signature algorithm in external database signature file\n"); + result = CL_EVERIFY; + goto done; + } + + // Parse the integer into our signature algorithm selector + int sig_algorithm = atoi(sig_alg); + + // Store the start of the hash + char *ext_hash = strtok(NULL, ":"); + if (ext_hash == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't find hash in external database signature file\n"); + result = CL_EVERIFY; + goto done; + } + + // Extract the signature hex string from the signature file + char *sig_hex = strtok(NULL, ":"); + if (sig_hex == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't find signature in external database signature file\n"); result = CL_EVERIFY; goto done; } - *sig_seperator = 0; - sig_seperator++; - siglen = strlen(sig_seperator) / 2; - sig_bin = (unsigned char *)malloc(siglen); + + // Now extract the binary of the signature + int siglen = strlen(sig_hex) / 2; + sig_bin = (unsigned char *)malloc(siglen); if (sig_bin == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for signature binary\n"); result = CL_EMEM; @@ -570,16 +562,88 @@ cl_error_t cli_sigver_external(const char *file) } // convert the signature to binary - if (cli_hex2bin(sig_seperator, sig_bin, siglen) == -1) { + if (cli_hex2bin(sig_hex, sig_bin, siglen) == -1) { cli_errmsg("cli_cvd_ext_sig_verify: Can't convert signature to binary\n"); result = CL_EVERIFY; } - // If we are using a verson of openssl less than 3.0.0, we need to use the RSA_verify function + // + // The signature file has been parsed. Now hash the database file using the selected algorithm + // + + // Use the built-in method to hash the CVD file. + FILE *fq = fopen(file, "rb"); + if (fq == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't open file %s\n", file); + return CL_EOPEN; + } + fseek(fq, 512, SEEK_SET); + char *db_hash = cli_hashstream_ex(fq, NULL, hash_algorithm, &hash_len); + fclose(fq); + if (db_hash == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't generate hash for algorithm: %d\n", hash_algorithm); + result = CL_EMEM; + goto done; + } + cli_dbgmsg("HASH OF(.tar.gz) using alg %d = %s\n", hash_algorithm, db_hash); + + // Allocate memory for the hash + hash = (unsigned char *)malloc(hash_len); + if (hash == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't allocate memory for hash\n"); + result = CL_EMEM; + goto done; + } + + // Convert the hash to binary + if (cli_hex2bin(db_hash, hash, hash_len) == -1) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't convert hash to binary\n"); + result = CL_EVERIFY; + goto done; + } + + // + // Sanity check the hash in the external signature file vs the internally generated hash + // + + // Compare the hashes (we trust the hash we generate from the db file) + if (strncmp(db_hash, sig_hex, strlen(db_hash)) == 0) { + cli_errmsg("cli_cvd_ext_sig_verify: Hash in external database signature file does not match hash of database file\n"); + result = CL_EVERIFY; + goto done; + } + + // + // Build the public key from raw values for the selected algorithm + // #if OPENSSL_VERSION_MAJOR == 1 + RSA *key = cli_build_ext_signing_key(sig_algorithm); + if (key == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't create public key from values\n"); + result = CL_EVERIFY; + goto done; + } +#elif OPENSSL_VERSION_MAJOR == 3 + EVP_PKEY *key = cli_build_ext_signing_key(sig_algorithm); + if (key == NULL) { + cli_errmsg("cli_cvd_ext_sig_verify: Can't create public key from values\n"); + result = CL_EVERIFY; + goto done; + } +#else +#error "Unsupported OpenSSL version" +#endif + + cli_dbgmsg("cli_cvd_ext_sig_verify: Public key created from values\n"); + + // If we are using a version of openssl less than 3.0.0, we need to use the RSA_verify function +#if OPENSSL_VERSION_MAJOR == 1 + // OpenSSL library 1.1.x exclusively uses RSA_verify to verify RSA signatures + // The reasoning on this is that moving forward, FIPS systems will require + // versions of the OpenSSL library that are 3.0.x and above. + // verify the signature - // int sig_verify = RSA_verify(NID_sha256, sha256, strlen(sha256), sig_bin, siglen, rsa); - int sig_verify = RSA_verify(NID_sha256, sha256_bin, SHA256_DIGEST_LENGTH, sig_bin, siglen, rsa); + int sig_verify = RSA_verify(NID_sha256, hash, hash_Len, sig_bin, siglen, key); if (sig_verify != 1) { cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); result = CL_EVERIFY; @@ -592,7 +656,7 @@ cl_error_t cli_sigver_external(const char *file) // verify the signature EVP_PKEY_CTX *pctx = NULL; - pctx = EVP_PKEY_CTX_new(rsa, NULL); + pctx = EVP_PKEY_CTX_new(key, NULL); if (pctx == NULL) { cli_errmsg("cli_cvd_ext_sig_verify: Can't create EVP_PKEY_CTX\n"); result = CL_EVERIFY; @@ -605,18 +669,52 @@ cl_error_t cli_sigver_external(const char *file) goto done; } - if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha256()) != 1) { + const EVP_MD *md = NULL; + switch (hash_algorithm) { + case 1: + // MD5 is not allowed in FIPS mode, verify we are not in FIPS mode + if (cli_get_fips_mode()) { + cli_errmsg("cli_cvd_ext_sig_verify: MD5 is not allowed in FIPS mode\n"); + result = CL_EVERIFY; + goto done; + } else { + md = EVP_md5(); + } + break; + case 2: + // SHA1 is not allowed in FIPS mode, verify we are not in FIPS mode + if (cli_get_fips_mode()) { + cli_errmsg("cli_cvd_ext_sig_verify: SHA1 is not allowed in FIPS mode\n"); + result = CL_EVERIFY; + goto done; + } else { + md = EVP_sha1(); + } + break; + case 3: + md = EVP_sha256(); + break; + case 4: + md = EVP_sha3_512(); + break; + default: + cli_errmsg("cli_cvd_ext_sig_verify: Unsupported hash algorithm\n"); + result = CL_EVERIFY; + goto done; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) { cli_errmsg("cli_cvd_ext_sig_verify: Can't set signature MD\n"); result = CL_EVERIFY; goto done; } - if (EVP_PKEY_verify(pctx, sig_bin, siglen, sha256_bin, SHA256_DIGEST_LENGTH) != 1) { - cli_errmsg("cli_cvd_ext_sig_verify: RSA signature verification failed for external database signature\n"); + if (EVP_PKEY_verify(pctx, sig_bin, siglen, (const unsigned char *)hash, hash_len) != 1) { + cli_errmsg("cli_cvd_ext_sig_verify: Cryptographic signature verification failed for external database signature\n"); result = CL_EVERIFY; goto done; } else { - cli_dbgmsg("cli_cvd_ext_sig_verify: RSA signature verification successful for external database signature\n"); + cli_dbgmsg("cli_cvd_ext_sig_verify: Cryptographic signature verification successful for external database signature\n"); result = CL_SUCCESS; } @@ -627,15 +725,16 @@ cl_error_t cli_sigver_external(const char *file) done: // Clean up - if (sig) free(sig); - if (sigfile) free(sigfile); - if (sha256) free(sha256); + if (sig_filename) free(sig_filename); + if (sig_file) free(sig_file); + if (db_hash) free(db_hash); + if (hash) free(hash); if (sig_bin) free(sig_bin); #if OPENSSL_VERSION_NUMBER < 0x30000000L - if (rsa) RSA_free(rsa); + if (key) RSA_free(key); #else - if (rsa) EVP_PKEY_free(rsa); + if (key) EVP_PKEY_free(key); #endif return result; -} \ No newline at end of file +} diff --git a/libclamav/others.c b/libclamav/others.c index e4b19a0955..7e4088c45a 100644 --- a/libclamav/others.c +++ b/libclamav/others.c @@ -1233,18 +1233,19 @@ cl_error_t cli_checktimelimit(cli_ctx *ctx) return ret; } -/* - * Type: 1 = MD5, 2 = SHA1, 3 = SHA256 - */ -char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type) +// Not publicly exported, used for internal purposes to allow for hash length to be returned +char *cli_hashstream_ex(FILE *fs, unsigned char *digcpy, int type, unsigned int *hash_len) { - unsigned char digest[32]; + unsigned char *digest; char buff[FILEBUFF]; char *hashstr, *pt; const char *alg = NULL; - int i, bytes, size; + int i, bytes, size = 0; void *ctx; + if (!fs) + return NULL; + switch (type) { case 1: alg = "md5"; @@ -1254,23 +1255,38 @@ char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type) alg = "sha1"; size = 20; break; + case 3: + alg = "sha256"; + size = 32; + break; + case 4: + alg = "sha3-512"; + size = 64; + break; default: alg = "sha256"; size = 32; break; } + if (!(digest = (unsigned char *)calloc(size, sizeof(unsigned char)))) + return NULL; + ctx = cl_hash_init(alg); - if (!(ctx)) + if (!(ctx)) { + free(digest); return NULL; + } while ((bytes = fread(buff, 1, FILEBUFF, fs))) cl_update_hash(ctx, buff, bytes); cl_finish_hash(ctx, digest); - if (!(hashstr = (char *)calloc(size * 2 + 1, sizeof(char)))) + if (!(hashstr = (char *)calloc(size * 2 + 1, sizeof(unsigned char)))) { + free(digest); return NULL; + } pt = hashstr; for (i = 0; i < size; i++) { @@ -1281,9 +1297,22 @@ char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type) if (digcpy) memcpy(digcpy, digest, size); + if (hash_len != NULL) { + *hash_len = size; + } + + free(digest); return hashstr; } +/* + * Type: 1 = MD5, 2 = SHA1, 3 = SHA256, 4 = SHA3-512 + */ +char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type) +{ + return cli_hashstream_ex(fs, digcpy, type, 0); +} + char *cli_hashfile(const char *filename, int type) { FILE *fs; diff --git a/libclamav/others.h b/libclamav/others.h index 8cebf78d35..b3511556f8 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -1026,6 +1026,7 @@ char *cli_safer_strdup(const char *s); int cli_rmdirs(const char *dirname); char *cli_hashstream(FILE *fs, unsigned char *digcpy, int type); +char *cli_hashstream_ex(FILE *fs, unsigned char *digcpy, int type, unsigned int *hash_len); char *cli_hashfile(const char *filename, int type); /** diff --git a/sigext/cvd_ext_sign.sh b/sigext/cvd_ext_sign.sh new file mode 100644 index 0000000000..666b066def --- /dev/null +++ b/sigext/cvd_ext_sign.sh @@ -0,0 +1,64 @@ +#!/bin/bash + + +if [ "$#" -ne 4 ]; then + echo "Usage: $0 " + echo "Example: $0 /path/to/file private_key.pem 1 1" + echo "Hash Algorithms: " + echo "1 - MD5" + echo "2 - SHA-256" + echo "3 - SHA3-512" + echo "Signature Algorithms: " + echo "1 - RSA" + echo "2 - Dilithium2" + exit 1 +fi + +file_path="$1" +private_key="$2" +hash_algorithm="$3" +signature_algorithm="$4" +offset=512 # 0x200 in decimal + +# Base on the $algorithm, select the right way to hash and sign +case $hash_algorithm in + '1') + hash_alg_name="md5" + # Generate the hexadecimal MD5 hash + hash_hex=$(tail -c +$((offset + 1)) "$file_path" | md5sum | awk '{print $1}') + ;; + '2') + hash_alg_name="sha1" + # Generate the hexadecimal SHA-1 hash + hash_hex=$(tail -c +$((offset + 1)) "$file_path" | shasum | awk '{print $1}') + ;; + '3') + hash_alg_name="sha256" + # Generate the hexadecimal SHA-256 hash + hash_hex=$(tail -c +$((offset + 1)) "$file_path" | sha256sum | awk '{print $1}') + ;; + '4') + hash_alg_name="sha3-512" + # Generate the hexidecimal SHA3-512 hash + hash_hex=$(tail -c +$((offset + 1)) "$file_path" | openssl dgst -sha3-512 | awk '{print $2}') + ;; +esac + +case $signature_algorithm in + '1') + # Create the RSA keypair with: + # openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 + # openssl rsa -pubout -in private_key.pem -out public_key.pem + signature_hex=$(echo -n "$hash_hex" | xxd -r -p | openssl pkeyutl -sign -inkey "$private_key" -pkeyopt digest:$hash_alg_name | xxd -p -c 8192) + ;; + + '2') + # Create the dilithium2 key with: + # openssl genpkey -algorithm dilithium2 -out private_key_di2.pem + # openssl pkey -in private_key_di2.pem -pubout -out public_key_di2.pem + signature_hex=$(echo -n "$hash_hex" | xxd -r -p | openssl pkeyutl -sign -inkey "$private_key" -pkeyopt digest:$hash_alg_name | xxd -p -c 8192) + ;; +esac + +# Output the hexadecimal hash and the base64-encoded signature +echo "$hash_algorithm:$signature_algorithm:$hash_hex:$signature_hex" diff --git a/sigext/cvd_hash_sha256_sign.sh b/sigext/cvd_hash_sha256_sign.sh deleted file mode 100644 index 68aedc7360..0000000000 --- a/sigext/cvd_hash_sha256_sign.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# Create the keypair with: -# openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 -# openssl rsa -pubout -in private_key.pem -out public_key.pem - -# The public exponent and modulus can be extracted from the public key with: -# openssl rsa -pubin -in public_key.pem -text -noout - -# The public exponent and modulus can be used to create a public key in C with: -#define CLI_NSTR_EXT_SIG "E32B3AC1D501EE975296A45BA65DD699100DADD340FF3BBD1F1030C66D6BB16DBFBD53DF4D97BBD42EF8FC777E7C114A6074A87DD8095A5C08B3DD7B85817713047647EF396C58358C5C22B5C3ADF85CE8D0ABC429F89E936EC917B64DD00E02A712E6666FAE1A71591092BCEE59E3141758C4719B4B08589117B0FF7CDBDBB261F8486A193E2E720AE0B16D40DD5E56E97346CBD8010DC81B35332F41C9E93E61490802DDCDFC823D581BA6888588968C68A3C95B93949AF411682E73323F7469473F668B0958F6966849FF03BDE808866D127A2C058B16F17C741A9EE50812A5C7841224E55BF7ADDB5AEAE8EB5476F9BC8740178AB35926D5DC375583C641" -#define CLI_ESTR_EXT_SIG "010001" - - -if [ "$#" -ne 2 ]; then - echo "Usage: $0 " - exit 1 -fi - -file_path="$1" -private_key="$2" -offset=512 # 0x200 in decimal - -# Generate the hexadecimal SHA-256 hash -sha256_hash_hex=$(tail -c +$((offset + 1)) "$file_path" | sha256sum | awk '{print $1}') - -# Sign the binary hash (using the binary data corresponding to the hex hash) and output the signature in base64 -signature_hex=$(echo -n "$sha256_hash_hex" | xxd -r -p | openssl pkeyutl -sign -inkey "$private_key" -pkeyopt digest:sha256 | xxd -p -c 1024) - -# Output the hexadecimal hash and the base64-encoded signature -echo "$sha256_hash_hex:$signature_hex" - diff --git a/sigext/extract_di2_key.sh b/sigext/extract_di2_key.sh new file mode 100644 index 0000000000..fccd460dcd --- /dev/null +++ b/sigext/extract_di2_key.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Check if a key file is provided +if [ $# -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Input PEM file +PEM_FILE=$1 +DEFINE_NAME=$2 + +# The below command dumps out only the public key and formats it for teh define +HEX_STRING=$(openssl pkey -in "$PEM_FILE" -text -noout -pubout | grep -v "dilithium2 public key:" | grep -v "PQ key material:" | tr -d '\n\t :') + +# Print the compact hex string +echo "#define $DEFINE_NAME \"$HEX_STRING\"" +