/* 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 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; }
netsnmp_transport * netsnmp_udpipv4base_transport(struct sockaddr_in *addr, int local) { netsnmp_transport *t = NULL; int rc = 0, rc2; char *str = NULL; char *client_socket = NULL; netsnmp_indexed_addr_pair addr_pair; socklen_t local_addr_len; if (addr == NULL || addr->sin_family != AF_INET) { return NULL; } memset(&addr_pair, 0, sizeof(netsnmp_indexed_addr_pair)); memcpy(&(addr_pair.remote_addr), addr, sizeof(struct sockaddr_in)); t = SNMP_MALLOC_TYPEDEF(netsnmp_transport); netsnmp_assert_or_return(t != NULL, NULL); str = netsnmp_udp_fmtaddr(NULL, (void *)&addr_pair, sizeof(netsnmp_indexed_addr_pair)); DEBUGMSGTL(("netsnmp_udpbase", "open %s %s\n", local ? "local" : "remote", str)); free(str); t->sock = socket(PF_INET, SOCK_DGRAM, 0); DEBUGMSGTL(("UDPBase", "openned socket %d as local=%d\n", t->sock, local)); if (t->sock < 0) { netsnmp_transport_free(t); return NULL; } _netsnmp_udp_sockopt_set(t->sock, local); if (local) { /* * This session is inteneded as a server, so we must bind on to the * given IP address, which may include an interface address, or could * be INADDR_ANY, but certainly includes a port number. */ t->local = (u_char *) malloc(6); if (t->local == NULL) { netsnmp_transport_free(t); return NULL; } memcpy(t->local, (u_char *) & (addr->sin_addr.s_addr), 4); t->local[4] = (htons(addr->sin_port) & 0xff00) >> 8; t->local[5] = (htons(addr->sin_port) & 0x00ff) >> 0; t->local_length = 6; #if defined(linux) && defined(IP_PKTINFO) { int sockopt = 1; if (setsockopt(t->sock, SOL_IP, IP_PKTINFO, &sockopt, sizeof sockopt) == -1) { DEBUGMSGTL(("netsnmp_udpbase", "couldn't set IP_PKTINFO: %s\n", strerror(errno))); netsnmp_transport_free(t); return NULL; } DEBUGMSGTL(("netsnmp_udpbase", "set IP_PKTINFO\n")); } #endif rc = bind(t->sock, (struct sockaddr *) addr, sizeof(struct sockaddr)); if (rc != 0) { netsnmp_socketbase_close(t); netsnmp_transport_free(t); return NULL; } t->data = NULL; t->data_length = 0; } else {
/* 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; }
int tsm_process_in_msg(struct snmp_secmod_incoming_params *parms) { u_char type_value; size_t remaining; u_char *data_ptr; netsnmp_tmStateReference *tmStateRef; netsnmp_tsmSecurityReference *tsmSecRef; u_char ourEngineID[SNMP_MAX_ENG_SIZE]; static size_t ourEngineID_len = sizeof(ourEngineID); /* Section 5.2, step 1: Set the securityEngineID to the local snmpEngineID. */ ourEngineID_len = snmpv3_get_engineID((u_char*) ourEngineID, ourEngineID_len); netsnmp_assert_or_return(ourEngineID_len != 0 && ourEngineID_len <= *parms->secEngineIDLen, SNMPERR_GENERR); memcpy(parms->secEngineID, ourEngineID, *parms->secEngineIDLen); /* Section 5.2, step 2: If tmStateReference does not refer to a cache containing values for tmTransportDomain, tmTransportAddress, tmSecurityName, and tmTransportSecurityLevel, then the snmpTsmInvalidCaches counter is incremented, an error indication is returned to the calling module, and Security Model processing stops for this message. */ if (!parms->pdu->transport_data || sizeof(netsnmp_tmStateReference) != parms->pdu->transport_data_length) { /* if we're not coming in over a proper transport; bail! */ DEBUGMSGTL(("tsm","improper transport data\n")); return -1; } tmStateRef = (netsnmp_tmStateReference *) parms->pdu->transport_data; parms->pdu->transport_data = NULL; if (tmStateRef == NULL || /* not needed: tmStateRef->transportDomain == NULL || */ /* not needed: tmStateRef->transportAddress == NULL || */ tmStateRef->securityName[0] == '\0' ) { snmp_increment_statistic(STAT_TSM_SNMPTSMINVALIDCACHES); return SNMPERR_GENERR; } /* Section 5.2, step 3: Copy the tmSecurityName to securityName. */ if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_TSM_USE_PREFIX)) { /* Section 5.2, step 3: If the snmpTsmConfigurationUsePrefix object is set to true, then use the tmTransportDomain to look up the corresponding prefix. */ const char *prefix = NULL; /* possibilities: |--------------------+-------| | snmpTLSTCPDomain | tls: | | snmpDTLSUDPDomain | dtls: | | snmpSSHDomain | ssh: | |--------------------+-------| */ if (tmStateRef->transportDomain == NULL) { /* XXX: snmpTsmInvalidCaches++ ??? */ return SNMPERR_GENERR; } /* XXX: cache in session! */ #ifdef NETSNMP_TRANSPORT_SSH_DOMAIN if (netsnmp_oid_equals(netsnmp_snmpSSHDomain, netsnmp_snmpSSHDomain_len, tmStateRef->transportDomain, tmStateRef->transportDomainLen) == 0) { prefix = "ssh"; } #endif /* NETSNMP_TRANSPORT_SSH_DOMAIN */ #ifdef NETSNMP_TRANSPORT_DTLSUDP_DOMAIN if (netsnmp_oid_equals(netsnmpDTLSUDPDomain, netsnmpDTLSUDPDomain_len, tmStateRef->transportDomain, tmStateRef->transportDomainLen) == 0) { prefix = "dtls"; } #endif /* NETSNMP_TRANSPORT_DTLSUDP_DOMAIN */ #ifdef NETSNMP_TRANSPORT_TLSTCP_DOMAIN if (netsnmp_oid_equals(netsnmpTLSTCPDomain, netsnmpTLSTCPDomain_len, tmStateRef->transportDomain, tmStateRef->transportDomainLen) == 0) { prefix = "tls"; } #endif /* NETSNMP_TRANSPORT_TLSTCP_DOMAIN */ /* Section 5.2, step 3: If the prefix lookup fails for any reason, then the snmpTsmUnknownPrefixes counter is incremented, an error indication is returned to the calling module, and message processing stops. */ if (prefix == NULL) { snmp_increment_statistic(STAT_TSM_SNMPTSMUNKNOWNPREFIXES); return SNMPERR_GENERR; } /* Section 5.2, step 3: If the lookup succeeds but the prefix length is less than 1 or greater than 4 octets, then the snmpTsmInvalidPrefixes counter is incremented, an error indication is returned to the calling module, and message processing stops. */ #ifdef NOT_USING_HARDCODED_PREFIXES /* the above code actually ensures this will never happen as we don't support a dynamic prefix database where this might happen. */ if (strlen(prefix) < 1 || strlen(prefix) > 4) { /* XXX: snmpTsmInvalidPrefixes++ */ return SNMPERR_GENERR; } #endif /* Section 5.2, step 3: Set the securityName to be the concatenation of the prefix, a ':' character (US-ASCII 0x3a), and the tmSecurityName. */ snprintf(parms->secName, *parms->secNameLen, "%s:%s", prefix, tmStateRef->securityName); } else { /* if the use prefix flag wasn't set, do a straight copy */ strncpy(parms->secName, tmStateRef->securityName, *parms->secNameLen); } /* set the length of the security name */ *parms->secNameLen = strlen(parms->secName); DEBUGMSGTL(("tsm", "user: %s/%d\n", parms->secName, (int)*parms->secNameLen)); /* Section 5.2 Step 4: Compare the value of tmTransportSecurityLevel in the tmStateReference cache to the value of the securityLevel parameter passed in the processIncomingMsg ASI. If securityLevel specifies privacy (Priv) and tmTransportSecurityLevel specifies no privacy (noPriv), or if securityLevel specifies authentication (auth) and tmTransportSecurityLevel specifies no authentication (noAuth) was provided by the Transport Model, then the snmpTsmInadequateSecurityLevels counter is incremented, an error indication (unsupportedSecurityLevel) together with the OID and value of the incremented counter is returned to the calling module, and Transport Security Model processing stops for this message.*/ if (parms->secLevel > tmStateRef->transportSecurityLevel) { snmp_increment_statistic(STAT_TSM_SNMPTSMINADEQUATESECURITYLEVELS); DEBUGMSGTL(("tsm", "inadequate security level: %d\n", parms->secLevel)); /* net-snmp returns error codes not OIDs, which are dealt with later */ return SNMPERR_UNSUPPORTED_SEC_LEVEL; } /* Section 5.2 Step 5 The tmStateReference is cached as cachedSecurityData so that a possible response to this message will use the same security parameters. Then securityStateReference is set for subsequent references to this cached data. */ if (NULL == *parms->secStateRef) { tsmSecRef = SNMP_MALLOC_TYPEDEF(netsnmp_tsmSecurityReference); } else { tsmSecRef = *parms->secStateRef; } netsnmp_assert_or_return(NULL != tsmSecRef, SNMPERR_GENERR); *parms->secStateRef = tsmSecRef; tsmSecRef->tmStateRef = tmStateRef; /* If this did not come through a tunneled connection, this security model is inappropriate (and would be a HUGE security hole to assume otherwise). This is functionally a double check since the pdu wouldn't have transport data otherwise. But this is safer though is functionally an extra step beyond the TSM RFC. */ DEBUGMSGTL(("tsm","checking how we got here\n")); if (!(parms->pdu->flags & UCD_MSG_FLAG_TUNNELED)) { DEBUGMSGTL(("tsm"," pdu not tunneled\n")); if (!(parms->sess->flags & NETSNMP_TRANSPORT_FLAG_TUNNELED)) { DEBUGMSGTL(("tsm"," session not tunneled\n")); return SNMPERR_USM_AUTHENTICATIONFAILURE; } DEBUGMSGTL(("tsm"," but session is tunneled\n")); } else { DEBUGMSGTL(("tsm"," tunneled\n")); } /* Section 5.2, Step 6: The scopedPDU component is extracted from the wholeMsg. */ /* * Eat the first octet header. */ remaining = parms->wholeMsgLen - (parms->secParams - parms->wholeMsg); if ((data_ptr = asn_parse_sequence(parms->secParams, &remaining, &type_value, (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), "tsm first octet")) == NULL) { /* * RETURN parse error */ return SNMPERR_ASN_PARSE_ERR; } *parms->scopedPdu = data_ptr; *parms->scopedPduLen = parms->wholeMsgLen - (data_ptr - parms->wholeMsg); /* Section 5.2, Step 7: The maxSizeResponseScopedPDU is calculated. This is the maximum size allowed for a scopedPDU for a possible Response message. */ *parms->maxSizeResponse = parms->maxMsgSize; /* XXX */ /* Section 5.2, Step 8: The statusInformation is set to success and a return is made to the calling module passing back the OUT parameters as specified in the processIncomingMsg ASI. */ return SNMPERR_SUCCESS; }
int tsm_rgenerate_out_msg(struct snmp_secmod_outgoing_params *parms) { u_char **wholeMsg = parms->wholeMsg; size_t *offset = parms->wholeMsgOffset; int rc; size_t *wholeMsgLen = parms->wholeMsgLen; netsnmp_tsmSecurityReference *tsmSecRef; netsnmp_tmStateReference *tmStateRef; int tmStateRefLocal = 0; DEBUGMSGTL(("tsm", "Starting TSM processing\n")); /* if we have this, then this message to be sent is in response to something that came in earlier and the tsmSecRef was created by the tsm_process_in_msg. */ tsmSecRef = parms->secStateRef; if (tsmSecRef) { /* 4.2, step 1: If there is a securityStateReference (Response or Report message), then this Security Model uses the cached information rather than the information provided by the ASI. */ /* 4.2, step 1: Extract the tmStateReference from the securityStateReference cache. */ netsnmp_assert_or_return(NULL != tsmSecRef->tmStateRef, SNMPERR_GENERR); tmStateRef = tsmSecRef->tmStateRef; /* 4.2 step 1: Set the tmRequestedSecurityLevel to the value of the extracted tmTransportSecurityLevel. */ tmStateRef->requestedSecurityLevel = tmStateRef->transportSecurityLevel; /* 4.2 step 1: Set the tmSameSecurity parameter in the tmStateReference cache to true. */ tmStateRef->sameSecurity = NETSNMP_TM_USE_SAME_SECURITY; /* 4.2 step 1: The cachedSecurityData for this message can now be discarded. */ SNMP_FREE(parms->secStateRef); } else { /* 4.2, step 2: If there is no securityStateReference (e.g., a Request-type or Notification message), then create a tmStateReference cache. */ tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference); netsnmp_assert_or_return(NULL != tmStateRef, SNMPERR_GENERR); tmStateRefLocal = 1; /* XXX: we don't actually use this really in our implementation */ /* 4.2, step 2: Set tmTransportDomain to the value of transportDomain, tmTransportAddress to the value of transportAddress */ /* 4.2, step 2: and tmRequestedSecurityLevel to the value of securityLevel. */ tmStateRef->requestedSecurityLevel = parms->secLevel; /* 4.2, step 2: Set the transaction-specific tmSameSecurity parameter to false. */ tmStateRef->sameSecurity = NETSNMP_TM_SAME_SECURITY_NOT_REQUIRED; if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_TSM_USE_PREFIX)) { /* XXX: probably shouldn't be a hard-coded list of supported transports */ /* 4.2, step 2: If the snmpTsmConfigurationUsePrefix object is set to true, then use the transportDomain to look up the corresponding prefix. */ const char *prefix; if (strncmp("ssh:",parms->session->peername,4) == 0) prefix = "ssh:"; else if (strncmp("dtls:",parms->session->peername,5) == 0) prefix = "dtls:"; else if (strncmp("tls:",parms->session->peername,4) == 0) prefix = "tls:"; else { /* 4.2, step 2: If the prefix lookup fails for any reason, then the snmpTsmUnknownPrefixes counter is incremented, an error indication is returned to the calling module, and message processing stops. */ snmp_increment_statistic(STAT_TSM_SNMPTSMUNKNOWNPREFIXES); SNMP_FREE(tmStateRef); return SNMPERR_GENERR; } /* 4.2, step 2: If the lookup succeeds, but there is no prefix in the securityName, or the prefix returned does not match the prefix in the securityName, or the length of the prefix is less than 1 or greater than 4 US-ASCII alpha-numeric characters, then the snmpTsmInvalidPrefixes counter is incremented, an error indication is returned to the calling module, and message processing stops. */ if (strchr(parms->secName, ':') == 0 || strlen(prefix)+1 >= parms->secNameLen || strncmp(parms->secName, prefix, strlen(prefix)) != 0 || parms->secName[strlen(prefix)] != ':') { /* Note: since we're assiging the prefixes above the prefix lengths always meet the 1-4 criteria */ snmp_increment_statistic(STAT_TSM_SNMPTSMINVALIDPREFIXES); SNMP_FREE(tmStateRef); return SNMPERR_GENERR; } /* 4.2, step 2: Strip the transport-specific prefix and trailing ':' character (US-ASCII 0x3a) from the securityName. Set tmSecurityName to the value of securityName. */ memcpy(tmStateRef->securityName, parms->secName + strlen(prefix) + 1, parms->secNameLen - strlen(prefix) - 1); tmStateRef->securityNameLen = parms->secNameLen - strlen(prefix) -1; } else { /* 4.2, step 2: If the snmpTsmConfigurationUsePrefix object is set to false, then set tmSecurityName to the value of securityName. */ memcpy(tmStateRef->securityName, parms->secName, parms->secNameLen); tmStateRef->securityNameLen = parms->secNameLen; } } /* truncate the security name with a '\0' for safety */ tmStateRef->securityName[tmStateRef->securityNameLen] = '\0'; /* 4.2, step 3: Set securityParameters to a zero-length OCTET * STRING ('0400'). */ DEBUGDUMPHEADER("send", "tsm security parameters"); rc = asn_realloc_rbuild_header(wholeMsg, wholeMsgLen, offset, 1, (u_char) (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), 0); DEBUGINDENTLESS(); if (rc == 0) { DEBUGMSGTL(("tsm", "building msgSecurityParameters failed.\n")); if (tmStateRefLocal) SNMP_FREE(tmStateRef); return SNMPERR_TOO_LONG; } /* 4.2, step 4: Combine the message parts into a wholeMsg and calculate wholeMsgLength. */ while ((*wholeMsgLen - *offset) < parms->globalDataLen) { if (!asn_realloc(wholeMsg, wholeMsgLen)) { DEBUGMSGTL(("tsm", "building global data failed.\n")); if (tmStateRefLocal) SNMP_FREE(tmStateRef); return SNMPERR_TOO_LONG; } } *offset += parms->globalDataLen; memcpy(*wholeMsg + *wholeMsgLen - *offset, parms->globalData, parms->globalDataLen); /* 4.2, step 5: The wholeMsg, wholeMsgLength, securityParameters, and tmStateReference are returned to the calling Message Processing Model with the statusInformation set to success. */ /* For the Net-SNMP implemantion that actually means we start encoding the full packet sequence from here before returning it */ /* * Total packet sequence. */ rc = asn_realloc_rbuild_sequence(wholeMsg, wholeMsgLen, offset, 1, (u_char) (ASN_SEQUENCE | ASN_CONSTRUCTOR), *offset); if (rc == 0) { DEBUGMSGTL(("tsm", "building master packet sequence failed.\n")); if (tmStateRefLocal) SNMP_FREE(tmStateRef); return SNMPERR_TOO_LONG; } if (parms->pdu->transport_data && parms->pdu->transport_data != tmStateRef) { snmp_log(LOG_ERR, "tsm: needed to free transport data\n"); SNMP_FREE(parms->pdu->transport_data); } /* put the transport state reference into the PDU for the transport */ parms->pdu->transport_data = netsnmp_memdup(tmStateRef, sizeof(*tmStateRef)); if (!parms->pdu->transport_data) snmp_log(LOG_ERR, "tsm: malloc failure\n"); parms->pdu->transport_data_length = sizeof(*tmStateRef); if (tmStateRefLocal) SNMP_FREE(tmStateRef); DEBUGMSGTL(("tsm", "TSM processing completed.\n")); return SNMPERR_SUCCESS; }
netsnmp_transport * netsnmp_tlstcp_open(netsnmp_transport *t) { _netsnmpTLSBaseData *tlsdata; BIO *bio; SSL_CTX *ctx; SSL *ssl; int rc = 0; _netsnmp_verify_info *verify_info; netsnmp_assert_or_return(t != NULL, NULL); netsnmp_assert_or_return(t->data != NULL, NULL); netsnmp_assert_or_return(sizeof(_netsnmpTLSBaseData) == t->data_length, NULL); tlsdata = t->data; if (tlsdata->flags & NETSNMP_TLSBASE_IS_CLIENT) { /* Is the client */ /* RFC5953 Section 5.3.1: Establishing a Session as a Client * 1) The snmpTlstmSessionOpens counter is incremented. */ snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENS); /* RFC5953 Section 5.3.1: Establishing a Session as a Client 2) The client selects the appropriate certificate and cipher_suites for the key agreement based on the tmSecurityName and the tmRequestedSecurityLevel for the session. For sessions being established as a result of a SNMP-TARGET-MIB based operation, the certificate will potentially have been identified via the snmpTlstmParamsTable mapping and the cipher_suites will have to be taken from system-wide or implementation-specific configuration. If no row in the snmpTlstmParamsTable exists then implementations MAY choose to establish the connection using a default client certificate available to the application. Otherwise, the certificate and appropriate cipher_suites will need to be passed to the openSession() ASI as supplemental information or configured through an implementation-dependent mechanism. It is also implementation-dependent and possibly policy-dependent how tmRequestedSecurityLevel will be used to influence the security capabilities provided by the (D)TLS connection. However this is done, the security capabilities provided by (D)TLS MUST be at least as high as the level of security indicated by the tmRequestedSecurityLevel parameter. The actual security level of the session is reported in the tmStateReference cache as tmSecurityLevel. For (D)TLS to provide strong authentication, each principal acting as a command generator SHOULD have its own certificate. */ /* Implementation notes: we do most of this in the sslctx_client_setup The transport should have been f_config()ed with the proper fingerprints to use (which is stored in tlsdata), or we'll use the default identity fingerprint if that can be found. */ /* XXX: check securityLevel and ensure no NULL fingerprints are used */ /* set up the needed SSL context */ tlsdata->ssl_context = ctx = sslctx_client_setup(TLSv1_method(), tlsdata); if (!ctx) { snmp_log(LOG_ERR, "failed to create TLS context\n"); return NULL; } /* RFC5953 Section 5.3.1: Establishing a Session as a Client 3) Using the destTransportDomain and destTransportAddress values, the client will initiate the (D)TLS handshake protocol to establish session keys for message integrity and encryption. */ /* Implementation note: The transport domain and address are pre-processed by this point */ /* Create a BIO connection for it */ DEBUGMSGTL(("tlstcp", "connecting to tlstcp %s\n", tlsdata->addr_string)); t->remote = (void *) strdup(tlsdata->addr_string); t->remote_length = strlen(tlsdata->addr_string) + 1; bio = BIO_new_connect(tlsdata->addr_string); /* RFC5953 Section 5.3.1: Establishing a Session as a Client 3) continued: If the attempt to establish a session is unsuccessful, then snmpTlstmSessionOpenErrors is incremented, an error indication is returned, and processing stops. */ if (NULL == bio) { snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); snmp_log(LOG_ERR, "tlstcp: failed to create bio\n"); _openssl_log_error(rc, NULL, "BIO creation"); return NULL; } /* Tell the BIO to actually do the connection */ if ((rc = BIO_do_connect(bio)) <= 0) { snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); snmp_log(LOG_ERR, "tlstcp: failed to connect to %s\n", tlsdata->addr_string); _openssl_log_error(rc, NULL, "BIO_do_connect"); BIO_free(bio); return NULL; } /* Create the SSL layer on top of the socket bio */ ssl = tlsdata->ssl = SSL_new(ctx); if (NULL == ssl) { snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); snmp_log(LOG_ERR, "tlstcp: failed to create a SSL connection\n"); BIO_free(bio); return NULL; } /* Bind the SSL layer to the BIO */ SSL_set_bio(ssl, bio, bio); SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); verify_info = SNMP_MALLOC_TYPEDEF(_netsnmp_verify_info); if (NULL == verify_info) { snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); snmp_log(LOG_ERR, "tlstcp: failed to create a SSL connection\n"); SSL_shutdown(ssl); BIO_free(bio); return NULL; } SSL_set_ex_data(ssl, tls_get_verify_info_index(), verify_info); /* Then have SSL do it's connection over the BIO */ if ((rc = SSL_connect(ssl)) <= 0) { snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); snmp_log(LOG_ERR, "tlstcp: failed to ssl_connect\n"); BIO_free(bio); return NULL; } /* RFC5953 Section 5.3.1: Establishing a Session as a Client 3) continued: If the session failed to open because the presented server certificate was unknown or invalid then the snmpTlstmSessionUnknownServerCertificate or snmpTlstmSessionInvalidServerCertificates MUST be incremented and a snmpTlstmServerCertificateUnknown or snmpTlstmServerInvalidCertificate notification SHOULD be sent as appropriate. Reasons for server certificate invalidation includes, but is not limited to, cryptographic validation failures and an unexpected presented certificate identity. */ /* RFC5953 Section 5.3.1: Establishing a Session as a Client 4) The (D)TLS client MUST then verify that the (D)TLS server's presented certificate is the expected certificate. The (D)TLS client MUST NOT transmit SNMP messages until the server certificate has been authenticated, the client certificate has been transmitted and the TLS connection has been fully established. If the connection is being established from configuration based on SNMP-TARGET-MIB configuration, then the snmpTlstmAddrTable DESCRIPTION clause describes how the verification is done (using either a certificate fingerprint, or an identity authenticated via certification path validation). If the connection is being established for reasons other than configuration found in the SNMP-TARGET-MIB then configuration and procedures outside the scope of this document should be followed. Configuration mechanisms SHOULD be similar in nature to those defined in the snmpTlstmAddrTable to ensure consistency across management configuration systems. For example, a command-line tool for generating SNMP GETs might support specifying either the server's certificate fingerprint or the expected host name as a command line argument. */ /* Implementation notes: - All remote certificate fingerprints are expected to be stored in the transport's config information. This is true both for CLI clients and TARGET-MIB sessions. - netsnmp_tlsbase_verify_server_cert implements these checks */ if (netsnmp_tlsbase_verify_server_cert(ssl, tlsdata) != SNMPERR_SUCCESS) { /* XXX: unknown vs invalid; two counters */ snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONUNKNOWNSERVERCERTIFICATE); snmp_log(LOG_ERR, "tlstcp: failed to verify ssl certificate\n"); SSL_shutdown(ssl); BIO_free(bio); return NULL; } /* RFC5953 Section 5.3.1: Establishing a Session as a Client 5) (D)TLS provides assurance that the authenticated identity has been signed by a trusted configured certification authority. If verification of the server's certificate fails in any way (for example because of failures in cryptographic verification or the presented identity did not match the expected named entity) then the session establishment MUST fail, the snmpTlstmSessionInvalidServerCertificates 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. */ /* XXX: add snmpTlstmSessionInvalidServerCertificates on crypto failure */ /* RFC5953 Section 5.3.1: Establishing a Session as a Client 6) The TLSTM-specific session identifier (tlstmSessionID) is set in the tmSessionID of the tmStateReference passed to the TLS Transport Model to indicate that the session has been established successfully and to point to a specific (D)TLS connection for future use. The tlstmSessionID is also stored in the LCD for later lookup during processing of incoming messages (Section 5.1.2). */ /* Implementation notes: - the tlsdata pointer is used as our session identifier, as noted in the netsnmp_tlstcp_recv() function comments. */ t->sock = BIO_get_fd(bio, NULL); } else { #ifndef NETSNMP_NO_LISTEN_SUPPORT /* Is the server */ /* Create the socket bio */ DEBUGMSGTL(("tlstcp", "listening on tlstcp port %s\n", tlsdata->addr_string)); tlsdata->accept_bio = BIO_new_accept(tlsdata->addr_string); t->local = (void *) strdup(tlsdata->addr_string); t->local_length = strlen(tlsdata->addr_string)+1; if (NULL == tlsdata->accept_bio) { snmp_log(LOG_ERR, "TLSTCP: Falied to create a accept BIO\n"); return NULL; } /* openssl requires an initial accept to bind() the socket */ if (BIO_do_accept(tlsdata->accept_bio) <= 0) { _openssl_log_error(rc, tlsdata->ssl, "BIO_do__accept"); snmp_log(LOG_ERR, "TLSTCP: Falied to do first accept on the TLS accept BIO\n"); return NULL; } /* create the OpenSSL TLS context */ tlsdata->ssl_context = sslctx_server_setup(TLSv1_method()); t->sock = BIO_get_fd(tlsdata->accept_bio, NULL); t->flags |= NETSNMP_TRANSPORT_FLAG_LISTEN; #else /* NETSNMP_NO_LISTEN_SUPPORT */ return NULL; #endif /* NETSNMP_NO_LISTEN_SUPPORT */ } return t; }