diff -urNp a/include/net-snmp/library/cert_util.h b/include/net-snmp/library/cert_util.h --- a/include/net-snmp/library/cert_util.h 2021-06-09 10:55:22.767954797 +0200 +++ b/include/net-snmp/library/cert_util.h 2021-06-09 10:56:36.725272293 +0200 @@ -55,7 +55,8 @@ extern "C" { char *common_name; u_char hash_type; - u_char _pad[3]; /* for future use */ + u_char _pad[1]; /* for future use */ + u_short offset; } netsnmp_cert; /** types */ @@ -100,6 +101,7 @@ extern "C" { NETSNMP_IMPORT netsnmp_cert *netsnmp_cert_find(int what, int where, void *hint); + netsnmp_void_array *netsnmp_certs_find(int what, int where, void *hint); int netsnmp_cert_check_vb_fingerprint(const netsnmp_variable_list *var); diff -urNp a/include/net-snmp/library/dir_utils.h b/include/net-snmp/library/dir_utils.h --- a/include/net-snmp/library/dir_utils.h 2021-06-09 10:55:22.767954797 +0200 +++ b/include/net-snmp/library/dir_utils.h 2021-06-09 10:56:36.726272298 +0200 @@ -53,6 +53,8 @@ extern "C" { #define NETSNMP_DIR_NSFILE 0x0010 /** load stats in netsnmp_file */ #define NETSNMP_DIR_NSFILE_STATS 0x0020 +/** allow files to be indexed more than once */ +#define NETSNMP_DIR_ALLOW_DUPLICATES 0x0040 diff -urNp a/snmplib/cert_util.c b/snmplib/cert_util.c --- a/snmplib/cert_util.c 2021-06-09 10:55:22.785954874 +0200 +++ b/snmplib/cert_util.c 2021-06-09 11:02:43.890848394 +0200 @@ -104,7 +104,7 @@ netsnmp_feature_child_of(tls_fingerprint * bump this value whenever cert index format changes, so indexes * will be regenerated with new format. */ -#define CERT_INDEX_FORMAT 1 +#define CERT_INDEX_FORMAT 2 static netsnmp_container *_certs = NULL; static netsnmp_container *_keys = NULL; @@ -130,6 +130,8 @@ static int _cert_fn_ncompare(netsnmp_ce netsnmp_cert_common *rhs); static void _find_partner(netsnmp_cert *cert, netsnmp_key *key); static netsnmp_cert *_find_issuer(netsnmp_cert *cert); +static netsnmp_void_array *_cert_reduce_subset_first(netsnmp_void_array *matching); +static netsnmp_void_array *_cert_reduce_subset_what(netsnmp_void_array *matching, int what); static netsnmp_void_array *_cert_find_subset_fn(const char *filename, const char *directory); static netsnmp_void_array *_cert_find_subset_sn(const char *subject); @@ -349,6 +351,8 @@ _get_cert_container(const char *use) { netsnmp_container *c; + int rc; + c = netsnmp_container_find("certs:binary_array"); if (NULL == c) { snmp_log(LOG_ERR, "could not create container for %s\n", use); @@ -358,6 +362,8 @@ _get_cert_container(const char *use) c->free_item = (netsnmp_container_obj_func*)_cert_free; c->compare = (netsnmp_container_compare*)_cert_compare; + CONTAINER_SET_OPTIONS(c, CONTAINER_KEY_ALLOW_DUPLICATES, rc); + return c; } @@ -366,6 +372,8 @@ _setup_containers(void) { netsnmp_container *additional_keys; + int rc; + _certs = _get_cert_container("netsnmp certificates"); if (NULL == _certs) return; @@ -380,6 +388,7 @@ _setup_containers(void) additional_keys->container_name = strdup("certs_cn"); additional_keys->free_item = NULL; additional_keys->compare = (netsnmp_container_compare*)_cert_cn_compare; + CONTAINER_SET_OPTIONS(additional_keys, CONTAINER_KEY_ALLOW_DUPLICATES, rc); netsnmp_container_add_index(_certs, additional_keys); /** additional keys: subject name */ @@ -393,6 +402,7 @@ _setup_containers(void) additional_keys->free_item = NULL; additional_keys->compare = (netsnmp_container_compare*)_cert_sn_compare; additional_keys->ncompare = (netsnmp_container_compare*)_cert_sn_ncompare; + CONTAINER_SET_OPTIONS(additional_keys, CONTAINER_KEY_ALLOW_DUPLICATES, rc); netsnmp_container_add_index(_certs, additional_keys); /** additional keys: file name */ @@ -406,6 +416,7 @@ _setup_containers(void) additional_keys->free_item = NULL; additional_keys->compare = (netsnmp_container_compare*)_cert_fn_compare; additional_keys->ncompare = (netsnmp_container_compare*)_cert_fn_ncompare; + CONTAINER_SET_OPTIONS(additional_keys, CONTAINER_KEY_ALLOW_DUPLICATES, rc); netsnmp_container_add_index(_certs, additional_keys); _keys = netsnmp_container_find("cert_keys:binary_array"); @@ -428,9 +439,9 @@ netsnmp_cert_map_container(void) } static netsnmp_cert * -_new_cert(const char *dirname, const char *filename, int certType, - int hashType, const char *fingerprint, const char *common_name, - const char *subject) +_new_cert(const char *dirname, const char *filename, int certType, int offset, + int allowed_uses, int hashType, const char *fingerprint, + const char *common_name, const char *subject) { netsnmp_cert *cert; @@ -450,8 +461,10 @@ _new_cert(const char *dirname, const cha cert->info.dir = strdup(dirname); cert->info.filename = strdup(filename); - cert->info.allowed_uses = NS_CERT_REMOTE_PEER; + /* only the first certificate is allowed to be a remote peer */ + cert->info.allowed_uses = allowed_uses; cert->info.type = certType; + cert->offset = offset; if (fingerprint) { cert->hash_type = hashType; cert->fingerprint = strdup(fingerprint); @@ -888,14 +901,86 @@ _certindex_new( const char *dirname ) * certificate utility functions * */ +static BIO * +netsnmp_open_bio(const char *dir, const char *filename) +{ + BIO *certbio; + char file[SNMP_MAXPATH]; + + DEBUGMSGT(("9:cert:read", "Checking file %s\n", filename)); + + certbio = BIO_new(BIO_s_file()); + if (NULL == certbio) { + snmp_log(LOG_ERR, "error creating BIO\n"); + return NULL; + } + + snprintf(file, sizeof(file),"%s/%s", dir, filename); + if (BIO_read_filename(certbio, file) <=0) { + snmp_log(LOG_ERR, "error reading certificate/key %s into BIO\n", file); + BIO_vfree(certbio); + return NULL; + } + + return certbio; +} + +static void +netsnmp_ocert_parse(netsnmp_cert *cert, X509 *ocert) +{ + int is_ca; + + cert->ocert = ocert; + + /* + * X509_check_ca return codes: + * 0 not a CA + * 1 is a CA + * 2 basicConstraints absent so "maybe" a CA + * 3 basicConstraints absent but self signed V1. + * 4 basicConstraints absent but keyUsage present and keyCertSign asserted. + * 5 outdated Netscape Certificate Type CA extension. + */ + is_ca = X509_check_ca(ocert); + if (1 == is_ca) + cert->info.allowed_uses |= NS_CERT_CA; + + if (NULL == cert->subject) { + cert->subject = X509_NAME_oneline(X509_get_subject_name(ocert), NULL, + 0); + DEBUGMSGT(("9:cert:add:subject", "subject name: %s\n", cert->subject)); + } + + if (NULL == cert->issuer) { + cert->issuer = X509_NAME_oneline(X509_get_issuer_name(ocert), NULL, 0); + if (strcmp(cert->subject, cert->issuer) == 0) { + free(cert->issuer); + cert->issuer = strdup("self-signed"); + } + DEBUGMSGT(("9:cert:add:issuer", "CA issuer: %s\n", cert->issuer)); + } + + if (NULL == cert->fingerprint) { + cert->hash_type = netsnmp_openssl_cert_get_hash_type(ocert); + cert->fingerprint = + netsnmp_openssl_cert_get_fingerprint(ocert, cert->hash_type); + } + + if (NULL == cert->common_name) { + cert->common_name =netsnmp_openssl_cert_get_commonName(ocert, NULL, + NULL); + DEBUGMSGT(("9:cert:add:name","%s\n", cert->common_name)); + } + +} + static X509 * netsnmp_ocert_get(netsnmp_cert *cert) { BIO *certbio; X509 *ocert = NULL; + X509 *ncert = NULL; EVP_PKEY *okey = NULL; - char file[SNMP_MAXPATH]; - int is_ca; if (NULL == cert) return NULL; @@ -912,51 +997,33 @@ netsnmp_ocert_get(netsnmp_cert *cert) } } - DEBUGMSGT(("9:cert:read", "Checking file %s\n", cert->info.filename)); - - certbio = BIO_new(BIO_s_file()); - if (NULL == certbio) { - snmp_log(LOG_ERR, "error creating BIO\n"); - return NULL; - } - - snprintf(file, sizeof(file),"%s/%s", cert->info.dir, cert->info.filename); - if (BIO_read_filename(certbio, file) <=0) { - snmp_log(LOG_ERR, "error reading certificate %s into BIO\n", file); - BIO_vfree(certbio); + certbio = netsnmp_open_bio(cert->info.dir, cert->info.filename); + if (!certbio) { return NULL; } - if (NS_CERT_TYPE_UNKNOWN == cert->info.type) { - char *pos = strrchr(cert->info.filename, '.'); - if (NULL == pos) - return NULL; - cert->info.type = _cert_ext_type(++pos); - netsnmp_assert(cert->info.type != NS_CERT_TYPE_UNKNOWN); - } - switch (cert->info.type) { case NS_CERT_TYPE_DER: + (void)BIO_seek(certbio, cert->offset); ocert = d2i_X509_bio(certbio,NULL); /* DER/ASN1 */ if (NULL != ocert) break; - (void)BIO_reset(certbio); /* Check for PEM if DER didn't work */ /* FALLTHROUGH */ case NS_CERT_TYPE_PEM: - ocert = PEM_read_bio_X509_AUX(certbio, NULL, NULL, NULL); + (void)BIO_seek(certbio, cert->offset); + ocert = ncert = PEM_read_bio_X509_AUX(certbio, NULL, NULL, NULL); if (NULL == ocert) break; if (NS_CERT_TYPE_DER == cert->info.type) { DEBUGMSGT(("9:cert:read", "Changing type from DER to PEM\n")); cert->info.type = NS_CERT_TYPE_PEM; } - /** check for private key too */ - if (NULL == cert->key) { - (void)BIO_reset(certbio); - okey = PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL); + /** check for private key too, but only if we're the first certificate */ + if (0 == cert->offset && NULL == cert->key) { + okey = PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL); if (NULL != okey) { netsnmp_key *key; DEBUGMSGT(("cert:read:key", "found key with cert in %s\n", @@ -983,7 +1050,7 @@ netsnmp_ocert_get(netsnmp_cert *cert) break; #ifdef CERT_PKCS12_SUPPORT_MAYBE_LATER case NS_CERT_TYPE_PKCS12: - (void)BIO_reset(certbio); + (void)BIO_seek(certbio, cert->offset); PKCS12 *p12 = d2i_PKCS12_bio(certbio, NULL); if ( (NULL != p12) && (PKCS12_verify_mac(p12, "", 0) || PKCS12_verify_mac(p12, NULL, 0))) @@ -1003,46 +1070,7 @@ netsnmp_ocert_get(netsnmp_cert *cert) return NULL; } - cert->ocert = ocert; - /* - * X509_check_ca return codes: - * 0 not a CA - * 1 is a CA - * 2 basicConstraints absent so "maybe" a CA - * 3 basicConstraints absent but self signed V1. - * 4 basicConstraints absent but keyUsage present and keyCertSign asserted. - * 5 outdated Netscape Certificate Type CA extension. - */ - is_ca = X509_check_ca(ocert); - if (1 == is_ca) - cert->info.allowed_uses |= NS_CERT_CA; - - if (NULL == cert->subject) { - cert->subject = X509_NAME_oneline(X509_get_subject_name(ocert), NULL, - 0); - DEBUGMSGT(("9:cert:add:subject", "subject name: %s\n", cert->subject)); - } - - if (NULL == cert->issuer) { - cert->issuer = X509_NAME_oneline(X509_get_issuer_name(ocert), NULL, 0); - if (strcmp(cert->subject, cert->issuer) == 0) { - free(cert->issuer); - cert->issuer = strdup("self-signed"); - } - DEBUGMSGT(("9:cert:add:issuer", "CA issuer: %s\n", cert->issuer)); - } - - if (NULL == cert->fingerprint) { - cert->hash_type = netsnmp_openssl_cert_get_hash_type(ocert); - cert->fingerprint = - netsnmp_openssl_cert_get_fingerprint(ocert, cert->hash_type); - } - - if (NULL == cert->common_name) { - cert->common_name =netsnmp_openssl_cert_get_commonName(ocert, NULL, - NULL); - DEBUGMSGT(("9:cert:add:name","%s\n", cert->common_name)); - } + netsnmp_ocert_parse(cert, ocert); return ocert; } @@ -1052,7 +1080,6 @@ netsnmp_okey_get(netsnmp_key *key) { BIO *keybio; EVP_PKEY *okey; - char file[SNMP_MAXPATH]; if (NULL == key) return NULL; @@ -1060,19 +1087,8 @@ netsnmp_okey_get(netsnmp_key *key) if (key->okey) return key->okey; - snprintf(file, sizeof(file),"%s/%s", key->info.dir, key->info.filename); - DEBUGMSGT(("cert:key:read", "Checking file %s\n", key->info.filename)); - - keybio = BIO_new(BIO_s_file()); - if (NULL == keybio) { - snmp_log(LOG_ERR, "error creating BIO\n"); - return NULL; - } - - if (BIO_read_filename(keybio, file) <=0) { - snmp_log(LOG_ERR, "error reading certificate %s into BIO\n", - key->info.filename); - BIO_vfree(keybio); + keybio = netsnmp_open_bio(key->info.dir, key->info.filename); + if (!keybio) { return NULL; } @@ -1158,7 +1174,7 @@ netsnmp_cert_load_x509(netsnmp_cert *cer cert->issuer_cert = _find_issuer(cert); if (NULL == cert->issuer_cert) { DEBUGMSGT(("cert:load:warn", - "couldn't load CA chain for cert %s\n", + "couldn't load full CA chain for cert %s\n", cert->info.filename)); rc = CERT_LOAD_PARTIAL; break; @@ -1167,7 +1183,7 @@ netsnmp_cert_load_x509(netsnmp_cert *cer /** get issuer ocert */ if ((NULL == cert->issuer_cert->ocert) && (netsnmp_ocert_get(cert->issuer_cert) == NULL)) { - DEBUGMSGT(("cert:load:warn", "couldn't load cert chain for %s\n", + DEBUGMSGT(("cert:load:warn", "couldn't load full cert chain for %s\n", cert->info.filename)); rc = CERT_LOAD_PARTIAL; break; @@ -1188,7 +1204,7 @@ _find_partner(netsnmp_cert *cert, netsnm return; } - if(key) { + if (key) { if (key->cert) { DEBUGMSGT(("cert:partner", "key already has partner\n")); return; @@ -1201,7 +1217,8 @@ _find_partner(netsnmp_cert *cert, netsnm return; *pos = 0; - matching = _cert_find_subset_fn( filename, key->info.dir ); + matching = _cert_reduce_subset_first(_cert_find_subset_fn( filename, + key->info.dir )); if (!matching) return; if (1 == matching->size) { @@ -1221,7 +1238,7 @@ _find_partner(netsnmp_cert *cert, netsnm DEBUGMSGT(("cert:partner", "%s matches multiple certs\n", key->info.filename)); } - else if(cert) { + else if (cert) { if (cert->key) { DEBUGMSGT(("cert:partner", "cert already has partner\n")); return; @@ -1259,76 +1276,189 @@ _find_partner(netsnmp_cert *cert, netsnm } } +static netsnmp_key * +_add_key(EVP_PKEY *okey, const char* dirname, const char* filename, FILE *index) +{ + netsnmp_key *key; + + key = _new_key(dirname, filename); + if (NULL == key) { + return NULL; + } + + key->okey = okey; + + if (-1 == CONTAINER_INSERT(_keys, key)) { + DEBUGMSGT(("cert:key:file:add:err", + "error inserting key into container\n")); + netsnmp_key_free(key); + key = NULL; + } + if (index) { + fprintf(index, "k:%s\n", filename); + } + + return key; +} + +static netsnmp_cert * +_add_cert(X509 *ocert, const char* dirname, const char* filename, int type, int offset, + int allowed_uses, FILE *index) +{ + netsnmp_cert *cert; + + cert = _new_cert(dirname, filename, type, offset, + allowed_uses, -1, NULL, NULL, NULL); + if (NULL == cert) + return NULL; + + netsnmp_ocert_parse(cert, ocert); + + if (-1 == CONTAINER_INSERT(_certs, cert)) { + DEBUGMSGT(("cert:file:add:err", + "error inserting cert into container\n")); + netsnmp_cert_free(cert); + return NULL; + } + + if (index) { + /** filename = NAME_MAX = 255 */ + /** fingerprint max = 64*3=192 for sha512 */ + /** common name / CN = 64 */ + if (cert) + fprintf(index, "c:%s %d %d %d %d %s '%s' '%s'\n", filename, + cert->info.type, cert->offset, cert->info.allowed_uses, + cert->hash_type, cert->fingerprint, + cert->common_name, cert->subject); + } + + return cert; +} + static int _add_certfile(const char* dirname, const char* filename, FILE *index) { - X509 *ocert; - EVP_PKEY *okey; + BIO *certbio; + X509 *ocert = NULL; + X509 *ncert; + EVP_PKEY *okey = NULL; netsnmp_cert *cert = NULL; netsnmp_key *key = NULL; char certfile[SNMP_MAXPATH]; int type; + int offset = 0; if (((const void*)NULL == dirname) || (NULL == filename)) return -1; type = _type_from_filename(filename); - netsnmp_assert(type != NS_CERT_TYPE_UNKNOWN); + if (type == NS_CERT_TYPE_UNKNOWN) { + snmp_log(LOG_ERR, "certificate file '%s' type not recognised, ignoring\n", filename); + return -1; + } - snprintf(certfile, sizeof(certfile),"%s/%s", dirname, filename); + certbio = netsnmp_open_bio(dirname, filename); + if (!certbio) { + return -1; + } - DEBUGMSGT(("9:cert:file:add", "Checking file: %s (type %d)\n", filename, - type)); + switch (type) { - if (NS_CERT_TYPE_KEY == type) { - key = _new_key(dirname, filename); - if (NULL == key) - return -1; - okey = netsnmp_okey_get(key); - if (NULL == okey) { - netsnmp_key_free(key); - return -1; - } - key->okey = okey; - if (-1 == CONTAINER_INSERT(_keys, key)) { - DEBUGMSGT(("cert:key:file:add:err", - "error inserting key into container\n")); - netsnmp_key_free(key); - key = NULL; - } - } - else { - cert = _new_cert(dirname, filename, type, -1, NULL, NULL, NULL); - if (NULL == cert) - return -1; - ocert = netsnmp_ocert_get(cert); - if (NULL == ocert) { - netsnmp_cert_free(cert); - return -1; - } - cert->ocert = ocert; - if (-1 == CONTAINER_INSERT(_certs, cert)) { - DEBUGMSGT(("cert:file:add:err", - "error inserting cert into container\n")); - netsnmp_cert_free(cert); - cert = NULL; - } - } - if ((NULL == cert) && (NULL == key)) { - DEBUGMSGT(("cert:file:add:failure", "for %s\n", certfile)); - return -1; + case NS_CERT_TYPE_KEY: + + okey = PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL); + if (NULL == okey) + snmp_log(LOG_ERR, "error parsing key file %s\n", + key->info.filename); + else { + key = _add_key(okey, dirname, filename, index); + if (NULL == key) { + EVP_PKEY_free(okey); + okey = NULL; + } + } + break; + + case NS_CERT_TYPE_DER: + + ocert = d2i_X509_bio(certbio, NULL); /* DER/ASN1 */ + if (NULL != ocert) { + if (!_add_cert(ocert, dirname, filename, type, 0, + NS_CERT_REMOTE_PEER, index)) { + X509_free(ocert); + ocert = NULL; + } + break; + } + (void)BIO_reset(certbio); + /* Check for PEM if DER didn't work */ + /* FALLTHROUGH */ + + case NS_CERT_TYPE_PEM: + + if (NS_CERT_TYPE_DER == type) { + DEBUGMSGT(("9:cert:read", "Changing type from DER to PEM\n")); + type = NS_CERT_TYPE_PEM; + } + + /* read the private key first so we can record this in the index */ + okey = PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL); + + (void)BIO_reset(certbio); + + /* certs are read after the key */ + ocert = ncert = PEM_read_bio_X509_AUX(certbio, NULL, NULL, NULL); + if (NULL != ocert) { + cert = _add_cert(ncert, dirname, filename, type, 0, + okey ? NS_CERT_IDENTITY | NS_CERT_REMOTE_PEER : + NS_CERT_REMOTE_PEER, index); + if (NULL == cert) { + X509_free(ocert); + ocert = ncert = NULL; + } + } + while (NULL != ncert) { + offset = BIO_tell(certbio); + ncert = PEM_read_bio_X509_AUX(certbio, NULL, NULL, NULL); + if (ncert) { + if (NULL == _add_cert(ncert, dirname, filename, type, offset, 0, index)) { + X509_free(ncert); + ncert = NULL; + } + } + } + + if (NULL != okey) { + DEBUGMSGT(("cert:read:key", "found key with cert in %s\n", + cert->info.filename)); + key = _add_key(okey, dirname, filename, NULL); + if (NULL != key) { + DEBUGMSGT(("cert:read:partner", "%s match found!\n", + cert->info.filename)); + key->cert = cert; + cert->key = key; + } + else { + EVP_PKEY_free(okey); + okey = NULL; + } + } + + break; + +#ifdef CERT_PKCS12_SUPPORT_MAYBE_LATER + case NS_CERT_TYPE_PKCS12: +#endif + + default: + break; } - if (index) { - /** filename = NAME_MAX = 255 */ - /** fingerprint max = 64*3=192 for sha512 */ - /** common name / CN = 64 */ - if (cert) - fprintf(index, "c:%s %d %d %s '%s' '%s'\n", filename, - cert->info.type, cert->hash_type, cert->fingerprint, - cert->common_name, cert->subject); - else if (key) - fprintf(index, "k:%s\n", filename); + BIO_vfree(certbio); + + if ((NULL == ocert) && (NULL == okey)) { + snmp_log(LOG_ERR, "certificate file '%s' contained neither certificate nor key, ignoring\n", certfile); + return -1; } return 0; @@ -1342,8 +1472,10 @@ _cert_read_index(const char *dirname, st struct stat idx_stat; char tmpstr[SNMP_MAXPATH + 5], filename[NAME_MAX]; char fingerprint[EVP_MAX_MD_SIZE*3], common_name[64+1], type_str[15]; - char subject[SNMP_MAXBUF_SMALL], hash_str[15]; - int count = 0, type, hash, version; + char subject[SNMP_MAXBUF_SMALL], hash_str[15], offset_str[15]; + char allowed_uses_str[15]; + ssize_t offset; + int count = 0, type, allowed_uses, hash, version; netsnmp_cert *cert; netsnmp_key *key; netsnmp_container *newer, *found; @@ -1386,7 +1518,8 @@ _cert_read_index(const char *dirname, st (netsnmp_directory_filter*) _time_filter,(void*)&idx_stat, NETSNMP_DIR_NSFILE | - NETSNMP_DIR_NSFILE_STATS); + NETSNMP_DIR_NSFILE_STATS | + NETSNMP_DIR_ALLOW_DUPLICATES); if (newer) { DEBUGMSGT(("cert:index:parse", "Index outdated; files modified\n")); CONTAINER_FREE_ALL(newer, NULL); @@ -1430,6 +1563,8 @@ _cert_read_index(const char *dirname, st pos = &tmpstr[2]; if ((NULL == (pos=copy_nword(pos, filename, sizeof(filename)))) || (NULL == (pos=copy_nword(pos, type_str, sizeof(type_str)))) || + (NULL == (pos=copy_nword(pos, offset_str, sizeof(offset_str)))) || + (NULL == (pos=copy_nword(pos, allowed_uses_str, sizeof(allowed_uses_str)))) || (NULL == (pos=copy_nword(pos, hash_str, sizeof(hash_str)))) || (NULL == (pos=copy_nword(pos, fingerprint, sizeof(fingerprint)))) || @@ -1442,9 +1577,11 @@ _cert_read_index(const char *dirname, st break; } type = atoi(type_str); + offset = atoi(offset_str); + allowed_uses = atoi(allowed_uses_str); hash = atoi(hash_str); - cert = (void*)_new_cert(dirname, filename, type, hash, fingerprint, - common_name, subject); + cert = _new_cert(dirname, filename, type, offset, allowed_uses, hash, + fingerprint, common_name, subject); if (cert && 0 == CONTAINER_INSERT(found, cert)) ++count; else { @@ -1549,7 +1686,8 @@ _add_certdir(const char *dirname) (netsnmp_directory_filter*) &_cert_cert_filter, NULL, NETSNMP_DIR_RELATIVE_PATH | - NETSNMP_DIR_EMPTY_OK ); + NETSNMP_DIR_EMPTY_OK | + NETSNMP_DIR_ALLOW_DUPLICATES); if (NULL == cert_container) { DEBUGMSGT(("cert:index:dir", "error creating container for cert files\n")); @@ -1637,7 +1775,7 @@ _cert_print(netsnmp_cert *c, void *conte if (NULL == c) return; - DEBUGMSGT(("cert:dump", "cert %s in %s\n", c->info.filename, c->info.dir)); + DEBUGMSGT(("cert:dump", "cert %s in %s at offset %d\n", c->info.filename, c->info.dir, c->offset)); DEBUGMSGT(("cert:dump", " type %d flags 0x%x (%s)\n", c->info.type, c->info.allowed_uses, _mode_str(c->info.allowed_uses))); @@ -1841,7 +1979,8 @@ netsnmp_cert_find(int what, int where, v netsnmp_void_array *matching; DEBUGMSGT(("cert:find:params", " hint = %s\n", (char *)hint)); - matching = _cert_find_subset_fn( filename, NULL ); + matching = _cert_reduce_subset_what(_cert_find_subset_fn( + filename, NULL ), what); if (!matching) return NULL; if (1 == matching->size) @@ -1887,6 +2026,32 @@ netsnmp_cert_find(int what, int where, v return result; } +netsnmp_void_array * +netsnmp_certs_find(int what, int where, void *hint) +{ + + DEBUGMSGT(("certs:find:params", "looking for %s(%d) in %s(0x%x), hint %p\n", + _mode_str(what), what, _where_str(where), where, hint)); + + if (NS_CERTKEY_FILE == where) { + /** hint == filename */ + char *filename = (char*)hint; + netsnmp_void_array *matching; + + DEBUGMSGT(("cert:find:params", " hint = %s\n", (char *)hint)); + matching = _cert_reduce_subset_what(_cert_find_subset_fn( + filename, NULL ), what); + + return matching; + } /* where = NS_CERTKEY_FILE */ + else { /* unknown location */ + + DEBUGMSGT(("certs:find:err", "unhandled location %d for %d\n", where, + what)); + return NULL; + } +} + #ifndef NETSNMP_FEATURE_REMOVE_CERT_FINGERPRINTS int netsnmp_cert_check_vb_fingerprint(const netsnmp_variable_list *var) @@ -2284,6 +2449,124 @@ _reduce_subset_dir(netsnmp_void_array *m } } +/* + * reduce subset by eliminating any certificates that are not the + * first certficate in a file. This allows us to ignore certificate + * chains when testing for specific certificates, and to match keys + * to the first certificate only. + */ +static netsnmp_void_array * +_cert_reduce_subset_first(netsnmp_void_array *matching) +{ + netsnmp_cert *cc; + int i = 0, j, newsize; + + if ((NULL == matching)) + return matching; + + newsize = matching->size; + + for( ; i < matching->size; ) { + /* + * if we've shifted matches down we'll hit a NULL entry before + * we hit the end of the array. + */ + if (NULL == matching->array[i]) + break; + /* + * skip over valid matches. The first entry has an offset of zero. + */ + cc = (netsnmp_cert*)matching->array[i]; + if (0 == cc->offset) { + ++i; + continue; + } + /* + * shrink array by shifting everything down a spot. Might not be + * the most efficient soloution, but this is just happening at + * startup and hopefully most certs won't have common prefixes. + */ + --newsize; + for ( j=i; j < newsize; ++j ) + matching->array[j] = matching->array[j+1]; + matching->array[j] = NULL; + /** no ++i; just shifted down, need to look at same position again */ + } + /* + * if we shifted, set the new size + */ + if (newsize != matching->size) { + DEBUGMSGT(("9:cert:subset:first", "shrank from %" NETSNMP_PRIz "d to %d\n", + matching->size, newsize)); + matching->size = newsize; + } + + if (0 == matching->size) { + free(matching->array); + SNMP_FREE(matching); + } + + return matching; +} + +/* + * reduce subset by eliminating any certificates that do not match + * purpose specified. + */ +static netsnmp_void_array * +_cert_reduce_subset_what(netsnmp_void_array *matching, int what) +{ + netsnmp_cert_common *cc; + int i = 0, j, newsize; + + if ((NULL == matching)) + return matching; + + newsize = matching->size; + + for( ; i < matching->size; ) { + /* + * if we've shifted matches down we'll hit a NULL entry before + * we hit the end of the array. + */ + if (NULL == matching->array[i]) + break; + /* + * skip over valid matches. The first entry has an offset of zero. + */ + cc = (netsnmp_cert_common *)matching->array[i]; + if ((cc->allowed_uses & what)) { + ++i; + continue; + } + /* + * shrink array by shifting everything down a spot. Might not be + * the most efficient soloution, but this is just happening at + * startup and hopefully most certs won't have common prefixes. + */ + --newsize; + for ( j=i; j < newsize; ++j ) + matching->array[j] = matching->array[j+1]; + matching->array[j] = NULL; + /** no ++i; just shifted down, need to look at same position again */ + } + /* + * if we shifted, set the new size + */ + if (newsize != matching->size) { + DEBUGMSGT(("9:cert:subset:what", "shrank from %" NETSNMP_PRIz "d to %d\n", + matching->size, newsize)); + matching->size = newsize; + } + + if (0 == matching->size) { + free(matching->array); + SNMP_FREE(matching); + } + + return matching; +} + static netsnmp_void_array * _cert_find_subset_common(const char *filename, netsnmp_container *container) { diff -urNp a/snmplib/transports/snmpTLSBaseDomain.c b/snmplib/transports/snmpTLSBaseDomain.c --- a/snmplib/transports/snmpTLSBaseDomain.c 2021-06-09 10:55:22.791954900 +0200 +++ b/snmplib/transports/snmpTLSBaseDomain.c 2021-06-09 10:56:36.727272302 +0200 @@ -59,7 +59,7 @@ int openssl_local_index; /* this is called during negotiation */ int verify_callback(int ok, X509_STORE_CTX *ctx) { int err, depth; - char buf[1024], *fingerprint; + char subject[SNMP_MAXBUF_MEDIUM], issuer[SNMP_MAXBUF_MEDIUM], *fingerprint; X509 *thecert; netsnmp_cert *cert; _netsnmp_verify_info *verify_info; @@ -71,10 +71,12 @@ int verify_callback(int ok, X509_STORE_C /* things to do: */ - X509_NAME_oneline(X509_get_subject_name(thecert), buf, sizeof(buf)); + X509_NAME_oneline(X509_get_subject_name(thecert), subject, sizeof(subject)); + X509_NAME_oneline(X509_get_issuer_name(thecert), issuer, sizeof(issuer)); fingerprint = netsnmp_openssl_cert_get_fingerprint(thecert, -1); - DEBUGMSGTL(("tls_x509:verify", "Cert: %s\n", buf)); - DEBUGMSGTL(("tls_x509:verify", " fp: %s\n", fingerprint ? + DEBUGMSGTL(("tls_x509:verify", " subject: %s\n", subject)); + DEBUGMSGTL(("tls_x509:verify", " issuer: %s\n", issuer)); + DEBUGMSGTL(("tls_x509:verify", " fp: %s\n", fingerprint ? fingerprint : "unknown")); ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); @@ -109,7 +111,7 @@ int verify_callback(int ok, X509_STORE_C } else { DEBUGMSGTL(("tls_x509:verify", " no matching fp found\n")); /* log where we are and why called */ - snmp_log(LOG_ERR, "tls verification failure: ok=%d ctx=%p depth=%d err=%i:%s\n", ok, ctx, depth, err, X509_verify_cert_error_string(err)); + snmp_log(LOG_ERR, "tls verification failure: ok=%d ctx=%p depth=%d fp=%s subject='%s' issuer='%s' err=%i:%s\n", ok, ctx, depth, fingerprint, subject, issuer, err, X509_verify_cert_error_string(err)); SNMP_FREE(fingerprint); return 0; } @@ -425,23 +427,50 @@ netsnmp_tlsbase_extract_security_name(SS int _trust_this_cert(SSL_CTX *the_ctx, char *certspec) { netsnmp_cert *trustcert; + netsnmp_cert *candidate; + netsnmp_void_array *matching = NULL; + + int i; DEBUGMSGTL(("sslctx_client", "Trying to load a trusted certificate: %s\n", certspec)); /* load this identifier into the trust chain */ trustcert = netsnmp_cert_find(NS_CERT_CA, - NS_CERTKEY_MULTIPLE, + NS_CERTKEY_FINGERPRINT, certspec); + + /* loop through all CA certs in the given files */ + if (!trustcert) { + matching = netsnmp_certs_find(NS_CERT_CA, + NS_CERTKEY_FILE, + certspec); + for (i = 0; (matching) && (i < matching->size); ++i) { + candidate = (netsnmp_cert*)matching->array[i]; + if (netsnmp_cert_trust(the_ctx, candidate) != SNMPERR_SUCCESS) { + free(matching->array); + free(matching); + LOGANDDIE("failed to load trust certificate"); + } + } /** matching loop */ + + if (matching) { + free(matching->array); + free(matching); + return 1; + } + } + + /* fall back to trusting the remote peer certificate */ if (!trustcert) trustcert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_MULTIPLE, certspec); if (!trustcert) LOGANDDIE("failed to find requested certificate to trust"); - + /* Add the certificate to the context */ - if (netsnmp_cert_trust_ca(the_ctx, trustcert) != SNMPERR_SUCCESS) + if (netsnmp_cert_trust(the_ctx, trustcert) != SNMPERR_SUCCESS) LOGANDDIE("failed to load trust certificate"); return 1; @@ -481,7 +510,7 @@ _sslctx_common_setup(SSL_CTX *the_ctx, _ NETSNMP_DS_LIB_X509_CRL_FILE); if (NULL != crlFile) { cert_store = SSL_CTX_get_cert_store(the_ctx); - DEBUGMSGTL(("sslctx_client", "loading CRL: %s\n", crlFile)); + DEBUGMSGTL(("sslctx_common", "loading CRL: %s\n", crlFile)); if (!cert_store) LOGANDDIE("failed to find certificate store"); if (!(lookup = X509_STORE_add_lookup(cert_store, X509_LOOKUP_file()))) @@ -546,13 +575,19 @@ sslctx_client_setup(const SSL_METHOD *me id_cert->key->info.filename)); if (SSL_CTX_use_certificate(the_ctx, id_cert->ocert) <= 0) - LOGANDDIE("failed to set the certificate to use"); + LOGANDDIE("failed to set the client certificate to use"); if (SSL_CTX_use_PrivateKey(the_ctx, id_cert->key->okey) <= 0) - LOGANDDIE("failed to set the private key to use"); + LOGANDDIE("failed to set the client private key to use"); if (!SSL_CTX_check_private_key(the_ctx)) - LOGANDDIE("public and private keys incompatible"); + LOGANDDIE("client public and private keys incompatible"); + + while (id_cert->issuer_cert) { + id_cert = id_cert->issuer_cert; + if (!SSL_CTX_add_extra_chain_cert(the_ctx, id_cert->ocert)) + LOGANDDIE("failed to add intermediate client certificate"); + } if (tlsbase->their_identity) peer_cert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, @@ -566,11 +601,11 @@ sslctx_client_setup(const SSL_METHOD *me peer_cert ? peer_cert->info.filename : "none")); /* Trust the expected certificate */ - if (netsnmp_cert_trust_ca(the_ctx, peer_cert) != SNMPERR_SUCCESS) + if (netsnmp_cert_trust(the_ctx, peer_cert) != SNMPERR_SUCCESS) LOGANDDIE ("failed to set verify paths"); } - /* trust a certificate (possibly a CA) aspecifically passed in */ + /* trust a certificate (possibly a CA) specifically passed in */ if (tlsbase->trust_cert) { if (!_trust_this_cert(the_ctx, tlsbase->trust_cert)) return 0; @@ -589,7 +624,7 @@ sslctx_server_setup(const SSL_METHOD *me /* setting up for ssl */ SSL_CTX *the_ctx = SSL_CTX_new(NETSNMP_REMOVE_CONST(SSL_METHOD *, method)); if (!the_ctx) { - LOGANDDIE("can't create a new context"); + LOGANDDIE("can't create a new server context"); } id_cert = netsnmp_cert_find(NS_CERT_IDENTITY, NS_CERTKEY_DEFAULT, NULL); @@ -597,7 +632,7 @@ sslctx_server_setup(const SSL_METHOD *me LOGANDDIE ("error finding server identity keys"); if (!id_cert->key || !id_cert->key->okey) - LOGANDDIE("failed to load private key"); + LOGANDDIE("failed to load server private key"); DEBUGMSGTL(("sslctx_server", "using public key: %s\n", id_cert->info.filename)); @@ -605,13 +640,19 @@ sslctx_server_setup(const SSL_METHOD *me id_cert->key->info.filename)); if (SSL_CTX_use_certificate(the_ctx, id_cert->ocert) <= 0) - LOGANDDIE("failed to set the certificate to use"); + LOGANDDIE("failed to set the server certificate to use"); if (SSL_CTX_use_PrivateKey(the_ctx, id_cert->key->okey) <= 0) - LOGANDDIE("failed to set the private key to use"); + LOGANDDIE("failed to set the server private key to use"); if (!SSL_CTX_check_private_key(the_ctx)) - LOGANDDIE("public and private keys incompatible"); + LOGANDDIE("server public and private keys incompatible"); + + while (id_cert->issuer_cert) { + id_cert = id_cert->issuer_cert; + if (!SSL_CTX_add_extra_chain_cert(the_ctx, id_cert->ocert)) + LOGANDDIE("failed to add intermediate server certificate"); + } SSL_CTX_set_read_ahead(the_ctx, 1); /* XXX: DTLS only? */