diff options
Diffstat (limited to 'snmplib/transports/snmpTLSBaseDomain.c')
-rw-r--r-- | snmplib/transports/snmpTLSBaseDomain.c | 1183 |
1 files changed, 1183 insertions, 0 deletions
diff --git a/snmplib/transports/snmpTLSBaseDomain.c b/snmplib/transports/snmpTLSBaseDomain.c new file mode 100644 index 0000000..c54d6ad --- /dev/null +++ b/snmplib/transports/snmpTLSBaseDomain.c @@ -0,0 +1,1183 @@ +#include <net-snmp/net-snmp-config.h> + +#include <net-snmp/net-snmp-features.h> + +netsnmp_feature_require(cert_util) + +#include <net-snmp/library/snmpTLSBaseDomain.h> + +#if HAVE_DMALLOC_H +#include <dmalloc.h> +#endif +#if HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#if HAVE_NETDB_H +#include <netdb.h> +#endif +#include <errno.h> +#include <ctype.h> + +/* OpenSSL Includes */ +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/x509_vfy.h> +#include <openssl/x509v3.h> + +#include <net-snmp/types.h> +#include <net-snmp/config_api.h> +#include <net-snmp/library/cert_util.h> +#include <net-snmp/library/snmp_openssl.h> +#include <net-snmp/library/default_store.h> +#include <net-snmp/library/callback.h> +#include <net-snmp/library/snmp_logging.h> +#include <net-snmp/library/snmp_api.h> +#include <net-snmp/library/tools.h> +#include <net-snmp/library/snmp_debug.h> +#include <net-snmp/library/snmp_assert.h> +#include <net-snmp/library/snmp_transport.h> +#include <net-snmp/library/snmp_secmod.h> +#include <net-snmp/library/read_config.h> +#include <net-snmp/library/system.h> + +#define LOGANDDIE(msg) do { snmp_log(LOG_ERR, "%s\n", msg); return 0; } while(0) + +int openssl_local_index; + +/* this is called during negotiationn */ +int verify_callback(int ok, X509_STORE_CTX *ctx) { + int err, depth; + char buf[1024], *fingerprint; + X509 *thecert; + netsnmp_cert *cert; + _netsnmp_verify_info *verify_info; + SSL *ssl; + + thecert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + /* things to do: */ + + X509_NAME_oneline(X509_get_subject_name(thecert), buf, sizeof(buf)); + fingerprint = netsnmp_openssl_cert_get_fingerprint(thecert, -1); + DEBUGMSGTL(("tls_x509:verify", "Cert: %s\n", buf)); + 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()); + verify_info = SSL_get_ex_data(ssl, openssl_local_index); + + if (verify_info && ok && depth > 0) { + /* remember that a parent certificate has been marked as trusted */ + verify_info->flags |= VRFY_PARENT_WAS_OK; + } + + /* this ensures for self-signed certificates we have a valid + locally known fingerprint and then accept it */ + if (!ok && + (X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT == err || + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY == err || + X509_V_ERR_CERT_UNTRUSTED == err || + X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE == err || + X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN == err)) { + + cert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_FINGERPRINT, + (void*)fingerprint); + if (cert) + DEBUGMSGTL(("tls_x509:verify", " Found locally: %s/%s\n", + cert->info.dir, cert->info.filename)); + + + if (cert) { + DEBUGMSGTL(("tls_x509:verify", "verify_callback called with: ok=%d ctx=%p depth=%d err=%i:%s\n", ok, ctx, depth, err, X509_verify_cert_error_string(err))); + DEBUGMSGTL(("tls_x509:verify", " accepting matching fp of self-signed certificate found in: %s\n", + cert->info.filename)); + SNMP_FREE(fingerprint); + return 1; + } 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_FREE(fingerprint); + return 0; + } + + if (0 == depth && verify_info && + (verify_info->flags & VRFY_PARENT_WAS_OK)) { + DEBUGMSGTL(("tls_x509:verify", "verify_callback called with: ok=%d ctx=%p depth=%d err=%i:%s\n", ok, ctx, depth, err, X509_verify_cert_error_string(err))); + DEBUGMSGTL(("tls_x509:verify", " a parent was ok, so returning ok for this child certificate\n")); + SNMP_FREE(fingerprint); + return 1; /* we'll check the hostname later at this level */ + } + } + + if (0 == ok) + 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)); + else + DEBUGMSGTL(("tls_x509:verify", "verify_callback called with: ok=%d ctx=%p depth=%d err=%i:%s\n", ok, ctx, depth, err, X509_verify_cert_error_string(err))); + DEBUGMSGTL(("tls_x509:verify", " returning the passed in value of %d\n", + ok)); + SNMP_FREE(fingerprint); + return(ok); +} + +#define VERIFIED_FINGERPRINT 0 +#define NO_FINGERPRINT_AVAILABLE 1 +#define FAILED_FINGERPRINT_VERIFY 2 + +static int +_netsnmp_tlsbase_verify_remote_fingerprint(X509 *remote_cert, + _netsnmpTLSBaseData *tlsdata, + int try_default) { + + char *fingerprint; + + fingerprint = + netsnmp_openssl_cert_get_fingerprint(remote_cert, -1); + + if (!fingerprint) { + /* no peer cert */ + snmp_log(LOG_ERR, "failed to get fingerprint of remote certificate\n"); + return FAILED_FINGERPRINT_VERIFY; + } + + if (!tlsdata->their_fingerprint && tlsdata->their_identity) { + /* we have an identity; try and find it's fingerprint */ + netsnmp_cert *peer_cert; + peer_cert = + netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_MULTIPLE, + tlsdata->their_identity); + + if (peer_cert) + tlsdata->their_fingerprint = + netsnmp_openssl_cert_get_fingerprint(peer_cert->ocert, -1); + } + + if (!tlsdata->their_fingerprint && try_default) { + /* try for the default instead */ + netsnmp_cert *peer_cert; + peer_cert = + netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_DEFAULT, + NULL); + + if (peer_cert) + tlsdata->their_fingerprint = + netsnmp_openssl_cert_get_fingerprint(peer_cert->ocert, -1); + } + + if (tlsdata->their_fingerprint) { + netsnmp_fp_lowercase_and_strip_colon(tlsdata->their_fingerprint); + if (0 != strcmp(tlsdata->their_fingerprint, fingerprint)) { + snmp_log(LOG_ERR, "The fingerprint from the remote side's certificate didn't match the expected\n"); + snmp_log(LOG_ERR, " got %s, expected %s\n", + fingerprint, tlsdata->their_fingerprint); + free(fingerprint); + return FAILED_FINGERPRINT_VERIFY; + } + } else { + DEBUGMSGTL(("tls_x509:verify", "No fingerprint for the remote entity available to verify\n")); + free(fingerprint); + return NO_FINGERPRINT_AVAILABLE; + } + + free(fingerprint); + return VERIFIED_FINGERPRINT; +} + +/* this is called after the connection on the client side by us to check + other aspects about the connection */ +int +netsnmp_tlsbase_verify_server_cert(SSL *ssl, _netsnmpTLSBaseData *tlsdata) { + /* XXX */ + X509 *remote_cert; + char *check_name; + int ret; + + netsnmp_assert_or_return(ssl != NULL, SNMPERR_GENERR); + netsnmp_assert_or_return(tlsdata != NULL, SNMPERR_GENERR); + + if (NULL == (remote_cert = SSL_get_peer_certificate(ssl))) { + /* no peer cert */ + DEBUGMSGTL(("tls_x509:verify", + "remote connection provided no certificate (yet)\n")); + return SNMPERR_TLS_NO_CERTIFICATE; + } + + /* make sure that the fingerprint matches */ + ret = _netsnmp_tlsbase_verify_remote_fingerprint(remote_cert, tlsdata, 1); + switch(ret) { + case VERIFIED_FINGERPRINT: + return SNMPERR_SUCCESS; + + case FAILED_FINGERPRINT_VERIFY: + return SNMPERR_GENERR; + + case NO_FINGERPRINT_AVAILABLE: + if (tlsdata->their_hostname && tlsdata->their_hostname[0] != '\0') { + GENERAL_NAMES *onames; + const GENERAL_NAME *oname = NULL; + int i, j; + int count; + char buf[SPRINT_MAX_LEN]; + int is_wildcarded = 0; + char *compare_to; + + /* see if the requested hostname has a wildcard prefix */ + if (strncmp(tlsdata->their_hostname, "*.", 2) == 0) { + is_wildcarded = 1; + compare_to = tlsdata->their_hostname + 2; + } else { + compare_to = tlsdata->their_hostname; + } + + /* if the hostname we were expecting to talk to matches + the cert, then we can accept this connection. */ + + /* check against the DNS subjectAltName */ + onames = (GENERAL_NAMES *)X509_get_ext_d2i(remote_cert, + NID_subject_alt_name, + NULL, NULL ); + if (NULL != onames) { + count = sk_GENERAL_NAME_num(onames); + + for (i=0 ; i <count; ++i) { + oname = sk_GENERAL_NAME_value(onames, i); + if (GEN_DNS == oname->type) { + + /* get the value */ + ASN1_STRING_to_UTF8((unsigned char**)&check_name, + oname->d.ia5); + + /* convert to lowercase for comparisons */ + for (j = 0 ; + *check_name && j < sizeof(buf)-1; + ++check_name, ++j ) { + if (isascii(*check_name)) + buf[j] = tolower(0xFF & *check_name); + } + if (j < sizeof(buf)) + buf[j] = '\0'; + check_name = buf; + + if (is_wildcarded) { + /* we *only* allow passing till the first '.' */ + /* ie *.example.com can't match a.b.example.com */ + check_name = strchr(check_name, '.') + 1; + } + + DEBUGMSGTL(("tls_x509:verify", "checking subjectAltname of dns:%s\n", check_name)); + if (strcmp(compare_to, check_name) == 0) { + + DEBUGMSGTL(("tls_x509:verify", "Successful match on a subjectAltname of dns:%s\n", check_name)); + return SNMPERR_SUCCESS; + } + } + } + } + + /* check the common name for a match */ + check_name = + netsnmp_openssl_cert_get_commonName(remote_cert, NULL, NULL); + + if (is_wildcarded) { + /* we *only* allow passing till the first '.' */ + /* ie *.example.com can't match a.b.example.com */ + check_name = strchr(check_name, '.') + 1; + } + + if (strcmp(compare_to, check_name) == 0) { + DEBUGMSGTL(("tls_x509:verify", "Successful match on a common name of %s\n", check_name)); + return SNMPERR_SUCCESS; + } + + snmp_log(LOG_ERR, "No matching names in the certificate to match the expected %s\n", tlsdata->their_hostname); + return SNMPERR_GENERR; + + } + /* XXX: check for hostname match instead */ + snmp_log(LOG_ERR, "Can not verify a remote server identity without configuration\n"); + return SNMPERR_GENERR; + } + DEBUGMSGTL(("tls_x509:verify", "shouldn't get here\n")); + return SNMPERR_GENERR; +} + +/* this is called after the connection on the server side by us to check + the validity of the client's certificate */ +int +netsnmp_tlsbase_verify_client_cert(SSL *ssl, _netsnmpTLSBaseData *tlsdata) { + /* XXX */ + X509 *remote_cert; + int ret; + + /* RFC5953: section 5.3.2, paragraph 1: + A (D)TLS server should accept new session connections from any client + that it is able to verify the client's credentials for. This is done + by authenticating the client's presented certificate through a + certificate path validation process (e.g. [RFC5280]) or through + certificate fingerprint verification using fingerprints configured in + the snmpTlstmCertToTSNTable. Afterward the server will determine the + identity of the remote entity using the following procedures. + */ + /* Implementation notes: + + path validation is taken care of during the openssl verify + routines, our part of which is hanlded in verify_callback + above. + + fingerprint verification happens below. + */ + if (NULL == (remote_cert = SSL_get_peer_certificate(ssl))) { + /* no peer cert */ + DEBUGMSGTL(("tls_x509:verify", + "remote connection provided no certificate (yet)\n")); + return SNMPERR_TLS_NO_CERTIFICATE; + } + + /* we don't force a known remote fingerprint for a client since we + will accept any certificate we know about (and later derive the + securityName from it and apply access control) */ + ret = _netsnmp_tlsbase_verify_remote_fingerprint(remote_cert, tlsdata, 0); + switch(ret) { + case FAILED_FINGERPRINT_VERIFY: + DEBUGMSGTL(("tls_x509:verify", "failed to verify a client fingerprint\n")); + return SNMPERR_GENERR; + + case NO_FINGERPRINT_AVAILABLE: + DEBUGMSGTL(("tls_x509:verify", "no known fingerprint available (not a failure case)\n")); + return SNMPERR_SUCCESS; + + case VERIFIED_FINGERPRINT: + DEBUGMSGTL(("tls_x509:verify", "Verified client fingerprint\n")); + tlsdata->flags |= NETSNMP_TLSBASE_CERT_FP_VERIFIED; + return SNMPERR_SUCCESS; + } + + DEBUGMSGTL(("tls_x509:verify", "shouldn't get here\n")); + return SNMPERR_GENERR; +} + +/* this is called after the connection on the server side by us to + check other aspects about the connection and obtain the + securityName from the remote certificate. */ +int +netsnmp_tlsbase_extract_security_name(SSL *ssl, _netsnmpTLSBaseData *tlsdata) { + netsnmp_container *chain_maps; + netsnmp_cert_map *cert_map, *peer_cert; + netsnmp_iterator *itr; + int rc; + + netsnmp_assert_or_return(ssl != NULL, SNMPERR_GENERR); + netsnmp_assert_or_return(tlsdata != NULL, SNMPERR_GENERR); + + if (NULL == (chain_maps = netsnmp_openssl_get_cert_chain(ssl))) + return SNMPERR_GENERR; + /* + * map fingerprints to mapping entries + */ + rc = netsnmp_cert_get_secname_maps(chain_maps); + if ((-1 == rc) || (CONTAINER_SIZE(chain_maps) == 0)) { + netsnmp_cert_map_container_free(chain_maps); + return SNMPERR_GENERR; + } + + /* + * change container to sorted (by clearing unsorted option), then + * iterate over it until we find a map that returns a secname. + */ + CONTAINER_SET_OPTIONS(chain_maps, 0, rc); + itr = CONTAINER_ITERATOR(chain_maps); + if (NULL == itr) { + snmp_log(LOG_ERR, "could not get iterator for secname fingerprints\n"); + netsnmp_cert_map_container_free(chain_maps); + return SNMPERR_GENERR; + } + peer_cert = cert_map = ITERATOR_FIRST(itr); + for( ; !tlsdata->securityName && cert_map; cert_map = ITERATOR_NEXT(itr)) + tlsdata->securityName = + netsnmp_openssl_extract_secname(cert_map, peer_cert); + ITERATOR_RELEASE(itr); + + netsnmp_cert_map_container_free(chain_maps); + + return (tlsdata->securityName ? SNMPERR_SUCCESS : SNMPERR_GENERR); +} + +int +_trust_this_cert(SSL_CTX *the_ctx, char *certspec) { + netsnmp_cert *trustcert; + + 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, + certspec); + 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) + LOGANDDIE("failed to load trust certificate"); + + return 1; +} + +void +_load_trusted_certs(SSL_CTX *the_ctx) { + netsnmp_container *trusted_certs = NULL; + netsnmp_iterator *trusted_cert_iterator = NULL; + char *fingerprint; + + trusted_certs = netsnmp_cert_get_trustlist(); + trusted_cert_iterator = CONTAINER_ITERATOR(trusted_certs); + if (trusted_cert_iterator) { + for (fingerprint = (char *) ITERATOR_FIRST(trusted_cert_iterator); + fingerprint; fingerprint = ITERATOR_NEXT(trusted_cert_iterator)) { + if (!_trust_this_cert(the_ctx, fingerprint)) + snmp_log(LOG_ERR, "failed to load trust cert: %s\n", + fingerprint); + } + ITERATOR_RELEASE(trusted_cert_iterator); + } +} + +SSL_CTX * +_sslctx_common_setup(SSL_CTX *the_ctx, _netsnmpTLSBaseData *tlsbase) { + char *crlFile; + char *cipherList; + X509_LOOKUP *lookup; + X509_STORE *cert_store = NULL; + + _load_trusted_certs(the_ctx); + + /* add in the CRLs if available */ + + crlFile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + 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)); + if (!cert_store) + LOGANDDIE("failed to find certificate store"); + if (!(lookup = X509_STORE_add_lookup(cert_store, X509_LOOKUP_file()))) + LOGANDDIE("failed to create a lookup function for the CRL file"); + if (X509_load_crl_file(lookup, crlFile, X509_FILETYPE_PEM) != 1) + LOGANDDIE("failed to load the CRL file"); + /* tell openssl to check CRLs */ + X509_STORE_set_flags(cert_store, + X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + } + + cipherList = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_TLS_ALGORITMS); + if (NULL != cipherList) { + if (SSL_CTX_set_cipher_list(the_ctx, cipherList) != 1) + LOGANDDIE("failed to set the cipher list to the requested value"); + else + snmp_log(LOG_INFO,"set SSL cipher list to '%s'\n", cipherList); + } + return the_ctx; +} + +SSL_CTX * +sslctx_client_setup(const SSL_METHOD *method, _netsnmpTLSBaseData *tlsbase) { + netsnmp_cert *id_cert, *peer_cert; + SSL_CTX *the_ctx; + + /*********************************************************************** + * Set up the client context + */ + the_ctx = SSL_CTX_new(NETSNMP_REMOVE_CONST(SSL_METHOD *, method)); + if (!the_ctx) { + snmp_log(LOG_ERR, "ack: %p\n", the_ctx); + LOGANDDIE("can't create a new context"); + } + SSL_CTX_set_read_ahead (the_ctx, 1); /* Required for DTLS */ + + SSL_CTX_set_verify(the_ctx, + SSL_VERIFY_PEER| + SSL_VERIFY_FAIL_IF_NO_PEER_CERT| + SSL_VERIFY_CLIENT_ONCE, + &verify_callback); + + if (tlsbase->our_identity) { + DEBUGMSGTL(("sslctx_client", "looking for local id: %s\n", tlsbase->our_identity)); + id_cert = netsnmp_cert_find(NS_CERT_IDENTITY, NS_CERTKEY_MULTIPLE, + tlsbase->our_identity); + } else { + DEBUGMSGTL(("sslctx_client", "looking for default local id: %s\n", tlsbase->our_identity)); + id_cert = netsnmp_cert_find(NS_CERT_IDENTITY, NS_CERTKEY_DEFAULT, NULL); + } + + if (!id_cert) + LOGANDDIE ("error finding client identity keys"); + + if (!id_cert->key || !id_cert->key->okey) + LOGANDDIE("failed to load private key"); + + DEBUGMSGTL(("sslctx_client", "using public key: %s\n", + id_cert->info.filename)); + DEBUGMSGTL(("sslctx_client", "using private key: %s\n", + id_cert->key->info.filename)); + + if (SSL_CTX_use_certificate(the_ctx, id_cert->ocert) <= 0) + LOGANDDIE("failed to set the certificate to use"); + + if (SSL_CTX_use_PrivateKey(the_ctx, id_cert->key->okey) <= 0) + LOGANDDIE("failed to set the private key to use"); + + if (!SSL_CTX_check_private_key(the_ctx)) + LOGANDDIE("public and private keys incompatible"); + + if (tlsbase->their_identity) + peer_cert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, + NS_CERTKEY_MULTIPLE, + tlsbase->their_identity); + else + peer_cert = netsnmp_cert_find(NS_CERT_REMOTE_PEER, NS_CERTKEY_DEFAULT, + NULL); + if (peer_cert) { + DEBUGMSGTL(("sslctx_client", "server's expected public key: %s\n", + peer_cert ? peer_cert->info.filename : "none")); + + /* Trust the expected certificate */ + if (netsnmp_cert_trust_ca(the_ctx, peer_cert) != SNMPERR_SUCCESS) + LOGANDDIE ("failed to set verify paths"); + } + + /* trust a certificate (possibly a CA) aspecifically passed in */ + if (tlsbase->trust_cert) { + if (!_trust_this_cert(the_ctx, tlsbase->trust_cert)) + return 0; + } + + return _sslctx_common_setup(the_ctx, tlsbase); +} + +SSL_CTX * +sslctx_server_setup(const SSL_METHOD *method) { + netsnmp_cert *id_cert; + + /*********************************************************************** + * Set up the server context + */ + /* 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"); + } + + id_cert = netsnmp_cert_find(NS_CERT_IDENTITY, NS_CERTKEY_DEFAULT, NULL); + if (!id_cert) + LOGANDDIE ("error finding server identity keys"); + + if (!id_cert->key || !id_cert->key->okey) + LOGANDDIE("failed to load private key"); + + DEBUGMSGTL(("sslctx_server", "using public key: %s\n", + id_cert->info.filename)); + DEBUGMSGTL(("sslctx_server", "using private key: %s\n", + id_cert->key->info.filename)); + + if (SSL_CTX_use_certificate(the_ctx, id_cert->ocert) <= 0) + LOGANDDIE("failed to set the certificate to use"); + + if (SSL_CTX_use_PrivateKey(the_ctx, id_cert->key->okey) <= 0) + LOGANDDIE("failed to set the private key to use"); + + if (!SSL_CTX_check_private_key(the_ctx)) + LOGANDDIE("public and private keys incompatible"); + + SSL_CTX_set_read_ahead(the_ctx, 1); /* XXX: DTLS only? */ + + SSL_CTX_set_verify(the_ctx, + SSL_VERIFY_PEER| + SSL_VERIFY_FAIL_IF_NO_PEER_CERT| + SSL_VERIFY_CLIENT_ONCE, + &verify_callback); + + return _sslctx_common_setup(the_ctx, NULL); +} + +int +netsnmp_tlsbase_config(struct netsnmp_transport_s *t, const char *token, const char *value) { + _netsnmpTLSBaseData *tlsdata; + + netsnmp_assert_or_return(t != NULL, -1); + netsnmp_assert_or_return(t->data != NULL, -1); + + tlsdata = t->data; + + if ((strcmp(token, "localCert") == 0) || + (strcmp(token, "our_identity") == 0)) { + SNMP_FREE(tlsdata->our_identity); + tlsdata->our_identity = strdup(value); + DEBUGMSGT(("tls:config","our identity %s\n", value)); + } + + if ((strcmp(token, "peerCert") == 0) || + (strcmp(token, "their_identity") == 0)) { + SNMP_FREE(tlsdata->their_identity); + tlsdata->their_identity = strdup(value); + DEBUGMSGT(("tls:config","their identity %s\n", value)); + } + + if ((strcmp(token, "peerHostname") == 0) || + (strcmp(token, "their_hostname") == 0)) { + SNMP_FREE(tlsdata->their_hostname); + tlsdata->their_hostname = strdup(value); + } + + if ((strcmp(token, "trust_cert") == 0) || + (strcmp(token, "trustCert") == 0)) { + SNMP_FREE(tlsdata->trust_cert); + tlsdata->trust_cert = strdup(value); + } + + return SNMPERR_SUCCESS; +} + +int +netsnmp_tlsbase_session_init(struct netsnmp_transport_s *transport, + struct snmp_session *sess) { + /* the default security model here should be TSM; most other + things won't work with TLS because we'll throw out the packet + if it doesn't have a proper tmStateRef (and onyl TSM generates + this at the moment */ + if (!(transport->flags & NETSNMP_TRANSPORT_FLAG_LISTEN)) { + if (sess->securityModel == SNMP_DEFAULT_SECMODEL) { + sess->securityModel = SNMP_SEC_MODEL_TSM; + } else if (sess->securityModel != SNMP_SEC_MODEL_TSM) { + sess->securityModel = SNMP_SEC_MODEL_TSM; + snmp_log(LOG_ERR, "A security model other than TSM is being used with (D)TLS; using TSM anyways\n"); + } + + if (NULL == sess->securityName) { + /* client side doesn't need a real securityName */ + /* XXX: call-home issues require them to set one for VACM; but + we don't do callhome yet */ + sess->securityName = strdup("__BOGUS__"); + sess->securityNameLen = strlen(sess->securityName); + } + + if (sess->version != SNMP_VERSION_3) { + sess->version = SNMP_VERSION_3; + snmp_log(LOG_ERR, "A SNMP version other than 3 was requested with (D)TLS; using 3 anyways\n"); + } + + if (sess->securityLevel <= 0) { + sess->securityLevel = SNMP_SEC_LEVEL_AUTHPRIV; + } + } + return SNMPERR_SUCCESS; +} + +static int have_done_bootstrap = 0; + +static int +tls_bootstrap(int majorid, int minorid, void *serverarg, void *clientarg) { + char indexname[] = "_netsnmp_verify_info"; + + /* don't do this more than once */ + if (have_done_bootstrap) + return 0; + have_done_bootstrap = 1; + + netsnmp_certs_load(); + + openssl_local_index = + SSL_get_ex_new_index(0, indexname, NULL, NULL, NULL); + + return 0; +} + +int +tls_get_verify_info_index() { + return openssl_local_index; +} + +static void _parse_client_cert(const char *tok, char *line) +{ + config_pwarn("clientCert is deprecated. Clients should use localCert, servers should use peerCert"); + if (*line == '"') { + char buf[SNMP_MAXBUF]; + copy_nword(line, buf, sizeof(buf)); + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_X509_CLIENT_PUB, buf); + } else + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_X509_CLIENT_PUB, line); +} + +static void _parse_server_cert(const char *tok, char *line) +{ + config_pwarn("serverCert is deprecated. Clients should use peerCert, servers should use localCert."); + if (*line == '"') { + char buf[SNMP_MAXBUF]; + copy_nword(line, buf, sizeof(buf)); + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_X509_CLIENT_PUB, buf); + } else + netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_X509_SERVER_PUB, line); +} + +void +netsnmp_tlsbase_ctor(void) { + + /* bootstrap ssl since we'll need it */ + netsnmp_init_openssl(); + + /* the private client cert to authenticate with */ + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "extraX509SubDir", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_CERT_EXTRA_SUBDIR); + + /* Do we have a CRL list? */ + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "x509CRLFile", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_X509_CRL_FILE); + + /* What TLS algorithms should be use */ + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "tlsAlgorithms", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_TLS_ALGORITMS); + + /* + * for the client + */ + + /* the public client cert to authenticate with */ + register_config_handler("snmp", "clientCert", _parse_client_cert, NULL, + NULL); + + /* + * for the server + */ + + /* The X509 server key to use */ + register_config_handler("snmp", "serverCert", _parse_server_cert, NULL, + NULL); + /* + * remove cert config ambiguity: localCert, peerCert + */ + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "localCert", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_TLS_LOCAL_CERT); + netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "peerCert", + NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_TLS_PEER_CERT); + + /* + * register our boot-strapping needs + */ + snmp_register_callback(SNMP_CALLBACK_LIBRARY, + SNMP_CALLBACK_POST_PREMIB_READ_CONFIG, + tls_bootstrap, NULL); + +} + +_netsnmpTLSBaseData * +netsnmp_tlsbase_allocate_tlsdata(netsnmp_transport *t, int isserver) { + + _netsnmpTLSBaseData *tlsdata; + + if (NULL == t) + return NULL; + + /* allocate our TLS specific data */ + tlsdata = SNMP_MALLOC_TYPEDEF(_netsnmpTLSBaseData); + if (NULL == tlsdata) { + SNMP_FREE(t); + return NULL; + } + + if (!isserver) + tlsdata->flags |= NETSNMP_TLSBASE_IS_CLIENT; + + return tlsdata; +} + +void netsnmp_tlsbase_free_tlsdata(_netsnmpTLSBaseData *tlsbase) { + if (!tlsbase) + return; + + DEBUGMSGTL(("tlsbase","Freeing TLS Base data for a session\n")); + + if (tlsbase->ssl) + SSL_free(tlsbase->ssl); + + if (tlsbase->ssl_context) + SSL_CTX_free(tlsbase->ssl_context); + /* don't free the accept_bio since it's the parent bio */ + /* + if (tlsbase->accept_bio) + BIO_free(tlsbase->accept_bio); + */ + /* and this is freed by the SSL shutdown */ + /* + if (tlsbase->accepted_bio) + BIO_free(tlsbase->accept_bio); + */ + + /* free the config data */ + SNMP_FREE(tlsbase->securityName); + SNMP_FREE(tlsbase->addr_string); + SNMP_FREE(tlsbase->our_identity); + SNMP_FREE(tlsbase->their_identity); + SNMP_FREE(tlsbase->their_fingerprint); + SNMP_FREE(tlsbase->their_hostname); + SNMP_FREE(tlsbase->trust_cert); + + /* free the base itself */ + SNMP_FREE(tlsbase); +} + +int netsnmp_tlsbase_wrapup_recv(netsnmp_tmStateReference *tmStateRef, + _netsnmpTLSBaseData *tlsdata, + void **opaque, int *olength) { + int no_auth, no_priv; + + if (NULL == tlsdata) + return SNMPERR_GENERR; + + /* RFC5953 Section 5.1.2 step 2: tmSecurityLevel */ + /* + * Don't accept null authentication. Null encryption ok. + * + * XXX: this should actually check for a configured list of encryption + * algorithms to map to NOPRIV, but for the moment we'll + * accept any encryption alogrithms that openssl is using. + */ + netsnmp_openssl_null_checks(tlsdata->ssl, &no_auth, &no_priv); + if (no_auth == 1) { /* null/unknown authentication */ + /* xxx-rks: snmp_increment_statistic(STAT_???); */ + snmp_log(LOG_ERR, "tls connection with NULL authentication\n"); + SNMP_FREE(tmStateRef); + return SNMPERR_GENERR; + } + else if (no_priv == 1) /* null/unknown encryption */ + tmStateRef->transportSecurityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV; + else + tmStateRef->transportSecurityLevel = SNMP_SEC_LEVEL_AUTHPRIV; + DEBUGMSGTL(("tls:secLevel", "SecLevel %d\n", + tmStateRef->transportSecurityLevel)); + + /* use x509 cert to do lookup to secname if DNE in cachep yet */ + + /* RFC5953: section 5.3.2, paragraph 2: + The (D)TLS server identifies the authenticated identity from the + (D)TLS client's principal certificate using configuration information + from the snmpTlstmCertToTSNTable mapping table. The (D)TLS server + MUST request and expect a certificate from the client and MUST NOT + accept SNMP messages over the (D)TLS connection until the client has + sent a certificate and it has been authenticated. The resulting + derived tmSecurityName is recorded in the tmStateReference cache as + tmSecurityName. The details of the lookup process are fully + described in the DESCRIPTION clause of the snmpTlstmCertToTSNTable + MIB object. If any verification fails in any way (for example + because of failures in cryptographic verification or because of the + lack of an appropriate row in the snmpTlstmCertToTSNTable) then the + session establishment MUST fail, and the + snmpTlstmSessionInvalidClientCertificates object is incremented. If + the session can not be opened for any reason at all, including + cryptographic verification failures, then the + snmpTlstmSessionOpenErrors counter is incremented and processing + stops. + */ + + if (!tlsdata->securityName) { + netsnmp_tlsbase_extract_security_name(tlsdata->ssl, tlsdata); + if (NULL != tlsdata->securityName) { + DEBUGMSGTL(("tls", "set SecName to: %s\n", tlsdata->securityName)); + } else { + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCLIENTCERTIFICATES); + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); + SNMP_FREE(tmStateRef); + return SNMPERR_GENERR; + } + } + + /* RFC5953 Section 5.1.2 step 2: tmSecurityName */ + /* XXX: detect and throw out overflow secname sizes rather + than truncating. */ + strlcpy(tmStateRef->securityName, tlsdata->securityName, + sizeof(tmStateRef->securityName)); + tmStateRef->securityNameLen = strlen(tmStateRef->securityName); + + /* RFC5953 Section 5.1.2 step 2: tmSessionID */ + /* use our TLSData pointer as the session ID */ + memcpy(tmStateRef->sessionID, &tlsdata, sizeof(netsnmp_tmStateReference *)); + + /* save the tmStateRef in our special pointer */ + *opaque = tmStateRef; + *olength = sizeof(netsnmp_tmStateReference); + + return SNMPERR_SUCCESS; +} + +netsnmp_feature_child_of(_x509_get_error, netsnmp_unused) +#ifndef NETSNMP_FEATURE_REMOVE__X509_GET_ERROR +const char * _x509_get_error(int x509failvalue, const char *location) { + static const char *reason = NULL; + + /* XXX: use this instead: X509_verify_cert_error_string(err) */ + + switch (x509failvalue) { + case X509_V_OK: + reason = "X509_V_OK"; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"; + break; + case X509_V_ERR_UNABLE_TO_GET_CRL: + reason = "X509_V_ERR_UNABLE_TO_GET_CRL"; + break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"; + break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"; + break; + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + reason = "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"; + break; + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + reason = "X509_V_ERR_CERT_SIGNATURE_FAILURE"; + break; + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + reason = "X509_V_ERR_CRL_SIGNATURE_FAILURE"; + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + reason = "X509_V_ERR_CERT_NOT_YET_VALID"; + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + reason = "X509_V_ERR_CERT_HAS_EXPIRED"; + break; + case X509_V_ERR_CRL_NOT_YET_VALID: + reason = "X509_V_ERR_CRL_NOT_YET_VALID"; + break; + case X509_V_ERR_CRL_HAS_EXPIRED: + reason = "X509_V_ERR_CRL_HAS_EXPIRED"; + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + reason = "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"; + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + reason = "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"; + break; + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + reason = "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"; + break; + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + reason = "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"; + break; + case X509_V_ERR_OUT_OF_MEM: + reason = "X509_V_ERR_OUT_OF_MEM"; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + reason = "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"; + break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + reason = "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"; + break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + reason = "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"; + break; + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + reason = "X509_V_ERR_CERT_CHAIN_TOO_LONG"; + break; + case X509_V_ERR_CERT_REVOKED: + reason = "X509_V_ERR_CERT_REVOKED"; + break; + case X509_V_ERR_INVALID_CA: + reason = "X509_V_ERR_INVALID_CA"; + break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + reason = "X509_V_ERR_PATH_LENGTH_EXCEEDED"; + break; + case X509_V_ERR_INVALID_PURPOSE: + reason = "X509_V_ERR_INVALID_PURPOSE"; + break; + case X509_V_ERR_CERT_UNTRUSTED: + reason = "X509_V_ERR_CERT_UNTRUSTED"; + break; + case X509_V_ERR_CERT_REJECTED: + reason = "X509_V_ERR_CERT_REJECTED"; + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + reason = "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"; + break; + case X509_V_ERR_AKID_SKID_MISMATCH: + reason = "X509_V_ERR_AKID_SKID_MISMATCH"; + break; + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + reason = "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"; + break; + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + reason = "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"; + break; + case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: + reason = "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"; + break; + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: + reason = "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"; + break; + case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: + reason = "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"; + break; + case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: + reason = "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"; + break; + case X509_V_ERR_INVALID_NON_CA: + reason = "X509_V_ERR_INVALID_NON_CA"; + break; + case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: + reason = "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"; + break; + case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: + reason = "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"; + break; + case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: + reason = "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"; + break; +#ifdef X509_V_ERR_INVALID_EXTENSION /* not avail on darwin */ + case X509_V_ERR_INVALID_EXTENSION: + reason = "X509_V_ERR_INVALID_EXTENSION"; + break; +#endif +#ifdef X509_V_ERR_INVALID_POLICY_EXTENSION /* not avail on darwin */ + case X509_V_ERR_INVALID_POLICY_EXTENSION: + reason = "X509_V_ERR_INVALID_POLICY_EXTENSION"; + break; +#endif +#ifdef X509_V_ERR_NO_EXPLICIT_POLICY /* not avail on darwin */ + case X509_V_ERR_NO_EXPLICIT_POLICY: + reason = "X509_V_ERR_NO_EXPLICIT_POLICY"; + break; +#endif +#ifdef X509_V_ERR_UNNESTED_RESOURCE /* not avail on darwin */ + case X509_V_ERR_UNNESTED_RESOURCE: + reason = "X509_V_ERR_UNNESTED_RESOURCE"; + break; +#endif + case X509_V_ERR_APPLICATION_VERIFICATION: + reason = "X509_V_ERR_APPLICATION_VERIFICATION"; + break; + default: + reason = "unknown failure code"; + } + + return reason; +} +#endif /* NETSNMP_FEATURE_REMOVE__X509_GET_ERROR */ + +void _openssl_log_error(int rc, SSL *con, const char *location) { + const char *reason, *file, *data; + unsigned long numerical_reason; + int flags, line; + + snmp_log(LOG_ERR, "---- OpenSSL Related Errors: ----\n"); + + /* SSL specific errors */ + if (con) { + + int sslnum = SSL_get_error(con, rc); + + switch(sslnum) { + case SSL_ERROR_NONE: + reason = "SSL_ERROR_NONE"; + break; + + case SSL_ERROR_SSL: + reason = "SSL_ERROR_SSL"; + break; + + case SSL_ERROR_WANT_READ: + reason = "SSL_ERROR_WANT_READ"; + break; + + case SSL_ERROR_WANT_WRITE: + reason = "SSL_ERROR_WANT_WRITE"; + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + reason = "SSL_ERROR_WANT_X509_LOOKUP"; + break; + + case SSL_ERROR_SYSCALL: + reason = "SSL_ERROR_SYSCALL"; + snmp_log(LOG_ERR, "TLS error: %s: rc=%d, sslerror = %d (%s): system_error=%d (%s)\n", + location, rc, sslnum, reason, errno, strerror(errno)); + snmp_log(LOG_ERR, "TLS Error: %s\n", + ERR_reason_error_string(ERR_get_error())); + return; + + case SSL_ERROR_ZERO_RETURN: + reason = "SSL_ERROR_ZERO_RETURN"; + break; + + case SSL_ERROR_WANT_CONNECT: + reason = "SSL_ERROR_WANT_CONNECT"; + break; + + case SSL_ERROR_WANT_ACCEPT: + reason = "SSL_ERROR_WANT_ACCEPT"; + break; + + default: + reason = "unknown"; + } + + snmp_log(LOG_ERR, " TLS error: %s: rc=%d, sslerror = %d (%s)\n", + location, rc, sslnum, reason); + + snmp_log(LOG_ERR, " TLS Error: %s\n", + ERR_reason_error_string(ERR_get_error())); + + } + + /* other errors */ + while ((numerical_reason = + ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) { + snmp_log(LOG_ERR, " error: #%lu (file %s, line %d)\n", + numerical_reason, file, line); + + /* if we have a text translation: */ + if (data && (flags & ERR_TXT_STRING)) { + snmp_log(LOG_ERR, " Textual Error: %s\n", data); + /* + * per openssl man page: If it has been allocated by + * OPENSSL_malloc(), *flags&ERR_TXT_MALLOCED is true. + * + * arggh... stupid openssl prototype for ERR_get_error_line_data + * wants a const char **, but returns something that we might + * need to free?? + */ + if (flags & ERR_TXT_MALLOCED) + OPENSSL_free(NETSNMP_REMOVE_CONST(void *, data)); } + } + + snmp_log(LOG_ERR, "---- End of OpenSSL Errors ----\n"); +} |