/* * Copyright (C) 2011 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 "asn1.h" #include "sha1.h" #include "md5.h" #include "bignum.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_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_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_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_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 #define lenof(x) (sizeof((x))-1) /* --------------------------------------------------------------------------- OIDS */ struct cli_asn1 { uint8_t type; unsigned int size; void *content; void *next; }; static int map_sha1(fmap_t *map, void *data, unsigned int len, uint8_t sha1[SHA1_HASH_SIZE]) { SHA1Context ctx; if(!fmap_need_ptr_once(map, data, len)) { cli_dbgmsg("map_sha1: failed to read hash data\n"); return 1; } SHA1Init(&ctx); SHA1Update(&ctx, data, len); SHA1Final(&ctx, sha1); return 0; } static int map_md5(fmap_t *map, void *data, unsigned int len, uint8_t *md5) { cli_md5_ctx ctx; if(!fmap_need_ptr_once(map, data, len)) { cli_dbgmsg("map_md5: failed to read hash data\n"); return 1; } cli_md5_init(&ctx); cli_md5_update(&ctx, data, len); cli_md5_final(md5, &ctx); return 0; } static int asn1_get_obj(fmap_t *map, void *asn1data, unsigned int *asn1len, struct cli_asn1 *obj) { unsigned int asn1_sz = *asn1len; unsigned int readbytes = MIN(6, asn1_sz), i; 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, 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, 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, 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, 0x30))) /* SEQUENCE */ return ret; avail = obj.size; *asn1data = obj.next; if((ret = asn1_expect_obj(map, &obj.content, &avail, 0x06, algo_size, algo))) /* ALGO */ return ret; if(avail && (ret = asn1_expect_obj(map, &obj.content, &avail, 0x05, 0, NULL))) /* NULL */ return ret; if(avail) { cli_dbgmsg("asn1_expect_algo: extra data found in SEQUENCE\n"); return 1; } return 0; } static int asn1_expect_rsa(fmap_t *map, void **asn1data, unsigned int *asn1len, cli_crt_hashtype *hashtype) { struct cli_asn1 obj; unsigned int avail; int ret; if((ret = asn1_expect_objtype(map, *asn1data, asn1len, &obj, 0x30))) /* SEQUENCE */ return ret; avail = obj.size; *asn1data = obj.next; if(asn1_expect_objtype(map, obj.content, &avail, &obj, 0x06)) return 1; if(obj.size != lenof(OID_sha1WithRSA) && obj.size != lenof(OID_sha1WithRSAEncryption)) { /* lenof(OID_sha1WithRSAEncryption) = lenof(OID_md5WithRSAEncryption) = 9 */ cli_dbgmsg("asn1_expect_rsa: expecting OID with size 5 or 9, got %02x with size %u\n", obj.type, obj.size); return 1; } if(!fmap_need_ptr_once(map, obj.content, obj.size)) { cli_dbgmsg("asn1_expect_rsa: failed to read OID\n"); return 1; } if(obj.size == lenof(OID_sha1WithRSA) && !memcmp(obj.content, OID_sha1WithRSA, lenof(OID_sha1WithRSA))) *hashtype = CLI_SHA1RSA; /* Obsolete sha1rsa 1.3.14.3.2.29 */ else if(obj.size == lenof(OID_sha1WithRSAEncryption) && !memcmp(obj.content, OID_sha1WithRSAEncryption, lenof(OID_sha1WithRSAEncryption))) *hashtype = CLI_SHA1RSA; /* sha1withRSAEncryption 1.2.840.113549.1.1.5 */ else if(obj.size == lenof(OID_md5WithRSAEncryption) && !memcmp(obj.content, OID_md5WithRSAEncryption, lenof(OID_md5WithRSAEncryption))) *hashtype = CLI_MD5RSA; /* md5withRSAEncryption 1.2.840.113549.1.1.4 */ else { cli_dbgmsg("asn1_expect_rsa: OID mismatch\n"); return 1; } if((ret = asn1_expect_obj(map, &obj.next, &avail, 0x05, 0, NULL))) /* NULL */ return ret; if(avail) { cli_dbgmsg("asn1_expect_rsa: extra data found in SEQUENCE\n"); return 1; } return 0; } static int ms_asn1_get_sha1(fmap_t *map, void *asn1data, unsigned int avail, unsigned int emb, uint8_t sha1[SHA1_HASH_SIZE], unsigned int *type) { /* ret * 0 - success * 1 - unexpected obj (ok for cat) * 2 - severe */ struct cli_asn1 obj, obj2; unsigned int avail2; /* Manual parsing to avoid spamming */ if(asn1_expect_objtype(map, asn1data, &avail, &obj, 0x06)) return 2; if(obj.size != lenof(OID_SPC_INDIRECT_DATA_OBJID)) return 1; if(!fmap_need_ptr_once(map, obj.content, lenof(OID_SPC_INDIRECT_DATA_OBJID))) { cli_dbgmsg("ms_asn1_get_sha1: failed to read content\n"); return 2; } if(memcmp(obj.content, OID_SPC_INDIRECT_DATA_OBJID, lenof(OID_SPC_INDIRECT_DATA_OBJID))) /* OBJECT 1.3.6.1.4.1.311.2.1.4 - SPC_INDIRECT_DATA_OBJID */ return 1; if(asn1_expect_objtype(map, obj.next, &avail, &obj, emb ? 0xa0 : 0x31)) return 2; avail = obj.size; if(asn1_expect_objtype(map, obj.content, &avail, &obj, 0x30)) /* SEQUENCE */ return 2; avail = obj.size; if(asn1_expect_objtype(map, obj.content, &avail, &obj, 0x30)) /* data - contains an objid 1.3.6.1.4.1.311.2.1.15 or 1.3.6.1.4.1.311.2.1.25 */ return 2; avail2 = obj.size; if(asn1_expect_objtype(map, obj.content, &avail2, &obj2, 0x06)) /* OBJECT */ return 2; if(obj2.size != lenof(OID_SPC_PE_IMAGE_DATA_OBJID)) { cli_dbgmsg("ms_asn1_get_sha1: expected data object size 10, got %u\n", obj2.size); return 2; } if(!fmap_need_ptr_once(map, obj2.content, lenof(OID_SPC_PE_IMAGE_DATA_OBJID))) { cli_dbgmsg("ms_asn1_get_sha1: failed to read data content\n"); return 2; } if(!memcmp(obj2.content, OID_SPC_PE_IMAGE_DATA_OBJID, lenof(OID_SPC_PE_IMAGE_DATA_OBJID))) { /* SPC_PE_IMAGE_DATA_OBJID */ if(type) *type = 1; } else if (!emb && !memcmp(obj2.content, OID_SPC_CAB_DATA_OBJID, lenof(OID_SPC_CAB_DATA_OBJID))) { /* SPC_CAB_DATA_OBJID */ if(type) *type = 0; } else { cli_dbgmsg("ms_asn1_get_sha1: data object id mismatch\n"); return 2; } if(asn1_expect_objtype(map, obj.next, &avail, &obj, 0x30)) /* messageDigest */ return 2; avail = obj.size; if(asn1_expect_algo(map, &obj.content, &avail, lenof(OID_sha1), OID_sha1)) /* objid 1.3.14.3.2.26 - sha1 */ return 2; if(asn1_expect_objtype(map, obj.content, &avail, &obj, 0x04)) return 2; if(avail) { cli_dbgmsg("ms_asn1_get_sha1: found unexpected extra data\n"); return 2; } if(obj.size != SHA1_HASH_SIZE) { cli_dbgmsg("ms_asn1_get_sha1: expected sha1 lenght(%u), but got %u\n", SHA1_HASH_SIZE, obj.size); return 2; } if(!fmap_need_ptr_once(map, obj.content, SHA1_HASH_SIZE)) { cli_dbgmsg("ms_asn1_get_sha1: failed to read sha1 content\n"); return 2; } memcpy(sha1, obj.content, SHA1_HASH_SIZE); 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, void **asn1data, unsigned int *size, time_t *time) { 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; } 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; 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; 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; } *time = mktime(&t); *asn1data = obj.next; return 0; } static int asn1_get_rsa_pubkey(fmap_t *map, void **asn1data, unsigned int *size, cli_crt *x509) { struct cli_asn1 obj; unsigned int avail, avail2; if(asn1_expect_objtype(map, *asn1data, size, &obj, 0x30)) /* subjectPublicKeyInfo */ return 1; *asn1data = obj.next; avail = obj.size; if(asn1_expect_algo(map, &obj.content, &avail, lenof(OID_rsaEncryption), OID_rsaEncryption)) /* rsaEncryption */ return 1; if(asn1_expect_objtype(map, obj.content, &avail, &obj, 0x03)) /* 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, 0x30)) /* 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, 0x02)) /* 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(mp_read_unsigned_bin(&x509->n, obj.content, avail2)) { cli_dbgmsg("asn1_get_rsa_pubkey: cannot convert n to big number\n"); return 1; } if(asn1_expect_objtype(map, obj.next, &avail, &obj, 0x02)) /* 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(mp_read_unsigned_bin(&x509->e, obj.content, obj.size)) { cli_dbgmsg("asn1_get_rsa_pubkey: cannot convert e to big number\n"); return 1; } return 0; } static int asn1_get_x509(fmap_t *map, void **asn1data, unsigned int *size, crtmgr *master, crtmgr *other) { struct cli_asn1 crt, tbs, obj; unsigned int avail, tbssize, issuersize; cli_crt_hashtype hashtype1, hashtype2; cli_crt x509; uint8_t *tbsdata; void *next, *issuer; if(cli_crt_init(&x509)) return 1; do { if(asn1_expect_objtype(map, *asn1data, size, &crt, 0x30)) /* SEQUENCE */ break; *asn1data = crt.next; tbsdata = crt.content; if(asn1_expect_objtype(map, crt.content, &crt.size, &tbs, 0x30)) /* SEQUENCE - TBSCertificate */ break; tbssize = (uint8_t *)tbs.next - tbsdata; if(asn1_expect_objtype(map, tbs.content, &tbs.size, &obj, 0xa0)) /* [0] */ break; avail = obj.size; next = obj.next; if(asn1_expect_obj(map, &obj.content, &avail, 0x02, 1, "\x02")) /* version 3 only */ break; if(avail) { cli_dbgmsg("asn1_get_x509: found unexpected extra data in version\n"); break; } if(asn1_expect_objtype(map, next, &tbs.size, &obj, 0x02)) /* serialNumber */ break; if(asn1_expect_rsa(map, &obj.next, &tbs.size, &hashtype1)) /* algo = sha1WithRSAEncryption | md5WithRSAEncryption */ break; if(asn1_expect_objtype(map, obj.next, &tbs.size, &obj, 0x30)) /* issuer */ break; issuer = obj.content; issuersize = obj.size; if(asn1_expect_objtype(map, obj.next, &tbs.size, &obj, 0x30)) /* validity */ break; avail = obj.size; next = obj.content; if(asn1_get_time(map, &next, &avail, &x509.not_before)) /* notBefore */ break; if(asn1_get_time(map, &next, &avail, &x509.not_after)) /* notAfter */ 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, 0x30)) /* subject */ break; if(map_sha1(map, obj.content, obj.size, x509.subject)) break; if(asn1_get_rsa_pubkey(map, &obj.next, &tbs.size, &x509)) break; if(crtmgr_lookup(master, &x509) || crtmgr_lookup(other, &x509)) { cli_dbgmsg("asn1_get_x509: certificate already exists\n"); cli_crt_clear(&x509); return 0; } if(map_sha1(map, issuer, issuersize, x509.issuer)) break; avail = 0; while(tbs.size) { /* FIXME parse extensions */ 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(tbs.size) break; if(asn1_expect_rsa(map, &tbs.next, &crt.size, &hashtype2)) /* signature algo = sha1WithRSAEncryption | md5WithRSAEncryption */ break; if(hashtype1 != hashtype2) { cli_dbgmsg("asn1_get_x509: found conflicting rsa hash types\n"); break; } x509.hashtype = hashtype1; if(asn1_expect_objtype(map, tbs.next, &crt.size, &obj, 0x03)) /* signature */ 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(mp_read_unsigned_bin(&x509.sig, obj.content, obj.size)) { cli_dbgmsg("asn1_get_x509: cannot convert signature to big number\n"); break; } if(crt.size) { cli_dbgmsg("asn1_get_x509: found unexpected extra data in signature\n"); break; } if((x509.hashtype == CLI_SHA1RSA && map_sha1(map, tbsdata, tbssize, x509.tbshash)) || (x509.hashtype == CLI_MD5RSA && (map_md5(map, tbsdata, tbssize, x509.tbshash)))) break; if(crtmgr_add(other, &x509)) break; cli_crt_clear(&x509); return 0; } while(0); cli_crt_clear(&x509); return 1; } static int asn1_parse_mscat(fmap_t *map, void *start, unsigned int size, crtmgr *cmgr, int embedded, void **hashes, unsigned int *hashes_size) { struct cli_asn1 asn1, deep, deeper; uint8_t sha1[SHA1_HASH_SIZE], issuer[SHA1_HASH_SIZE], md[SHA1_HASH_SIZE], *message, *attrs; unsigned int dsize, message_size, attrs_size; cli_crt_hashtype hashtype; SHA1Context ctx; int result; cli_dbgmsg("in asn1_parse_mscat\n"); do { if(asn1_expect_objtype(map, start, &size, &asn1, 0x30)) /* SEQUENCE */ break; 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, 0x06, lenof(OID_signedData), OID_signedData)) /* OBJECT 1.2.840.113549.1.7.2 - contentType = signedData */ break; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0xa0)) /* [0] - content */ 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, 0x30)) /* SEQUENCE */ 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, 0x02, 1, "\x01")) /* INTEGER - VERSION 1 */ break; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x31)) /* SET OF DigestAlgorithmIdentifier */ break; if(asn1_expect_algo(map, &asn1.content, &asn1.size, lenof(OID_sha1), OID_sha1)) /* DigestAlgorithmIdentifier[0] == sha1 */ break; if(asn1.size) { cli_dbgmsg("asn1_parse_mscat: only one digestAlgorithmIdentifier is allowed\n"); break; } if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0x30)) /* SEQUENCE - contentInfo */ break; /* 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, 0x06, lenof(OID_szOID_CTL), OID_szOID_CTL)) || (embedded && asn1_expect_obj(map, &asn1.content, &asn1.size, 0x06, lenof(OID_SPC_INDIRECT_DATA_OBJID), OID_SPC_INDIRECT_DATA_OBJID)) ) break; if(asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, 0xa0)) 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, 0x30)) break; if(dsize) { cli_dbgmsg("asn1_parse_mscat: found extra data in content\n"); break; } *hashes = deep.content; *hashes_size = deep.size; if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa0)) /* certificates */ break; dsize = asn1.size; if(dsize) { crtmgr newcerts; crtmgr_init(&newcerts); while(dsize) { if(asn1_get_x509(map, &asn1.content, &dsize, cmgr, &newcerts)) { dsize = 1; break; } } if(dsize) break; if(newcerts.crts) { unsigned int orig = newcerts.items; cli_crt *x509 = newcerts.crts; cli_dbgmsg("------------------------\n"); while(x509) { if(!crtmgr_verify_crt(cmgr, x509)) { if(crtmgr_add(cmgr, x509)) { /* FIXME handle error */ } crtmgr_del(&newcerts, x509); x509 = newcerts.crts; continue; } x509 = x509->next; } if(newcerts.items) cli_errmsg("asn1_parse_mscat: got %u certs, %u left unverified\n", orig, newcerts.items); crtmgr_free(&newcerts); } } if(asn1_get_obj(map, asn1.next, &size, &asn1)) break; if(asn1.type == 0xa1 && asn1_get_obj(map, asn1.next, &size, &asn1)) /* crls - unused shouldn't be present */ break; if(asn1.type != 0x31) { /* signerInfos */ cli_dbgmsg("asn1_parse_mscat: unexpected type %02x for signerInfos\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, 0x30)) 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, 0x02, 1, "\x01")) /* Version = 1 */ break; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x30)) /* issuerAndSerialNumber */ break; dsize = asn1.size; if(asn1_expect_objtype(map, asn1.content, &dsize, &deep, 0x30)) /* issuer */ break; if(map_sha1(map, deep.content, deep.size, issuer)) break; if(asn1_expect_objtype(map, deep.next, &dsize, &deep, 0x02)) /* serial */ break; if(dsize) { cli_dbgmsg("asn1_parse_mscat: extra data inside issuerAndSerialNumber\n"); break; } if(asn1_expect_algo(map, &asn1.next, &size, lenof(OID_sha1), OID_sha1)) /* digestAlgorithm == sha1 */ break; attrs = asn1.next; if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa0)) /* authenticatedAttributes */ break; attrs_size = (uint8_t *)(asn1.next) - attrs; if(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, 0x30)) { /* attribute */ dsize = 1; break; } if(asn1_expect_objtype(map, deep.content, &deep.size, &deeper, 0x06)) { /* attribute type */ 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, 0x31)) { /* set - contents */ 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, 0x06, lenof(OID_szOID_CTL), OID_szOID_CTL)) || /* cat file */ (embedded && asn1_expect_obj(map, &deeper.content, &deeper.size, 0x06, lenof(OID_SPC_INDIRECT_DATA_OBJID), OID_SPC_INDIRECT_DATA_OBJID)) /* embedded cat */ ) { dsize = 1; break; } result |= 1; } else { /* messageDigest */ if(asn1_expect_objtype(map, deeper.content, &deeper.size, &cobj, 0x04)) { dsize = 1; break; } if(cobj.size != SHA1_HASH_SIZE) { cli_dbgmsg("asn1_parse_mscat: messageDigest attribute has got the wrong size (%u)\n", cobj.size); dsize = 1; break; } if(!fmap_need_ptr_once(map, cobj.content, SHA1_HASH_SIZE)) { cli_dbgmsg("asn1_parse_mscat: failed to read authenticated attribute\n"); dsize = 1; break; } memcpy(md, cobj.content, SHA1_HASH_SIZE); 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 == sha1 */ break; if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0x04)) /* encryptedDigest */ break; if(map_sha1(map, *hashes, *hashes_size, sha1)) break; if(memcmp(sha1, md, sizeof(sha1))) { 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; } SHA1Init(&ctx); SHA1Update(&ctx, "\x31", 1); SHA1Update(&ctx, attrs + 1, attrs_size - 1); SHA1Final(&ctx, sha1); if(!fmap_need_ptr_once(map, asn1.content, asn1.size)) { cli_dbgmsg("asn1_parse_mscat: failed to read encryptedDigest\n"); break; } if(crtmgr_verify_pkcs7(cmgr, issuer, asn1.content, asn1.size, CLI_SHA1RSA, sha1)) { cli_dbgmsg("asn1_parse_mscat: pkcs7 signature verification failed\n"); break; } message = asn1.content; message_size = asn1.size; if(!size) return 0; /* FIXME NO TIMESTAMP/COUNTERSIG */ if(size && asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa1)) /* unauthenticatedAttributes */ break; if(size) { cli_dbgmsg("asn1_parse_mscat: extra data inside signerInfo\n"); break; } size = asn1.size; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x30)) break; if(size) { cli_dbgmsg("asn1_parse_mscat: extra data inside unauthenticatedAttributes\n"); break; } size = asn1.size; /* 1.2.840.113549.1.9.6 - counterSignature */ if(asn1_expect_obj(map, &asn1.content, &size, 0x06, lenof(OID_countersignature), OID_countersignature)) break; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x31)) break; if(size) { cli_dbgmsg("asn1_parse_mscat: extra data inside counterSignature\n"); break; } size = asn1.size; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x30)) break; if(size) { cli_dbgmsg("asn1_parse_mscat: extra data inside unauthenticatedAttributes\n"); break; } size = asn1.size; if(asn1_expect_obj(map, &asn1.content, &size, 0x02, 1, "\x01")) /* Version = 1*/ break; if(asn1_expect_objtype(map, asn1.content, &size, &asn1, 0x30)) /* issuerAndSerialNumber */ break; if(asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, 0x30)) /* issuer */ break; if(map_sha1(map, deep.content, deep.size, issuer)) break; if(asn1_expect_objtype(map, deep.next, &asn1.size, &deep, 0x02)) /* serial */ break; if(asn1.size) { cli_dbgmsg("asn1_parse_mscat: extra data inside countersignature issuer\n"); break; } if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0x30)) /* digestAlgorithm */ break; if(asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, 0x06)) break; if(deep.size != lenof(OID_sha1) && deep.size != lenof(OID_md5)) { cli_dbgmsg("asn1_parse_mscat: wrong digestAlgorithm size\n"); break; } if(!fmap_need_ptr_once(map, deep.content, deep.size)) { cli_dbgmsg("asn1_parse_mscat: failed to read digestAlgorithm OID\n"); break; } if(deep.size == lenof(OID_sha1) && !memcmp(deep.content, OID_sha1, lenof(OID_sha1))) { hashtype = CLI_SHA1RSA; if(map_sha1(map, message, message_size, md)) break; } else if(deep.size == lenof(OID_md5) && !memcmp(deep.content, OID_md5, lenof(OID_md5))) { hashtype = CLI_MD5RSA; if(map_md5(map, message, message_size, md)) break; } else { cli_dbgmsg("asn1_parse_mscat: unknown digest oid in countersignature\n"); break; } if(asn1.size && asn1_expect_obj(map, &deep.next, &asn1.size, 0x05, 0, NULL)) break; if(asn1.size) { cli_dbgmsg("asn1_parse_mscat: extra data in countersignature oid\n"); break; } attrs = asn1.next; if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0xa0)) /* authenticatedAttributes */ break; attrs_size = (uint8_t *)(asn1.next) - attrs; if(attrs_size < 2) { cli_dbgmsg("asn1_parse_mscat: 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, 0x30)) { /* attribute */ dsize = 1; break; } if(asn1_expect_objtype(map, deep.content, &deep.size, &deeper, 0x06)) { /* attribute type */ 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))) { 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_mscat: duplicate field in countersignature\n"); dsize = 1; break; } result |= (1<<content); if(asn1_expect_objtype(map, deeper.next, &deep.size, &deeper, 0x31)) { /* attribute type */ dsize = 1; break; } if(deep.size) { cli_dbgmsg("asn1_parse_mscat: extra data in countersignature value\n"); dsize = 1; break; } deep.size = deeper.size; switch(content) { case 0: /* contentType = pkcs7-data */ if(asn1_expect_obj(map, &deeper.content, &deep.size, 0x06, lenof(OID_pkcs7_data), OID_pkcs7_data)) deep.size = 1; else if(deep.size) cli_dbgmsg("asn1_parse_mscat: extra data in countersignature content-type\n"); break; case 1: /* messageDigest */ if(asn1_expect_obj(map, &deeper.content, &deep.size, 0x04, (hashtype == CLI_SHA1RSA) ? SHA1_HASH_SIZE : 16, md)) { deep.size = 1; cli_dbgmsg("asn1_parse_mscat: countersignature hash mismatch\n"); } else if(deep.size) cli_dbgmsg("asn1_parse_mscat: 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)) deep.size = 1; else if(deep.size) cli_dbgmsg("asn1_parse_mscat: extra data in countersignature signing-time\n"); break; } } if(deep.size) { dsize = 1; break; } } if(dsize) break; if(result != 7) { cli_dbgmsg("asn1_parse_mscat: some important attributes are missing in countersignature\n"); break; } if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0x30)) /* digestEncryptionAlgorithm == sha1 */ break; if(asn1_expect_objtype(map, asn1.content, &asn1.size, &deep, 0x06)) /* digestEncryptionAlgorithm == sha1 */ break; if(deep.size != lenof(OID_rsaEncryption)) { /* lenof(OID_rsaEncryption) = lenof(OID_sha1WithRSAEncryption) = 9 */ cli_dbgmsg("asn1_parse_mscat: wrong digestEncryptionAlgorithm size in countersignature\n"); break; } if(!fmap_need_ptr_once(map, deep.content, lenof(OID_rsaEncryption))) { cli_dbgmsg("asn1_parse_mscat: cannot read digestEncryptionAlgorithm in countersignature\n"); break; } /* rsaEncryption or sha1withRSAEncryption */ if(memcmp(deep.content, OID_rsaEncryption, lenof(OID_rsaEncryption)) && memcmp(deep.content, OID_sha1WithRSAEncryption, lenof(OID_sha1WithRSAEncryption))) { cli_dbgmsg("asn1_parse_mscat: digestEncryptionAlgorithm in countersignature is not sha1\n"); break; } if(asn1.size && asn1_expect_obj(map, &deep.next, &asn1.size, 0x05, 0, NULL)) break; if(asn1.size) { cli_dbgmsg("asn1_parse_mscat: extra data in digestEncryptionAlgorithm in countersignature\n"); break; } if(asn1_expect_objtype(map, asn1.next, &size, &asn1, 0x04)) /* encryptedDigest */ break; if(size) { cli_dbgmsg("asn1_parse_mscat: extra data inside countersignature\n"); break; } if(!fmap_need_ptr_once(map, attrs, attrs_size)) { cli_dbgmsg("asn1_parse_mscat: failed to read authenticatedAttributes\n"); return 1; } if(hashtype == CLI_SHA1RSA) { SHA1Init(&ctx); SHA1Update(&ctx, "\x31", 1); SHA1Update(&ctx, attrs + 1, attrs_size - 1); SHA1Final(&ctx, sha1); } else { cli_md5_ctx ctx; cli_md5_init(&ctx); cli_md5_update(&ctx, "\x31", 1); cli_md5_update(&ctx, attrs + 1, attrs_size - 1); cli_md5_final(sha1, &ctx); } if(!fmap_need_ptr_once(map, asn1.content, asn1.size)) { cli_dbgmsg("asn1_parse_mscat: failed to read countersignature encryptedDigest\n"); break; } if(crtmgr_verify_pkcs7(cmgr, issuer, asn1.content, asn1.size, hashtype, sha1)) { cli_dbgmsg("asn1_parse_mscat: pkcs7 countersignature verification failed\n"); break; } cli_dbgmsg("asn1_parse_mscat: catalog succesfully parsed\n"); return 0; } while(0); cli_dbgmsg("asn1_parse_mscat: failed to parse catalog\n"); return 1; } int asn1_load_mscat(fmap_t *map, void *start, unsigned int size, struct cl_engine *engine) { void *hashes; unsigned int hashes_size; struct cli_asn1 c; if(asn1_parse_mscat(map, start, size, &engine->cmgr, 0, &hashes, &hashes_size)) return 1; if(asn1_expect_objtype(map, hashes, &hashes_size, &c, 0x30)) return 1; if(asn1_expect_obj(map, &c.content, &c.size, 0x06, 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, &hashes_size, &c, 0x4)) /* List ID */ return 1; if(asn1_expect_objtype(map, c.next, &hashes_size, &c, 0x17)) /* Effective date - WTF?! */ return 1; if(asn1_expect_algo(map, &c.next, &hashes_size, lenof(OID_szOID_CATALOG_LIST_MEMBER), OID_szOID_CATALOG_LIST_MEMBER)) /* szOID_CATALOG_LIST_MEMBER */ return 1; if(asn1_expect_objtype(map, c.next, &hashes_size, &c, 0x30)) /* hashes here */ return 1; cli_errmsg("ACAB: %u\n", hashes_size); while(c.size) { struct cli_asn1 tag; if(asn1_expect_objtype(map, c.content, &c.size, &tag, 0x30)) { c.size = 1; break; } c.content = tag.next; /* if(asn1_expect_objtype(map, deeper.content, &deeper.size, &tag, 0x04)) { /\* TAG NAME *\/ */ /* deep.size = 1; */ /* break; */ /* } */ /* if(asn1_expect_objtype(map, tag.next, &deeper.size, &tag, 0x31)) { /\* set *\/ */ /* deep.size = 1; */ /* break; */ /* } */ /* if(deeper.size) { */ /* cli_dbgmsg("asn1_parse_mscat: found extra data in tag\n"); */ /* deep.size = 1; */ /* break; */ /* } */ /* while(tag.size) { */ /* /\* FIXME this should be delayed till after the cert is verified *\/ */ /* struct cli_asn1 tagval; */ /* unsigned int tsize, tsize2, shatype; */ /* void *tagc; */ /* int i; */ /* if(asn1_expect_objtype(map, tag.content, &tag.size, &tagval, 0x30)) { */ /* tag.size = 1; */ /* break; */ /* } */ /* tag.content = tagval.next; */ /* tsize = tsize2 = tagval.size; */ /* tagc = tagval.content; */ /* if(asn1_expect_objtype(map, tagval.content, &tsize, &tagval, 0x06)) { */ /* tag.size = 1; */ /* break; */ /* } */ /* i = ms_asn1_get_sha1(map, tagc, tsize2, 0, sha1, &shatype); */ /* if(!i) { */ /* char sha1txt[SHA1_HASH_SIZE*2+1]; */ /* for(i=0;i<SHA1_HASH_SIZE; i++) */ /* sprintf(&sha1txt[i*2], "%02x", sha1[i]); */ /* cli_dbgmsg("asn1_parse_cat: found hash %s (type %s)\n", sha1txt, shatype ? "PE" : "CAB"); */ /* } else if(i==1){ */ /* /\* expect to hit here on CAT_NAMEVALUE_OBJID(1.3.6.1.4.1.311.12.2.1) and CAT_MEMBERINFO_OBJID(.2) *\/ */ /* } else { */ /* tag.size = 1; */ /* cli_dbgmsg("asn1_parse_mscat: bad field in tag value\n"); */ /* break; */ /* } */ /* if(asn1_expect_objtype(map, tagval.next, &tsize, &tagval, 0x31)) { */ /* tag.size = 1; */ /* break; */ /* } */ /* if(tsize) { */ /* tag.size = 1; */ /* cli_dbgmsg("asn1_parse_mscat: extra data in value\n"); */ /* break; */ /* } */ /* } */ /* if(tag.size) { */ /* deep.size = 1; */ /* break; */ /* } */ } if(c.size) return 1; return 0; } int asn1_check_mscat(fmap_t *map, void *start, unsigned int size, const struct cl_engine *engine, uint8_t *computed_sha1) { unsigned int content_size; struct cli_asn1 c; void *content; crtmgr certs; int ret; crtmgr_init(&certs); if(crtmgr_add_roots(&certs)) { /* FIXME: do smthng here */ crtmgr_free(&certs); return CL_CLEAN; } ret = asn1_parse_mscat(map, start, size, &certs, 1, &content, &content_size); crtmgr_free(&certs); if(ret) return CL_VIRUS; /* FIXME */ if(asn1_expect_objtype(map, content, &content_size, &c, 0x30)) return CL_VIRUS; if(asn1_expect_obj(map, &c.content, &c.size, 0x06, lenof(OID_SPC_PE_IMAGE_DATA_OBJID), OID_SPC_PE_IMAGE_DATA_OBJID)) return CL_VIRUS; if(asn1_expect_objtype(map, c.next, &content_size, &c, 0x30)) return CL_VIRUS; if(content_size) { cli_dbgmsg("asn1_check_mscat: extra data in content\n"); return CL_VIRUS; } if(asn1_expect_algo(map, &c.content, &c.size, lenof(OID_sha1), OID_sha1)) return CL_VIRUS; if(asn1_expect_obj(map, &c.content, &c.size, 0x04, SHA1_HASH_SIZE, computed_sha1)) return CL_VIRUS; cli_dbgmsg("asn1_check_mscat: file with valid authenicode signature, whitelisted\n"); return CL_CLEAN; }