From e6d93eff5dc05ecaa598af7399b0f67f69767114 Mon Sep 17 00:00:00 2001 From: Mark Carey Date: Wed, 21 Aug 2024 20:40:11 -0400 Subject: [PATCH] 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\"" +