| #include <net-snmp/net-snmp-config.h> |
| |
| #if defined(NETSNMP_USE_OPENSSL) && defined(HAVE_LIBSSL) |
| |
| #if HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #include <ctype.h> |
| |
| #if HAVE_STRING_H |
| #include <string.h> |
| #else |
| #include <strings.h> |
| #endif |
| |
| #if HAVE_SYS_STAT_H |
| # include <sys/stat.h> |
| #endif |
| #if HAVE_DIRENT_H |
| # include <dirent.h> |
| # define NAMLEN(dirent) strlen((dirent)->d_name) |
| #else |
| # define dirent direct |
| # define NAMLEN(dirent) (dirent)->d_namlen |
| # if HAVE_SYS_NDIR_H |
| # include <sys/ndir.h> |
| # endif |
| # if HAVE_SYS_DIR_H |
| # include <sys/dir.h> |
| # endif |
| # if HAVE_NDIR_H |
| # include <ndir.h> |
| # endif |
| #endif |
| |
| #if HAVE_DMALLOC_H |
| #include <dmalloc.h> |
| #endif |
| |
| #include <net-snmp/types.h> |
| #include <net-snmp/output_api.h> |
| #include <net-snmp/config_api.h> |
| |
| #include <net-snmp/library/snmp_assert.h> |
| #include <net-snmp/library/snmp_transport.h> |
| #include <net-snmp/library/system.h> |
| #include <net-snmp/library/tools.h> |
| #include <net-snmp/library/container.h> |
| #include <net-snmp/library/data_list.h> |
| #include <net-snmp/library/file_utils.h> |
| #include <net-snmp/library/dir_utils.h> |
| #include <net-snmp/library/read_config.h> |
| |
| #include <openssl/ssl.h> |
| #include <openssl/err.h> |
| #include <openssl/x509v3.h> |
| #include <net-snmp/library/cert_util.h> |
| #include <net-snmp/library/snmp_openssl.h> |
| |
| #ifndef NAME_MAX |
| #define NAME_MAX 255 |
| #endif |
| |
| /* |
| * bump this value whenever cert index format changes, so indexes |
| * will be regenerated with new format. |
| */ |
| #define CERT_INDEX_FORMAT 1 |
| |
| static netsnmp_container *_certs = NULL; |
| static netsnmp_container *_keys = NULL; |
| static netsnmp_container *_maps = NULL; |
| static netsnmp_container *_tlstmParams = NULL; |
| static netsnmp_container *_tlstmAddr = NULL; |
| static struct snmp_enum_list *_certindexes = NULL; |
| |
| static netsnmp_container *_trusted_certs = NULL; |
| |
| static void _setup_containers(void); |
| |
| static void _cert_indexes_load(void); |
| static void _cert_free(netsnmp_cert *cert, void *context); |
| static void _key_free(netsnmp_key *key, void *context); |
| static int _cert_compare(netsnmp_cert *lhs, netsnmp_cert *rhs); |
| static int _cert_sn_compare(netsnmp_cert *lhs, netsnmp_cert *rhs); |
| static int _cert_sn_ncompare(netsnmp_cert *lhs, netsnmp_cert *rhs); |
| static int _cert_cn_compare(netsnmp_cert *lhs, netsnmp_cert *rhs); |
| static int _cert_fn_compare(netsnmp_cert_common *lhs, |
| netsnmp_cert_common *rhs); |
| static int _cert_fn_ncompare(netsnmp_cert_common *lhs, |
| 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_find_subset_fn(const char *filename, |
| const char *directory); |
| static netsnmp_void_array *_cert_find_subset_sn(const char *subject); |
| static netsnmp_void_array *_key_find_subset(const char *filename); |
| static netsnmp_cert *_cert_find_fp(const char *fingerprint); |
| static char *_find_tlstmParams_fingerprint(const char *param); |
| static char *_find_tlstmAddr_fingerprint(const char *name); |
| static const char *_mode_str(u_char mode); |
| static const char *_where_str(u_int what); |
| void netsnmp_cert_dump_all(void); |
| |
| int netsnmp_cert_load_x509(netsnmp_cert *cert); |
| |
| void netsnmp_cert_free(netsnmp_cert *cert); |
| void netsnmp_key_free(netsnmp_key *key); |
| |
| static int _certindex_add( const char *dirname, int i ); |
| |
| static int _time_filter(netsnmp_file *f, struct stat *idx); |
| |
| static void _init_tlstmCertToTSN(void); |
| #define TRUSTCERT_CONFIG_TOKEN "trustCert" |
| static void _parse_trustcert(const char *token, char *line); |
| |
| static void _init_tlstmParams(void); |
| static void _init_tlstmAddr(void); |
| |
| /** mode descriptions should match up with header */ |
| static const char _modes[][256] = |
| { |
| "none", |
| "identity", |
| "remote_peer", |
| "identity+remote_peer", |
| "reserved1", |
| "reserved1+identity", |
| "reserved1+remote_peer", |
| "reserved1+identity+remote_peer", |
| "CA", |
| "CA+identity", |
| "CA+remote_peer", |
| "CA+identity+remote_peer", |
| "CA+reserved1", |
| "CA+reserved1+identity", |
| "CA+reserved1+remote_peer", |
| "CA+reserved1+identity+remote_peer", |
| }; |
| |
| /* ##################################################################### |
| * |
| * init and shutdown functions |
| * |
| */ |
| |
| void |
| _netsnmp_release_trustcerts(void) |
| { |
| if (NULL != _trusted_certs) { |
| CONTAINER_FREE_ALL(_trusted_certs, NULL); |
| CONTAINER_FREE(_trusted_certs); |
| _trusted_certs = NULL; |
| } |
| } |
| |
| void |
| _setup_trusted_certs(void) |
| { |
| _trusted_certs = netsnmp_container_find("trusted_certs:fifo"); |
| if (NULL == _trusted_certs) { |
| snmp_log(LOG_ERR, "could not create container for trusted certs\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| _trusted_certs->container_name = strdup("trusted certificates"); |
| _trusted_certs->free_item = (netsnmp_container_obj_func*) free; |
| _trusted_certs->compare = (netsnmp_container_compare*) strcmp; |
| } |
| |
| /* |
| * secname mapping for servers. |
| */ |
| void |
| netsnmp_certs_agent_init(void) |
| { |
| _init_tlstmCertToTSN(); |
| _init_tlstmParams(); |
| _init_tlstmAddr(); |
| } |
| |
| void |
| netsnmp_certs_init(void) |
| { |
| const char *trustCert_help = TRUSTCERT_CONFIG_TOKEN |
| " FINGERPRINT|FILENAME"; |
| |
| register_config_handler("snmp", TRUSTCERT_CONFIG_TOKEN, |
| _parse_trustcert, _netsnmp_release_trustcerts, |
| trustCert_help); |
| _setup_containers(); |
| |
| /** add certificate type mapping */ |
| se_add_pair_to_slist("cert_types", strdup("pem"), NS_CERT_TYPE_PEM); |
| se_add_pair_to_slist("cert_types", strdup("crt"), NS_CERT_TYPE_DER); |
| se_add_pair_to_slist("cert_types", strdup("cer"), NS_CERT_TYPE_DER); |
| se_add_pair_to_slist("cert_types", strdup("cert"), NS_CERT_TYPE_DER); |
| se_add_pair_to_slist("cert_types", strdup("der"), NS_CERT_TYPE_DER); |
| se_add_pair_to_slist("cert_types", strdup("key"), NS_CERT_TYPE_KEY); |
| se_add_pair_to_slist("cert_types", strdup("private"), NS_CERT_TYPE_KEY); |
| |
| /** hash algs */ |
| se_add_pair_to_slist("cert_hash_alg", strdup("sha1"), NS_HASH_SHA1); |
| se_add_pair_to_slist("cert_hash_alg", strdup("md5"), NS_HASH_MD5); |
| se_add_pair_to_slist("cert_hash_alg", strdup("sha224"), NS_HASH_SHA224); |
| se_add_pair_to_slist("cert_hash_alg", strdup("sha256"), NS_HASH_SHA256); |
| se_add_pair_to_slist("cert_hash_alg", strdup("sha384"), NS_HASH_SHA384); |
| se_add_pair_to_slist("cert_hash_alg", strdup("sha512"), NS_HASH_SHA512); |
| |
| /** map types */ |
| se_add_pair_to_slist("cert_map_type", strdup("cn"), |
| TSNM_tlstmCertCommonName); |
| se_add_pair_to_slist("cert_map_type", strdup("ip"), |
| TSNM_tlstmCertSANIpAddress); |
| se_add_pair_to_slist("cert_map_type", strdup("rfc822"), |
| TSNM_tlstmCertSANRFC822Name); |
| se_add_pair_to_slist("cert_map_type", strdup("dns"), |
| TSNM_tlstmCertSANDNSName); |
| se_add_pair_to_slist("cert_map_type", strdup("any"), TSNM_tlstmCertSANAny); |
| se_add_pair_to_slist("cert_map_type", strdup("sn"), |
| TSNM_tlstmCertSpecified); |
| |
| } |
| |
| void |
| netsnmp_certs_shutdown(void) |
| { |
| DEBUGMSGT(("cert:util:shutdown","shutdown\n")); |
| if (NULL != _certs) { |
| CONTAINER_FREE_ALL(_certs, NULL); |
| CONTAINER_FREE(_certs); |
| _certs = NULL; |
| } |
| if (NULL != _keys) { |
| CONTAINER_FREE_ALL(_keys, NULL); |
| CONTAINER_FREE(_keys); |
| _keys = NULL; |
| } |
| _netsnmp_release_trustcerts(); |
| } |
| |
| void |
| netsnmp_certs_load(void) |
| { |
| netsnmp_iterator *itr; |
| netsnmp_key *key; |
| netsnmp_cert *cert; |
| |
| DEBUGMSGT(("cert:util:init","init\n")); |
| |
| if (NULL == _certs) { |
| snmp_log(LOG_ERR, "cant load certs without container\n"); |
| return; |
| } |
| |
| if (CONTAINER_SIZE(_certs) != 0) { |
| DEBUGMSGT(("cert:util:init", "ignoring duplicate init\n")); |
| return; |
| } |
| |
| netsnmp_init_openssl(); |
| |
| /** scan config dirs for certs */ |
| _cert_indexes_load(); |
| |
| /** match up keys w/certs */ |
| itr = CONTAINER_ITERATOR(_keys); |
| if (NULL == itr) { |
| snmp_log(LOG_ERR, "could not get iterator for keys\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| key = ITERATOR_FIRST(itr); |
| for( ; key; key = ITERATOR_NEXT(itr)) |
| _find_partner(NULL, key); |
| ITERATOR_RELEASE(itr); |
| |
| DEBUGIF("cert:dump") { |
| itr = CONTAINER_ITERATOR(_certs); |
| if (NULL == itr) { |
| snmp_log(LOG_ERR, "could not get iterator for certs\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| cert = ITERATOR_FIRST(itr); |
| for( ; cert; cert = ITERATOR_NEXT(itr)) { |
| netsnmp_cert_load_x509(cert); |
| } |
| ITERATOR_RELEASE(itr); |
| DEBUGMSGT(("cert:dump", |
| "-------------------- Certificates -----------------\n")); |
| netsnmp_cert_dump_all(); |
| DEBUGMSGT(("cert:dump", |
| "------------------------ End ----------------------\n")); |
| } |
| } |
| |
| /* ##################################################################### |
| * |
| * cert container functions |
| */ |
| |
| static netsnmp_container * |
| _get_cert_container(const char *use) |
| { |
| netsnmp_container *c; |
| |
| c = netsnmp_container_find("certs:binary_array"); |
| if (NULL == c) { |
| snmp_log(LOG_ERR, "could not create container for %s\n", use); |
| return NULL; |
| } |
| c->container_name = strdup(use); |
| c->free_item = (netsnmp_container_obj_func*)_cert_free; |
| c->compare = (netsnmp_container_compare*)_cert_compare; |
| |
| return c; |
| } |
| |
| static void |
| _setup_containers(void) |
| { |
| netsnmp_container *additional_keys; |
| |
| _certs = _get_cert_container("netsnmp certificates"); |
| if (NULL == _certs) |
| return; |
| |
| /** additional keys: common name */ |
| additional_keys = netsnmp_container_find("certs_cn:binary_array"); |
| if (NULL == additional_keys) { |
| snmp_log(LOG_ERR, "could not create CN container for certificates\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| additional_keys->container_name = strdup("certs_cn"); |
| additional_keys->free_item = NULL; |
| additional_keys->compare = (netsnmp_container_compare*)_cert_cn_compare; |
| netsnmp_container_add_index(_certs, additional_keys); |
| |
| /** additional keys: subject name */ |
| additional_keys = netsnmp_container_find("certs_sn:binary_array"); |
| if (NULL == additional_keys) { |
| snmp_log(LOG_ERR, "could not create SN container for certificates\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| additional_keys->container_name = strdup("certs_sn"); |
| additional_keys->free_item = NULL; |
| additional_keys->compare = (netsnmp_container_compare*)_cert_sn_compare; |
| additional_keys->ncompare = (netsnmp_container_compare*)_cert_sn_ncompare; |
| netsnmp_container_add_index(_certs, additional_keys); |
| |
| /** additional keys: file name */ |
| additional_keys = netsnmp_container_find("certs_fn:binary_array"); |
| if (NULL == additional_keys) { |
| snmp_log(LOG_ERR, "could not create FN container for certificates\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| additional_keys->container_name = strdup("certs_fn"); |
| additional_keys->free_item = NULL; |
| additional_keys->compare = (netsnmp_container_compare*)_cert_fn_compare; |
| additional_keys->ncompare = (netsnmp_container_compare*)_cert_fn_ncompare; |
| netsnmp_container_add_index(_certs, additional_keys); |
| |
| _keys = netsnmp_container_find("cert_keys:binary_array"); |
| if (NULL == _keys) { |
| snmp_log(LOG_ERR, "could not create container for certificate keys\n"); |
| netsnmp_certs_shutdown(); |
| return; |
| } |
| _keys->container_name = strdup("netsnmp certificate keys"); |
| _keys->free_item = (netsnmp_container_obj_func*)_key_free; |
| _keys->compare = (netsnmp_container_compare*)_cert_fn_compare; |
| |
| _setup_trusted_certs(); |
| } |
| |
| netsnmp_container * |
| netsnmp_cert_map_container(void) |
| { |
| return _maps; |
| } |
| |
| 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) |
| { |
| netsnmp_cert *cert; |
| |
| if ((NULL == dirname) || (NULL == filename)) { |
| snmp_log(LOG_ERR, "bad parameters to _new_cert\n"); |
| return NULL; |
| } |
| |
| cert = SNMP_MALLOC_TYPEDEF(netsnmp_cert); |
| if (NULL == cert) { |
| snmp_log(LOG_ERR,"could not allocate memory for certificate at %s/%s\n", |
| dirname, filename); |
| return NULL; |
| } |
| |
| DEBUGMSGT(("9:cert:struct:new","new cert 0x%#lx for %s\n", (u_long)cert, |
| filename)); |
| |
| cert->info.dir = strdup(dirname); |
| cert->info.filename = strdup(filename); |
| cert->info.allowed_uses = NS_CERT_REMOTE_PEER; |
| cert->info.type = certType; |
| if (fingerprint) { |
| cert->hash_type = hashType; |
| cert->fingerprint = strdup(fingerprint); |
| } |
| if (common_name) |
| cert->common_name = strdup(common_name); |
| if (subject) |
| cert->subject = strdup(subject); |
| |
| return cert; |
| } |
| |
| static netsnmp_key * |
| _new_key(const char *dirname, const char *filename) |
| { |
| netsnmp_key *key; |
| struct stat fstat; |
| char fn[SNMP_MAXPATH]; |
| |
| if ((NULL == dirname) || (NULL == filename)) { |
| snmp_log(LOG_ERR, "bad parameters to _new_key\n"); |
| return NULL; |
| } |
| |
| /** check file permissions */ |
| snprintf(fn, sizeof(fn), "%s/%s", dirname, filename); |
| if (stat(fn, &fstat) != 0) { |
| snmp_log(LOG_ERR, "could not stat %s\n", fn); |
| return NULL; |
| } |
| |
| if ((fstat.st_mode & S_IROTH) || (fstat.st_mode & S_IROTH)) { |
| snmp_log(LOG_ERR, |
| "refusing to read world readable or writable key %s\n", fn); |
| return NULL; |
| } |
| |
| key = SNMP_MALLOC_TYPEDEF(netsnmp_key); |
| if (NULL == key) { |
| snmp_log(LOG_ERR, "could not allocate memory for key at %s/%s\n", |
| dirname, filename); |
| return NULL; |
| } |
| |
| DEBUGMSGT(("cert:key:struct:new","new key 0x%#lx for %s\n", (u_long)key, |
| filename)); |
| |
| key->info.type = NS_CERT_TYPE_KEY; |
| key->info.dir = strdup(dirname); |
| key->info.filename = strdup(filename); |
| key->info.allowed_uses = NS_CERT_IDENTITY; |
| |
| return key; |
| } |
| |
| void |
| netsnmp_cert_free(netsnmp_cert *cert) |
| { |
| if (NULL == cert) |
| return; |
| |
| DEBUGMSGT(("9:cert:struct:free","freeing cert 0x%#lx, %s (fp %s; CN %s)\n", |
| (u_long)cert, cert->info.filename ? cert->info.filename : "UNK", |
| cert->fingerprint ? cert->fingerprint : "UNK", |
| cert->common_name ? cert->common_name : "UNK")); |
| |
| SNMP_FREE(cert->info.dir); |
| SNMP_FREE(cert->info.filename); |
| SNMP_FREE(cert->subject); |
| SNMP_FREE(cert->issuer); |
| SNMP_FREE(cert->fingerprint); |
| SNMP_FREE(cert->common_name); |
| if (cert->ocert) |
| X509_free(cert->ocert); |
| if (cert->key && cert->key->cert == cert) |
| cert->key->cert = NULL; |
| |
| free(cert); /* SNMP_FREE not needed on parameters */ |
| } |
| |
| void |
| netsnmp_key_free(netsnmp_key *key) |
| { |
| if (NULL == key) |
| return; |
| |
| DEBUGMSGT(("cert:key:struct:free","freeing key 0x%#lx, %s\n", |
| (u_long)key, key->info.filename ? key->info.filename : "UNK")); |
| |
| SNMP_FREE(key->info.dir); |
| SNMP_FREE(key->info.filename); |
| EVP_PKEY_free(key->okey); |
| if (key->cert && key->cert->key == key) |
| key->cert->key = NULL; |
| |
| free(key); /* SNMP_FREE not needed on parameters */ |
| } |
| |
| static void |
| _cert_free(netsnmp_cert *cert, void *context) |
| { |
| netsnmp_cert_free(cert); |
| } |
| |
| static void |
| _key_free(netsnmp_key *key, void *context) |
| { |
| netsnmp_key_free(key); |
| } |
| |
| static int |
| _cert_compare(netsnmp_cert *lhs, netsnmp_cert *rhs) |
| { |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| netsnmp_assert((lhs->fingerprint != NULL) && |
| (rhs->fingerprint != NULL)); |
| |
| /** ignore hash type? */ |
| return strcmp(lhs->fingerprint, rhs->fingerprint); |
| } |
| |
| static int |
| _cert_path_compare(netsnmp_cert_common *lhs, netsnmp_cert_common *rhs) |
| { |
| int rc; |
| |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| /** dir name first */ |
| rc = strcmp(lhs->dir, rhs->dir); |
| if (rc) |
| return rc; |
| |
| /** filename */ |
| return strcmp(lhs->filename, rhs->filename); |
| } |
| |
| static int |
| _cert_cn_compare(netsnmp_cert *lhs, netsnmp_cert *rhs) |
| { |
| int rc; |
| const char *lhcn, *rhcn; |
| |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| if (NULL == lhs->common_name) |
| lhcn = ""; |
| else |
| lhcn = lhs->common_name; |
| if (NULL == rhs->common_name) |
| rhcn = ""; |
| else |
| rhcn = rhs->common_name; |
| |
| rc = strcmp(lhcn, rhcn); |
| if (rc) |
| return rc; |
| |
| /** in case of equal common names, sub-sort by path */ |
| return _cert_path_compare((netsnmp_cert_common*)lhs, |
| (netsnmp_cert_common*)rhs); |
| } |
| |
| static int |
| _cert_sn_compare(netsnmp_cert *lhs, netsnmp_cert *rhs) |
| { |
| int rc; |
| const char *lhsn, *rhsn; |
| |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| if (NULL == lhs->subject) |
| lhsn = ""; |
| else |
| lhsn = lhs->subject; |
| if (NULL == rhs->subject) |
| rhsn = ""; |
| else |
| rhsn = rhs->subject; |
| |
| rc = strcmp(lhsn, rhsn); |
| if (rc) |
| return rc; |
| |
| /** in case of equal common names, sub-sort by path */ |
| return _cert_path_compare((netsnmp_cert_common*)lhs, |
| (netsnmp_cert_common*)rhs); |
| } |
| |
| static int |
| _cert_fn_compare(netsnmp_cert_common *lhs, netsnmp_cert_common *rhs) |
| { |
| int rc; |
| |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| rc = strcmp(lhs->filename, rhs->filename); |
| if (rc) |
| return rc; |
| |
| /** in case of equal common names, sub-sort by dir */ |
| return strcmp(lhs->dir, rhs->dir); |
| } |
| |
| static int |
| _cert_fn_ncompare(netsnmp_cert_common *lhs, netsnmp_cert_common *rhs) |
| { |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| netsnmp_assert((lhs->filename != NULL) && (rhs->filename != NULL)); |
| |
| return strncmp(lhs->filename, rhs->filename, strlen(rhs->filename)); |
| } |
| |
| static int |
| _cert_sn_ncompare(netsnmp_cert *lhs, netsnmp_cert *rhs) |
| { |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| netsnmp_assert((lhs->subject != NULL) && (rhs->subject != NULL)); |
| |
| return strncmp(lhs->subject, rhs->subject, strlen(rhs->subject)); |
| } |
| |
| static int |
| _cert_ext_type(const char *ext) |
| { |
| int rc = se_find_value_in_slist("cert_types", ext); |
| if (SE_DNE == rc) |
| return NS_CERT_TYPE_UNKNOWN; |
| return rc; |
| } |
| |
| static int |
| _type_from_filename(const char *filename) |
| { |
| char *pos; |
| int type; |
| |
| if (NULL == filename) |
| return NS_CERT_TYPE_UNKNOWN; |
| |
| pos = strrchr(filename, '.'); |
| if (NULL == pos) |
| return NS_CERT_TYPE_UNKNOWN; |
| |
| type = _cert_ext_type(++pos); |
| return type; |
| } |
| |
| /* |
| * filter functions; return 1 to include file, 0 to exclude |
| */ |
| static int |
| _cert_cert_filter(const char *filename) |
| { |
| int len = strlen(filename); |
| const char *pos; |
| |
| if (len < 5) /* shortest name: x.YYY */ |
| return 0; |
| |
| pos = strrchr(filename, '.'); |
| if (NULL == pos) |
| return 0; |
| |
| if (_cert_ext_type(++pos) != NS_CERT_TYPE_UNKNOWN) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* ##################################################################### |
| * |
| * cert index functions |
| * |
| * This code mimics what the mib index code does. The persistent |
| * directory will have a subdirectory named 'cert_indexes'. Inside |
| * this directory will be some number of files with ascii numeric |
| * names (0, 1, 2, etc). Each of these files will start with a line |
| * with the text "DIR ", followed by a directory name. The rest of the |
| * file will be certificate fields and the certificate file name, one |
| * certificate per line. The numeric file name is the integer 'directory |
| * index'. |
| */ |
| |
| /** |
| * _certindex_add |
| * |
| * add a directory name to the indexes |
| */ |
| static int |
| _certindex_add( const char *dirname, int i ) |
| { |
| int rc; |
| char *dirname_copy = strdup(dirname); |
| |
| if ( i == -1 ) { |
| int max = se_find_free_value_in_list(_certindexes); |
| if (SE_DNE == max) |
| i = 0; |
| else |
| i = max; |
| } |
| |
| DEBUGMSGT(("cert:index:add","dir %s at index %d\n", dirname, i )); |
| rc = se_add_pair_to_list(&_certindexes, dirname_copy, i); |
| if (SE_OK != rc) { |
| snmp_log(LOG_ERR, "adding certindex dirname failed; " |
| "%d (%s) not added\n", i, dirname); |
| return -1; |
| } |
| |
| return i; |
| } |
| |
| /** |
| * _certindex_load |
| * |
| * read in the existing indexes |
| */ |
| static void |
| _certindexes_load( void ) |
| { |
| DIR *dir; |
| struct dirent *file; |
| FILE *fp; |
| char filename[SNMP_MAXPATH], line[300]; |
| int i; |
| char *cp, *pos; |
| |
| /* |
| * Open the CERT index directory, or create it (empty) |
| */ |
| snprintf( filename, sizeof(filename), "%s/cert_indexes", |
| get_persistent_directory()); |
| filename[sizeof(filename)-1] = 0; |
| dir = opendir( filename ); |
| if ( dir == NULL ) { |
| DEBUGMSGT(("cert:index:load", |
| "creating new cert_indexes directory\n")); |
| mkdirhier( filename, NETSNMP_AGENT_DIRECTORY_MODE, 0); |
| return; |
| } |
| |
| /* |
| * Create a list of which directory each file refers to |
| */ |
| while ((file = readdir( dir ))) { |
| if ( !isdigit(0xFF & file->d_name[0])) |
| continue; |
| i = atoi( file->d_name ); |
| |
| snprintf( filename, sizeof(filename), "%s/cert_indexes/%d", |
| get_persistent_directory(), i ); |
| filename[sizeof(filename)-1] = 0; |
| fp = fopen( filename, "r" ); |
| if ( !fp ) { |
| DEBUGMSGT(("cert:index:load", "error opening index (%d)\n", i)); |
| continue; |
| } |
| cp = fgets( line, sizeof(line), fp ); |
| if ( cp ) { |
| line[strlen(line)-1] = 0; |
| pos = strrchr(line, ' '); |
| if (pos) |
| *pos = '\0'; |
| DEBUGMSGT(("9:cert:index:load","adding (%d) %s\n", i, line)); |
| (void)_certindex_add( line+4, i ); /* Skip 'DIR ' */ |
| } else { |
| DEBUGMSGT(("cert:index:load", "Empty index (%d)\n", i)); |
| } |
| fclose( fp ); |
| } |
| closedir( dir ); |
| } |
| |
| /** |
| * _certindex_lookup |
| * |
| * find index for a directory |
| */ |
| static char * |
| _certindex_lookup( const char *dirname ) |
| { |
| int i; |
| char filename[SNMP_MAXPATH]; |
| |
| |
| i = se_find_value_in_list(_certindexes, dirname); |
| if (SE_DNE == i) { |
| DEBUGMSGT(("9:cert:index:lookup","%s : (none)\n", dirname)); |
| return NULL; |
| } |
| |
| snprintf(filename, sizeof(filename), "%s/cert_indexes/%d", |
| get_persistent_directory(), i); |
| filename[sizeof(filename)-1] = 0; |
| DEBUGMSGT(("cert:index:lookup", "%s (%d) %s\n", dirname, i, filename )); |
| return strdup(filename); |
| } |
| |
| static FILE * |
| _certindex_new( const char *dirname ) |
| { |
| FILE *fp; |
| char filename[SNMP_MAXPATH], *cp; |
| int i; |
| |
| cp = _certindex_lookup( dirname ); |
| if (!cp) { |
| i = _certindex_add( dirname, -1 ); |
| if (-1 == i) |
| return NULL; /* msg already logged */ |
| snprintf( filename, sizeof(filename), "%s/cert_indexes/%d", |
| get_persistent_directory(), i ); |
| filename[sizeof(filename)-1] = 0; |
| cp = filename; |
| } |
| DEBUGMSGT(("9:cert:index:new", "%s (%s)\n", dirname, cp )); |
| fp = fopen( cp, "w" ); |
| if (fp) |
| fprintf( fp, "DIR %s %d\n", dirname, CERT_INDEX_FORMAT ); |
| else |
| DEBUGMSGTL(("cert:index", "error opening new index file %s\n", dirname)); |
| |
| if (cp != filename) |
| free(cp); |
| |
| return fp; |
| } |
| |
| /* ##################################################################### |
| * |
| * certificate utility functions |
| * |
| */ |
| static X509 * |
| netsnmp_ocert_get(netsnmp_cert *cert) |
| { |
| BIO *certbio; |
| X509 *ocert = NULL; |
| EVP_PKEY *okey = NULL; |
| char file[SNMP_MAXPATH]; |
| int is_ca; |
| |
| if (NULL == cert) |
| return NULL; |
| |
| if (cert->ocert) |
| return cert->ocert; |
| |
| if (NS_CERT_TYPE_UNKNOWN == cert->info.type) { |
| cert->info.type = _type_from_filename(cert->info.filename); |
| if (NS_CERT_TYPE_UNKNOWN == cert->info.type) { |
| snmp_log(LOG_ERR, "unknown certificate type %d for %s\n", |
| cert->info.type, cert->info.filename); |
| return NULL; |
| } |
| } |
| |
| 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); |
| 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: |
| ocert = d2i_X509_bio(certbio,NULL); /* DER/ASN1 */ |
| if (NULL != ocert) |
| break; |
| (void)BIO_reset(certbio); |
| /** FALLTHROUGH: check for PEM if DER didn't work */ |
| |
| case NS_CERT_TYPE_PEM: |
| ocert = 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); |
| if (NULL != okey) { |
| netsnmp_key *key; |
| DEBUGMSGT(("cert:read:key", "found key with cert in %s\n", |
| cert->info.filename)); |
| key = _new_key(cert->info.dir, cert->info.filename); |
| if (NULL != key) { |
| key->okey = okey; |
| if (-1 == CONTAINER_INSERT(_keys, key)) { |
| DEBUGMSGT(("cert:read:key:add", |
| "error inserting key into container\n")); |
| netsnmp_key_free(key); |
| key = NULL; |
| } |
| else { |
| DEBUGMSGT(("cert:read:partner", "%s match found!\n", |
| cert->info.filename)); |
| key->cert = cert; |
| cert->key = key; |
| cert->info.allowed_uses |= NS_CERT_IDENTITY; |
| } |
| } |
| } /* null return from read */ |
| } /* null key */ |
| break; |
| #ifdef CERT_PKCS12_SUPPORT_MAYBE_LATER |
| case NS_CERT_TYPE_PKCS12: |
| (void)BIO_reset(certbio); |
| PKCS12 *p12 = d2i_PKCS12_bio(certbio, NULL); |
| if ( (NULL != p12) && (PKCS12_verify_mac(p12, "", 0) || |
| PKCS12_verify_mac(p12, NULL, 0))) |
| PKCS12_parse(p12, "", NULL, &cert, NULL); |
| break; |
| #endif |
| default: |
| snmp_log(LOG_ERR, "unknown certificate type %d for %s\n", |
| cert->info.type, cert->info.filename); |
| } |
| |
| BIO_vfree(certbio); |
| |
| if (NULL == ocert) { |
| snmp_log(LOG_ERR, "error parsing certificate file %s\n", |
| cert->info.filename); |
| 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)); |
| } |
| |
| return ocert; |
| } |
| |
| EVP_PKEY * |
| netsnmp_okey_get(netsnmp_key *key) |
| { |
| BIO *keybio; |
| EVP_PKEY *okey; |
| char file[SNMP_MAXPATH]; |
| |
| if (NULL == key) |
| return NULL; |
| |
| 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); |
| return NULL; |
| } |
| |
| okey = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL); |
| if (NULL == okey) |
| snmp_log(LOG_ERR, "error parsing certificate file %s\n", |
| key->info.filename); |
| else |
| key->okey = okey; |
| |
| BIO_vfree(keybio); |
| |
| return okey; |
| } |
| |
| static netsnmp_cert * |
| _find_issuer(netsnmp_cert *cert) |
| { |
| netsnmp_void_array *matching; |
| netsnmp_cert *candidate, *issuer = NULL; |
| int i; |
| |
| if ((NULL == cert) || (NULL == cert->issuer)) |
| return NULL; |
| |
| /** find matching subject names */ |
| |
| matching = _cert_find_subset_sn(cert->issuer); |
| if (NULL == matching) |
| return NULL; |
| |
| /** check each to see if it's the issuer */ |
| for ( i=0; (NULL == issuer) && (i < matching->size); ++i) { |
| /** make sure we have ocert */ |
| candidate = (netsnmp_cert*)matching->array[i]; |
| if ((NULL == candidate->ocert) && |
| (netsnmp_ocert_get(candidate) == NULL)) |
| continue; |
| |
| /** compare **/ |
| if (netsnmp_openssl_cert_issued_by(candidate->ocert, cert->ocert)) |
| issuer = candidate; |
| } /** candidate loop */ |
| |
| free(matching->array); |
| free(matching); |
| |
| return issuer; |
| } |
| |
| #define CERT_LOAD_OK 0 |
| #define CERT_LOAD_ERR -1 |
| #define CERT_LOAD_PARTIAL -2 |
| int |
| netsnmp_cert_load_x509(netsnmp_cert *cert) |
| { |
| int rc = CERT_LOAD_OK; |
| |
| /** load ocert */ |
| if ((NULL == cert->ocert) && (netsnmp_ocert_get(cert) == NULL)) { |
| DEBUGMSGT(("cert:load:err", "couldn't load cert for %s\n", |
| cert->info.filename)); |
| rc = CERT_LOAD_ERR; |
| } |
| |
| /** load key */ |
| if ((NULL != cert->key) && (NULL == cert->key->okey) && |
| (netsnmp_okey_get(cert->key) == NULL)) { |
| DEBUGMSGT(("cert:load:err", "couldn't load key for cert %s\n", |
| cert->info.filename)); |
| rc = CERT_LOAD_ERR; |
| } |
| |
| /** make sure we have cert chain */ |
| for (; cert && cert->issuer; cert = cert->issuer_cert) { |
| /** skip self signed */ |
| if (strcmp(cert->issuer, "self-signed") == 0) { |
| netsnmp_assert(cert->issuer_cert == NULL); |
| break; |
| } |
| /** get issuer cert */ |
| if (NULL == cert->issuer_cert) { |
| cert->issuer_cert = _find_issuer(cert); |
| if (NULL == cert->issuer_cert) { |
| DEBUGMSGT(("cert:load:warn", |
| "couldn't load CA chain for cert %s\n", |
| cert->info.filename)); |
| rc = CERT_LOAD_PARTIAL; |
| break; |
| } |
| } |
| /** 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", |
| cert->info.filename)); |
| rc = CERT_LOAD_PARTIAL; |
| break; |
| } |
| } /* cert CA for loop */ |
| |
| return rc; |
| } |
| |
| static void |
| _find_partner(netsnmp_cert *cert, netsnmp_key *key) |
| { |
| netsnmp_void_array *matching; |
| char filename[NAME_MAX], *pos; |
| |
| if ((cert && key) || (!cert && ! key)) { |
| DEBUGMSGT(("cert:partner", "bad parameters searching for partner\n")); |
| return; |
| } |
| |
| if(key) { |
| if (key->cert) { |
| DEBUGMSGT(("cert:partner", "key already has partner\n")); |
| return; |
| } |
| DEBUGMSGT(("9:cert:partner", "%s looking for partner near %s\n", |
| key->info.filename, key->info.dir)); |
| snprintf(filename, sizeof(filename), "%s", key->info.filename); |
| pos = strrchr(filename, '.'); |
| if (NULL == pos) |
| return; |
| *pos = 0; |
| |
| matching = _cert_find_subset_fn( filename, key->info.dir ); |
| if (!matching) |
| return; |
| if (1 == matching->size) { |
| cert = (netsnmp_cert*)matching->array[0]; |
| if (NULL == cert->key) { |
| DEBUGMSGT(("cert:partner", "%s match found!\n", |
| cert->info.filename)); |
| key->cert = cert; |
| cert->key = key; |
| cert->info.allowed_uses |= NS_CERT_IDENTITY; |
| } |
| else if (cert->key != key) |
| snmp_log(LOG_ERR, "%s matching cert already has partner\n", |
| cert->info.filename); |
| } |
| else |
| DEBUGMSGT(("cert:partner", "%s matches multiple certs\n", |
| key->info.filename)); |
| } |
| else if(cert) { |
| if (cert->key) { |
| DEBUGMSGT(("cert:partner", "cert already has partner\n")); |
| return; |
| } |
| DEBUGMSGT(("9:cert:partner", "%s looking for partner\n", |
| cert->info.filename)); |
| snprintf(filename, sizeof(filename), "%s", cert->info.filename); |
| pos = strrchr(filename, '.'); |
| if (NULL == pos) |
| return; |
| *pos = 0; |
| |
| matching = _key_find_subset(filename); |
| if (!matching) |
| return; |
| if (1 == matching->size) { |
| key = (netsnmp_key*)matching->array[0]; |
| if (NULL == key->cert) { |
| DEBUGMSGT(("cert:partner", "%s found!\n", cert->info.filename)); |
| key->cert = cert; |
| cert->key = key; |
| } |
| else if (key->cert != cert) |
| snmp_log(LOG_ERR, "%s matching key already has partner\n", |
| cert->info.filename); |
| } |
| else |
| DEBUGMSGT(("cert:partner", "%s matches multiple keys\n", |
| cert->info.filename)); |
| } |
| |
| if (matching) { |
| free(matching->array); |
| free(matching); |
| } |
| } |
| |
| static int |
| _add_certfile(const char* dirname, const char* filename, FILE *index) |
| { |
| X509 *ocert; |
| EVP_PKEY *okey; |
| netsnmp_cert *cert = NULL; |
| netsnmp_key *key = NULL; |
| char certfile[SNMP_MAXPATH]; |
| int type; |
| |
| if (((const void*)NULL == dirname) || (NULL == filename)) |
| return -1; |
| |
| type = _type_from_filename(filename); |
| netsnmp_assert(type != NS_CERT_TYPE_UNKNOWN); |
| |
| snprintf(certfile, sizeof(certfile),"%s/%s", dirname, filename); |
| |
| DEBUGMSGT(("9:cert:file:add", "Checking file: %s (type %d)\n", filename, |
| 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; |
| } |
| |
| if (index) { |
| /** filename = NAME_MAX = 255 */ |
| /** fingerprint = 60 */ |
| /** 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); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| _cert_read_index(const char *dirname, struct stat *dirstat) |
| { |
| FILE *index; |
| char *idxname, *pos; |
| struct stat idx_stat; |
| char tmpstr[SNMP_MAXPATH + 5], filename[NAME_MAX]; |
| char fingerprint[60+1], common_name[64+1], type_str[15]; |
| char subject[SNMP_MAXBUF_SMALL], hash_str[15]; |
| int count = 0, type, hash, version; |
| netsnmp_cert *cert; |
| netsnmp_key *key; |
| netsnmp_container *newer, *found; |
| |
| netsnmp_assert(NULL != dirname); |
| |
| idxname = _certindex_lookup( dirname ); |
| if (NULL == idxname) { |
| DEBUGMSGT(("cert:index:parse", "no index for cert directory\n")); |
| return -1; |
| } |
| |
| /* |
| * see if directory has been modified more recently than the index |
| */ |
| if (stat(idxname, &idx_stat) != 0) { |
| DEBUGMSGT(("cert:index:parse", "error getting index file stats\n")); |
| SNMP_FREE(idxname); |
| return -1; |
| } |
| |
| #if (defined(WIN32) || defined(cygwin)) |
| /* For Win32 platforms, the directory does not maintain a last modification |
| * date that we can compare with the modification date of the .index file. |
| */ |
| #else |
| if (dirstat->st_mtime >= idx_stat.st_mtime) { |
| DEBUGMSGT(("cert:index:parse", "Index outdated; dir modified\n")); |
| SNMP_FREE(idxname); |
| return -1; |
| } |
| #endif |
| |
| /* |
| * dir mtime doesn't change when files are touched, so we need to check |
| * each file against the index in case a file has been modified. |
| */ |
| newer = |
| netsnmp_directory_container_read_some(NULL, dirname, |
| (netsnmp_directory_filter*) |
| _time_filter,(void*)&idx_stat, |
| NETSNMP_DIR_NSFILE | |
| NETSNMP_DIR_NSFILE_STATS); |
| if (newer) { |
| DEBUGMSGT(("cert:index:parse", "Index outdated; files modified\n")); |
| CONTAINER_FREE_ALL(newer, NULL); |
| CONTAINER_FREE(newer); |
| SNMP_FREE(idxname); |
| return -1; |
| } |
| |
| DEBUGMSGT(("cert:index:parse", "The index for %s looks good\n", dirname)); |
| |
| index = fopen(idxname, "r"); |
| if (NULL == index) { |
| snmp_log(LOG_ERR, "cert:index:parse can't open index for %s\n", |
| dirname); |
| SNMP_FREE(idxname); |
| return -1; |
| } |
| |
| found = _get_cert_container(idxname); |
| |
| /* |
| * check index format version |
| */ |
| fgets(tmpstr, sizeof(tmpstr), index); |
| pos = strrchr(tmpstr, ' '); |
| if (pos) { |
| ++pos; |
| version = atoi(pos); |
| } |
| if ((NULL == pos) || (version != CERT_INDEX_FORMAT)) { |
| DEBUGMSGT(("cert:index:add", "missing or wrong index format!\n")); |
| fclose(index); |
| SNMP_FREE(idxname); |
| return -1; |
| } |
| while (1) { |
| if (NULL == fgets(tmpstr, sizeof(tmpstr), index)) |
| break; |
| |
| if ('c' == tmpstr[0]) { |
| 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, hash_str, sizeof(hash_str)))) || |
| (NULL == (pos=copy_nword(pos, fingerprint, |
| sizeof(fingerprint)))) || |
| (NULL == (pos=copy_nword(pos, common_name, |
| sizeof(common_name)))) || |
| (NULL != copy_nword(pos, subject, sizeof(subject)))) { |
| snmp_log(LOG_ERR, "_cert_read_index: error parsing line: %s\n", |
| tmpstr); |
| count = -1; |
| break; |
| } |
| type = atoi(type_str); |
| hash = atoi(hash_str); |
| cert = (void*)_new_cert(dirname, filename, type, hash, fingerprint, |
| common_name, subject); |
| if (cert && 0 == CONTAINER_INSERT(found, cert)) |
| ++count; |
| else { |
| DEBUGMSGT(("cert:index:add", |
| "error inserting cert into container\n")); |
| netsnmp_cert_free(cert); |
| cert = NULL; |
| } |
| } |
| else if ('k' == tmpstr[0]) { |
| if (NULL != copy_nword(&tmpstr[2], filename, sizeof(filename))) { |
| snmp_log(LOG_ERR, "_cert_read_index: error parsing line %s\n", |
| tmpstr); |
| continue; |
| } |
| key = _new_key(dirname, filename); |
| if (key && 0 == CONTAINER_INSERT(_keys, key)) |
| ++count; |
| else { |
| DEBUGMSGT(("cert:index:add:key", |
| "error inserting key into container\n")); |
| netsnmp_key_free(key); |
| } |
| } |
| else { |
| snmp_log(LOG_ERR, "unknown line in cert index for %s\n", dirname); |
| continue; |
| } |
| } /* while */ |
| fclose(index); |
| SNMP_FREE(idxname); |
| |
| if (count > 0) { |
| netsnmp_iterator *itr = CONTAINER_ITERATOR(found); |
| if (NULL == itr) { |
| snmp_log(LOG_ERR, "could not get iterator for found certs\n"); |
| count = -1; |
| } |
| else { |
| cert = ITERATOR_FIRST(itr); |
| for( ; cert; cert = ITERATOR_NEXT(itr)) |
| CONTAINER_INSERT(_certs, cert); |
| ITERATOR_RELEASE(itr); |
| DEBUGMSGT(("cert:index:parse","added %d certs from index\n", |
| count)); |
| } |
| } |
| if (count < 0) |
| CONTAINER_FREE_ALL(found, NULL); |
| CONTAINER_FREE(found); |
| |
| return count; |
| } |
| |
| static int |
| _add_certdir(const char *dirname) |
| { |
| FILE *index; |
| char *file; |
| int count = 0; |
| netsnmp_container *cert_container; |
| netsnmp_iterator *it; |
| struct stat statbuf; |
| |
| netsnmp_assert(NULL != dirname); |
| |
| DEBUGMSGT(("9:cert:dir:add", " config dir: %s\n", dirname )); |
| |
| if (stat(dirname, &statbuf) != 0) { |
| DEBUGMSGT(("9:cert:dir:add", " dir not present: %s\n", |
| dirname )); |
| return -1; |
| } |
| #ifdef S_ISDIR |
| if (!S_ISDIR(statbuf.st_mode)) { |
| DEBUGMSGT(("9:cert:dir:add", " not a dir: %s\n", dirname )); |
| return -1; |
| } |
| #endif |
| |
| DEBUGMSGT(("cert:index:dir", "Scanning directory %s\n", dirname)); |
| |
| /* |
| * look for existing index |
| */ |
| count = _cert_read_index(dirname, &statbuf); |
| if (count >= 0) |
| return count; |
| |
| index = _certindex_new( dirname ); |
| if (NULL == index) { |
| DEBUGMSGT(("9:cert:index:dir", |
| "error opening index for cert directory\n")); |
| DEBUGMSGTL(("cert:index", "could not open certificate index file\n")); |
| } |
| |
| /* |
| * index was missing, out of date or bad. rescan directory. |
| */ |
| cert_container = |
| netsnmp_directory_container_read_some(NULL, dirname, |
| (netsnmp_directory_filter*) |
| &_cert_cert_filter, NULL, |
| NETSNMP_DIR_RELATIVE_PATH | |
| NETSNMP_DIR_EMPTY_OK ); |
| if (NULL == cert_container) { |
| DEBUGMSGT(("cert:index:dir", |
| "error creating container for cert files\n")); |
| goto err_index; |
| } |
| |
| /* |
| * iterate through the found files and add them to index |
| */ |
| it = CONTAINER_ITERATOR(cert_container); |
| if (NULL == it) { |
| DEBUGMSGT(("cert:index:dir", |
| "error creating iterator for cert files\n")); |
| goto err_container; |
| } |
| |
| for (file = ITERATOR_FIRST(it); file; file = ITERATOR_NEXT(it)) { |
| DEBUGMSGT(("cert:index:dir", "adding %s to index\n", file)); |
| if ( 0 == _add_certfile( dirname, file, index )) |
| count++; |
| else |
| DEBUGMSGT(("cert:index:dir", "error adding %s to index\n", |
| file)); |
| } |
| |
| /* |
| * clean up and return |
| */ |
| ITERATOR_RELEASE(it); |
| |
| err_container: |
| netsnmp_directory_container_free(cert_container); |
| |
| err_index: |
| if (index) |
| fclose(index); |
| |
| return count; |
| } |
| |
| static void |
| _cert_indexes_load(void) |
| { |
| const char *confpath; |
| char *confpath_copy, *dir, *st = NULL; |
| char certdir[SNMP_MAXPATH]; |
| const char *subdirs[] = { NULL, "ca-certs", "certs", "private", NULL }; |
| int i = 0; |
| |
| /* |
| * load indexes from persistent dir |
| */ |
| _certindexes_load(); |
| |
| /* |
| * duplicate path building from read_config_files_of_type() in |
| * read_config.c. That is, use SNMPCONFPATH environment variable if |
| * it is defined, otherwise use configuration directory. |
| */ |
| confpath = netsnmp_getenv("SNMPCONFPATH"); |
| if (NULL == confpath) |
| confpath = get_configuration_directory(); |
| |
| subdirs[0] = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_CERT_EXTRA_SUBDIR); |
| confpath_copy = strdup(confpath); |
| for ( dir = strtok_r(confpath_copy, ENV_SEPARATOR, &st); |
| dir; dir = strtok_r(NULL, ENV_SEPARATOR, &st)) { |
| |
| i = (NULL == subdirs[0]) ? 1 : 0; |
| for ( ; subdirs[i] ; ++i ) { |
| /** check tls subdir */ |
| snprintf(certdir, sizeof(certdir), "%s/tls/%s", dir, subdirs[i]); |
| _add_certdir(certdir); |
| } /* for subdirs */ |
| } /* for conf path dirs */ |
| SNMP_FREE(confpath_copy); |
| } |
| |
| static void |
| _cert_print(netsnmp_cert *c, void *context) |
| { |
| if (NULL == c) |
| return; |
| |
| DEBUGMSGT(("cert:dump", "cert %s in %s\n", c->info.filename, c->info.dir)); |
| DEBUGMSGT(("cert:dump", " type %d flags 0x%x (%s)\n", |
| c->info.type, c->info.allowed_uses, |
| _mode_str(c->info.allowed_uses))); |
| DEBUGIF("9:cert:dump") { |
| if (NS_CERT_TYPE_KEY != c->info.type) { |
| if(c->subject) { |
| if (c->info.allowed_uses & NS_CERT_CA) |
| DEBUGMSGT(("9:cert:dump", " CA: %s\n", c->subject)); |
| else |
| DEBUGMSGT(("9:cert:dump", " subject: %s\n", c->subject)); |
| } |
| if(c->issuer) |
| DEBUGMSGT(("9:cert:dump", " issuer: %s\n", c->issuer)); |
| if(c->fingerprint) |
| DEBUGMSGT(("9:cert:dump", " fingerprint: %s(%d):%s\n", |
| se_find_label_in_slist("cert_hash_alg", c->hash_type), |
| c->hash_type, c->fingerprint)); |
| } |
| /* netsnmp_openssl_cert_dump_names(c->ocert); */ |
| netsnmp_openssl_cert_dump_extensions(c->ocert); |
| } |
| |
| } |
| |
| static void |
| _key_print(netsnmp_key *k, void *context) |
| { |
| if (NULL == k) |
| return; |
| |
| DEBUGMSGT(("cert:dump", "key %s in %s\n", k->info.filename, k->info.dir)); |
| DEBUGMSGT(("cert:dump", " type %d flags 0x%x (%s)\n", k->info.type, |
| k->info.allowed_uses, _mode_str(k->info.allowed_uses))); |
| } |
| |
| void |
| netsnmp_cert_dump_all(void) |
| { |
| CONTAINER_FOR_EACH(_certs, (netsnmp_container_obj_func*)_cert_print, NULL); |
| CONTAINER_FOR_EACH(_keys, (netsnmp_container_obj_func*)_key_print, NULL); |
| } |
| |
| #ifdef CERT_MAIN |
| /* |
| * export BLD=~/net-snmp/build/ SRC=~/net-snmp/src |
| * cc -DCERT_MAIN `$BLD/net-snmp-config --cflags` `$BLD/net-snmp-config --build-includes $BLD/` $SRC/snmplib/cert_util.c -o cert_util `$BLD/net-snmp-config --build-lib-dirs $BLD` `$BLD/net-snmp-config --libs` -lcrypto -lssl |
| * |
| */ |
| int |
| main(int argc, char** argv) |
| { |
| int ch; |
| extern char *optarg; |
| |
| while ((ch = getopt(argc, argv, "D:fHLMx:")) != EOF) |
| switch(ch) { |
| case 'D': |
| debug_register_tokens(optarg); |
| snmp_set_do_debugging(1); |
| break; |
| default: |
| fprintf(stderr,"unknown option %c\n", ch); |
| } |
| |
| init_snmp("dtlsapp"); |
| |
| netsnmp_cert_dump_all(); |
| |
| return 0; |
| } |
| |
| #endif /* CERT_MAIN */ |
| |
| static netsnmp_cert *_cert_find_fp(const char *fingerprint); |
| |
| void |
| netsnmp_fp_lowercase_and_strip_colon(char *fp) |
| { |
| char *pos, *dest=NULL; |
| |
| if(!fp) |
| return; |
| |
| /** skip to first : */ |
| for (pos = fp; *pos; ++pos ) { |
| if (':' == *pos) { |
| dest = pos; |
| break; |
| } |
| else |
| *pos = isalpha(0xFF & *pos) ? tolower(0xFF & *pos) : *pos; |
| } |
| if (!*pos) |
| return; |
| |
| /** copy, skipping any ':' */ |
| for (++pos; *pos; ++pos) { |
| if (':' == *pos) |
| continue; |
| *dest++ = isalpha(0xFF & *pos) ? tolower(0xFF & *pos) : *pos; |
| } |
| *dest = *pos; /* nul termination */ |
| } |
| |
| netsnmp_cert * |
| netsnmp_cert_find(int what, int where, void *hint) |
| { |
| netsnmp_cert *result = NULL; |
| int tmp; |
| char *fp, *hint_str; |
| |
| DEBUGMSGT(("cert:find:params", "looking for %s(%d) in %s(0x%x), hint %lu\n", |
| _mode_str(what), what, _where_str(where), where, (u_long)hint)); |
| |
| if (NS_CERTKEY_DEFAULT == where) { |
| |
| switch (what) { |
| case NS_CERT_IDENTITY: /* want my ID */ |
| tmp = (ptrdiff_t)hint; |
| DEBUGMSGT(("cert:find:params", " hint = %s\n", |
| tmp ? "server" : "client")); |
| fp = |
| netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| tmp ? NETSNMP_DS_LIB_X509_SERVER_PUB : |
| NETSNMP_DS_LIB_X509_CLIENT_PUB ); |
| if (!fp) { |
| /* As a special case, use the application type to |
| determine a file name to pull the default identity |
| from. */ |
| return netsnmp_cert_find(what, NS_CERTKEY_FILE, netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_APPTYPE)); |
| } |
| break; |
| case NS_CERT_REMOTE_PEER: |
| fp = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_PUB); |
| break; |
| default: |
| DEBUGMSGT(("cert:find:err", "unhandled type %d for %s(%d)\n", |
| what, _where_str(where), where)); |
| return NULL; |
| } |
| if (fp) |
| return netsnmp_cert_find(what, NS_CERTKEY_MULTIPLE, fp); |
| return NULL; |
| } /* where = ds store */ |
| else if (NS_CERTKEY_MULTIPLE == where) { |
| /* tries multiple sources of certificates based on ascii lookup keys */ |
| |
| /* Try a fingerprint match first, which should always be done first */ |
| /* (to avoid people naming filenames with conflicting FPs) */ |
| result = netsnmp_cert_find(what, NS_CERTKEY_FINGERPRINT, hint); |
| if (!result) { |
| /* Then try a file name lookup */ |
| result = netsnmp_cert_find(what, NS_CERTKEY_FILE, hint); |
| } |
| } |
| else if (NS_CERTKEY_FINGERPRINT == where) { |
| DEBUGMSGT(("cert:find:params", " hint = %s\n", (char *)hint)); |
| result = _cert_find_fp((char *)hint); |
| } |
| else if (NS_CERTKEY_TARGET_PARAM == where) { |
| if (what != NS_CERT_IDENTITY) { |
| snmp_log(LOG_ERR, "only identity is valid for target params\n"); |
| return NULL; |
| } |
| /** hint == target mib data */ |
| hint_str = (char *)hint; |
| fp = _find_tlstmParams_fingerprint(hint_str); |
| if (NULL != fp) |
| result = _cert_find_fp(fp); |
| |
| } |
| else if (NS_CERTKEY_TARGET_ADDR == where) { |
| |
| /** hint == target mib data */ |
| if (what != NS_CERT_REMOTE_PEER) { |
| snmp_log(LOG_ERR, "only peer is valid for target addr\n"); |
| return NULL; |
| } |
| /** hint == target mib data */ |
| hint_str = (char *)hint; |
| fp = _find_tlstmAddr_fingerprint(hint_str); |
| if (NULL != fp) |
| result = _cert_find_fp(fp); |
| |
| } |
| else 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_find_subset_fn( filename, NULL ); |
| if (!matching) |
| return NULL; |
| if (1 == matching->size) |
| result = (netsnmp_cert*)matching->array[0]; |
| else { |
| DEBUGMSGT(("cert:find:err", "%s matches multiple certs\n", |
| filename)); |
| result = NULL; |
| } |
| free(matching->array); |
| free(matching); |
| } /* where = NS_CERTKEY_FILE */ |
| else { /* unknown location */ |
| |
| DEBUGMSGT(("cert:find:err", "unhandled location %d for %d\n", where, |
| what)); |
| return NULL; |
| } |
| |
| if (NULL == result) |
| return NULL; |
| |
| /** make sure result found can be used for specified type */ |
| if (!(result->info.allowed_uses & what)) { |
| DEBUGMSGT(("cert:find:err", |
| "cert %s / %s not allowed for %s(%d) (uses=%s (%d))\n", |
| result->info.filename, result->fingerprint, _mode_str(what), |
| what , _mode_str(result->info.allowed_uses), |
| result->info.allowed_uses)); |
| return NULL; |
| } |
| |
| /** make sure we have the cert data */ |
| if (netsnmp_cert_load_x509(result) == CERT_LOAD_ERR) |
| return NULL; |
| |
| DEBUGMSGT(("cert:find:found", |
| "using cert %s / %s for %s(%d) (uses=%s (%d))\n", |
| result->info.filename, result->fingerprint, _mode_str(what), |
| what , _mode_str(result->info.allowed_uses), |
| result->info.allowed_uses)); |
| |
| return result; |
| } |
| |
| int |
| netsnmp_cert_validate(int who, int how, X509 *cert) |
| { |
| return -1; |
| } |
| |
| int |
| netsnmp_cert_check_vb_fingerprint(const netsnmp_variable_list *var) |
| { |
| if (!var) |
| return SNMP_ERR_GENERR; |
| |
| if (0 == var->val_len) /* empty allowed in some cases */ |
| return SNMP_ERR_NOERROR; |
| |
| if (! (0x01 & var->val_len)) { /* odd len */ |
| DEBUGMSGT(("cert:varbind:fingerprint", |
| "expecting odd length for fingerprint\n")); |
| return SNMP_ERR_WRONGLENGTH; |
| } |
| |
| if (var->val.string[0] > NS_HASH_MAX) { |
| DEBUGMSGT(("cert:varbind:fingerprint", "hashtype %d > max %d\n", |
| var->val.string[0], NS_HASH_MAX)); |
| return SNMP_ERR_WRONGVALUE; |
| } |
| |
| return SNMP_ERR_NOERROR; |
| } |
| |
| /** |
| * break a SnmpTLSFingerprint into an integer hash type + hex string |
| * |
| * @return SNMPERR_SUCCESS : on success |
| * @return SNMPERR_GENERR : on failure |
| */ |
| int |
| netsnmp_tls_fingerprint_parse(const u_char *binary_fp, int fp_len, |
| char **fp_str_ptr, u_int *fp_str_len, int realloc, |
| u_char *hash_type_ptr) |
| { |
| int needed; |
| size_t fp_str_size; |
| |
| netsnmp_require_ptr_LRV( hash_type_ptr, SNMPERR_GENERR ); |
| netsnmp_require_ptr_LRV( fp_str_ptr, SNMPERR_GENERR ); |
| netsnmp_require_ptr_LRV( fp_str_len, SNMPERR_GENERR ); |
| |
| /* |
| * output string is binary fp length (minus 1 for initial hash type |
| * char) * 2 for bin to hex conversion, + 1 for null termination. |
| */ |
| needed = ((fp_len - 1) * 2) + 1; |
| if (*fp_str_len < needed) { |
| DEBUGMSGT(("tls:fp:parse", "need %d bytes for output\n", needed )); |
| return SNMPERR_GENERR; |
| } |
| |
| /* |
| * make sure hash type is in valid range |
| */ |
| if ((0 == binary_fp[0]) || (binary_fp[0] > NS_HASH_MAX)) { |
| DEBUGMSGT(("tls:fp:parse", "invalid hash type %d\n", |
| binary_fp[0])); |
| return SNMPERR_GENERR; |
| } |
| |
| /* |
| * netsnmp_binary_to_hex allocate space for string, if needed |
| */ |
| fp_str_size = *fp_str_len; |
| *hash_type_ptr = binary_fp[0]; |
| netsnmp_binary_to_hex((u_char**)fp_str_ptr, &fp_str_size, |
| realloc, &binary_fp[1], fp_len - 1); |
| *fp_str_len = fp_str_size; |
| if (0 == *fp_str_len) |
| return SNMPERR_GENERR; |
| |
| return SNMPERR_SUCCESS; |
| } |
| |
| /** |
| * combine a hash type and hex fingerprint into a SnmpTLSFingerprint |
| * |
| * On entry, tls_fp_len should point to the size of the tls_fp buffer. |
| * On a successful exit, tls_fp_len will contain the length of the |
| * fingerprint buffer. |
| */ |
| int |
| netsnmp_tls_fingerprint_build(int hash_type, const char *hex_fp, |
| u_char **tls_fp, size_t *tls_fp_len, |
| int realloc) |
| { |
| int hex_fp_len, rc; |
| size_t tls_fp_size; |
| size_t offset; |
| |
| netsnmp_require_ptr_LRV( hex_fp, SNMPERR_GENERR ); |
| netsnmp_require_ptr_LRV( tls_fp, SNMPERR_GENERR ); |
| netsnmp_require_ptr_LRV( tls_fp_len, SNMPERR_GENERR ); |
| |
| hex_fp_len = strlen(hex_fp); |
| if (0 == hex_fp_len) { |
| *tls_fp_len = 0; |
| return SNMPERR_SUCCESS; |
| } |
| |
| if ((hash_type <= NS_HASH_NONE) || (hash_type > NS_HASH_MAX)) { |
| DEBUGMSGT(("tls:fp:build", "invalid hash type %d\n", hash_type )); |
| return SNMPERR_GENERR; |
| } |
| |
| /* |
| * convert to binary |
| */ |
| offset = 1; |
| rc = netsnmp_hex_to_binary(tls_fp, &tls_fp_size, &offset, realloc, hex_fp, |
| ":"); |
| *tls_fp_len = tls_fp_size; |
| if (rc != 1) |
| return SNMPERR_GENERR; |
| *tls_fp_len = offset; |
| (*tls_fp)[0] = hash_type; |
| |
| return SNMPERR_SUCCESS; |
| } |
| |
| /** |
| * Trusts a given certificate for use in TLS translations. |
| * |
| * @param ctx The SSL context to trust the certificate in |
| * @param thiscert The netsnmp_cert certificate to trust |
| * |
| * @return SNMPERR_SUCCESS : on success |
| * @return SNMPERR_GENERR : on failure |
| */ |
| int |
| netsnmp_cert_trust(SSL_CTX *ctx, netsnmp_cert *thiscert) |
| { |
| X509_STORE *certstore; |
| X509 *cert; |
| char *fingerprint; |
| |
| /* ensure all needed pieces are present */ |
| netsnmp_assert_or_msgreturn(NULL != thiscert, "NULL certificate passed in", |
| SNMPERR_GENERR); |
| netsnmp_assert_or_msgreturn(NULL != thiscert->info.dir, |
| "NULL certificate directory name passed in", |
| SNMPERR_GENERR); |
| netsnmp_assert_or_msgreturn(NULL != thiscert->info.filename, |
| "NULL certificate filename name passed in", |
| SNMPERR_GENERR); |
| |
| /* get the trusted certificate store and the certificate to load into it */ |
| certstore = SSL_CTX_get_cert_store(ctx); |
| netsnmp_assert_or_msgreturn(NULL != certstore, |
| "failed to get certificate trust store", |
| SNMPERR_GENERR); |
| cert = netsnmp_ocert_get(thiscert); |
| netsnmp_assert_or_msgreturn(NULL != cert, |
| "failed to get certificate from netsnmp_cert", |
| SNMPERR_GENERR); |
| |
| /* Put the certificate into the store */ |
| fingerprint = netsnmp_openssl_cert_get_fingerprint(cert, -1); |
| DEBUGMSGTL(("cert:trust", |
| "putting trusted cert %p = %s in certstore %p\n", cert, |
| fingerprint, certstore)); |
| SNMP_FREE(fingerprint); |
| X509_STORE_add_cert(certstore, cert); |
| |
| return SNMPERR_SUCCESS; |
| } |
| |
| /** |
| * Trusts a given certificate's root CA for use in TLS translations. |
| * If no issuer is found the existing certificate will be trusted instead. |
| * |
| * @param ctx The SSL context to trust the certificate in |
| * @param thiscert The netsnmp_cert certificate |
| * |
| * @return SNMPERR_SUCCESS : on success |
| * @return SNMPERR_GENERR : on failure |
| */ |
| int |
| netsnmp_cert_trust_ca(SSL_CTX *ctx, netsnmp_cert *thiscert) |
| { |
| netsnmp_assert_or_msgreturn(NULL != thiscert, "NULL certificate passed in", |
| SNMPERR_GENERR); |
| |
| /* find the root CA certificate in the chain */ |
| DEBUGMSGTL(("cert:trust_ca", "checking roots for %p \n", thiscert)); |
| while (thiscert->issuer_cert) { |
| thiscert = thiscert->issuer_cert; |
| DEBUGMSGTL(("cert:trust_ca", " up one to %p\n", thiscert)); |
| } |
| |
| /* Add the found top lever certificate to the store */ |
| return netsnmp_cert_trust(ctx, thiscert); |
| } |
| |
| netsnmp_container * |
| netsnmp_cert_get_trustlist(void) |
| { |
| if (!_trusted_certs) |
| _setup_trusted_certs(); |
| return _trusted_certs; |
| } |
| |
| static void |
| _parse_trustcert(const char *token, char *line) |
| { |
| if (!_trusted_certs) |
| _setup_trusted_certs(); |
| |
| if (!_trusted_certs) |
| return; |
| |
| CONTAINER_INSERT(_trusted_certs, strdup(line)); |
| } |
| |
| /* *************************************************************************** |
| * |
| * mode text functions |
| * |
| */ |
| static const char *_mode_str(u_char mode) |
| { |
| return _modes[mode]; |
| } |
| |
| static const char *_where_str(u_int what) |
| { |
| switch (what) { |
| case NS_CERTKEY_DEFAULT: return "DEFAULT"; |
| case NS_CERTKEY_FILE: return "FILE"; |
| case NS_CERTKEY_FINGERPRINT: return "FINGERPRINT"; |
| case NS_CERTKEY_MULTIPLE: return "MULTIPLE"; |
| case NS_CERTKEY_CA: return "CA"; |
| case NS_CERTKEY_SAN_RFC822: return "SAN_RFC822"; |
| case NS_CERTKEY_SAN_DNS: return "SAN_DNS"; |
| case NS_CERTKEY_SAN_IPADDR: return "SAN_IPADDR"; |
| case NS_CERTKEY_COMMON_NAME: return "COMMON_NAME"; |
| case NS_CERTKEY_TARGET_PARAM: return "TARGET_PARAM"; |
| case NS_CERTKEY_TARGET_ADDR: return "TARGET_ADDR"; |
| } |
| |
| return "UNKNOWN"; |
| } |
| |
| /* *************************************************************************** |
| * |
| * find functions |
| * |
| */ |
| static netsnmp_cert * |
| _cert_find_fp(const char *fingerprint) |
| { |
| netsnmp_cert cert, *result = NULL; |
| char fp[EVP_MAX_MD_SIZE]; |
| |
| if (NULL == fingerprint) |
| return NULL; |
| |
| strlcpy(fp, fingerprint, sizeof(fp)); |
| netsnmp_fp_lowercase_and_strip_colon(fp); |
| |
| /** clear search key */ |
| memset(&cert, 0x00, sizeof(cert)); |
| |
| cert.fingerprint = fp; |
| |
| result = CONTAINER_FIND(_certs,&cert); |
| return result; |
| } |
| |
| /* |
| * reduce subset by eliminating any filenames that are longer than |
| * the specified file name. e.g. 'snmp' would match 'snmp.key' and |
| * 'snmpd.key'. We only want 'snmp.X', where X is a valid extension. |
| */ |
| static void |
| _reduce_subset(netsnmp_void_array *matching, const char *filename) |
| { |
| netsnmp_cert_common *cc; |
| int i = 0, j, newsize, pos; |
| |
| if ((NULL == matching) || (NULL == filename)) |
| return; |
| |
| pos = strlen(filename); |
| 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. Note that we do not want to use |
| * _type_from_filename. |
| */ |
| cc = (netsnmp_cert_common*)matching->array[i]; |
| if (('.' == cc->filename[pos]) && |
| (NS_CERT_TYPE_UNKNOWN != _cert_ext_type(&cc->filename[pos+1]))) { |
| ++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:reduce", "shrank from %" NETSNMP_PRIz "d to %d\n", |
| matching->size, newsize)); |
| matching->size = newsize; |
| } |
| } |
| |
| /* |
| * reduce subset by eliminating any filenames that are not under the |
| * specified directory path. |
| */ |
| static void |
| _reduce_subset_dir(netsnmp_void_array *matching, const char *directory) |
| { |
| netsnmp_cert_common *cc; |
| int i = 0, j, newsize, dir_len; |
| char dir[SNMP_MAXPATH], *pos; |
| |
| if ((NULL == matching) || (NULL == directory)) |
| return; |
| |
| newsize = matching->size; |
| |
| /* |
| * dir struct should be something like |
| * /usr/share/snmp/tls/certs |
| * /usr/share/snmp/tls/private |
| * |
| * so we want to backup up on directory for compares.. |
| */ |
| strlcpy(dir, directory, sizeof(dir)); |
| pos = strrchr(dir, '/'); |
| if (NULL == pos) { |
| DEBUGMSGTL(("cert:subset:dir", "no '/' in directory %s\n", directory)); |
| return; |
| } |
| *pos = '\0'; |
| dir_len = strlen(dir); |
| |
| 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. |
| */ |
| cc = (netsnmp_cert_common*)matching->array[i]; |
| if (strncmp(dir, cc->dir, dir_len) == 0) { |
| ++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:dir", "shrank from %" NETSNMP_PRIz "d to %d\n", |
| matching->size, newsize)); |
| matching->size = newsize; |
| } |
| } |
| |
| static netsnmp_void_array * |
| _cert_find_subset_common(const char *filename, netsnmp_container *container) |
| { |
| netsnmp_cert_common search; |
| netsnmp_void_array *matching; |
| |
| netsnmp_assert(filename && container); |
| |
| memset(&search, 0x00, sizeof(search)); /* clear search key */ |
| |
| search.filename = NETSNMP_REMOVE_CONST(char*,filename); |
| |
| matching = CONTAINER_GET_SUBSET(container, &search); |
| DEBUGMSGT(("9:cert:subset:found", "%" NETSNMP_PRIz "d matches\n", matching ? |
| matching->size : 0)); |
| if (matching && matching->size > 1) |
| _reduce_subset(matching, filename); |
| return matching; |
| } |
| |
| static netsnmp_void_array * |
| _cert_find_subset_fn(const char *filename, const char *directory) |
| { |
| netsnmp_container *fn_container; |
| netsnmp_void_array *matching; |
| |
| /** find subcontainer with filename as key */ |
| fn_container = SUBCONTAINER_FIND(_certs, "certs_fn"); |
| netsnmp_assert(fn_container); |
| |
| matching = _cert_find_subset_common(filename, fn_container); |
| if (matching && (matching->size > 1) && directory) |
| _reduce_subset_dir(matching, directory); |
| return matching; |
| } |
| |
| static netsnmp_void_array * |
| _cert_find_subset_sn(const char *subject) |
| { |
| netsnmp_cert search; |
| netsnmp_void_array *matching; |
| netsnmp_container *sn_container; |
| |
| /** find subcontainer with subject as key */ |
| sn_container = SUBCONTAINER_FIND(_certs, "certs_sn"); |
| netsnmp_assert(sn_container); |
| |
| memset(&search, 0x00, sizeof(search)); /* clear search key */ |
| |
| search.subject = NETSNMP_REMOVE_CONST(char*,subject); |
| |
| matching = CONTAINER_GET_SUBSET(sn_container, &search); |
| DEBUGMSGT(("9:cert:subset:found", "%" NETSNMP_PRIz "d matches\n", matching ? |
| matching->size : 0)); |
| return matching; |
| } |
| |
| static netsnmp_void_array * |
| _key_find_subset(const char *filename) |
| { |
| return _cert_find_subset_common(filename, _keys); |
| } |
| |
| /** find all entries matching given fingerprint */ |
| static netsnmp_void_array * |
| _find_subset_fp(netsnmp_container *certs, const char *fp) |
| { |
| netsnmp_cert_map entry; |
| netsnmp_container *fp_container; |
| netsnmp_void_array *va; |
| |
| if ((NULL == certs) || (NULL == fp)) |
| return NULL; |
| |
| fp_container = SUBCONTAINER_FIND(certs, "cert2sn_fp"); |
| netsnmp_assert_or_msgreturn(fp_container, "cert2sn_fp container missing", |
| NULL); |
| |
| memset(&entry, 0x0, sizeof(entry)); |
| |
| entry.fingerprint = NETSNMP_REMOVE_CONST(char*,fp); |
| |
| va = CONTAINER_GET_SUBSET(fp_container, &entry); |
| return va; |
| } |
| |
| #if 0 /* not used yet */ |
| static netsnmp_key * |
| _key_find_fn(const char *filename) |
| { |
| netsnmp_key key, *result = NULL; |
| |
| netsnmp_assert(NULL != filename); |
| |
| memset(&key, 0x00, sizeof(key)); /* clear search key */ |
| key.info.filename = NETSNMP_REMOVE_CONST(char*,filename); |
| result = CONTAINER_FIND(_keys,&key); |
| return result; |
| } |
| #endif |
| |
| static int |
| _time_filter(netsnmp_file *f, struct stat *idx) |
| { |
| /** include if mtime or ctime newer than index mtime */ |
| if (f && idx && f->stats && |
| ((f->stats->st_mtime >= idx->st_mtime) || |
| (f->stats->st_ctime >= idx->st_mtime))) |
| return NETSNMP_DIR_INCLUDE; |
| |
| return NETSNMP_DIR_EXCLUDE; |
| } |
| |
| /* *************************************************************************** |
| * *************************************************************************** |
| * |
| * |
| * cert map functions |
| * |
| * |
| * *************************************************************************** |
| * ***************************************************************************/ |
| #define MAP_CONFIG_TOKEN "certSecName" |
| static void _parse_map(const char *token, char *line); |
| static void _map_free(netsnmp_cert_map* entry, void *ctx); |
| static void _purge_config_entries(void); |
| |
| static void |
| _init_tlstmCertToTSN(void) |
| { |
| const char *certSecName_help = MAP_CONFIG_TOKEN " PRIORITY FINGERPRINT " |
| "[--shaNN|md5] <--sn SECNAME | --rfc822 | --dns | --ip | --cn | --any>"; |
| |
| /* |
| * container for cert to fingerprint mapping, with fingerprint key |
| */ |
| _maps = netsnmp_cert_map_container_create(1); |
| |
| register_config_handler(NULL, MAP_CONFIG_TOKEN, _parse_map, _purge_config_entries, |
| certSecName_help); |
| } |
| |
| netsnmp_cert_map * |
| netsnmp_cert_map_alloc(char *fingerprint, X509 *ocert) |
| { |
| netsnmp_cert_map *cert_map = SNMP_MALLOC_TYPEDEF(netsnmp_cert_map); |
| if (NULL == cert_map) { |
| snmp_log(LOG_ERR, "could not allocate netsnmp_cert_map\n"); |
| return NULL; |
| } |
| |
| if (fingerprint) { |
| /** MIB limits to 255 bytes; 2x since we've got ascii */ |
| if (strlen(fingerprint) > (SNMPADMINLENGTH * 2)) { |
| snmp_log(LOG_ERR, "fingerprint %s exceeds max length %d\n", |
| fingerprint, (SNMPADMINLENGTH * 2)); |
| free(cert_map); |
| return NULL; |
| } |
| cert_map->fingerprint = strdup(fingerprint); |
| } |
| if (ocert) { |
| cert_map->hashType = netsnmp_openssl_cert_get_hash_type(ocert); |
| cert_map->ocert = ocert; |
| } |
| |
| return cert_map; |
| } |
| |
| void |
| netsnmp_cert_map_free(netsnmp_cert_map *cert_map) |
| { |
| if (NULL == cert_map) |
| return; |
| |
| SNMP_FREE(cert_map->fingerprint); |
| SNMP_FREE(cert_map->data); |
| /** x509 cert isn't ours */ |
| free(cert_map); /* SNMP_FREE wasted on param */ |
| } |
| |
| int |
| netsnmp_cert_map_add(netsnmp_cert_map *map) |
| { |
| int rc; |
| |
| if (NULL == map) |
| return -1; |
| |
| DEBUGMSGTL(("cert:map:add", "pri %d, fp %s\n", |
| map->priority, map->fingerprint)); |
| |
| if ((rc = CONTAINER_INSERT(_maps, map)) != 0) |
| snmp_log(LOG_ERR, "could not insert new certificate map"); |
| |
| return rc; |
| } |
| |
| int |
| netsnmp_cert_map_remove(netsnmp_cert_map *map) |
| { |
| int rc; |
| |
| if (NULL == map) |
| return -1; |
| |
| DEBUGMSGTL(("cert:map:remove", "pri %d, fp %s\n", |
| map->priority, map->fingerprint)); |
| |
| if ((rc = CONTAINER_REMOVE(_maps, map)) != 0) |
| snmp_log(LOG_ERR, "could not remove certificate map"); |
| |
| return rc; |
| } |
| |
| netsnmp_cert_map * |
| netsnmp_cert_map_find(netsnmp_cert_map *map) |
| { |
| if (NULL == map) |
| return NULL; |
| |
| return CONTAINER_FIND(_maps, map); |
| } |
| |
| static void |
| _map_free(netsnmp_cert_map *map, void *context) |
| { |
| netsnmp_cert_map_free(map); |
| } |
| |
| static int |
| _map_compare(netsnmp_cert_map *lhs, netsnmp_cert_map *rhs) |
| { |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| if (lhs->priority < rhs->priority) |
| return -1; |
| else if (lhs->priority > rhs->priority) |
| return 1; |
| |
| return strcmp(lhs->fingerprint, rhs->fingerprint); |
| } |
| |
| static int |
| _map_fp_compare(netsnmp_cert_map *lhs, netsnmp_cert_map *rhs) |
| { |
| int rc; |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| if ((rc = strcmp(lhs->fingerprint, rhs->fingerprint)) != 0) |
| return rc; |
| |
| if (lhs->priority < rhs->priority) |
| return -1; |
| else if (lhs->priority > rhs->priority) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int |
| _map_fp_ncompare(netsnmp_cert_map *lhs, netsnmp_cert_map *rhs) |
| { |
| netsnmp_assert((lhs != NULL) && (rhs != NULL)); |
| |
| return strncmp(lhs->fingerprint, rhs->fingerprint, |
| strlen(rhs->fingerprint)); |
| } |
| |
| netsnmp_container * |
| netsnmp_cert_map_container_create(int with_fp) |
| { |
| netsnmp_container *chain_map = |
| netsnmp_container_find("cert_map:stack:binary_array"); |
| netsnmp_container *fp; |
| |
| if (NULL == chain_map) { |
| snmp_log(LOG_ERR, "could not allocate container for cert_map\n"); |
| return NULL; |
| } |
| |
| chain_map->container_name = strdup("cert_map"); |
| chain_map->free_item = (netsnmp_container_obj_func*)_map_free; |
| chain_map->compare = (netsnmp_container_compare*)_map_compare; |
| |
| if (!with_fp) |
| return chain_map; |
| |
| /* |
| * add a secondary index to the table container |
| */ |
| fp = netsnmp_container_find("cert2sn_fp:binary_array"); |
| if (NULL == fp) { |
| snmp_log(LOG_ERR, |
| "error creating sub-container for tlstmCertToTSNTable\n"); |
| CONTAINER_FREE(chain_map); |
| return NULL; |
| } |
| fp->container_name = strdup("cert2sn_fp"); |
| fp->compare = (netsnmp_container_compare*)_map_fp_compare; |
| fp->ncompare = (netsnmp_container_compare*)_map_fp_ncompare; |
| netsnmp_container_add_index(chain_map, fp); |
| |
| return chain_map; |
| } |
| |
| int |
| netsnmp_cert_parse_hash_type(const char *str) |
| { |
| int rc = se_find_value_in_slist("cert_hash_alg", str); |
| if (SE_DNE == rc) |
| return NS_HASH_NONE; |
| return rc; |
| } |
| |
| void |
| netsnmp_cert_map_container_free(netsnmp_container *c) |
| { |
| if (NULL == c) |
| return; |
| |
| CONTAINER_FREE_ALL(c, NULL); |
| CONTAINER_FREE(c); |
| } |
| |
| /** clear out config rows |
| * called during reconfig processing (e.g. SIGHUP) |
| */ |
| static void |
| _purge_config_entries(void) |
| { |
| /** |
| ** dup container |
| ** iterate looking for NSCM_FROM_CONFIG flag |
| ** delete from original |
| ** delete dup |
| **/ |
| netsnmp_iterator *itr; |
| netsnmp_cert_map *cert_map; |
| netsnmp_container *cert_maps = netsnmp_cert_map_container(); |
| netsnmp_container *tmp_maps = NULL; |
| |
| if ((NULL == cert_maps) || (CONTAINER_SIZE(cert_maps) == 0)) |
| return; |
| |
| DEBUGMSGT(("cert:map:reconfig", "removing locally configured rows\n")); |
| |
| /* |
| * duplicate cert_maps and then iterate over the copy. That way we can |
| * add/remove to cert_maps without distrubing the iterator. |
| xx |
| */ |
| tmp_maps = CONTAINER_DUP(cert_maps, NULL, 0); |
| if (NULL == tmp_maps) { |
| snmp_log(LOG_ERR, "could not duplicate maps for reconfig\n"); |
| return; |
| } |
| |
| itr = CONTAINER_ITERATOR(tmp_maps); |
| if (NULL == itr) { |
| snmp_log(LOG_ERR, "could not get iterator for reconfig\n"); |
| CONTAINER_FREE(tmp_maps); |
| return; |
| } |
| cert_map = ITERATOR_FIRST(itr); |
| for( ; cert_map; cert_map = ITERATOR_NEXT(itr)) { |
| |
| if (!(cert_map->flags & NSCM_FROM_CONFIG)) |
| continue; |
| |
| if (CONTAINER_REMOVE(cert_maps, cert_map) == 0) |
| netsnmp_cert_map_free(cert_map); |
| } |
| ITERATOR_RELEASE(itr); |
| CONTAINER_FREE(tmp_maps); |
| |
| return; |
| } |
| |
| /* |
| certSecName PRIORITY [--shaNN|md5] FINGERPRINT <--sn SECNAME | --rfc822 | --dns | --ip | --cn | --any> |
| |
| certSecName 100 ff:..11 --sn Wes |
| certSecName 200 ee:..:22 --sn JohnDoe |
| certSecName 300 ee:..:22 --rfc822 |
| */ |
| netsnmp_cert_map * |
| netsnmp_certToTSN_parse_common(char **line) |
| { |
| netsnmp_cert_map *map; |
| char *tmp, buf[SNMP_MAXBUF_SMALL]; |
| size_t len; |
| netsnmp_cert *tmpcert; |
| |
| if ((NULL == line) || (NULL == *line)) |
| return NULL; |
| |
| /** need somewhere to save rows */ |
| if (NULL == _maps) { |
| NETSNMP_LOGONCE((LOG_ERR, "no container for certificate mappings\n")); |
| return NULL; |
| } |
| |
| DEBUGMSGT(("cert:util:config", "parsing %s\n", *line)); |
| |
| /* read the priority */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| if (!isdigit(0xFF & tmp[0])) { |
| netsnmp_config_error("could not parse priority"); |
| return NULL; |
| } |
| map = netsnmp_cert_map_alloc(NULL, NULL); |
| if (NULL == map) { |
| netsnmp_config_error("could not allocate cert map struct"); |
| return NULL; |
| } |
| map->flags |= NSCM_FROM_CONFIG; |
| map->priority = atoi(buf); |
| |
| /* read the flag or the fingerprint */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| if ((buf[0] == '-') && (buf[1] == '-')) { |
| map->hashType = netsnmp_cert_parse_hash_type(&buf[2]); |
| if (NS_HASH_NONE == map->hashType) { |
| netsnmp_config_error("invalid hash type"); |
| goto end; |
| } |
| |
| /** set up for fingerprint */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| } |
| else |
| map->hashType = NS_HASH_SHA1; |
| |
| /* look up the fingerprint */ |
| tmpcert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_MULTIPLE, buf); |
| if (NULL == tmpcert) { |
| /* assume it's a raw fingerprint we don't have */ |
| netsnmp_fp_lowercase_and_strip_colon(buf); |
| map->fingerprint = strdup(buf); |
| } else { |
| map->fingerprint = |
| netsnmp_openssl_cert_get_fingerprint(tmpcert->ocert, -1); |
| } |
| |
| if (NULL == *line) { |
| netsnmp_config_error("must specify map type"); |
| goto end; |
| } |
| |
| /* read the mapping type */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| if ((buf[0] != '-') || (buf[1] != '-')) { |
| netsnmp_config_error("unexpected fromat: %s\n", *line); |
| goto end; |
| } |
| if (strcmp(&buf[2], "sn") == 0) { |
| if (NULL == *line) { |
| netsnmp_config_error("must specify secName for --sn"); |
| goto end; |
| } |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| map->data = strdup(buf); |
| if (map->data) |
| map->mapType = TSNM_tlstmCertSpecified; |
| } |
| else if (strcmp(&buf[2], "cn") == 0) |
| map->mapType = TSNM_tlstmCertCommonName; |
| else if (strcmp(&buf[2], "ip") == 0) |
| map->mapType = TSNM_tlstmCertSANIpAddress; |
| else if (strcmp(&buf[2], "rfc822") == 0) |
| map->mapType = TSNM_tlstmCertSANRFC822Name; |
| else if (strcmp(&buf[2], "dns") == 0) |
| map->mapType = TSNM_tlstmCertSANDNSName; |
| else if (strcmp(&buf[2], "any") == 0) |
| map->mapType = TSNM_tlstmCertSANAny; |
| else |
| netsnmp_config_error("unknown argument %s\n", buf); |
| |
| end: |
| if (0 == map->mapType) { |
| netsnmp_cert_map_free(map); |
| map = NULL; |
| } |
| |
| return map; |
| } |
| |
| static void |
| _parse_map(const char *token, char *line) |
| { |
| netsnmp_cert_map *map = netsnmp_certToTSN_parse_common(&line); |
| if (NULL == map) |
| return; |
| |
| if (netsnmp_cert_map_add(map) != 0) { |
| netsnmp_cert_map_free(map); |
| netsnmp_config_error(MAP_CONFIG_TOKEN |
| ": duplicate priority for certificate map"); |
| } |
| } |
| |
| static int |
| _fill_cert_map(netsnmp_cert_map *cert_map, netsnmp_cert_map *entry) |
| { |
| DEBUGMSGT(("cert:map:secname", "map: pri %d type %d data %s\n", |
| entry->priority, entry->mapType, entry->data)); |
| cert_map->priority = entry->priority; |
| cert_map->mapType = entry->mapType; |
| cert_map->hashType = entry->hashType; |
| if (entry->data) { |
| cert_map->data = strdup(entry->data); |
| if (NULL == cert_map->data ) { |
| snmp_log(LOG_ERR, "secname map data dup failed\n"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * get secname map(s) for fingerprints |
| */ |
| int |
| netsnmp_cert_get_secname_maps(netsnmp_container *cert_maps) |
| { |
| netsnmp_iterator *itr; |
| netsnmp_cert_map *cert_map, *new_cert_map, *entry; |
| netsnmp_container *new_maps = NULL; |
| netsnmp_void_array *results; |
| int j; |
| |
| if ((NULL == cert_maps) || (CONTAINER_SIZE(cert_maps) == 0)) |
| return -1; |
| |
| DEBUGMSGT(("cert:map:secname", "looking for matches for %" NETSNMP_PRIz "d fingerprints\n", |
| CONTAINER_SIZE(cert_maps))); |
| |
| /* |
| * duplicate cert_maps and then iterate over the copy. That way we can |
| * add/remove to cert_maps without distrubing the iterator. |
| */ |
| new_maps = CONTAINER_DUP(cert_maps, NULL, 0); |
| if (NULL == new_maps) { |
| snmp_log(LOG_ERR, "could not duplicate maps for secname mapping\n"); |
| return -1; |
| } |
| |
| itr = CONTAINER_ITERATOR(new_maps); |
| if (NULL == itr) { |
| snmp_log(LOG_ERR, "could not get iterator for secname mappings\n"); |
| CONTAINER_FREE(new_maps); |
| return -1; |
| } |
| cert_map = ITERATOR_FIRST(itr); |
| for( ; cert_map; cert_map = ITERATOR_NEXT(itr)) { |
| |
| results = _find_subset_fp( netsnmp_cert_map_container(), |
| cert_map->fingerprint ); |
| if (NULL == results) { |
| DEBUGMSGT(("cert:map:secname", "no match for %s\n", |
| cert_map->fingerprint)); |
| if (CONTAINER_REMOVE(cert_maps, cert_map) != 0) |
| goto fail; |
| continue; |
| } |
| DEBUGMSGT(("cert:map:secname", "%" NETSNMP_PRIz "d matches for %s\n", |
| results->size, cert_map->fingerprint)); |
| /* |
| * first entry is a freebie |
| */ |
| entry = (netsnmp_cert_map*)results->array[0]; |
| if (_fill_cert_map(cert_map, entry) != 0) |
| goto fail; |
| |
| /* |
| * additional entries must be allocated/inserted |
| */ |
| if (results->size > 1) { |
| for(j=1; j < results->size; ++j) { |
| entry = (netsnmp_cert_map*)results->array[j]; |
| new_cert_map = netsnmp_cert_map_alloc(entry->fingerprint, |
| entry->ocert); |
| if (NULL == new_cert_map) { |
| snmp_log(LOG_ERR, |
| "could not allocate new cert map entry\n"); |
| goto fail; |
| } |
| if (_fill_cert_map(new_cert_map, entry) != 0) { |
| netsnmp_cert_map_free(new_cert_map); |
| goto fail; |
| } |
| new_cert_map->ocert = cert_map->ocert; |
| if (CONTAINER_INSERT(cert_maps,new_cert_map) != 0) { |
| netsnmp_cert_map_free(new_cert_map); |
| goto fail; |
| } |
| } /* for results */ |
| } /* results size > 1 */ |
| |
| free(results->array); |
| SNMP_FREE(results); |
| } |
| ITERATOR_RELEASE(itr); |
| CONTAINER_FREE(new_maps); |
| |
| DEBUGMSGT(("cert:map:secname", |
| "found %" NETSNMP_PRIz "d matches for fingerprints\n", |
| CONTAINER_SIZE(cert_maps))); |
| return 0; |
| |
| fail: |
| if (results) { |
| free(results->array); |
| free(results); |
| } |
| ITERATOR_RELEASE(itr); |
| CONTAINER_FREE(new_maps); |
| return -1; |
| } |
| |
| /* *************************************************************************** |
| * *************************************************************************** |
| * |
| * |
| * snmpTlstmParmsTable data |
| * |
| * |
| * *************************************************************************** |
| * ***************************************************************************/ |
| #define PARAMS_CONFIG_TOKEN "snmpTlstmParams" |
| static void _parse_params(const char *token, char *line); |
| |
| static void |
| _init_tlstmParams(void) |
| { |
| const char *params_help = |
| PARAMS_CONFIG_TOKEN " targetParamsName hashType:fingerPrint"; |
| |
| /* |
| * container for snmpTlstmParamsTable data |
| */ |
| _tlstmParams = netsnmp_container_find("tlstmParams:string"); |
| if (NULL == _tlstmParams) |
| snmp_log(LOG_ERR, |
| "error creating sub-container for tlstmParamsTable\n"); |
| else |
| _tlstmParams->container_name = strdup("tlstmParams"); |
| |
| register_config_handler(NULL, PARAMS_CONFIG_TOKEN, _parse_params, NULL, |
| params_help); |
| } |
| |
| netsnmp_container * |
| netsnmp_tlstmParams_container(void) |
| { |
| return _tlstmParams; |
| } |
| |
| snmpTlstmParams * |
| netsnmp_tlstmParams_create(const char *name, int hashType, const char *fp, |
| int fp_len) |
| { |
| snmpTlstmParams *stp = SNMP_MALLOC_TYPEDEF(snmpTlstmParams); |
| if (NULL == stp) |
| return NULL; |
| |
| if (name) |
| stp->name = strdup(name); |
| stp->hashType = hashType; |
| if (fp) |
| stp->fingerprint = strdup(fp); |
| DEBUGMSGT(("9:tlstmParams:create", "0x%lx: %s\n", (u_long)stp, |
| stp->name ? stp->name : "null")); |
| |
| return stp; |
| } |
| |
| void |
| netsnmp_tlstmParams_free(snmpTlstmParams *stp) |
| { |
| if (NULL == stp) |
| return; |
| |
| DEBUGMSGT(("9:tlstmParams:release", "0x%lx %s\n", (u_long)stp, |
| stp->name ? stp->name : "null")); |
| SNMP_FREE(stp->name); |
| SNMP_FREE(stp->fingerprint); |
| free(stp); /* SNMP_FREE pointless on parameter */ |
| } |
| |
| snmpTlstmParams * |
| netsnmp_tlstmParams_restore_common(char **line) |
| { |
| snmpTlstmParams *stp; |
| char *tmp, buf[SNMP_MAXBUF_SMALL]; |
| size_t len; |
| |
| if ((NULL == line) || (NULL == *line)) |
| return NULL; |
| |
| /** need somewhere to save rows */ |
| netsnmp_assert(_tlstmParams); |
| |
| stp = netsnmp_tlstmParams_create(NULL, 0, NULL, 0); |
| if (NULL == stp) |
| return NULL; |
| |
| /** name */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| /** xxx-rks: validate snmpadminstring? */ |
| if (len) |
| stp->name = strdup(buf); |
| |
| /** fingerprint hash type*/ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| if ((buf[0] == '-') && (buf[1] == '-')) { |
| stp->hashType = netsnmp_cert_parse_hash_type(&buf[2]); |
| |
| /** set up for fingerprint */ |
| len = sizeof(buf); |
| tmp = buf; |
| *line = read_config_read_octet_string(*line, (u_char **)&tmp, &len); |
| tmp[len] = 0; |
| } |
| else |
| stp->hashType =NS_HASH_SHA1; |
| |
| netsnmp_fp_lowercase_and_strip_colon(buf); |
| stp->fingerprint = strdup(buf); |
| stp->fingerprint_len = strlen(buf); |
| |
| DEBUGMSGTL(("tlstmParams:restore:common", "name '%s'\n", stp->name)); |
| |
| return stp; |
| } |
| |
| int |
| netsnmp_tlstmParams_add(snmpTlstmParams *stp) |
| { |
| if (NULL == stp) |
| return -1; |
| |
| DEBUGMSGTL(("tlstmParams:add", "adding entry 0x%lx %s\n", (u_long)stp, |
| stp->name)); |
| |
| if (CONTAINER_INSERT(_tlstmParams, stp) != 0) { |
| snmp_log(LOG_ERR, "error inserting tlstmParams %s", stp->name); |
| netsnmp_tlstmParams_free(stp); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| netsnmp_tlstmParams_remove(snmpTlstmParams *stp) |
| { |
| if (NULL == stp) |
| return -1; |
| |
| DEBUGMSGTL(("tlstmParams:remove", "removing entry 0x%lx %s\n", (u_long)stp, |
| stp->name)); |
| |
| if (CONTAINER_REMOVE(_tlstmParams, stp) != 0) { |
| snmp_log(LOG_ERR, "error removing tlstmParams %s", stp->name); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| snmpTlstmParams * |
| netsnmp_tlstmParams_find(snmpTlstmParams *stp) |
| { |
| snmpTlstmParams *found; |
| |
| if (NULL == stp) |
| return NULL; |
| |
| found = CONTAINER_FIND(_tlstmParams, stp); |
| return found; |
| } |
| |
| static void |
| _parse_params(const char *token, char *line) |
| { |
| snmpTlstmParams *stp = netsnmp_tlstmParams_restore_common(&line); |
| |
| if (!stp) |
| return; |
| |
| stp->flags = TLSTM_PARAMS_FROM_CONFIG | TLSTM_PARAMS_NONVOLATILE; |
| |
| netsnmp_tlstmParams_add(stp); |
| } |
| |
| static char * |
| _find_tlstmParams_fingerprint(const char *name) |
| { |
| snmpTlstmParams lookup_key, *result; |
| |
| if (NULL == name) |
| return NULL; |
| |
| lookup_key.name = NETSNMP_REMOVE_CONST(char*, name); |
| |
| result = CONTAINER_FIND(_tlstmParams, &lookup_key); |
| if ((NULL == result) || (NULL == result->fingerprint)) |
| return NULL; |
| |
| return strdup(result->fingerprint); |
| } |
| /* |
| * END snmpTlstmParmsTable data |
| * ***************************************************************************/ |
| |
| /* *************************************************************************** |
| * *************************************************************************** |
| * |
| * |
| * snmpTlstmAddrTable data |
| * |
| * |
| * *************************************************************************** |
| * ***************************************************************************/ |
| #define ADDR_CONFIG_TOKEN "snmpTlstmAddr" |
| static void _parse_addr(const char *token, char *line); |
| |
| static void |
| _init_tlstmAddr(void) |
| { |
| const char *addr_help = |
| ADDR_CONFIG_TOKEN " targetAddrName hashType:fingerprint serverIdentity"; |
| |
| /* |
| * container for snmpTlstmAddrTable data |
| */ |
| _tlstmAddr = netsnmp_container_find("tlstmAddr:string"); |
| if (NULL == _tlstmAddr) |
| snmp_log(LOG_ERR, |
| "error creating sub-container for tlstmAddrTable\n"); |
| else |
| _tlstmAddr->container_name = strdup("tlstmAddr"); |
| |
| register_config_handler(NULL, ADDR_CONFIG_TOKEN, _parse_addr, NULL, |
| addr_help); |
| } |
| |
| netsnmp_container * |
| netsnmp_tlstmAddr_container(void) |
| { |
| return _tlstmAddr; |
| } |
| |
| /* |
| * create a new row in the table |
| */ |
| snmpTlstmAddr * |
| netsnmp_tlstmAddr_create(char *targetAddrName) |
| { |
| snmpTlstmAddr *entry; |
| |
| if (NULL == targetAddrName) |
| return NULL; |
| |
| entry = SNMP_MALLOC_TYPEDEF(snmpTlstmAddr); |
| if (!entry) |
| return NULL; |
| |
| DEBUGMSGT(("tlstmAddr:entry:create", "entry %p %s\n", entry, |
| targetAddrName ? targetAddrName : "NULL")); |
| |
| entry->name = strdup(targetAddrName); |
| |
| return entry; |
| } |
| |
| void |
| netsnmp_tlstmAddr_free(snmpTlstmAddr *entry) |
| { |
| if (!entry) |
| return; |
| |
| SNMP_FREE(entry->name); |
| SNMP_FREE(entry->fingerprint); |
| SNMP_FREE(entry->identity); |
| free(entry); |
| } |
| |
| int |
| netsnmp_tlstmAddr_restore_common(char **line, char *name, size_t *name_len, |
| char *id, size_t *id_len, char *fp, |
| size_t *fp_len, u_char *ht) |
| { |
| size_t fp_len_save = *fp_len; |
| |
| *line = read_config_read_octet_string(*line, (u_char **)&name, name_len); |
| if (NULL == *line) { |
| config_perror("incomplete line"); |
| return -1; |
| } |
| name[*name_len] = 0; |
| |
| *line = read_config_read_octet_string(*line, (u_char **)&fp, fp_len); |
| if (NULL == *line) { |
| config_perror("incomplete line"); |
| return -1; |
| } |
| fp[*fp_len] = 0; |
| if ((fp[0] == '-') && (fp[1] == '-')) { |
| *ht = netsnmp_cert_parse_hash_type(&fp[2]); |
| |
| /** set up for fingerprint */ |
| *fp_len = fp_len_save; |
| *line = read_config_read_octet_string(*line, (u_char **)&fp, fp_len); |
| fp[*fp_len] = 0; |
| } |
| else |
| *ht = NS_HASH_SHA1; |
| netsnmp_fp_lowercase_and_strip_colon(fp); |
| *fp_len = strlen(fp); |
| |
| *line = read_config_read_octet_string(*line, (u_char **)&id, id_len); |
| id[*id_len] = 0; |
| |
| if (*ht <= NS_HASH_NONE || *ht > NS_HASH_MAX) { |
| config_perror("invalid algorithm for fingerprint"); |
| return -1; |
| } |
| |
| if ((0 == *fp_len) && ((0 == *id_len || (*id_len == 1 && id[0] == '*')))) { |
| /* |
| * empty fingerprint not allowed with '*' identity |
| */ |
| config_perror("must specify fingerprint for '*' identity"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| netsnmp_tlstmAddr_add(snmpTlstmAddr *entry) |
| { |
| if (!entry) |
| return -1; |
| |
| DEBUGMSGTL(("tlstmAddr:add", "adding entry 0x%lx %s %s\n", |
| (u_long)entry, entry->name, entry->fingerprint)); |
| if (CONTAINER_INSERT(_tlstmAddr, entry) != 0) { |
| snmp_log(LOG_ERR, "could not insert addr %s", entry->name); |
| netsnmp_tlstmAddr_free(entry); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| netsnmp_tlstmAddr_remove(snmpTlstmAddr *entry) |
| { |
| if (!entry) |
| return -1; |
| |
| if (CONTAINER_REMOVE(_tlstmAddr, entry) != 0) { |
| snmp_log(LOG_ERR, "could not remove addr %s", entry->name); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| _parse_addr(const char *token, char *line) |
| { |
| snmpTlstmAddr *entry; |
| char name[SNMPADMINLENGTH + 1], id[SNMPADMINLENGTH + 1], |
| fingerprint[SNMPTLSFINGERPRINT_MAX_LEN + 1]; |
| size_t name_len = sizeof(name), id_len = sizeof(id), |
| fp_len = sizeof(fingerprint); |
| u_char hashType; |
| int rc; |
| |
| rc = netsnmp_tlstmAddr_restore_common(&line, name, &name_len, id, &id_len, |
| fingerprint, &fp_len, &hashType); |
| if (rc < 0) |
| return; |
| |
| if (NULL != line) |
| config_pwarn("ignore extra tokens on line"); |
| |
| entry = netsnmp_tlstmAddr_create(name); |
| if (NULL == entry) |
| return; |
| |
| entry->flags |= TLSTM_ADDR_FROM_CONFIG; |
| entry->hashType = hashType; |
| if (fp_len) |
| entry->fingerprint = strdup(fingerprint); |
| if (id_len) |
| entry->identity = strdup(id); |
| |
| netsnmp_tlstmAddr_add(entry); |
| } |
| |
| static char * |
| _find_tlstmAddr_fingerprint(const char *name) |
| { |
| snmpTlstmAddr lookup_key, *result; |
| |
| if (NULL == name) |
| return NULL; |
| |
| lookup_key.name = NETSNMP_REMOVE_CONST(char*, name); |
| |
| result = CONTAINER_FIND(_tlstmAddr, &lookup_key); |
| if (NULL == result) |
| return NULL; |
| |
| return result->fingerprint; |
| } |
| |
| char * |
| netsnmp_tlstmAddr_get_serverId(const char *name) |
| { |
| snmpTlstmAddr lookup_key, *result; |
| |
| if (NULL == name) |
| return NULL; |
| |
| lookup_key.name = NETSNMP_REMOVE_CONST(char*, name); |
| |
| result = CONTAINER_FIND(_tlstmAddr, &lookup_key); |
| if (NULL == result) |
| return NULL; |
| |
| return result->identity; |
| } |
| /* |
| * END snmpTlstmAddrTable data |
| * ***************************************************************************/ |
| |
| #else |
| int cert_unused; /* Suppress "empty translation unit" warning */ |
| #endif /* defined(NETSNMP_USE_OPENSSL) && defined(HAVE_LIBSSL) */ |