Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- Add external FIPS 140-2 / 140-3 compliant signatures for the Clam Databases. #1344

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 353 additions & 7 deletions libclamav/crypto.c

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions libclamav/crypto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 <openssl/opensslv.h>
#include <openssl/evp.h>

void cli_setup_fips_configuration(void);
int cli_get_fips_mode(void);

#if OPENSSL_VERSION_MAJOR == 1
RSA *cli_build_ext_signing_key(unsigned int keytype);
#elif OPENSSL_VERSION_MAJOR == 3
EVP_PKEY *cli_build_ext_signing_key(unsigned int keytype);
#else
#error "Unsupported OpenSSL version"

Check failure on line 35 in libclamav/crypto.h

View workflow job for this annotation

GitHub Actions / build-windows

#error: "Unsupported OpenSSL version"

Check failure on line 35 in libclamav/crypto.h

View workflow job for this annotation

GitHub Actions / build-windows

#error: "Unsupported OpenSSL version"
#endif

#endif
11 changes: 11 additions & 0 deletions libclamav/cvd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <openssl/rsa.h>

#define TAR_BLOCKSIZE 512

static void cli_untgz_cleanup(char *path, gzFile infile, FILE *outfile, int fdd)
Expand Down Expand Up @@ -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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call should not be guarded by cli_fips_mode, external signature file should be preferred if present.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concur. If we're going to add a better way to validate signing, it should be preferred. We should only fallback to the legacy signature verification if A. the new-style signature is not present and B. not FIPS-mode.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding comments all over the code to this extent. Every place that guards the validation/fetching of the external signature file with cli_fips_mode should be removed so that the new format is the default and only trusted signature in versions that support it.

if (ret != CL_SUCCESS) {
return ret;
}
}

/* verify */
if ((ret = cli_cvdverify(fs, &cvd, dbtype)))
return ret;
Expand Down
314 changes: 314 additions & 0 deletions libclamav/dsig.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include "clamav.h"
#include "others.h"
#include "crypto.h"
#include "dsig.h"
#include "str.h"

Expand Down Expand Up @@ -424,3 +425,316 @@ 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, unsigned 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 *hash = NULL;
unsigned int hash_len = 0;
unsigned char *sig_bin = NULL;

//
// External Signature processing
//

// Load the external signature file
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(sig_filename + strlen(sig_filename) - 4, ".sig", 4);
#pragma GCC diagnostic pop
FILE *fs = fopen(sig_filename, "rb");
if (fs == NULL) {
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 sigfile_len = (size_t)ftell(fs);
fseek(fs, 0, SEEK_SET);
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_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_file[sigfile_len] = 0;
fclose(fs);

//
// 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;
}

// 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;
goto done;
}

// convert the signature to binary
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;
}

//
// 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, 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;
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(key, 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;
}

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, (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: Cryptographic 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_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 (key) RSA_free(key);
#else
if (key) EVP_PKEY_free(key);
#endif

return result;
}
Loading
Loading