mirror of
https://github.com/Cisco-Talos/clamav.git
synced 2025-10-19 10:23:17 +00:00

ClamAV will not function when using a FIPS-enabled OpenSSL 3.x. This is because ClamAV uses MD5 and SHA1 algorithms for a variety of purposes including matching for malware detection, matching to prevent false positives on known-clean files, and for verification of MD5-based RSA digital signatures for determining CVD (signature database archive) authenticity. Interestingly, FIPS had been intentionally bypassed when creating hashes based whole buffers and whole files (by descriptor or `FILE`-pointer):78d4a9985a
Note: this bypassed FIPS the 1.x way with: `EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);` It was NOT disabled when using `cl_hash_init()` / `cl_update_hash()` / `cl_finish_hash()`. That likely worked by coincidence in that the hash was already calculated most of the time. It certainly would have made use of those functions if the hash had not been calculated prior:78d4a9985a/libclamav/matcher.c (L743)
Regardless, bypassing FIPS entirely is not the correct solution. The FIPS restrictions against using MD5 and SHA1 are valid, particularly when verifying CVD digital siganatures, but also I think when using a hash to determine if the file is known-clean (i.e. the "clean cache" and also MD5-based and SHA1-based FP signatures). This commit extends the work to bypass FIPS using the newer 3.x method: `md = EVP_MD_fetch(NULL, alg, "-fips");` It does this for the legacy `cl_hash*()` functions including `cl_hash_init()` / `cl_update_hash()` / `cl_finish_hash()`. It also introduces extended versions that allow the caller to choose if they want to bypass FIPS: - `cl_hash_data_ex()` - `cl_hash_init_ex()` - `cl_update_hash_ex()` - `cl_finish_hash_ex()` - `cl_hash_destroy_ex()` - `cl_hash_file_fd_ex()` See the `flags` parameter for each. Ironically, this commit does NOT use the new functions at this time. The rational is that ClamAV may need MD5, SHA1, and SHA-256 hashes of the same files both for determining if the file is malware, and for determining if the file is clean. So instead, this commit will do a checks when: 1. Creating a new ClamAV scanning engine. If FIPS-mode enabled, it will automatically toggle the "FIPS limits" engine option. When loading signatures, if the engine "FIPS limits" option is enabled, then MD5 and SHA1 FP signatures will be skipped. 2. Before verifying a CVD (e.g. also for loading, unpacking when verification enabled). If "FIPS limits" or FIPS-mode are enabled, then the legacy MD5-based RSA method is disabled. Note: This commit also refactors the interface for `cl_cvdverify_ex()` and `cl_cvdunpack_ex()` so they take a `flags` parameters, rather than a single `bool`. As these functions are new in this version, it does not break the ABI. The cache was already switched to use SHA2-256, so that's not a concern for checking FIPS-mode / FIPS limits options. This adds an option for `freshclam.conf` and `clamd.conf`: FIPSCryptoHashLimits yes And an equivalent command-line option for `clamscan` and `sigtool`: --fips-limits You may programmatically enable FIPS-limits for a ClamAV engine like this: ```C cl_engine_set_num(engine, CL_ENGINE_FIPS_LIMITS, 1); ``` CLAM-2792
2444 lines
99 KiB
C
2444 lines
99 KiB
C
/*
|
|
* Copyright (C) 2013-2025 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.
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <time.h>
|
|
#include <openssl/bn.h>
|
|
|
|
#include "clamav.h"
|
|
#include "asn1.h"
|
|
#include "matcher-hash.h"
|
|
|
|
/* --------------------------------------------------------------------------- OIDS */
|
|
#define OID_1_3_14_3_2_26 "\x2b\x0e\x03\x02\x1a"
|
|
#define OID_sha1 OID_1_3_14_3_2_26
|
|
|
|
#define OID_1_3_14_3_2_29 "\x2b\x0e\x03\x02\x1d"
|
|
#define OID_sha1WithRSA OID_1_3_14_3_2_29
|
|
|
|
#define OID_1_2_840_113549_1_1_1 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01"
|
|
#define OID_rsaEncryption OID_1_2_840_113549_1_1_1
|
|
|
|
#define OID_1_2_840_113549_1_1_2 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x02"
|
|
#define OID_md2WithRSAEncryption OID_1_2_840_113549_1_1_2
|
|
|
|
#define OID_1_2_840_113549_1_1_4 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x04"
|
|
#define OID_md5WithRSAEncryption OID_1_2_840_113549_1_1_4
|
|
|
|
#define OID_1_2_840_113549_1_1_5 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x05"
|
|
#define OID_sha1WithRSAEncryption OID_1_2_840_113549_1_1_5
|
|
|
|
#define OID_1_2_840_113549_1_1_11 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b"
|
|
#define OID_sha256WithRSAEncryption OID_1_2_840_113549_1_1_11
|
|
|
|
#define OID_1_2_840_113549_1_1_12 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0c"
|
|
#define OID_sha384WithRSAEncryption OID_1_2_840_113549_1_1_12
|
|
|
|
#define OID_1_2_840_113549_1_1_13 "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0d"
|
|
#define OID_sha512WithRSAEncryption OID_1_2_840_113549_1_1_13
|
|
|
|
#define OID_1_2_840_113549_1_7_1 "\x2a\x86\x48\x86\xf7\x0d\x01\x07\x01"
|
|
#define OID_pkcs7_data OID_1_2_840_113549_1_7_1
|
|
|
|
#define OID_1_2_840_113549_1_7_2 "\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02"
|
|
#define OID_signedData OID_1_2_840_113549_1_7_2
|
|
|
|
#define OID_1_2_840_113549_1_9_3 "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x03"
|
|
#define OID_contentType OID_1_2_840_113549_1_9_3
|
|
|
|
#define OID_1_2_840_113549_1_9_4 "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04"
|
|
#define OID_messageDigest OID_1_2_840_113549_1_9_4
|
|
|
|
#define OID_1_2_840_113549_1_9_5 "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x05"
|
|
#define OID_signingTime OID_1_2_840_113549_1_9_5
|
|
|
|
#define OID_1_2_840_113549_2_5 "\x2a\x86\x48\x86\xf7\x0d\x02\x05"
|
|
#define OID_md5 OID_1_2_840_113549_2_5
|
|
|
|
#define OID_1_2_840_113549_1_9_6 "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x06"
|
|
#define OID_countersignature OID_1_2_840_113549_1_9_6
|
|
|
|
#define OID_1_2_840_113549_1_9_16_1_4 "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x10\x01\x04"
|
|
#define OID_timestampToken OID_1_2_840_113549_1_9_16_1_4
|
|
|
|
#define OID_1_3_6_1_4_1_311_2_1_4 "\x2b\x06\x01\x04\x01\x82\x37\x02\x01\x04"
|
|
#define OID_SPC_INDIRECT_DATA_OBJID OID_1_3_6_1_4_1_311_2_1_4
|
|
|
|
#define OID_1_3_6_1_4_1_311_2_1_15 "\x2b\x06\x01\x04\x01\x82\x37\x02\x01\x0f"
|
|
#define OID_SPC_PE_IMAGE_DATA_OBJID OID_1_3_6_1_4_1_311_2_1_15
|
|
|
|
#define OID_1_3_6_1_4_1_311_2_1_25 "\x2b\x06\x01\x04\x01\x82\x37\x02\x01\x19"
|
|
#define OID_SPC_CAB_DATA_OBJID OID_1_3_6_1_4_1_311_2_1_25
|
|
|
|
#define OID_1_3_6_1_4_1_311_2_4_1 "\x2b\x06\x01\x04\x01\x82\x37\x02\x04\x01"
|
|
#define OID_nestedSignatures OID_1_3_6_1_4_1_311_2_4_1
|
|
|
|
#define OID_1_3_6_1_4_1_311_3_3_1 "\x2b\x06\x01\x04\x01\x82\x37\x03\x03\x01"
|
|
#define OID_RFC3161_countersignature OID_1_3_6_1_4_1_311_3_3_1
|
|
|
|
#define OID_1_3_6_1_4_1_311_10_1 "\x2b\x06\x01\x04\x01\x82\x37\x0a\x01"
|
|
#define OID_szOID_CTL OID_1_3_6_1_4_1_311_10_1
|
|
|
|
#define OID_1_3_6_1_4_1_311_12_1_1 "\x2b\x06\x01\x04\x01\x82\x37\x0c\x01\x01"
|
|
#define OID_szOID_CATALOG_LIST OID_1_3_6_1_4_1_311_12_1_1
|
|
|
|
#define OID_1_3_6_1_4_1_311_12_1_2 "\x2b\x06\x01\x04\x01\x82\x37\x0c\x01\x02"
|
|
#define OID_szOID_CATALOG_LIST_MEMBER OID_1_3_6_1_4_1_311_12_1_2
|
|
|
|
/* CATALOG_LIST_MEMBER2 seems to be what's used by the SHA2-256-based CAT files */
|
|
#define OID_1_3_6_1_4_1_311_12_1_3 "\x2b\x06\x01\x04\x01\x82\x37\x0c\x01\x03"
|
|
#define OID_szOID_CATALOG_LIST_MEMBER2 OID_1_3_6_1_4_1_311_12_1_3
|
|
|
|
#define OID_2_16_840_1_101_3_4_2_1 "\x60\x86\x48\x01\x65\x03\x04\x02\x01"
|
|
#define OID_sha256 OID_2_16_840_1_101_3_4_2_1
|
|
|
|
#define OID_2_16_840_1_101_3_4_2_2 "\x60\x86\x48\x01\x65\x03\x04\x02\x02"
|
|
#define OID_sha384 OID_2_16_840_1_101_3_4_2_2
|
|
|
|
#define OID_2_16_840_1_101_3_4_2_3 "\x60\x86\x48\x01\x65\x03\x04\x02\x03"
|
|
#define OID_sha512 OID_2_16_840_1_101_3_4_2_3
|
|
|
|
/* --------------------------------------------------------------------------- OIDS */
|
|
#define lenof(x) (sizeof((x)) - 1)
|
|
|
|
#define ASN1_TYPE_BOOLEAN 0x01
|
|
#define ASN1_TYPE_INTEGER 0x02
|
|
#define ASN1_TYPE_BIT_STRING 0x03
|
|
#define ASN1_TYPE_OCTET_STRING 0x04
|
|
#define ASN1_TYPE_NULL 0x05
|
|
#define ASN1_TYPE_OBJECT_ID 0x06
|
|
#define ASN1_TYPE_SEQUENCE 0x30
|
|
#define ASN1_TYPE_SET 0x31
|
|
|
|
#define MAX_HASH_SIZE SHA512_HASH_SIZE
|
|
|
|
struct cli_asn1 {
|
|
uint8_t type;
|
|
unsigned int size;
|
|
const void *content;
|
|
const void *next;
|
|
};
|
|
|
|
static int map_raw(fmap_t *map, const void *data, unsigned int len, uint8_t raw[CRT_RAWMAXLEN])
|
|
{
|
|
unsigned int elen = MIN(len, CRT_RAWMAXLEN - 1);
|
|
|
|
if (!fmap_need_ptr_once(map, data, elen)) {
|
|
cli_dbgmsg("map_raw: failed to read map data\n");
|
|
return 1;
|
|
}
|
|
memset(raw, 0, CRT_RAWMAXLEN);
|
|
raw[0] = (uint8_t)elen;
|
|
memcpy(&raw[1], data, elen);
|
|
return 0;
|
|
}
|
|
|
|
static int map_sha2_512(fmap_t *map, const void *data, unsigned int len, uint8_t sha2_512[SHA512_HASH_SIZE])
|
|
{
|
|
if (!fmap_need_ptr_once(map, data, len)) {
|
|
cli_dbgmsg("map_sha2_512: failed to read hash data\n");
|
|
return 1;
|
|
}
|
|
return (cl_sha512(data, len, sha2_512, NULL) == NULL);
|
|
}
|
|
|
|
static int map_sha2_384(fmap_t *map, const void *data, unsigned int len, uint8_t sha2_384[SHA384_HASH_SIZE])
|
|
{
|
|
if (!fmap_need_ptr_once(map, data, len)) {
|
|
cli_dbgmsg("map_sha2_384: failed to read hash data\n");
|
|
return 1;
|
|
}
|
|
return (cl_sha384(data, len, sha2_384, NULL) == NULL);
|
|
}
|
|
|
|
static int map_sha2_256(fmap_t *map, const void *data, unsigned int len, uint8_t sha2_256[SHA256_HASH_SIZE])
|
|
{
|
|
if (!fmap_need_ptr_once(map, data, len)) {
|
|
cli_dbgmsg("map_sha2_256: failed to read hash data\n");
|
|
return 1;
|
|
}
|
|
return (cl_sha256(data, len, sha2_256, NULL) == NULL);
|
|
}
|
|
|
|
static int map_sha1(fmap_t *map, const void *data, unsigned int len, uint8_t sha1[SHA1_HASH_SIZE])
|
|
{
|
|
if (!fmap_need_ptr_once(map, data, len)) {
|
|
cli_dbgmsg("map_sha1: failed to read hash data\n");
|
|
return 1;
|
|
}
|
|
return (cl_sha1(data, len, sha1, NULL) == NULL);
|
|
}
|
|
|
|
static int map_md5(fmap_t *map, const void *data, unsigned int len, uint8_t *md5)
|
|
{
|
|
if (!fmap_need_ptr_once(map, data, len)) {
|
|
cli_dbgmsg("map_md5: failed to read hash data\n");
|
|
return 1;
|
|
}
|
|
return (cl_hash_data("md5", data, len, md5, NULL) == NULL);
|
|
}
|
|
|
|
static int map_hash(fmap_t *map, const void *data, unsigned int len, uint8_t *out_hash, cli_crt_hashtype hashtype)
|
|
{
|
|
|
|
if (hashtype == CLI_SHA1RSA) {
|
|
if (map_sha1(map, data, len, out_hash)) {
|
|
return 1;
|
|
}
|
|
} else if (hashtype == CLI_MD5RSA) {
|
|
if (map_md5(map, data, len, out_hash)) {
|
|
return 1;
|
|
}
|
|
} else if (hashtype == CLI_SHA256RSA) {
|
|
if (map_sha2_256(map, data, len, out_hash)) {
|
|
return 1;
|
|
}
|
|
} else if (hashtype == CLI_SHA384RSA) {
|
|
if (map_sha2_384(map, data, len, out_hash)) {
|
|
return 1;
|
|
}
|
|
} else if (hashtype == CLI_SHA512RSA) {
|
|
if (map_sha2_512(map, data, len, out_hash)) {
|
|
return 1;
|
|
}
|
|
} else {
|
|
cli_dbgmsg("asn1_map_hash: unsupported hashtype\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *get_hash_ctx(cli_crt_hashtype hashtype)
|
|
{
|
|
void *ctx = NULL;
|
|
if (hashtype == CLI_SHA1RSA) {
|
|
ctx = cl_hash_init("sha1");
|
|
} else if (hashtype == CLI_MD5RSA) {
|
|
ctx = cl_hash_init("md5");
|
|
} else if (hashtype == CLI_SHA256RSA) {
|
|
ctx = cl_hash_init("sha2-256");
|
|
} else if (hashtype == CLI_SHA384RSA) {
|
|
ctx = cl_hash_init("sha2-384");
|
|
} else if (hashtype == CLI_SHA512RSA) {
|
|
ctx = cl_hash_init("sha2-512");
|
|
} else {
|
|
cli_dbgmsg("asn1_get_hash_ctx: unsupported hashtype\n");
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
static int asn1_get_obj(fmap_t *map, const void *asn1data, unsigned int *asn1len, struct cli_asn1 *obj)
|
|
{
|
|
unsigned int asn1_sz = *asn1len;
|
|
unsigned int readbytes = MIN(6, asn1_sz), i;
|
|
const uint8_t *data;
|
|
|
|
if (asn1_sz < 2) {
|
|
cli_dbgmsg("asn1_get_obj: insufficient data length\n");
|
|
return 1;
|
|
}
|
|
data = fmap_need_ptr_once(map, asn1data, readbytes);
|
|
if (!data) {
|
|
cli_dbgmsg("asn1_get_obj: obj out of file\n");
|
|
return 1;
|
|
}
|
|
|
|
obj->type = data[0];
|
|
i = data[1];
|
|
data += 2;
|
|
if (i & 0x80) {
|
|
if (i == 0x80) {
|
|
/* Not allowed in DER */
|
|
cli_dbgmsg("asn1_get_obj: unsupported indefinite length object\n");
|
|
return 1;
|
|
}
|
|
i &= ~0x80;
|
|
if (i > readbytes - 2) {
|
|
cli_dbgmsg("asn1_get_obj: len octets overflow (or just too many)\n");
|
|
return 1;
|
|
}
|
|
obj->size = 0;
|
|
while (i--) {
|
|
obj->size <<= 8;
|
|
obj->size |= *data;
|
|
data++;
|
|
}
|
|
} else
|
|
obj->size = i;
|
|
|
|
asn1_sz -= data - (uint8_t *)asn1data;
|
|
if (obj->size > asn1_sz) {
|
|
cli_dbgmsg("asn1_get_obj: content overflow\n");
|
|
return 1;
|
|
}
|
|
|
|
obj->content = data;
|
|
if (obj->size == asn1_sz)
|
|
obj->next = NULL;
|
|
else
|
|
obj->next = data + obj->size;
|
|
*asn1len = asn1_sz - obj->size;
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_expect_objtype(fmap_t *map, const void *asn1data, unsigned int *asn1len, struct cli_asn1 *obj, uint8_t type)
|
|
{
|
|
int ret = asn1_get_obj(map, asn1data, asn1len, obj);
|
|
if (ret)
|
|
return ret;
|
|
if (obj->type != type) {
|
|
cli_dbgmsg("asn1_expect_objtype: expected type %02x, got %02x\n", type, obj->type);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_expect_obj(fmap_t *map, const void **asn1data, unsigned int *asn1len, uint8_t type, unsigned int size, const void *content)
|
|
{
|
|
struct cli_asn1 obj;
|
|
int ret = asn1_expect_objtype(map, *asn1data, asn1len, &obj, type);
|
|
if (ret)
|
|
return ret;
|
|
if (obj.size != size) {
|
|
cli_dbgmsg("asn1_expect_obj: expected size %u, got %u\n", size, obj.size);
|
|
return 1;
|
|
}
|
|
if (size) {
|
|
if (!fmap_need_ptr_once(map, obj.content, size)) {
|
|
cli_dbgmsg("asn1_expect_obj: failed to read content\n");
|
|
return 1;
|
|
}
|
|
if (memcmp(obj.content, content, size)) {
|
|
cli_dbgmsg("asn1_expect_obj: content mismatch\n");
|
|
return 1;
|
|
}
|
|
}
|
|
*asn1data = obj.next;
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_expect_algo(fmap_t *map, const void **asn1data, unsigned int *asn1len, unsigned int algo_size, const void *algo)
|
|
{
|
|
struct cli_asn1 obj;
|
|
unsigned int avail;
|
|
int ret;
|
|
if ((ret = asn1_expect_objtype(map, *asn1data, asn1len, &obj, ASN1_TYPE_SEQUENCE))) /* SEQUENCE */
|
|
return ret;
|
|
avail = obj.size;
|
|
*asn1data = obj.next;
|
|
|
|
if ((ret = asn1_expect_obj(map, &obj.content, &avail, ASN1_TYPE_OBJECT_ID, algo_size, algo))) /* ALGO */
|
|
return ret;
|
|
|
|
// The specification says that the NULL is a required parameter for this
|
|
// data type, but in practice it doesn't always exist in the ASN1. If
|
|
// there is something after the ALGO OID, assume it's the NULL
|
|
if (avail && (ret = asn1_expect_obj(map, &obj.content, &avail, ASN1_TYPE_NULL, 0, NULL))) { /* NULL */
|
|
cli_dbgmsg("asn1_expect_algo: expected NULL after AlgorithmIdentifier OID\n");
|
|
return ret;
|
|
}
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_expect_algo: extra data found in SEQUENCE\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct _oid_alternative {
|
|
const char *oid_bytes;
|
|
const unsigned int oid_bytes_len;
|
|
const int context[2];
|
|
} oid_alternative_t;
|
|
|
|
static const oid_alternative_t *asn1_expect_algo_multi(fmap_t *map, const void **asn1data, unsigned int *asn1len,
|
|
const oid_alternative_t alts[], unsigned int alts_count)
|
|
{
|
|
struct cli_asn1 obj;
|
|
unsigned int avail;
|
|
unsigned int i;
|
|
const oid_alternative_t *oid_alt_ptr = NULL;
|
|
|
|
if (asn1_expect_objtype(map, *asn1data, asn1len, &obj, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE */
|
|
cli_dbgmsg("asn1_expect_algo_multi: expecting SEQUENCE at the start of the algo\n");
|
|
return NULL;
|
|
}
|
|
avail = obj.size;
|
|
*asn1data = obj.next;
|
|
|
|
if (asn1_expect_objtype(map, obj.content, &avail, &obj, ASN1_TYPE_OBJECT_ID)) {
|
|
cli_dbgmsg("asn1_expect_algo_multi: expected OID in algo\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (0 == obj.size) {
|
|
cli_dbgmsg("asn1_expect_algo_multi: Unexpected length value of zero when parsing OID obj\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, obj.content, obj.size)) {
|
|
cli_dbgmsg("asn1_expect_algo_multi: failed to read OID\n");
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < alts_count; i++) {
|
|
oid_alt_ptr = &(alts[i]);
|
|
|
|
if (obj.size != oid_alt_ptr->oid_bytes_len) {
|
|
continue;
|
|
}
|
|
|
|
if (!memcmp(obj.content, oid_alt_ptr->oid_bytes, obj.size)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NULL == oid_alt_ptr) {
|
|
cli_dbgmsg("asn1_expect_algo_multi: Failed to find valid OID alternative\n");
|
|
return NULL;
|
|
}
|
|
|
|
// The specification says that the NULL is a required parameter for this
|
|
// data type, but in practice it doesn't always exist in the ASN1. If
|
|
// there is something after the ALGO OID, assume it's the NULL
|
|
if (avail && asn1_expect_obj(map, &obj.next, &avail, ASN1_TYPE_NULL, 0, NULL)) { /* NULL */
|
|
cli_dbgmsg("asn1_expect_algo_multi: expected NULL following OID\n");
|
|
return NULL;
|
|
}
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_expect_algo_multi: extra data found in SEQUENCE\n");
|
|
return NULL;
|
|
}
|
|
return oid_alt_ptr;
|
|
}
|
|
|
|
static int asn1_expect_hash_algo(fmap_t *map, const void **asn1data, unsigned int *asn1len, cli_crt_hashtype *hashtype, unsigned int *hashsize)
|
|
{
|
|
const oid_alternative_t *ret;
|
|
|
|
static const oid_alternative_t alts[] = {
|
|
{OID_sha1, lenof(OID_sha1), {CLI_SHA1RSA, SHA1_HASH_SIZE}},
|
|
{OID_sha1WithRSAEncryption, lenof(OID_sha1WithRSAEncryption), {CLI_SHA1RSA, SHA1_HASH_SIZE}},
|
|
{OID_md5, lenof(OID_md5), {CLI_MD5RSA, MD5_HASH_SIZE}},
|
|
{OID_md5WithRSAEncryption, lenof(OID_md5WithRSAEncryption), {CLI_MD5RSA, MD5_HASH_SIZE}},
|
|
{OID_sha256, lenof(OID_sha256), {CLI_SHA256RSA, SHA256_HASH_SIZE}},
|
|
{OID_sha256WithRSAEncryption, lenof(OID_sha256WithRSAEncryption), {CLI_SHA256RSA, SHA256_HASH_SIZE}},
|
|
{OID_sha384, lenof(OID_sha384), {CLI_SHA384RSA, SHA384_HASH_SIZE}},
|
|
{OID_sha384WithRSAEncryption, lenof(OID_sha384WithRSAEncryption), {CLI_SHA384RSA, SHA384_HASH_SIZE}},
|
|
{OID_sha512, lenof(OID_sha512), {CLI_SHA512RSA, SHA512_HASH_SIZE}},
|
|
{OID_sha512WithRSAEncryption, lenof(OID_sha512WithRSAEncryption), {CLI_SHA512RSA, SHA512_HASH_SIZE}},
|
|
};
|
|
|
|
ret = asn1_expect_algo_multi(map, asn1data, asn1len, alts, sizeof(alts) / sizeof(alts[0]));
|
|
if (NULL == ret) {
|
|
cli_dbgmsg("asn1_expect_hash_algo: Failed to find valid OID alternative for hash algo\n");
|
|
return 1;
|
|
}
|
|
|
|
*hashtype = ret->context[0];
|
|
*hashsize = ret->context[1];
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_expect_rsa(fmap_t *map, const void **asn1data, unsigned int *asn1len, cli_crt_hashtype *hashtype)
|
|
{
|
|
const oid_alternative_t *ret;
|
|
|
|
static const oid_alternative_t alts[] = {
|
|
/* Obsolete sha1rsa 1.3.14.3.2.29 */
|
|
{OID_sha1WithRSA, lenof(OID_sha1WithRSA), {CLI_SHA1RSA, 0}},
|
|
|
|
/* sha1withRSAEncryption 1.2.840.113549.1.1.5 */
|
|
{OID_sha1WithRSAEncryption, lenof(OID_sha1WithRSAEncryption), {CLI_SHA1RSA, 0}},
|
|
|
|
/* md5withRSAEncryption 1.2.840.113549.1.1.4 */
|
|
{OID_md5WithRSAEncryption, lenof(OID_md5WithRSAEncryption), {CLI_MD5RSA, 0}},
|
|
|
|
/* rsaEncryption 1.2.840.113549.1.1.1 */
|
|
{OID_rsaEncryption, lenof(OID_rsaEncryption), {CLI_RSA, 0}},
|
|
|
|
/* md2withRSAEncryption 1.2.840.113549.1.1.2 */
|
|
{OID_md2WithRSAEncryption, lenof(OID_md2WithRSAEncryption), {CLI_MD2RSA, 0}},
|
|
|
|
/* sha256WithRSAEncryption 1.2.840.113549.1.1.11 */
|
|
{OID_sha256WithRSAEncryption, lenof(OID_sha256WithRSAEncryption), {CLI_SHA256RSA, 0}},
|
|
|
|
/* sha384WithRSAEncryption 1.2.840.113549.1.1.12 */
|
|
{OID_sha384WithRSAEncryption, lenof(OID_sha384WithRSAEncryption), {CLI_SHA384RSA, 0}},
|
|
|
|
/* sha512WithRSAEncryption 1.2.840.113549.1.1.13 */
|
|
{OID_sha512WithRSAEncryption, lenof(OID_sha512WithRSAEncryption), {CLI_SHA512RSA, 0}},
|
|
};
|
|
|
|
ret = asn1_expect_algo_multi(map, asn1data, asn1len, alts, sizeof(alts) / sizeof(alts[0]));
|
|
if (NULL == ret) {
|
|
cli_dbgmsg("asn1_expect_rsa: Failed to find valid OID alternative for RSA algo\n");
|
|
return 1;
|
|
}
|
|
|
|
*hashtype = ret->context[0];
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_expect_list_member(fmap_t *map, const void **asn1data, unsigned int *asn1len)
|
|
{
|
|
const oid_alternative_t *ret;
|
|
|
|
static const oid_alternative_t alts[] = {
|
|
{OID_szOID_CATALOG_LIST_MEMBER, lenof(OID_szOID_CATALOG_LIST_MEMBER), {0, 0}},
|
|
{OID_szOID_CATALOG_LIST_MEMBER2, lenof(OID_szOID_CATALOG_LIST_MEMBER2), {0, 0}},
|
|
};
|
|
|
|
ret = asn1_expect_algo_multi(map, asn1data, asn1len, alts, sizeof(alts) / sizeof(alts[0]));
|
|
if (NULL == ret) {
|
|
cli_dbgmsg("asn1_expect_list_member: Failed to find valid OID alternative for list member algo\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static int asn1_getnum(const char *s)
|
|
{
|
|
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') {
|
|
cli_dbgmsg("asn1_getnum: expecting digits, found '%c%c'\n", s[0], s[1]);
|
|
return -1;
|
|
}
|
|
return (s[0] - '0') * 10 + (s[1] - '0');
|
|
}
|
|
|
|
static int asn1_get_time(fmap_t *map, const void **asn1data, unsigned int *size, time_t *tm)
|
|
{
|
|
struct cli_asn1 obj;
|
|
int ret = asn1_get_obj(map, *asn1data, size, &obj);
|
|
unsigned int len;
|
|
char *ptr;
|
|
struct tm t;
|
|
int n;
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (obj.type == 0x17) /* UTCTime - YYMMDDHHMMSSZ */
|
|
len = 13;
|
|
else if (obj.type == 0x18) /* GeneralizedTime - YYYYMMDDHHMMSSZ */
|
|
len = 15;
|
|
else {
|
|
cli_dbgmsg("asn1_get_time: expected UTCTime or GeneralizedTime, got %02x\n", obj.type);
|
|
return 1;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, obj.content, len)) {
|
|
cli_dbgmsg("asn1_get_time: failed to read content\n");
|
|
return 1;
|
|
}
|
|
|
|
memset(&t, 0, sizeof(t));
|
|
ptr = (char *)obj.content;
|
|
if (obj.type == 0x18) {
|
|
t.tm_year = asn1_getnum(ptr) * 100;
|
|
if (t.tm_year < 0)
|
|
return 1;
|
|
n = asn1_getnum(ptr);
|
|
if (n < 0)
|
|
return 1;
|
|
t.tm_year += n;
|
|
ptr += 4;
|
|
} else {
|
|
n = asn1_getnum(ptr);
|
|
if (n < 0)
|
|
return 1;
|
|
if (n >= 50)
|
|
t.tm_year = 1900 + n;
|
|
else
|
|
t.tm_year = 2000 + n;
|
|
ptr += 2;
|
|
}
|
|
t.tm_year -= 1900;
|
|
n = asn1_getnum(ptr);
|
|
if (n < 1 || n > 12) {
|
|
cli_dbgmsg("asn1_get_time: invalid month %u\n", n);
|
|
return 1;
|
|
}
|
|
t.tm_mon = n - 1;
|
|
ptr += 2;
|
|
|
|
n = asn1_getnum(ptr);
|
|
if (n < 1 || n > 31) {
|
|
cli_dbgmsg("asn1_get_time: invalid day %u\n", n);
|
|
return 1;
|
|
}
|
|
t.tm_mday = n;
|
|
ptr += 2;
|
|
|
|
n = asn1_getnum(ptr);
|
|
if (n < 0 || n > 23) {
|
|
cli_dbgmsg("asn1_get_time: invalid hour %u\n", n);
|
|
return 1;
|
|
}
|
|
t.tm_hour = n;
|
|
ptr += 2;
|
|
|
|
n = asn1_getnum(ptr);
|
|
if (n < 0 || n > 59) {
|
|
cli_dbgmsg("asn1_get_time: invalid minute %u\n", n);
|
|
return 1;
|
|
}
|
|
t.tm_min = n;
|
|
ptr += 2;
|
|
|
|
if (*ptr == 'Z') {
|
|
/* NOTE: RFC5280 requires that the UTCDate fields in X509 certs
|
|
* include the seconds (it's optional in the UTCDate definition),
|
|
* but one CA time-stamping cert used by ~1,700 samples on VirusTotal
|
|
* omits the seconds. These samples still validate successfully,
|
|
* though, so allow it here.
|
|
*
|
|
* In this case we will have fmap'd in two extra bytes unrelated to
|
|
* the UTCDate (and failed if there weren't two bytes afterward),
|
|
* but that shouldn't have an affect in practice since there will
|
|
* always be more signature data following the UTCDate data that we
|
|
* parse. */
|
|
t.tm_sec = 0;
|
|
|
|
} else {
|
|
n = asn1_getnum(ptr);
|
|
if (n < 0 || n > 59) {
|
|
cli_dbgmsg("asn1_get_time: invalid second %u\n", n);
|
|
return 1;
|
|
}
|
|
t.tm_sec = n;
|
|
ptr += 2;
|
|
|
|
if (*ptr != 'Z') {
|
|
cli_dbgmsg("asn1_get_time: expected UTC time 'Z', got '%c'\n", *ptr);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
*tm = mktime(&t);
|
|
*asn1data = obj.next;
|
|
return 0;
|
|
}
|
|
|
|
static int asn1_get_rsa_pubkey(fmap_t *map, const void **asn1data, unsigned int *size, cli_crt *x509)
|
|
{
|
|
struct cli_asn1 obj;
|
|
unsigned int avail, avail2;
|
|
|
|
if (asn1_expect_objtype(map, *asn1data, size, &obj, ASN1_TYPE_SEQUENCE)) /* subjectPublicKeyInfo */
|
|
return 1;
|
|
*asn1data = obj.next;
|
|
|
|
avail = obj.size;
|
|
if (asn1_expect_algo(map, &obj.content, &avail, lenof(OID_rsaEncryption), OID_rsaEncryption)) { /* rsaEncryption */
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: AlgorithmIdentifier other than RSA not yet supported\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, obj.content, &avail, &obj, ASN1_TYPE_BIT_STRING)) /* BIT STRING - subjectPublicKey */
|
|
return 1;
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: found unexpected extra data in subjectPublicKeyInfo\n");
|
|
return 1;
|
|
}
|
|
/* if(obj.size != 141 && obj.size != 271) /\* encoded len of 1024 and 2048 bit public keys *\/ */
|
|
/* return 1; */
|
|
|
|
if (!fmap_need_ptr_once(map, obj.content, 1)) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: cannot read public key content\n");
|
|
return 1;
|
|
}
|
|
if (((uint8_t *)obj.content)[0] != 0) { /* no byte fragments */
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: unexpected byte frags in public key\n");
|
|
return 1;
|
|
}
|
|
|
|
avail = obj.size - 1;
|
|
obj.content = ((uint8_t *)obj.content) + 1;
|
|
if (asn1_expect_objtype(map, obj.content, &avail, &obj, ASN1_TYPE_SEQUENCE)) /* SEQUENCE */
|
|
return 1;
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: found unexpected extra data in public key content\n");
|
|
return 1;
|
|
}
|
|
|
|
avail = obj.size;
|
|
if (asn1_expect_objtype(map, obj.content, &avail, &obj, ASN1_TYPE_INTEGER)) /* INTEGER - mod */
|
|
return 1;
|
|
if (obj.size < 1024 / 8 || obj.size > 4096 / 8 + 1) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: modulus has got an unsupported length (%u)\n", obj.size * 8);
|
|
return 1;
|
|
}
|
|
avail2 = obj.size;
|
|
if (!fmap_need_ptr_once(map, obj.content, avail2)) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: cannot read n\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!BN_bin2bn(obj.content, avail2, x509->n))
|
|
return 1;
|
|
|
|
if (asn1_expect_objtype(map, obj.next, &avail, &obj, ASN1_TYPE_INTEGER)) /* INTEGER - exp */
|
|
return 1;
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: found unexpected extra data after exp\n");
|
|
return 1;
|
|
}
|
|
if (obj.size < 1 || obj.size > avail2) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: exponent has got an unsupported length (%u)\n", obj.size * 8);
|
|
return 1;
|
|
}
|
|
if (!fmap_need_ptr_once(map, obj.content, obj.size)) {
|
|
cli_dbgmsg("asn1_get_rsa_pubkey: cannot read e\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!BN_bin2bn(obj.content, obj.size, x509->e))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ASN1_GET_X509_SUCCESS 0
|
|
#define ASN1_GET_X509_CERT_ERROR 1
|
|
#define ASN1_GET_X509_UNRECOVERABLE_ERROR 2
|
|
|
|
/* Parse the asn1data associated with an x509 certificate and add the cert
|
|
* to the crtmgr certs if it doesn't already exist there.
|
|
* ASN1_GET_X509_CERT_ERROR will be returned in the case that an invalid x509
|
|
* certificate is encountered but asn1data and size are suitable for continued
|
|
* signature parsing. ASN1_GET_X509_UNRECOVERABLE_ERROR will be returned in
|
|
* the case where asn1data and size are not suitable for continued use. */
|
|
static int asn1_get_x509(fmap_t *map, const void **asn1data, unsigned int *size, crtmgr *crts)
|
|
{
|
|
struct cli_asn1 crt, tbs, obj;
|
|
unsigned int avail, tbssize, issuersize;
|
|
cli_crt_hashtype hashtype1, hashtype2;
|
|
cli_crt x509;
|
|
const uint8_t *tbsdata;
|
|
const void *next, *issuer;
|
|
int ret = ASN1_GET_X509_UNRECOVERABLE_ERROR;
|
|
unsigned int version;
|
|
|
|
do {
|
|
if (cli_crt_init(&x509) < 0) {
|
|
cli_dbgmsg("asn1_get_x509: failed to initialize x509.\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, *asn1data, size, &crt, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE */
|
|
cli_dbgmsg("asn1_get_x509: expected SEQUENCE at the x509 start\n");
|
|
break;
|
|
}
|
|
*asn1data = crt.next;
|
|
|
|
/* After this point, an error is recoverable because asn1data and size
|
|
* will be suitable for continued use by the caller, so change ret */
|
|
ret = ASN1_GET_X509_CERT_ERROR;
|
|
|
|
tbsdata = crt.content;
|
|
if (asn1_expect_objtype(map, crt.content, &crt.size, &tbs, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE - TBSCertificate */
|
|
cli_dbgmsg("asn1_get_x509: expected SEQUENCE at the TBSCertificate start\n");
|
|
break;
|
|
}
|
|
tbssize = (uint8_t *)tbs.next - tbsdata;
|
|
|
|
/* The version field of the x509 certificate is optional, defaulting
|
|
* to 1 if the field is not present. Version 3 is backward compatible,
|
|
* adding the optional issuerUniqueID, sujectUniqueID, and extensions
|
|
* fields. We'll try to handle both cases, since the Windows API
|
|
* appears to allow for both (despite the fact that the 2008 spec doc
|
|
* says that v3 certificates are used for everything) */
|
|
|
|
if (asn1_get_obj(map, tbs.content, &tbs.size, &obj)) {
|
|
cli_dbgmsg("asn1_get_x509: failed to get first item in the TBSCertificate\n");
|
|
break;
|
|
}
|
|
if (0xa0 == obj.type) { /* [0] */
|
|
avail = obj.size;
|
|
next = obj.next;
|
|
// TODO Should we support v2 certs? Supposedly they are not widely used...
|
|
if (asn1_expect_obj(map, &obj.content, &avail, ASN1_TYPE_INTEGER, 1, "\x02")) { /* version 3 only (indicated by '\x02')*/
|
|
cli_dbgmsg("asn1_get_x509: unexpected type or value for TBSCertificate version\n");
|
|
break;
|
|
}
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_get_x509: found unexpected extra data in version\n");
|
|
break;
|
|
}
|
|
version = 3;
|
|
|
|
if (asn1_expect_objtype(map, next, &tbs.size, &obj, ASN1_TYPE_INTEGER)) { /* serialNumber */
|
|
cli_dbgmsg("asn1_get_x509: expected x509 serial INTEGER\n");
|
|
break;
|
|
}
|
|
} else if (ASN1_TYPE_INTEGER == obj.type) {
|
|
/* The version field is missing, so we'll assume that this is a
|
|
* version 1 certificate. obj points to the serialNumber
|
|
* INTEGER, then, so just continue on to map it. */
|
|
version = 1;
|
|
|
|
/* v1 certificates don't have enough information to convey the
|
|
* purpose of the certificate. I've only ever seen these used
|
|
* in the timestamp signing chain, so set the flags to indicate
|
|
* that. */
|
|
x509.certSign = 1;
|
|
x509.codeSign = 0;
|
|
x509.timeSign = 1;
|
|
|
|
} else {
|
|
cli_dbgmsg("asn1_get_x509: expected version or serialNumber as the first item in TBSCertificate\n");
|
|
break;
|
|
}
|
|
|
|
if (map_raw(map, obj.content, obj.size, x509.raw_serial))
|
|
break;
|
|
if (map_sha1(map, obj.content, obj.size, x509.serial))
|
|
break;
|
|
|
|
if (asn1_expect_rsa(map, &obj.next, &tbs.size, &hashtype1)) { /* algo - Ex: sha1WithRSAEncryption */
|
|
cli_dbgmsg("asn1_get_x509: unable to parse AlgorithmIdentifier\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, obj.next, &tbs.size, &obj, ASN1_TYPE_SEQUENCE)) { /* issuer */
|
|
cli_dbgmsg("asn1_get_x509: expected SEQUENCE when parsing cert issuer\n");
|
|
break;
|
|
}
|
|
issuer = obj.content;
|
|
issuersize = obj.size;
|
|
|
|
if (asn1_expect_objtype(map, obj.next, &tbs.size, &obj, ASN1_TYPE_SEQUENCE)) { /* validity */
|
|
cli_dbgmsg("asn1_get_x509: expected SEQUENCE when parsing cert validity\n");
|
|
break;
|
|
}
|
|
avail = obj.size;
|
|
next = obj.content;
|
|
|
|
if (asn1_get_time(map, &next, &avail, &x509.not_before)) { /* notBefore */
|
|
cli_dbgmsg("asn1_get_x509: unable to extract the notBefore time\n");
|
|
break;
|
|
}
|
|
if (asn1_get_time(map, &next, &avail, &x509.not_after)) { /* notAfter */
|
|
cli_dbgmsg("asn1_get_x509: unable to extract the notAfter time\n");
|
|
break;
|
|
}
|
|
if (x509.not_before >= x509.not_after) {
|
|
cli_dbgmsg("asn1_get_x509: bad validity\n");
|
|
break;
|
|
}
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_get_x509: found unexpected extra data in validity\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, obj.next, &tbs.size, &obj, ASN1_TYPE_SEQUENCE)) { /* subject */
|
|
cli_dbgmsg("asn1_get_x509: expected SEQUENCE when parsing cert subject\n");
|
|
break;
|
|
}
|
|
if (map_raw(map, obj.content, obj.size, x509.raw_subject))
|
|
break;
|
|
if (map_sha1(map, obj.content, obj.size, x509.subject))
|
|
break;
|
|
if (asn1_get_rsa_pubkey(map, &obj.next, &tbs.size, &x509)) { /* subjectPublicKeyInfo */
|
|
cli_dbgmsg("asn1_get_x509: failed to get RSA public key\n");
|
|
break;
|
|
}
|
|
|
|
if (1 == version && tbs.size) {
|
|
cli_dbgmsg("asn1_get_x509: TBSCertificate should not contain fields beyond subjectPublicKeyInfo if version == 1\n");
|
|
break;
|
|
}
|
|
|
|
avail = 0;
|
|
while (tbs.size) {
|
|
if (asn1_get_obj(map, obj.next, &tbs.size, &obj)) {
|
|
tbs.size = 1;
|
|
break;
|
|
}
|
|
if (obj.type <= 0xa0 + avail || obj.type > 0xa3) {
|
|
cli_dbgmsg("asn1_get_x509: found type %02x in extensions, expecting a1, a2 or a3\n", obj.type);
|
|
tbs.size = 1;
|
|
break;
|
|
}
|
|
avail = obj.type - 0xa0;
|
|
if (obj.type == 0xa3) {
|
|
struct cli_asn1 exts;
|
|
int have_key_usage = 0;
|
|
int have_ext_key = 0;
|
|
if (asn1_expect_objtype(map, obj.content, &obj.size, &exts, ASN1_TYPE_SEQUENCE)) {
|
|
tbs.size = 1;
|
|
break;
|
|
}
|
|
if (obj.size) {
|
|
cli_dbgmsg("asn1_get_x509: found unexpected extra data in extensions\n");
|
|
break;
|
|
}
|
|
while (exts.size) {
|
|
struct cli_asn1 ext, id, value;
|
|
if (asn1_expect_objtype(map, exts.content, &exts.size, &ext, ASN1_TYPE_SEQUENCE)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
exts.content = ext.next;
|
|
if (asn1_expect_objtype(map, ext.content, &ext.size, &id, ASN1_TYPE_OBJECT_ID)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (asn1_get_obj(map, id.next, &ext.size, &value)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (value.type == ASN1_TYPE_BOOLEAN) {
|
|
/* critical flag */
|
|
if (value.size != 1) {
|
|
cli_dbgmsg("asn1_get_x509: found boolean with wrong length\n");
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (asn1_get_obj(map, value.next, &ext.size, &value)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (value.type != ASN1_TYPE_OCTET_STRING) {
|
|
cli_dbgmsg("asn1_get_x509: bad extension value type %u\n", value.type);
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (ext.size) {
|
|
cli_dbgmsg("asn1_get_x509: extra data in extension\n");
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (id.size != 3)
|
|
continue;
|
|
|
|
if (!fmap_need_ptr_once(map, id.content, 3)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (!memcmp("\x55\x1d\x0f", id.content, 3)) {
|
|
/* KeyUsage 2.5.29.15 */
|
|
const uint8_t *keyusage = value.content;
|
|
uint8_t usage;
|
|
have_key_usage = 1;
|
|
if (value.size < 4 || value.size > 5) {
|
|
cli_dbgmsg("asn1_get_x509: bad KeyUsage\n");
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (!fmap_need_ptr_once(map, value.content, value.size)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (keyusage[0] != 0x03 || keyusage[1] != value.size - 2 || keyusage[2] > 7) {
|
|
cli_dbgmsg("asn1_get_x509: bad KeyUsage\n");
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
usage = keyusage[3];
|
|
if (value.size == 4)
|
|
usage &= ~((1 << keyusage[2]) - 1);
|
|
x509.certSign = ((usage & 4) != 0);
|
|
continue;
|
|
}
|
|
if (!memcmp("\x55\x1d\x25", id.content, 3)) {
|
|
/* ExtKeyUsage 2.5.29.37 */
|
|
struct cli_asn1 keypurp;
|
|
have_ext_key = 1;
|
|
if (asn1_expect_objtype(map, value.content, &value.size, &keypurp, ASN1_TYPE_SEQUENCE)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (value.size) {
|
|
cli_dbgmsg("asn1_get_x509: extra data in ExtKeyUsage\n");
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
ext.next = keypurp.content;
|
|
while (keypurp.size) {
|
|
if (asn1_expect_objtype(map, ext.next, &keypurp.size, &ext, ASN1_TYPE_OBJECT_ID)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (ext.size != 8 && ext.size != 10)
|
|
continue;
|
|
if (!fmap_need_ptr_once(map, ext.content, ext.size)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (!memcmp("\x2b\x06\x01\x05\x05\x07\x03\x03", ext.content, 8)) /* id_kp_codeSigning */
|
|
x509.codeSign = 1;
|
|
else if (!memcmp("\x2b\x06\x01\x05\x05\x07\x03\x08", ext.content, 8)) /* id_kp_timeStamping */
|
|
x509.timeSign = 1;
|
|
else if (!memcmp("\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x0d", ext.content, 10)) /* id_kp_lifetimeSigning */
|
|
cli_dbgmsg("asn1_get_x509: lifetime signing specified but enforcing this is not currently supported\n");
|
|
}
|
|
continue;
|
|
}
|
|
if (!memcmp("\x55\x1d\x13", id.content, 3)) {
|
|
/* Basic Constraints 2.5.29.19
|
|
*
|
|
* BasicConstraints ::= SEQUENCE {
|
|
* cA BOOLEAN DEFAULT FALSE,
|
|
* pathLenConstraint INTEGER (0..MAX) OPTIONAL } */
|
|
struct cli_asn1 constr;
|
|
if (asn1_expect_objtype(map, value.content, &value.size, &constr, ASN1_TYPE_SEQUENCE)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (!constr.size)
|
|
x509.certSign = 0;
|
|
else {
|
|
if (asn1_get_obj(map, constr.content, &constr.size, &ext)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
if (ext.type == ASN1_TYPE_BOOLEAN) {
|
|
|
|
if (ext.size != 1) {
|
|
cli_dbgmsg("asn1_get_x509: wrong bool size in basic constraint %u\n", ext.size);
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, ext.content, 1)) {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
x509.certSign = (((uint8_t *)(ext.content))[0] != 0);
|
|
|
|
} else if (ext.type == ASN1_TYPE_INTEGER) {
|
|
/* In this case, assume cA is missing and
|
|
* pathLenConstraint is present. Default cA
|
|
* to False. */
|
|
x509.certSign = 0;
|
|
} else {
|
|
exts.size = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (exts.size) {
|
|
tbs.size = 1;
|
|
break;
|
|
}
|
|
|
|
/* The 2008 spec doc says that for a certificate to be used for
|
|
* code signing, it must either have an EKU indicating code
|
|
* signing or the entire certificate chain must not have any
|
|
* EKUs.
|
|
* TODO We should actually enforce that last check.
|
|
* For time stamping, the doc says the EKU must be present, and
|
|
* makes no exception for EKUs being missing.
|
|
* TODO Should we not set timeSign = 1 in this case, then? */
|
|
if (!have_ext_key)
|
|
x509.codeSign = x509.timeSign = 1;
|
|
|
|
/* RFC 3280 section 4.2.1.3 says that if a certificate is
|
|
* used to validate digital signatures on other public key
|
|
* certificates, it MUST have a key usage extension with the
|
|
* appropriate bits set. However, the MS MD5 root authority
|
|
* certificate (A43489159A520F0D93D032CCAF37E7FE20A8B419)
|
|
* doesn't have a KU or any EKUs, and PEs with it in the
|
|
* chain validate successfully.
|
|
* TODO Flip the certSign bit for now, but revisit if
|
|
* a clarification on this becomes available */
|
|
if (!have_key_usage)
|
|
x509.certSign = 1;
|
|
}
|
|
}
|
|
if (tbs.size) {
|
|
cli_dbgmsg("asn1_get_x509: An error occurred when parsing x509 extensions\n");
|
|
break;
|
|
}
|
|
|
|
if (!x509.certSign && !x509.codeSign && !x509.timeSign) {
|
|
cli_dbgmsg("asn1_get_x509: encountered a certificate with no cert, code, or time signing capabilities\n");
|
|
}
|
|
|
|
if (map_raw(map, issuer, issuersize, x509.raw_issuer))
|
|
break;
|
|
if (map_sha1(map, issuer, issuersize, x509.issuer))
|
|
break;
|
|
|
|
if (asn1_expect_rsa(map, &tbs.next, &crt.size, &hashtype2)) /* signature algo - Ex: sha1WithRSAEncryption */
|
|
break;
|
|
|
|
if (hashtype1 != hashtype2) {
|
|
cli_dbgmsg("asn1_get_x509: found conflicting RSA hash types\n");
|
|
break;
|
|
}
|
|
x509.hashtype = hashtype1;
|
|
|
|
if (crtmgr_lookup(crts, &x509)) {
|
|
cli_dbgmsg("asn1_get_x509: duplicate embedded certificates detected\n");
|
|
cli_crt_clear(&x509);
|
|
return ASN1_GET_X509_SUCCESS;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, tbs.next, &crt.size, &obj, ASN1_TYPE_BIT_STRING)) { /* signature */
|
|
cli_dbgmsg("asn1_get_x509: Failed to parse x509 signature BIT STRING\n");
|
|
break;
|
|
}
|
|
if (obj.size > 513) {
|
|
cli_dbgmsg("asn1_get_x509: signature too long\n");
|
|
break;
|
|
}
|
|
if (!fmap_need_ptr_once(map, obj.content, obj.size)) {
|
|
cli_dbgmsg("asn1_get_x509: cannot read signature\n");
|
|
break;
|
|
}
|
|
|
|
if (!BN_bin2bn(obj.content, obj.size, x509.sig))
|
|
break;
|
|
|
|
if (crt.size) {
|
|
cli_dbgmsg("asn1_get_x509: found unexpected extra data in signature\n");
|
|
break;
|
|
}
|
|
|
|
if (map_hash(map, tbsdata, tbssize, x509.tbshash, x509.hashtype)) {
|
|
cli_dbgmsg("asn1_get_x509: Unsupported hashtype or hash computation failed\n");
|
|
break;
|
|
}
|
|
|
|
if (crtmgr_add(crts, &x509))
|
|
break;
|
|
cli_crt_clear(&x509);
|
|
return ASN1_GET_X509_SUCCESS;
|
|
} while (0);
|
|
cli_crt_clear(&x509);
|
|
return ret;
|
|
}
|
|
|
|
static int asn1_parse_countersignature(fmap_t *map, const void **asn1data, unsigned int *size, crtmgr *cmgr, const uint8_t *message, const unsigned int message_size, time_t not_before, time_t not_after)
|
|
{
|
|
|
|
struct cli_asn1 asn1, deep, deeper;
|
|
uint8_t issuer[SHA1_HASH_SIZE], serial[SHA1_HASH_SIZE];
|
|
const uint8_t *attrs;
|
|
unsigned int dsize, attrs_size;
|
|
unsigned int avail;
|
|
uint8_t hash[MAX_HASH_SIZE];
|
|
cli_crt_hashtype hashtype;
|
|
cli_crt_hashtype hashtype2;
|
|
unsigned int hashsize;
|
|
uint8_t md[MAX_HASH_SIZE];
|
|
int result;
|
|
void *ctx;
|
|
|
|
do {
|
|
if (asn1_expect_objtype(map, *asn1data, size, &asn1, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: expected SEQUENCE inside counterSignature SET\n");
|
|
break;
|
|
}
|
|
|
|
avail = asn1.size;
|
|
|
|
if (asn1_expect_objtype(map, asn1.content, &avail, &deep, ASN1_TYPE_INTEGER)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: expected INTEGER for counterSignature version");
|
|
break;
|
|
}
|
|
|
|
if (deep.size != 1) {
|
|
cli_dbgmsg("asn1_parse_countersignature: expected INTEGER of size 1, got size %u\n", deep.size);
|
|
break;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, deep.content, 1)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to read version\n");
|
|
break;
|
|
}
|
|
/* Allow either '0' or '1' for the version. The specification says
|
|
* that this field must be 1, but some binaries have 0 here and
|
|
* they appear to validate just fine via the Windows API */
|
|
if (memcmp(deep.content, "\x01", 1) && memcmp(deep.content, "\x00", 1)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: counterSignature version is not 1 or 0\n");
|
|
break;
|
|
}
|
|
asn1.content = deep.next;
|
|
|
|
if (asn1_expect_objtype(map, asn1.content, &avail, &asn1, ASN1_TYPE_SEQUENCE)) { /* issuerAndSerialNumber */
|
|
cli_dbgmsg("asn1_parse_countersignature: unable to parse issuerAndSerialNumber SEQUENCE in counterSignature\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, ASN1_TYPE_SEQUENCE)) { /* issuer */
|
|
cli_dbgmsg("asn1_parse_countersignature: unable to parse issuer SEQUENCE in counterSignature\n");
|
|
break;
|
|
}
|
|
// Compute the hash of the issuer section
|
|
if (map_sha1(map, deep.content, deep.size, issuer)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: error in call to map_sha1 for counterSignature issuer\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, deep.next, &asn1.size, &deep, ASN1_TYPE_INTEGER)) { /* serial */
|
|
cli_dbgmsg("asn1_parse_countersignature: expected ASN1_TYPE_INTEGER serial for counterSignature\n");
|
|
break;
|
|
}
|
|
|
|
// Compute the hash of the serial INTEGER
|
|
if (map_sha1(map, deep.content, deep.size, serial)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: error in call to map_sha1 for counterSignature serial\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1.size) {
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data inside counterSignature issuer\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_hash_algo(map, &asn1.next, &avail, &hashtype, &hashsize)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: error parsing counterSignature digestAlgorithm\n");
|
|
break;
|
|
}
|
|
|
|
if (map_hash(map, message, message_size, md, hashtype)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to map in message/compute countersignature hash\n");
|
|
break;
|
|
}
|
|
|
|
attrs = asn1.next;
|
|
if (asn1_expect_objtype(map, asn1.next, &avail, &asn1, 0xa0)) { /* authenticatedAttributes */
|
|
cli_dbgmsg("asn1_parse_countersignature: unable to parse counterSignature authenticatedAttributes section\n");
|
|
break;
|
|
}
|
|
attrs_size = (uint8_t *)(asn1.next) - attrs;
|
|
if (asn1.next == NULL && attrs_size < 2) {
|
|
cli_dbgmsg("asn1_parse_countersignature: counterSignature authenticatedAttributes are too small\n");
|
|
break;
|
|
}
|
|
result = 0;
|
|
dsize = asn1.size;
|
|
deep.next = asn1.content;
|
|
while (dsize) {
|
|
int content;
|
|
if (asn1_expect_objtype(map, deep.next, &dsize, &deep, ASN1_TYPE_SEQUENCE)) { /* attribute */
|
|
cli_dbgmsg("asn1_parse_countersignature: expected counterSignature attribute SEQUENCE\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (asn1_expect_objtype(map, deep.content, &deep.size, &deeper, ASN1_TYPE_OBJECT_ID)) { /* attribute type */
|
|
cli_dbgmsg("asn1_parse_countersignature: expected attribute type inside counterSignature attribute SEQUENCE\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (deeper.size != lenof(OID_contentType)) /* lenof(contentType) = lenof(messageDigest) = lenof(signingTime) = 9 */
|
|
continue;
|
|
|
|
if (!fmap_need_ptr_once(map, deeper.content, lenof(OID_contentType))) {
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to read counterSignature authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (!memcmp(deeper.content, OID_contentType, lenof(OID_contentType)))
|
|
content = 0; /* contentType */
|
|
else if (!memcmp(deeper.content, OID_messageDigest, lenof(OID_messageDigest)))
|
|
content = 1; /* messageDigest */
|
|
else if (!memcmp(deeper.content, OID_signingTime, lenof(OID_signingTime)))
|
|
content = 2; /* signingTime */
|
|
else
|
|
continue;
|
|
if (result & (1 << content)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: duplicate field in countersignature\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
result |= (1 << content);
|
|
if (asn1_expect_objtype(map, deeper.next, &deep.size, &deeper, ASN1_TYPE_SET)) { /* set - contents */
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to read counterSignature authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (deep.size) {
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data in countersignature value\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
deep.size = deeper.size;
|
|
switch (content) {
|
|
case 0: { /* contentType = pkcs7-data */
|
|
const void *backupPtr = deeper.content;
|
|
unsigned int backupSize = deep.size;
|
|
if (asn1_expect_obj(map, &deeper.content, &deep.size, ASN1_TYPE_OBJECT_ID, lenof(OID_pkcs7_data), OID_pkcs7_data)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: contentType != pkcs7-data, checking for timestampToken instead\n");
|
|
/* Some signatures use OID_timestampToken instead, so allow
|
|
* that also (despite the 2008 spec saying that this value
|
|
* must be pkcs7-data) */
|
|
deeper.content = backupPtr;
|
|
deep.size = backupSize;
|
|
if (asn1_expect_obj(map, &deeper.content, &deep.size, ASN1_TYPE_OBJECT_ID, lenof(OID_timestampToken), OID_timestampToken)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: contentType != timestampToken\n");
|
|
deep.size = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (deep.size)
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data in countersignature content-type\n");
|
|
break;
|
|
}
|
|
case 1: /* messageDigest */
|
|
if (asn1_expect_obj(map, &deeper.content, &deep.size, ASN1_TYPE_OCTET_STRING, hashsize, md)) {
|
|
deep.size = 1;
|
|
cli_dbgmsg("asn1_parse_countersignature: countersignature hash mismatch\n");
|
|
} else if (deep.size)
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data in countersignature message-digest\n");
|
|
break;
|
|
case 2: /* signingTime */
|
|
{
|
|
time_t sigdate; /* FIXME shall i use it?! */
|
|
if (asn1_get_time(map, &deeper.content, &deep.size, &sigdate)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: an error occurred when getting the time\n");
|
|
deep.size = 1;
|
|
} else if (deep.size)
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data in countersignature signing-time\n");
|
|
else if (sigdate < not_before || sigdate > not_after) {
|
|
cli_dbgmsg("asn1_parse_countersignature: countersignature timestamp outside cert validity\n");
|
|
deep.size = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (deep.size) {
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (dsize)
|
|
break;
|
|
if (result != 7) {
|
|
cli_dbgmsg("asn1_parse_countersignature: some important attributes are missing in countersignature\n");
|
|
break;
|
|
}
|
|
|
|
// TODO For some reason there tends to be more variability here than
|
|
// when parsing the regular signature - we have to support at least
|
|
// szOID_RSA_RSA and szOID_RSA_SHA1RSA based on samples seen in the
|
|
// wild. The spec says this should only be the RSA and DSA OIDs,
|
|
// though.
|
|
if (asn1_expect_rsa(map, &asn1.next, &avail, &hashtype2)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: unable to parse the digestEncryptionAlgorithm\n");
|
|
break;
|
|
}
|
|
|
|
if (hashtype2 != CLI_RSA && hashtype2 != hashtype) {
|
|
cli_dbgmsg("asn1_parse_countersignature: digestEncryptionAlgorithm conflicts with digestAlgorithm\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, asn1.next, &avail, &asn1, ASN1_TYPE_OCTET_STRING)) { /* encryptedDigest */
|
|
cli_dbgmsg("asn1_parse_countersignature: unexpected encryptedDigest value in counterSignature\n");
|
|
break;
|
|
}
|
|
if (asn1.size > 513) {
|
|
cli_dbgmsg("asn1_parse_countersignature: countersignature encryptedDigest too long\n");
|
|
break;
|
|
}
|
|
if (avail) {
|
|
cli_dbgmsg("asn1_parse_countersignature: extra data inside countersignature\n");
|
|
break;
|
|
}
|
|
if (!fmap_need_ptr_once(map, attrs, attrs_size)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to read authenticatedAttributes\n");
|
|
break;
|
|
}
|
|
|
|
if (NULL == (ctx = get_hash_ctx(hashtype))) {
|
|
break;
|
|
}
|
|
|
|
cl_update_hash(ctx, "\x31", 1);
|
|
cl_update_hash(ctx, (void *)(attrs + 1), attrs_size - 1);
|
|
cl_finish_hash(ctx, hash);
|
|
|
|
if (!fmap_need_ptr_once(map, asn1.content, asn1.size)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: failed to read countersignature encryptedDigest\n");
|
|
break;
|
|
}
|
|
if (!crtmgr_verify_pkcs7(cmgr, issuer, serial, asn1.content, asn1.size, hashtype, hash, VRFY_TIME)) {
|
|
cli_dbgmsg("asn1_parse_countersignature: pkcs7 countersignature verification failed\n");
|
|
break;
|
|
}
|
|
|
|
cli_dbgmsg("asn1_parse_countersignature: countersignature verification completed successfully\n");
|
|
|
|
return 0;
|
|
|
|
} while (0);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cl_error_t asn1_parse_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, crtmgr *cmgr, int embedded, const void **hashes, unsigned int *hashes_size, cli_ctx *ctx)
|
|
{
|
|
struct cli_asn1 asn1, deep, deeper;
|
|
uint8_t issuer[SHA1_HASH_SIZE], serial[SHA1_HASH_SIZE];
|
|
const uint8_t *message, *attrs;
|
|
unsigned int dsize, message_size, attrs_size;
|
|
// hash is used to hold the hashes we compute as part of sig verification
|
|
uint8_t hash[MAX_HASH_SIZE];
|
|
cli_crt_hashtype hashtype, hashtype2;
|
|
unsigned int hashsize;
|
|
// md is used to hold the message digest we extract from the signature
|
|
uint8_t md[MAX_HASH_SIZE];
|
|
cli_crt *x509;
|
|
void *hash_ctx;
|
|
int result;
|
|
cl_error_t ret = CL_EPARSE;
|
|
char *mod = NULL;
|
|
char *exp = NULL;
|
|
|
|
cli_dbgmsg("in asn1_parse_mscat\n");
|
|
|
|
do {
|
|
if (!(message = fmap_need_off_once(map, offset, 1))) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read pkcs#7 entry\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, message, &size, &asn1, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE */
|
|
cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE at top level\n");
|
|
break;
|
|
}
|
|
|
|
// Many signatures have zero bytes at the end (padding?)
|
|
/* if(size) { */
|
|
/* cli_dbgmsg("asn1_parse_mscat: found extra data after pkcs#7 %u\n", size); */
|
|
/* break; */
|
|
/* } */
|
|
size = asn1.size;
|
|
if (asn1_expect_obj(map, &asn1.content, &size, ASN1_TYPE_OBJECT_ID, lenof(OID_signedData), OID_signedData)) { /* OBJECT 1.2.840.113549.1.7.2 - contentType = signedData */
|
|
cli_dbgmsg("asn1_parse_mscat: expected contentType == signedData\n");
|
|
break;
|
|
}
|
|
if (asn1_expect_objtype(map, asn1.content, &size, &asn1, 0xa0)) { /* [0] - content */
|
|
cli_dbgmsg("asn1_parse_mscat: expected '[0] - content' following signedData contentType\n");
|
|
break;
|
|
}
|
|
if (size) {
|
|
cli_dbgmsg("asn1_parse_mscat: found extra data in pkcs#7\n");
|
|
break;
|
|
}
|
|
size = asn1.size;
|
|
if (asn1_expect_objtype(map, asn1.content, &size, &asn1, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE */
|
|
cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE inside signedData '[0] - content'\n");
|
|
break;
|
|
}
|
|
if (size) {
|
|
cli_dbgmsg("asn1_parse_mscat: found extra data in signedData\n");
|
|
break;
|
|
}
|
|
|
|
size = asn1.size;
|
|
if (asn1_expect_obj(map, &asn1.content, &size, ASN1_TYPE_INTEGER, 1, "\x01")) { /* INTEGER - VERSION 1 */
|
|
cli_dbgmsg("asn1_parse_mscat: expected 'INTEGER - VERSION 1' for signedData version\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, asn1.content, &size, &asn1, ASN1_TYPE_SET)) { /* SET OF DigestAlgorithmIdentifier */
|
|
cli_dbgmsg("asn1_parse_mscat: expected SET OF DigestAlgorithmIdentifier inside signedData\n");
|
|
break;
|
|
}
|
|
|
|
// At this point asn1.next points to the SEQUENCE following the
|
|
// DigestAlgorithmIdentifier SET, so we'll want to preserve it so
|
|
// we can continue parsing laterally. We also want to preserve
|
|
// size, since it tracks how much is left in the SignedData section.
|
|
if (asn1_expect_hash_algo(map, &asn1.content, &asn1.size, &hashtype, &hashsize)) {
|
|
cli_dbgmsg("asn1_parse_mscat: error parsing SignedData digestAlgorithm\n");
|
|
break;
|
|
}
|
|
if (asn1.size) {
|
|
cli_dbgmsg("asn1_parse_mscat: found extra data in the SignerData digestAlgorithm SET\n");
|
|
break;
|
|
}
|
|
|
|
// We've finished parsing the DigestAlgorithmIdentifiers SET, so start
|
|
// back parsing the SignedData
|
|
if (asn1_expect_objtype(map, asn1.next, &size, &asn1, ASN1_TYPE_SEQUENCE)) { /* SEQUENCE - contentInfo */
|
|
cli_dbgmsg("asn1_parse_mscat: expected 'SEQUENCE - contentInfo' inside SignedData following DigestAlgorithmIdentifiers\n");
|
|
break;
|
|
}
|
|
// Parse the contentInfo SEQUENCE. asn1.next and size point to the
|
|
// certificates, so these need to be preserved
|
|
|
|
/* Here there is either a PKCS #7 ContentType Object Identifier for Certificate Trust List (szOID_CTL)
|
|
* or a single SPC_INDIRECT_DATA_OBJID */
|
|
if (
|
|
(!embedded && asn1_expect_obj(map, &asn1.content, &asn1.size, ASN1_TYPE_OBJECT_ID, lenof(OID_szOID_CTL), OID_szOID_CTL)) ||
|
|
(embedded && asn1_expect_obj(map, &asn1.content, &asn1.size, ASN1_TYPE_OBJECT_ID, lenof(OID_SPC_INDIRECT_DATA_OBJID), OID_SPC_INDIRECT_DATA_OBJID))) {
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected ContentType for embedded mode %d\n", embedded);
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, 0xa0)) {
|
|
cli_dbgmsg("asn1_parse_mscat: expected '[0] - content' following DigestAlgorithmIdentifier contentType\n");
|
|
break;
|
|
}
|
|
if (asn1.size) {
|
|
cli_dbgmsg("asn1_parse_mscat: found extra data in contentInfo\n");
|
|
break;
|
|
}
|
|
dsize = deep.size;
|
|
if (asn1_expect_objtype(map, deep.content, &dsize, &deep, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE in DigestAlgorithmIdentifier '[0] - contentInfo'\n");
|
|
break;
|
|
}
|
|
if (dsize) {
|
|
cli_dbgmsg("asn1_parse_mscat: found extra data in content\n");
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Hashes should look like:
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER 1.3.6.1.4.1.311.2.1.15 spcPEImageData
|
|
* SEQUENCE(2 elem)
|
|
* BIT STRING(0 elem)
|
|
* [0](1 elem)
|
|
* [2](1 elem)
|
|
* [0]
|
|
* SEQUENCE(2 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER 1.3.14.3.2.26 sha1 (OIW)
|
|
* NULL
|
|
* OCTET STRING(20 byte)
|
|
*/
|
|
|
|
*hashes = deep.content;
|
|
*hashes_size = deep.size;
|
|
|
|
// Now resume parsing SignedData - certificates
|
|
|
|
if (asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa0)) { /* certificates */
|
|
cli_dbgmsg("asn1_parse_mscat: expected 0xa0 certificates entry\n");
|
|
break;
|
|
}
|
|
|
|
dsize = asn1.size;
|
|
if (dsize) {
|
|
crtmgr newcerts;
|
|
crtmgr_init(&newcerts);
|
|
while (dsize) {
|
|
result = asn1_get_x509(map, &asn1.content, &dsize, &newcerts);
|
|
if (ASN1_GET_X509_UNRECOVERABLE_ERROR == result) {
|
|
dsize = 1;
|
|
break;
|
|
} else if (ASN1_GET_X509_CERT_ERROR == result) {
|
|
cli_dbgmsg("asn1_parse_mscat: skipping x509 certificate with errors\n");
|
|
}
|
|
}
|
|
if (dsize) {
|
|
crtmgr_free(&newcerts);
|
|
cli_dbgmsg("asn1_parse_mscat: an unrecoverable error occurred while extracting x509 certificates\n");
|
|
break;
|
|
}
|
|
if (newcerts.crts) {
|
|
x509 = newcerts.crts;
|
|
cli_dbgmsg("asn1_parse_mscat: %u embedded certificates collected\n", newcerts.items);
|
|
if (engine->engine_options & ENGINE_OPTIONS_PE_DUMPCERTS) {
|
|
/* Dump the certs if requested before anything happens to them */
|
|
while (x509) {
|
|
char raw_issuer[CRT_RAWMAXLEN * 2 + 1], raw_subject[CRT_RAWMAXLEN * 2 + 1], raw_serial[CRT_RAWMAXLEN * 3 + 1];
|
|
char issuer[SHA1_HASH_SIZE * 2 + 1], subject[SHA1_HASH_SIZE * 2 + 1], serial[SHA1_HASH_SIZE * 2 + 1];
|
|
int j;
|
|
|
|
mod = BN_bn2hex(x509->n);
|
|
exp = BN_bn2hex(x509->e);
|
|
memset(raw_issuer, 0, CRT_RAWMAXLEN * 2 + 1);
|
|
memset(raw_subject, 0, CRT_RAWMAXLEN * 2 + 1);
|
|
memset(raw_serial, 0, CRT_RAWMAXLEN * 2 + 1);
|
|
for (j = 0; j < x509->raw_issuer[0]; j++)
|
|
sprintf(&raw_issuer[j * 2], "%02x", x509->raw_issuer[j + 1]);
|
|
for (j = 0; j < x509->raw_subject[0]; j++)
|
|
sprintf(&raw_subject[j * 2], "%02x", x509->raw_subject[j + 1]);
|
|
for (j = 0; j < x509->raw_serial[0]; j++)
|
|
sprintf(&raw_serial[j * 3], "%02x%c", x509->raw_serial[j + 1], (j != x509->raw_serial[0] - 1) ? ':' : '\0');
|
|
for (j = 0; j < SHA1_HASH_SIZE; j++) {
|
|
sprintf(&issuer[j * 2], "%02x", x509->issuer[j]);
|
|
sprintf(&subject[j * 2], "%02x", x509->subject[j]);
|
|
sprintf(&serial[j * 2], "%02x", x509->serial[j]);
|
|
}
|
|
|
|
// TODO The raw information we print out here isn't
|
|
// very helpful, since it's only the first 64-bytes...
|
|
// Change this so that raw is only populated when the
|
|
// debug flag is set, and then copy/display the full
|
|
// contents.
|
|
cli_dbgmsg("cert:\n");
|
|
cli_dbgmsg(" subject: %s\n", subject);
|
|
cli_dbgmsg(" serial: %s\n", serial);
|
|
cli_dbgmsg(" pubkey: %s\n", mod);
|
|
cli_dbgmsg(" i: %s %lu->%lu %s%s%s\n", issuer, (unsigned long)x509->not_before, (unsigned long)x509->not_after, x509->codeSign ? "code " : "", x509->timeSign ? "time " : "", x509->certSign ? "cert " : "");
|
|
cli_dbgmsg(" ==============RAW==============\n");
|
|
cli_dbgmsg(" raw_subject: %s\n", raw_subject);
|
|
cli_dbgmsg(" raw_serial: %s\n", raw_serial);
|
|
cli_dbgmsg(" raw_issuer: %s\n", raw_issuer);
|
|
|
|
x509 = x509->next;
|
|
OPENSSL_free(mod);
|
|
OPENSSL_free(exp);
|
|
mod = NULL;
|
|
exp = NULL;
|
|
}
|
|
x509 = newcerts.crts;
|
|
}
|
|
|
|
/* Determine whether the embedded certificate is blocked or
|
|
* trusted. If an embedded cert matches a block list rule,
|
|
* we can return immediately indicating that a sig matched.
|
|
* This isn't true for allow list matches, since otherwise an
|
|
* attacker could just include a known-good certificate in the
|
|
* signature and not use it. Instead, for those we will add the
|
|
* embedded cert to the trust store and continue on to ensure
|
|
* that a trusted cert is used for signing. */
|
|
while (x509) {
|
|
cli_crt *crt;
|
|
|
|
/* Use &(engine->cmgr) for this check, since we don't copy
|
|
* block list certs into cmgr and so that if there's a
|
|
* match, we have a long-lived pointer that we can pass
|
|
* back (via cli_append_virus) indicating the name of the
|
|
* sigs that matched (we can't just malloc new space for
|
|
* one, since nothing above here knows to free it.) */
|
|
if (NULL != (crt = crtmgr_block_list_lookup(&(engine->cmgr), x509))) {
|
|
ret = CL_VIRUS;
|
|
cli_dbgmsg("asn1_parse_mscat: Found Authenticode certificate blocked by %s\n", crt->name ? crt->name : "(unnamed CRB rule)");
|
|
if (NULL != ctx) {
|
|
ret = cli_append_virus(ctx, crt->name ? crt->name : "(unnamed CRB rule)");
|
|
if (ret == CL_VIRUS) {
|
|
crtmgr_free(&newcerts);
|
|
goto finish;
|
|
}
|
|
}
|
|
/* In the case where ctx is NULL, we don't care about
|
|
* block list matches - we are either using this
|
|
* function to parse .cat rules that were loaded in,
|
|
* or it's sigtool doing cert printing. */
|
|
}
|
|
|
|
/* NOTE: Since the 'issuer' cli_crt field is required for
|
|
* Authenticode validation, we rely on adding embedded
|
|
* certs with the 'issuer' actually set into our trust
|
|
* store for doing the time/code digital signature checks.
|
|
* This isn't required for cert-signing certs that
|
|
* we discover this way, since the CRB cli_crts have enough
|
|
* info to be able to trust other certs, but executing
|
|
* the following code for those has the benefit of removing
|
|
* them from newcerts so they aren't processed again while
|
|
* looking for chained trust. */
|
|
if (NULL != (crt = crtmgr_trust_list_lookup(cmgr, x509, 1))) {
|
|
cli_crt *tmp = x509->next;
|
|
cli_dbgmsg("asn1_parse_mscat: Directly trusting embedded cert based on %s\n", (crt->name ? crt->name : "(no name)"));
|
|
if (cli_debug_flag && crt->name) {
|
|
// Copy the name from the CRB entry for printing below
|
|
x509->name = strdup(crt->name);
|
|
}
|
|
if (crtmgr_add(cmgr, x509)) {
|
|
cli_dbgmsg("asn1_parse_mscat: adding x509 cert to crtmgr failed\n");
|
|
break;
|
|
}
|
|
crtmgr_del(&newcerts, x509);
|
|
x509 = tmp;
|
|
continue;
|
|
}
|
|
|
|
x509 = x509->next;
|
|
}
|
|
if (x509) {
|
|
crtmgr_free(&newcerts);
|
|
break;
|
|
}
|
|
|
|
if (CL_VIRUS == ret) {
|
|
crtmgr_free(&newcerts);
|
|
break;
|
|
}
|
|
|
|
x509 = newcerts.crts;
|
|
|
|
/* Now look for cases where embedded certs can be trusted
|
|
* indirectly because they are signed by trusted certs */
|
|
while (x509) {
|
|
cli_crt *parent;
|
|
|
|
/* If the certificate is in the trust store already, remove
|
|
* it from the newcerts list. This is legacy code that I'm
|
|
* assuming tries to prevent us from doing the expensive
|
|
* RSA verification in the case where the same cert is
|
|
* embedded multiple times? Sure, why not */
|
|
if (crtmgr_trust_list_lookup(cmgr, x509, 0)) {
|
|
cli_crt *tmp = x509->next;
|
|
cli_dbgmsg("asn1_parse_mscat: found embedded certificate matching one in the trust store\n");
|
|
crtmgr_del(&newcerts, x509);
|
|
x509 = tmp;
|
|
continue;
|
|
}
|
|
|
|
/* Determine whether the cert is signed by one in our trust
|
|
* store */
|
|
parent = crtmgr_verify_crt(cmgr, x509);
|
|
|
|
if (parent) {
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: Indirectly trusting embedded cert based on %s\n", (parent->name ? parent->name : "(no name)"));
|
|
|
|
// TODO Why is this done? It seems like you should be
|
|
// able to have a parent cert can only do cert signing
|
|
// and have that be able to sign a cert used for
|
|
// code-signing...
|
|
x509->codeSign &= parent->codeSign;
|
|
x509->timeSign &= parent->timeSign;
|
|
|
|
if (crtmgr_add(cmgr, x509)) {
|
|
cli_dbgmsg("asn1_parse_mscat: adding x509 cert to crtmgr failed\n");
|
|
break;
|
|
}
|
|
crtmgr_del(&newcerts, x509);
|
|
|
|
/* Start at the beginning of newcerts so that we can see
|
|
* whether adding this new trusted cert causes more
|
|
* certs to be trusted (via chaining). Otherwise we
|
|
* might miss valid certs if the ordering in the binary
|
|
* doesn't align with the chain ordering. */
|
|
x509 = newcerts.crts;
|
|
continue;
|
|
}
|
|
|
|
x509 = x509->next;
|
|
}
|
|
if (x509) {
|
|
crtmgr_free(&newcerts);
|
|
break;
|
|
}
|
|
if (newcerts.items)
|
|
cli_dbgmsg("asn1_parse_mscat: %u certificates did not verify\n", newcerts.items);
|
|
crtmgr_free(&newcerts);
|
|
}
|
|
}
|
|
|
|
// Parse the final section in SignedData - SignerInfos
|
|
if (asn1_get_obj(map, asn1.next, &size, &asn1)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to get next ASN1 section\n");
|
|
break;
|
|
}
|
|
if (asn1.type == 0xa1 && asn1_get_obj(map, asn1.next, &size, &asn1)) { /* crls - unused shouldn't be present */
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected CRL entries were found\n");
|
|
break;
|
|
}
|
|
if (asn1.type != ASN1_TYPE_SET) { /* signerInfos */
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected type %02x for signerInfo\n", asn1.type);
|
|
break;
|
|
}
|
|
if (size) {
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected extra data after signerInfos\n");
|
|
break;
|
|
}
|
|
size = asn1.size;
|
|
if (asn1_expect_objtype(map, asn1.content, &size, &asn1, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE in signerInfos");
|
|
break;
|
|
}
|
|
if (size) {
|
|
cli_dbgmsg("asn1_parse_mscat: only one signerInfo shall be present\n");
|
|
break;
|
|
}
|
|
size = asn1.size;
|
|
if (asn1_expect_obj(map, &asn1.content, &size, ASN1_TYPE_INTEGER, 1, "\x01")) { /* Version = 1 */
|
|
cli_dbgmsg("asn1_parse_mscat: expected Version == 1 for signerInfo\n");
|
|
break;
|
|
}
|
|
if (asn1_expect_objtype(map, asn1.content, &size, &asn1, ASN1_TYPE_SEQUENCE)) { /* issuerAndSerialNumber */
|
|
cli_dbgmsg("asn1_parse_mscat: expected issuerAndSerialNumber SEQUENCE\n");
|
|
break;
|
|
}
|
|
// asn1.next and size must be preserved so we can continue parsing
|
|
// SignerInfos, so switch to deep
|
|
dsize = asn1.size;
|
|
if (asn1_expect_objtype(map, asn1.content, &dsize, &deep, ASN1_TYPE_SEQUENCE)) { /* issuer */
|
|
cli_dbgmsg("asn1_parse_mscat: expected issuer SEQUENCE\n");
|
|
break;
|
|
}
|
|
|
|
/* Make sure the issuer ID is mapped into memory and then compute the
|
|
* SHA1 of it so we can use this value in verification later on. This
|
|
* will be a hash over all the values in the issuer SEQUENCE, which
|
|
* looks something like:
|
|
* SET(1 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER 2.5.4.6 countryName (X.520 DN component)
|
|
* PrintableString
|
|
* SET(1 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER2.5.4.8 stateOrProvinceName (X.520 DN component)
|
|
* PrintableString
|
|
* SET(1 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER2.5.4.7 localityName (X.520 DN component)
|
|
* PrintableString
|
|
* SET(1 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER2.5.4.10 organizationName (X.520 DN component)
|
|
* PrintableString
|
|
* SET(1 elem)
|
|
* SEQUENCE(2 elem)
|
|
* OBJECT IDENTIFIER2.5.4.3commonName(X.520 DN component)
|
|
* PrintableString
|
|
*/
|
|
if (map_sha1(map, deep.content, deep.size, issuer)) {
|
|
cli_dbgmsg("asn1_parse_mscat: error in call to map_sha1 for issuer\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, deep.next, &dsize, &deep, ASN1_TYPE_INTEGER)) { /* serial */
|
|
cli_dbgmsg("asn1_parse_mscat: expected ASN1_TYPE_INTEGER serial\n");
|
|
break;
|
|
}
|
|
|
|
/* Make sure the serial INTEGER is mapped into memory and compute the
|
|
* SHA1 of it so we can use this value in verification later on. */
|
|
if (map_sha1(map, deep.content, deep.size, serial)) {
|
|
cli_dbgmsg("asn1_parse_mscat: error in call to map_sha1 for serial\n");
|
|
break;
|
|
}
|
|
if (dsize) {
|
|
cli_dbgmsg("asn1_parse_mscat: extra data inside issuerAndSerialNumber\n");
|
|
break;
|
|
}
|
|
|
|
// Resume parsing the SignerInfos using asn1.next and size
|
|
if (asn1_expect_hash_algo(map, &asn1.next, &size, &hashtype2, &hashsize)) {
|
|
cli_dbgmsg("asn1_parse_mscat: error parsing SignerInfo digestAlgorithm\n");
|
|
break;
|
|
}
|
|
|
|
// Verify that the SignerInfo digestAlgorithm matches the one from the SignedData section
|
|
if (hashtype != hashtype2) {
|
|
cli_dbgmsg("asn1_parse_mscat: SignerInfo digestAlgorithm is not the same as the algorithm in SignedData\n");
|
|
break;
|
|
}
|
|
|
|
// Continue on to the authenticatedAttributes section within SignerInfo
|
|
attrs = asn1.next;
|
|
if (asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa0)) { /* authenticatedAttributes */
|
|
cli_dbgmsg("asn1_parse_mscat: unable to parse authenticatedAttributes section\n");
|
|
break;
|
|
}
|
|
attrs_size = (uint8_t *)(asn1.next) - attrs;
|
|
if (asn1.next == NULL || attrs_size < 2) {
|
|
cli_dbgmsg("asn1_parse_mscat: authenticatedAttributes size is too small\n");
|
|
break;
|
|
}
|
|
|
|
dsize = asn1.size;
|
|
deep.next = asn1.content;
|
|
result = 0;
|
|
while (dsize) {
|
|
struct cli_asn1 cobj;
|
|
int content;
|
|
if (asn1_expect_objtype(map, deep.next, &dsize, &deep, ASN1_TYPE_SEQUENCE)) { /* attribute */
|
|
cli_dbgmsg("asn1_parse_mscat: expected attribute SEQUENCE\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (asn1_expect_objtype(map, deep.content, &deep.size, &deeper, ASN1_TYPE_OBJECT_ID)) { /* attribute type */
|
|
cli_dbgmsg("asn1_parse_mscat: expected attribute type inside attribute SEQUENCE\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (deeper.size != lenof(OID_contentType))
|
|
continue;
|
|
if (!fmap_need_ptr_once(map, deeper.content, lenof(OID_contentType))) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (!memcmp(deeper.content, OID_contentType, lenof(OID_contentType)))
|
|
content = 0; /* contentType */
|
|
else if (!memcmp(deeper.content, OID_messageDigest, lenof(OID_messageDigest)))
|
|
content = 1; /* messageDigest */
|
|
else
|
|
continue;
|
|
if (asn1_expect_objtype(map, deeper.next, &deep.size, &deeper, ASN1_TYPE_SET)) { /* set - contents */
|
|
cli_dbgmsg("asn1_parse_mscat: expected 'set - contents' for authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (deep.size) {
|
|
cli_dbgmsg("asn1_parse_mscat: extra data in authenticated attributes\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
if (result & (1 << content)) {
|
|
cli_dbgmsg("asn1_parse_mscat: contentType or messageDigest appear twice\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
if (content == 0) { /* contentType */
|
|
if (
|
|
(!embedded && asn1_expect_obj(map, &deeper.content, &deeper.size, ASN1_TYPE_OBJECT_ID, lenof(OID_szOID_CTL), OID_szOID_CTL)) || /* cat file */
|
|
(embedded && asn1_expect_obj(map, &deeper.content, &deeper.size, ASN1_TYPE_OBJECT_ID, lenof(OID_SPC_INDIRECT_DATA_OBJID), OID_SPC_INDIRECT_DATA_OBJID)) /* embedded cat */
|
|
) {
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected ContentType for embedded mode %d (for authenticated attribute)\n", embedded);
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
result |= 1;
|
|
} else { /* messageDigest */
|
|
if (asn1_expect_objtype(map, deeper.content, &deeper.size, &cobj, ASN1_TYPE_OCTET_STRING)) {
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected messageDigest value\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (cobj.size != hashsize) {
|
|
cli_dbgmsg("asn1_parse_mscat: messageDigest attribute has the wrong size (%u)\n", cobj.size);
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (!fmap_need_ptr_once(map, cobj.content, hashsize)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
memcpy(md, cobj.content, hashsize);
|
|
result |= 2;
|
|
}
|
|
if (deeper.size) {
|
|
cli_dbgmsg("asn1_parse_mscat: extra data in authenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (dsize)
|
|
break;
|
|
if (result != 3) {
|
|
cli_dbgmsg("asn1_parse_mscat: contentType or messageDigest are missing\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_algo(map, &asn1.next, &size, lenof(OID_rsaEncryption), OID_rsaEncryption)) { /* digestEncryptionAlgorithm == rsa */
|
|
cli_dbgmsg("asn1_parse_mscat: digestEncryptionAlgorithms other than RSA are not yet supported\n");
|
|
break;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, asn1.next, &size, &asn1, ASN1_TYPE_OCTET_STRING)) { /* encryptedDigest */
|
|
cli_dbgmsg("asn1_parse_mscat: unexpected encryptedDigest value\n");
|
|
break;
|
|
}
|
|
|
|
// TODO Make this a #define with the greatest possible length (SHA512)
|
|
if (asn1.size > 513) {
|
|
cli_dbgmsg("asn1_parse_mscat: encryptedDigest too long\n");
|
|
break;
|
|
}
|
|
|
|
if (map_hash(map, *hashes, *hashes_size, hash, hashtype)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to map in message/compute message digest\n");
|
|
break;
|
|
}
|
|
|
|
if (memcmp(hash, md, hashsize)) {
|
|
cli_dbgmsg("asn1_parse_mscat: messageDigest mismatch\n");
|
|
break;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, attrs, attrs_size)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read authenticatedAttributes\n");
|
|
break;
|
|
}
|
|
|
|
if (NULL == (hash_ctx = get_hash_ctx(hashtype))) {
|
|
break;
|
|
}
|
|
|
|
cl_update_hash(hash_ctx, "\x31", 1);
|
|
cl_update_hash(hash_ctx, (void *)(attrs + 1), attrs_size - 1);
|
|
cl_finish_hash(hash_ctx, hash);
|
|
|
|
if (!fmap_need_ptr_once(map, asn1.content, asn1.size)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read encryptedDigest\n");
|
|
break;
|
|
}
|
|
|
|
// Verify the authenticatedAttributes
|
|
if (!(x509 = crtmgr_verify_pkcs7(cmgr, issuer, serial, asn1.content, asn1.size, hashtype, hash, VRFY_CODE))) {
|
|
cli_dbgmsg("asn1_parse_mscat: pkcs7 signature verification failed\n");
|
|
ret = CL_EVERIFY;
|
|
break;
|
|
}
|
|
message = asn1.content;
|
|
message_size = asn1.size;
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: authenticatedAttributes successfully parsed and verified\n");
|
|
|
|
/* We need to verify the time validity of the certificate. If a
|
|
* signature has a time-stamping countersignature, then we just need to
|
|
* verify that countersignature. Otherwise, we should determine
|
|
* whether the signing certificate is still valid (time-based, since at
|
|
* this point in the code no matching block list rules fired). */
|
|
|
|
if (!size) {
|
|
time_t now;
|
|
|
|
// No countersignature, so judge validity based on time
|
|
now = time(NULL);
|
|
|
|
if (now < x509->not_before || now > x509->not_after) {
|
|
cli_dbgmsg("asn1_parse_mscat: no countersignature (unauthAttrs missing) and signing certificate has expired\n");
|
|
ret = CL_EVERIFY;
|
|
break;
|
|
}
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: no countersignature (unauthAttrs missing) but the signing certificate is still valid\n");
|
|
ret = CL_CLEAN;
|
|
goto finish;
|
|
}
|
|
|
|
if (size && asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa1)) { /* unauthenticatedAttributes */
|
|
cli_dbgmsg("asn1_parse_mscat: unable to find unauthenticatedAttributes section\n");
|
|
break;
|
|
}
|
|
|
|
if (size) {
|
|
cli_dbgmsg("asn1_parse_mscat: extra data inside signerInfo\n");
|
|
break;
|
|
}
|
|
|
|
// Parse the unauthenticated attributes
|
|
|
|
dsize = asn1.size;
|
|
deep.next = asn1.content;
|
|
result = 0;
|
|
while (dsize) {
|
|
int content;
|
|
if (asn1_expect_objtype(map, deep.next, &dsize, &deep, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_parse_mscat: expected SEQUENCE starting an unauthenticatedAttribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (asn1_expect_objtype(map, deep.content, &deep.size, &deeper, ASN1_TYPE_OBJECT_ID)) {
|
|
cli_dbgmsg("asn1_parse_mscat: expected OID inside unauthenticatedAttribute SEQUENCE\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
// Supported OIDs include:
|
|
// - 1.2.840.113549.1.9.6 - counterSignature
|
|
// - 1.3.6.1.4.1.311.2.4.1 - nested signatures
|
|
|
|
// I've seen some other ones like 1.3.6.1.4.1.3845.3.9876.1.1.1,
|
|
// and the presence of those doesn't seem to mess up verification
|
|
// through the Windows API, so just skip those
|
|
|
|
if (deeper.size != lenof(OID_countersignature) &&
|
|
deeper.size != lenof(OID_nestedSignatures) &&
|
|
deeper.size != lenof(OID_RFC3161_countersignature)) {
|
|
continue;
|
|
}
|
|
|
|
if (!fmap_need_ptr_once(map, deeper.content, deeper.size)) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to read unauthenticated attribute OID\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
if (deeper.size == lenof(OID_countersignature) &&
|
|
!memcmp(deeper.content, OID_countersignature, lenof(OID_countersignature))) {
|
|
|
|
content = 0; /* counterSignature */
|
|
|
|
} else if (deeper.size == lenof(OID_nestedSignatures) &&
|
|
!memcmp(deeper.content, OID_nestedSignatures, lenof(OID_nestedSignatures))) {
|
|
|
|
content = 1; /* nested */
|
|
|
|
} else if (deeper.size == lenof(OID_RFC3161_countersignature) &&
|
|
!memcmp(deeper.content, OID_RFC3161_countersignature, lenof(OID_RFC3161_countersignature))) {
|
|
|
|
content = 2; /* RFC3161 Counter Signature */
|
|
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, deeper.next, &deep.size, &deeper, ASN1_TYPE_SET)) { /* set - contents */
|
|
cli_dbgmsg("asn1_parse_mscat: expected 'set - contents' inside unauthenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
if (deep.size) {
|
|
cli_dbgmsg("asn1_parse_mscat: extra data in unauthenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
if (result & (1 << content)) {
|
|
cli_dbgmsg("asn1_parse_mscat: counterSignature or nestedSignature appear twice\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
if (content == 0) { /* counterSignature */
|
|
|
|
if (asn1_parse_countersignature(map, &deeper.content, &deeper.size, cmgr, message, message_size, x509->not_before, x509->not_after)) {
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
|
|
result |= 1;
|
|
|
|
} else if (content == 1) { /* nestedSignature */
|
|
|
|
// TODO Support parsing these out in the future
|
|
cli_dbgmsg("asn1_parse_mscat: nested signatures detected but parsing them is not currently supported\n");
|
|
|
|
deeper.size = 0;
|
|
result |= 2;
|
|
} else if (content == 2) {
|
|
|
|
// TODO Support verifying these in the future
|
|
cli_dbgmsg("asn1_parse_mscat: RFC3161 timestamping countersignature detected but parsing them is not currently supported\n");
|
|
|
|
deeper.size = 0;
|
|
result |= 4;
|
|
}
|
|
if (deeper.size) {
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: extra data in unauthenticated attribute\n");
|
|
dsize = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (dsize)
|
|
break;
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: unauthenticatedAttributes successfully parsed\n");
|
|
|
|
if (1 != (result & 1)) {
|
|
time_t now;
|
|
|
|
// No countersignature, so judge validity based on time
|
|
now = time(NULL);
|
|
|
|
if (now < x509->not_before || now > x509->not_after) {
|
|
cli_dbgmsg("asn1_parse_mscat: no countersignature and signing certificate has expired\n");
|
|
ret = CL_EVERIFY;
|
|
break;
|
|
}
|
|
|
|
cli_dbgmsg("asn1_parse_mscat: no countersignature but the signing certificate is still valid\n");
|
|
}
|
|
|
|
ret = CL_CLEAN;
|
|
|
|
} while (0);
|
|
|
|
finish:
|
|
OPENSSL_free(mod);
|
|
OPENSSL_free(exp);
|
|
if (CL_EPARSE == ret) {
|
|
cli_dbgmsg("asn1_parse_mscat: failed to parse authenticode section\n");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int asn1_load_mscat(fmap_t *map, struct cl_engine *engine)
|
|
{
|
|
struct cli_asn1 c;
|
|
unsigned int size;
|
|
unsigned int i;
|
|
|
|
// TODO As currently implemented, loading in a .cat file with -d requires
|
|
// an accompanying .crb with trust entries that will cause the .cat
|
|
// file signatures to verify successfully. If a user is specifying a .cat
|
|
// file to use, though, we should assume they trust it and at least add the
|
|
// covered hashes from it to hm_fp
|
|
// TODO Since we pass engine->cmgr directly here, the whole chain of trust
|
|
// for this .cat file will get added to the global trust store assuming it
|
|
// verifies successfully. Is this a bug for a feature?
|
|
if (CL_CLEAN != asn1_parse_mscat(engine, map, 0, map->len, &engine->cmgr, 0, &c.next, &size, NULL))
|
|
return 1;
|
|
|
|
if (asn1_expect_objtype(map, c.next, &size, &c, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
if (asn1_expect_obj(map, &c.content, &c.size, ASN1_TYPE_OBJECT_ID, lenof(OID_szOID_CATALOG_LIST), OID_szOID_CATALOG_LIST))
|
|
return 1;
|
|
if (c.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data in szOID_CATALOG_LIST content\n");
|
|
return 1;
|
|
}
|
|
if (asn1_expect_objtype(map, c.next, &size, &c, 0x4)) /* List ID */
|
|
return 1;
|
|
if (asn1_expect_objtype(map, c.next, &size, &c, 0x17)) /* Effective date - WTF?! */
|
|
return 1;
|
|
|
|
if (asn1_expect_list_member(map, &c.next, &size)) /* szOID_CATALOG_LIST_MEMBER or szOID_CATALOG_LIST_MEMBER2 */
|
|
return 1;
|
|
if (asn1_expect_objtype(map, c.next, &size, &c, ASN1_TYPE_SEQUENCE)) /* hashes here */
|
|
return 1;
|
|
/* [0] is next but we don't care as it's really descriptives stuff */
|
|
|
|
size = c.size;
|
|
c.next = c.content;
|
|
while (size) {
|
|
struct cli_asn1 tag;
|
|
if (asn1_expect_objtype(map, c.next, &size, &c, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
if (asn1_expect_objtype(map, c.content, &c.size, &tag, ASN1_TYPE_OCTET_STRING)) /* TAG NAME */
|
|
return 1;
|
|
if (asn1_expect_objtype(map, tag.next, &c.size, &tag, ASN1_TYPE_SET)) /* set */
|
|
return 1;
|
|
if (c.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data in tag\n");
|
|
return 1;
|
|
}
|
|
while (tag.size) {
|
|
struct cli_asn1 tagval1, tagval2, tagval3;
|
|
int hashed_obj_type;
|
|
cli_crt_hashtype hashtype;
|
|
cli_hash_type_t hm_hashtype;
|
|
unsigned int hashsize;
|
|
|
|
if (asn1_expect_objtype(map, tag.content, &tag.size, &tagval1, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
tag.content = tagval1.next;
|
|
|
|
if (asn1_expect_objtype(map, tagval1.content, &tagval1.size, &tagval2, ASN1_TYPE_OBJECT_ID))
|
|
return 1;
|
|
if (tagval2.size != lenof(OID_SPC_INDIRECT_DATA_OBJID))
|
|
continue;
|
|
|
|
if (!fmap_need_ptr_once(map, tagval2.content, lenof(OID_SPC_INDIRECT_DATA_OBJID))) {
|
|
cli_dbgmsg("asn1_load_mscat: cannot read SPC_INDIRECT_DATA\n");
|
|
return 1;
|
|
}
|
|
if (memcmp(tagval2.content, OID_SPC_INDIRECT_DATA_OBJID, lenof(OID_SPC_INDIRECT_DATA_OBJID)))
|
|
continue; /* stuff like CAT_NAMEVALUE_OBJID(1.3.6.1.4.1.311.12.2.1) and CAT_MEMBERINFO_OBJID(.2).. */
|
|
|
|
if (asn1_expect_objtype(map, tagval2.next, &tagval1.size, &tagval2, ASN1_TYPE_SET))
|
|
return 1;
|
|
if (tagval1.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data in tag value\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, tagval2.content, &tagval2.size, &tagval1, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
if (tagval2.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data in SPC_INDIRECT_DATA_OBJID tag\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, tagval1.content, &tagval1.size, &tagval2, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
|
|
if (asn1_expect_objtype(map, tagval2.content, &tagval2.size, &tagval3, ASN1_TYPE_OBJECT_ID)) /* shall have an obj 1.3.6.1.4.1.311.2.1.15 or 1.3.6.1.4.1.311.2.1.25 inside */
|
|
return 1;
|
|
if (tagval3.size != lenof(OID_SPC_PE_IMAGE_DATA_OBJID)) { /* lenof(OID_SPC_PE_IMAGE_DATA_OBJID) = lenof(OID_SPC_CAB_DATA_OBJID) = 10*/
|
|
cli_dbgmsg("asn1_load_mscat: bad hash type size\n");
|
|
return 1;
|
|
}
|
|
if (!fmap_need_ptr_once(map, tagval3.content, lenof(OID_SPC_PE_IMAGE_DATA_OBJID))) {
|
|
cli_dbgmsg("asn1_load_mscat: cannot read hash type\n");
|
|
return 1;
|
|
}
|
|
if (!memcmp(tagval3.content, OID_SPC_PE_IMAGE_DATA_OBJID, lenof(OID_SPC_PE_IMAGE_DATA_OBJID)))
|
|
hashed_obj_type = 2;
|
|
else if (!memcmp(tagval3.content, OID_SPC_CAB_DATA_OBJID, lenof(OID_SPC_CAB_DATA_OBJID)))
|
|
hashed_obj_type = 1;
|
|
else {
|
|
cli_dbgmsg("asn1_load_mscat: unexpected hash type\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, tagval2.next, &tagval1.size, &tagval2, ASN1_TYPE_SEQUENCE))
|
|
return 1;
|
|
if (tagval1.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data after hash\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_hash_algo(map, &tagval2.content, &tagval2.size, &hashtype, &hashsize)) {
|
|
cli_dbgmsg("asn1_load_mscat: failed to identify hash algorithm used\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Translate hashtype to the enum used by the hasher */
|
|
if (CLI_SHA1RSA == hashtype) {
|
|
hm_hashtype = CLI_HASH_SHA1;
|
|
} else if (CLI_SHA256RSA == hashtype) {
|
|
hm_hashtype = CLI_HASH_SHA2_256;
|
|
} else {
|
|
cli_dbgmsg("asn1_load_mscat: only SHA1 and SHA2-256 hashes are supported for .cat file sigs\n");
|
|
return 1;
|
|
}
|
|
|
|
if (asn1_expect_objtype(map, tagval2.content, &tagval2.size, &tagval3, ASN1_TYPE_OCTET_STRING))
|
|
return 1;
|
|
if (tagval2.size) {
|
|
cli_dbgmsg("asn1_load_mscat: found extra data in hash\n");
|
|
return 1;
|
|
}
|
|
if (tagval3.size != hashsize) {
|
|
cli_dbgmsg("asn1_load_mscat: bad hash size %u\n", tagval3.size);
|
|
return 1;
|
|
}
|
|
if (!fmap_need_ptr_once(map, tagval3.content, hashsize)) {
|
|
cli_dbgmsg("asn1_load_mscat: cannot read hash\n");
|
|
return 1;
|
|
}
|
|
|
|
if (cli_debug_flag) {
|
|
char sha[SHA256_HASH_SIZE * 2 + 1] = {0};
|
|
for (i = 0; i < hashsize; i++)
|
|
sprintf(&sha[i * 2], "%02x", ((uint8_t *)(tagval3.content))[i]);
|
|
cli_dbgmsg("asn1_load_mscat: got hash %s (%s)\n", sha, (hashed_obj_type == 2) ? "PE" : "CAB");
|
|
}
|
|
if (!engine->hm_fp) {
|
|
if (!(engine->hm_fp = MPOOL_CALLOC(engine->mempool, 1, sizeof(*(engine->hm_fp))))) {
|
|
tag.size = 1;
|
|
return 1;
|
|
}
|
|
#ifdef USE_MPOOL
|
|
engine->hm_fp->mempool = engine->mempool;
|
|
#endif
|
|
}
|
|
|
|
/* Load the trusted hashes into hm_fp, using the size values
|
|
* 1 and 2 as sentinel values corresponding to CAB and PE hashes
|
|
* from .cat files respectively. */
|
|
if (CL_SUCCESS != hm_addhash_bin(engine, HASH_PURPOSE_WHOLE_FILE_FP_CHECK, tagval3.content, hm_hashtype, hashed_obj_type, NULL)) {
|
|
cli_warnmsg("asn1_load_mscat: failed to add hash\n");
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Check an embedded PE Authenticode section to determine whether it's trusted.
|
|
* This will return CL_VERIFIED if the file should be trusted, CL_EPARSE if an
|
|
* error occurred while parsing the signature, CL_EVERIFY if parsing was
|
|
* successful but there were no trust rules for the signature, and
|
|
* CL_VIRUS if a block list rule was found for an embedded certificate.
|
|
*
|
|
* If CL_VIRUS is returned, certname will be set to the certname of block list
|
|
* rule that matched (unless certname is NULL). */
|
|
cl_error_t asn1_check_mscat(struct cl_engine *engine, fmap_t *map, size_t offset, unsigned int size, struct cli_mapped_region *regions, uint32_t nregions, cli_ctx *ctx)
|
|
{
|
|
unsigned int content_size;
|
|
struct cli_asn1 c;
|
|
cli_crt_hashtype hashtype;
|
|
uint8_t hash[MAX_HASH_SIZE];
|
|
unsigned int hashsize;
|
|
const void *content;
|
|
crtmgr certs;
|
|
int ret;
|
|
void *hash_ctx;
|
|
unsigned int i;
|
|
|
|
cli_dbgmsg("in asn1_check_mscat (offset: %llu)\n", (long long unsigned)offset);
|
|
crtmgr_init(&certs);
|
|
/* Get a copy of all certs in the trust store, excluding block list certs */
|
|
if (crtmgr_add_roots(engine, &certs, 1)) {
|
|
crtmgr_free(&certs);
|
|
return CL_EVERIFY;
|
|
}
|
|
ret = asn1_parse_mscat(engine, map, offset, size, &certs, 1, &content, &content_size, ctx);
|
|
crtmgr_free(&certs);
|
|
if (CL_CLEAN != ret)
|
|
return ret;
|
|
|
|
if (asn1_expect_objtype(map, content, &content_size, &c, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_check_mscat: expected SEQUENCE at top level of hash container\n");
|
|
return CL_EPARSE;
|
|
}
|
|
if (asn1_expect_obj(map, &c.content, &c.size, ASN1_TYPE_OBJECT_ID, lenof(OID_SPC_PE_IMAGE_DATA_OBJID), OID_SPC_PE_IMAGE_DATA_OBJID)) {
|
|
cli_dbgmsg("asn1_check_mscat: expected spcPEImageData OID in the first hash SEQUENCE\n");
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
// TODO Should we do anything with the underlying SEQUENCE and data? From
|
|
// the 2008 spec doc it doesn't sound like many of the fields are used, so
|
|
// ignoring is probably fine for now
|
|
|
|
if (asn1_expect_objtype(map, c.next, &content_size, &c, ASN1_TYPE_SEQUENCE)) {
|
|
cli_dbgmsg("asn1_check_mscat: expected second hash container object to be a SEQUENCE\n");
|
|
return CL_EPARSE;
|
|
}
|
|
if (content_size) {
|
|
cli_dbgmsg("asn1_check_mscat: extra data in hash SEQUENCE\n");
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
if (asn1_expect_hash_algo(map, &c.content, &c.size, &hashtype, &hashsize)) {
|
|
cli_dbgmsg("asn1_check_mscat: unexpected file hash algo\n");
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
if (NULL == (hash_ctx = get_hash_ctx(hashtype))) {
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
// Now that we know the hash algorithm, compute the authenticode hash
|
|
// across the required regions of memory.
|
|
for (i = 0; i < nregions; i++) {
|
|
const uint8_t *hptr;
|
|
if (0 == regions[i].size) {
|
|
continue;
|
|
}
|
|
if (!(hptr = fmap_need_off_once(map, regions[i].offset, regions[i].size))) {
|
|
return CL_EVERIFY;
|
|
}
|
|
|
|
cl_update_hash(hash_ctx, hptr, regions[i].size);
|
|
}
|
|
|
|
cl_finish_hash(hash_ctx, hash);
|
|
|
|
if (cli_debug_flag) {
|
|
char hashtxt[MAX_HASH_SIZE * 2 + 1];
|
|
for (i = 0; i < hashsize; i++)
|
|
sprintf(&hashtxt[i * 2], "%02x", hash[i]);
|
|
cli_dbgmsg("Authenticode: %s\n", hashtxt);
|
|
}
|
|
|
|
if (asn1_expect_obj(map, &c.content, &c.size, ASN1_TYPE_OCTET_STRING, hashsize, hash)) {
|
|
cli_dbgmsg("asn1_check_mscat: computed authenticode hash did not match stored value\n");
|
|
return CL_EVERIFY;
|
|
}
|
|
if (c.size) {
|
|
cli_dbgmsg("asn1_check_mscat: extra data after the stored authenticode hash\n");
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
cli_dbgmsg("asn1_check_mscat: file with valid authenticode signature, trusted\n");
|
|
return CL_VERIFIED;
|
|
}
|