| /* Portions of this file are subject to the following copyright(s). See |
| * the Net-SNMP's COPYING file for more details and other copyrights |
| * that may apply: |
| */ |
| /* |
| * See the following web pages for useful documentation on this transport: |
| * http://www.net-snmp.org/wiki/index.php/TUT:Using_TLS |
| * http://www.net-snmp.org/wiki/index.php/Using_DTLS |
| */ |
| #include <net-snmp/net-snmp-config.h> |
| |
| #include <net-snmp/library/snmpTLSTCPDomain.h> |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <ctype.h> |
| #include <errno.h> |
| |
| #if HAVE_STRING_H |
| #include <string.h> |
| #else |
| #include <strings.h> |
| #endif |
| #if HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #if HAVE_UNISTD_H |
| #include <unistd.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 |
| #if HAVE_SYS_UIO_H |
| #include <sys/uio.h> |
| #endif |
| |
| #if HAVE_ARPA_INET_H |
| #include <arpa/inet.h> |
| #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/snmpIPv4BaseDomain.h> |
| #include <net-snmp/library/snmpSocketBaseDomain.h> |
| #include <net-snmp/library/snmpTLSBaseDomain.h> |
| #include <net-snmp/library/system.h> |
| #include <net-snmp/library/tools.h> |
| #include <net-snmp/library/cert_util.h> |
| #include <net-snmp/library/snmp_openssl.h> |
| #include <net-snmp/library/callback.h> |
| |
| #include "openssl/bio.h" |
| #include "openssl/ssl.h" |
| #include "openssl/err.h" |
| |
| #ifndef INADDR_NONE |
| #define INADDR_NONE -1 |
| #endif |
| |
| #define WE_ARE_SERVER 0 |
| #define WE_ARE_CLIENT 1 |
| |
| oid netsnmpTLSTCPDomain[] = { TRANSPORT_DOMAIN_TLS_TCP_IP }; |
| size_t netsnmpTLSTCPDomain_len = OID_LENGTH(netsnmpTLSTCPDomain); |
| |
| static netsnmp_tdomain tlstcpDomain; |
| |
| /* |
| * Return a string representing the address in data, or else the "far end" |
| * address if data is NULL. |
| */ |
| |
| static char * |
| netsnmp_tlstcp_fmtaddr(netsnmp_transport *t, void *data, int len) |
| { |
| if (NULL == data || 0 == len || 0 == ((char *) data)[0]) |
| return strdup("TLSTCP: unknown"); |
| else if (len == sizeof(netsnmp_indexed_addr_pair) || |
| len == sizeof(struct sockaddr_in)) |
| return netsnmp_ipv4_fmtaddr("TLSTCP", t, data, len); |
| else { |
| /* an already ascii formatted string */ |
| char buf[1024]; |
| snprintf(buf, sizeof(buf)-1, "TLSTCP: %s", (char *) data); |
| return strdup(buf); |
| } |
| } |
| /* |
| * You can write something into opaque that will subsequently get passed back |
| * to your send function if you like. For instance, you might want to |
| * remember where a PDU came from, so that you can send a reply there... |
| */ |
| |
| static int |
| netsnmp_tlstcp_copy(netsnmp_transport *oldt, netsnmp_transport *newt) |
| { |
| _netsnmpTLSBaseData *oldtlsdata = (_netsnmpTLSBaseData *) oldt->data; |
| _netsnmpTLSBaseData *newtlsdata = (_netsnmpTLSBaseData *) newt->data; |
| oldtlsdata->accepted_bio = NULL; |
| oldtlsdata->ssl = NULL; |
| newtlsdata->ssl_context = NULL; |
| |
| if (oldtlsdata->addr_string) |
| newtlsdata->addr_string = strdup(oldtlsdata->addr_string); |
| if (oldtlsdata->securityName) |
| newtlsdata->securityName = strdup(oldtlsdata->securityName); |
| if (oldtlsdata->our_identity) |
| newtlsdata->our_identity = strdup(oldtlsdata->our_identity); |
| if (oldtlsdata->their_identity) |
| newtlsdata->their_identity = strdup(oldtlsdata->their_identity); |
| if (oldtlsdata->their_fingerprint) |
| newtlsdata->their_fingerprint = strdup(oldtlsdata->their_fingerprint); |
| if (oldtlsdata->their_hostname) |
| newtlsdata->their_hostname = strdup(oldtlsdata->their_hostname); |
| if (oldtlsdata->trust_cert) |
| newtlsdata->trust_cert = strdup(oldtlsdata->trust_cert); |
| if (oldtlsdata->remote_addr) |
| newtlsdata->remote_addr = netsnmp_memdup(oldtlsdata->remote_addr, |
| sizeof(netsnmp_indexed_addr_pair)); |
| |
| return 0; |
| } |
| |
| static int |
| netsnmp_tlstcp_recv(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| _netsnmpTLSBaseData *tlsdata; |
| |
| if (NULL == t || t->sock < 0 || NULL == t->data) { |
| snmp_log(LOG_ERR, |
| "tlstcp received an invalid invocation with missing data\n"); |
| DEBUGMSGTL(("tlstcp", "recvfrom fd %d err %d (\"%s\")\n", |
| (t ? t->sock : -1), errno, strerror(errno))); |
| DEBUGMSGTL(("tlstcp", " tdata = %p\n", t->data)); |
| return -1; |
| } |
| |
| /* RFC5953 Section 5.1.2 step 1: |
| 1) Determine the tlstmSessionID for the incoming message. The |
| tlstmSessionID MUST be a unique session identifier for this |
| (D)TLS connection. The contents and format of this identifier |
| are implementation-dependent as long as it is unique to the |
| session. A session identifier MUST NOT be reused until all |
| references to it are no longer in use. The tmSessionID is |
| equal to the tlstmSessionID discussed in Section 5.1.1. |
| tmSessionID refers to the session identifier when stored in the |
| tmStateReference and tlstmSessionID refers to the session |
| identifier when stored in the LCD. They MUST always be equal |
| when processing a given session's traffic. |
| */ |
| /* For this implementation we use the t->data memory pointer as |
| the sessionID. As it's a pointer to session specific data tied |
| with the transport object we know it'll never be realloated |
| (ie, duplicated) until release by this transport object and is |
| safe to use as a unique session identifier. */ |
| |
| tlsdata = t->data; |
| if (NULL == tlsdata->ssl) { |
| snmp_log(LOG_ERR, |
| "tlstcp received an invalid invocation without ssl data\n"); |
| return -1; |
| } |
| |
| /* RFC5953 Section 5.1.2 step 1, part2: |
| * This part (incrementing the counter) is done in the |
| netsnmp_tlstcp_accept function. |
| */ |
| |
| |
| /* RFC5953 Section 5.1.2 step 2: |
| * Create a tmStateReference cache for the subsequent reference and |
| assign the following values within it: |
| |
| tmTransportDomain = snmpTLSTCPDomain or snmpDTLSUDPDomain as |
| appropriate. |
| |
| tmTransportAddress = The address the message originated from. |
| |
| tmSecurityLevel = The derived tmSecurityLevel for the session, |
| as discussed in Section 3.1.2 and Section 5.3. |
| |
| tmSecurityName = The fderived tmSecurityName for the session as |
| discussed in Section 5.3. This value MUST |
| be constant during the lifetime of the |
| session. |
| |
| tmSessionID = The tlstmSessionID described in step 1 above. |
| */ |
| |
| /* Implementation notes: |
| * - The tmTransportDomain is represented by the transport object |
| * - The tmpSessionID is represented by the tlsdata pointer (as |
| discussed above) |
| * - The following items are handled later in netsnmp_tlsbase_wrapup_recv: |
| - tmSecurityLevel |
| - tmSecurityName |
| - tmSessionID |
| */ |
| |
| /* create a tmStateRef cache for slow fill-in */ |
| tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference); |
| |
| if (tmStateRef == NULL) { |
| *opaque = NULL; |
| *olength = 0; |
| return -1; |
| } |
| |
| /* Set the transportDomain */ |
| memcpy(tmStateRef->transportDomain, |
| netsnmpTLSTCPDomain, sizeof(netsnmpTLSTCPDomain[0]) * |
| netsnmpTLSTCPDomain_len); |
| tmStateRef->transportDomainLen = netsnmpTLSTCPDomain_len; |
| |
| /* Set the tmTransportAddress */ |
| tmStateRef->have_addresses = 1; |
| |
| /* RFC5953 Section 5.1.2 step 1: |
| * 3) The incomingMessage and incomingMessageLength are assigned values |
| from the (D)TLS processing. |
| */ |
| |
| /* Implementation notes: |
| - incomingMessage = buf pointer |
| - incomingMessageLength = rc |
| */ |
| |
| /* read the packet from openssl */ |
| do { |
| rc = SSL_read(tlsdata->ssl, buf, size); |
| if (rc == 0) { |
| /* XXX closed connection */ |
| DEBUGMSGTL(("tlstcp", "remote side closed connection\n")); |
| /* XXX: openssl cleanup */ |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| if (rc == -1) { |
| int err = SSL_get_error(tlsdata->ssl, rc); |
| if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { |
| /* error detected */ |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_read"); |
| SNMP_FREE(tmStateRef); |
| return rc; |
| } |
| } |
| /* retry read for SSL_ERROR_WANT_READ || SSL_ERROR_WANT_WRITE */ |
| } while (rc <= 0); |
| |
| DEBUGMSGTL(("tlstcp", "received %d decoded bytes from tls\n", rc)); |
| |
| /* log the packet */ |
| DEBUGIF("tlstcp") { |
| char *str = netsnmp_tlstcp_fmtaddr(t, NULL, 0); |
| DEBUGMSGTL(("tlstcp", |
| "recvfrom fd %d got %d bytes (from %s)\n", |
| t->sock, rc, str)); |
| free(str); |
| } |
| |
| /* Other wrap-up things common to TLS and DTLS */ |
| if (netsnmp_tlsbase_wrapup_recv(tmStateRef, tlsdata, opaque, olength) != |
| SNMPERR_SUCCESS) |
| return SNMPERR_GENERR; |
| |
| /* RFC5953 Section 5.1.2 step 1: |
| * 4) The TLS Transport Model passes the transportDomain, |
| transportAddress, incomingMessage, and incomingMessageLength to |
| the Dispatcher using the receiveMessage ASI: |
| */ |
| |
| /* In our implementation, this is done simply by returning */ |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_tlstcp_send(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| _netsnmpTLSBaseData *tlsdata; |
| |
| DEBUGTRACETOK("tlstcp"); |
| |
| /* RFC5953 section 5.2: |
| 1) If tmStateReference does not refer to a cache containing values |
| for tmTransportDomain, tmTransportAddress, tmSecurityName, |
| tmRequestedSecurityLevel, and tmSameSecurity, then increment the |
| snmpTlstmSessionInvalidCaches counter, discard the message, and |
| return the error indication in the statusInformation. Processing |
| of this message stops. |
| */ |
| /* Implementation Notes: the tmStateReference is stored in the opaque ptr */ |
| if (opaque != NULL && *opaque != NULL && |
| *olength == sizeof(netsnmp_tmStateReference)) { |
| tmStateRef = (netsnmp_tmStateReference *) *opaque; |
| } else { |
| snmp_log(LOG_ERR, "TLSTCP was called with an invalid state; possibly the wrong security model is in use. It should be 'tsm'.\n"); |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES); |
| return SNMPERR_GENERR; |
| } |
| |
| /* RFC5953 section 5.2: |
| 2) Extract the tmSessionID, tmTransportDomain, tmTransportAddress, |
| tmSecurityName, tmRequestedSecurityLevel, and tmSameSecurity |
| values from the tmStateReference. Note: The tmSessionID value |
| may be undefined if no session exists yet over which the message |
| can be sent. |
| */ |
| /* Implementation Notes: |
| - Our session will always exist by now as it's created when the |
| transport object is created. Auto-session creation is handled |
| higher in the stack. |
| - We don't "extract" per say since we just leave the data in |
| the structure. |
| - The sessionID is stored in the t->data memory pointer. |
| */ |
| |
| /* RFC5953 section 5.2: |
| 3) If tmSameSecurity is true and either tmSessionID is undefined or |
| refers to a session that is no longer open then increment the |
| snmpTlstmSessionNoSessions counter, discard the message and |
| return the error indication in the statusInformation. Processing |
| of this message stops. |
| */ |
| /* Implementation Notes: |
| - We would never get here if the sessionID was either undefined |
| or different. We tie packets directly to the transport |
| object and it could never be sent back over a different |
| transport, which is what the above text is trying to prevent. |
| */ |
| |
| /* RFC5953 section 5.2: |
| 4) If tmSameSecurity is false and tmSessionID refers to a session |
| that is no longer available then an implementation SHOULD open a |
| new session using the openSession() ASI (described in greater |
| detail in step 5b). Instead of opening a new session an |
| implementation MAY return a snmpTlstmSessionNoSessions error to |
| the calling module and stop processing of the message. |
| */ |
| /* Implementation Notes: |
| - We would never get here if the sessionID was either undefined |
| or different. We tie packets directly to the transport |
| object and it could never be sent back over a different |
| transport, which is what the above text is trying to prevent. |
| - Auto-connections are handled higher in the Net-SNMP library stack |
| */ |
| |
| /* RFC5953 section 5.2: |
| 5) If tmSessionID is undefined, then use tmTransportDomain, |
| tmTransportAddress, tmSecurityName and tmRequestedSecurityLevel |
| to see if there is a corresponding entry in the LCD suitable to |
| send the message over. |
| |
| 5a) If there is a corresponding LCD entry, then this session |
| will be used to send the message. |
| |
| 5b) If there is not a corresponding LCD entry, then open a |
| session using the openSession() ASI (discussed further in |
| Section 5.3.1). Implementations MAY wish to offer message |
| buffering to prevent redundant openSession() calls for the |
| same cache entry. If an error is returned from |
| openSession(), then discard the message, discard the |
| tmStateReference, increment the snmpTlstmSessionOpenErrors, |
| return an error indication to the calling module and stop |
| processing of the message. |
| */ |
| /* Implementation Notes: |
| - We would never get here if the sessionID was either undefined |
| or different. We tie packets directly to the transport |
| object and it could never be sent back over a different |
| transport, which is what the above text is trying to prevent. |
| - Auto-connections are handled higher in the Net-SNMP library stack |
| */ |
| |
| /* our session pointer is functionally t->data */ |
| if (NULL == t->data) { |
| snmp_log(LOG_ERR, "netsnmp_tlstcp_send received no incoming data\n"); |
| return -1; |
| } |
| |
| tlsdata = t->data; |
| |
| if (tlsdata->ssl == NULL) { |
| snmp_log(LOG_ERR, "tlstcp_send was called without a SSL connection.\n"); |
| return SNMPERR_GENERR; |
| } |
| |
| /* If the first packet and we have no secname, then copy the |
| important securityName data into the longer-lived session |
| reference information. */ |
| if ((tlsdata->flags & NETSNMP_TLSBASE_IS_CLIENT) && |
| !tlsdata->securityName && tmStateRef && tmStateRef->securityNameLen > 0) |
| tlsdata->securityName = strdup(tmStateRef->securityName); |
| |
| |
| /* RFC5953 section 5.2: |
| 6) Using either the session indicated by the tmSessionID if there |
| was one or the session resulting from a previous step (4 or 5), |
| pass the outgoingMessage to (D)TLS for encapsulation and |
| transmission. |
| */ |
| rc = SSL_write(tlsdata->ssl, buf, size); |
| DEBUGMSGTL(("tlstcp", "wrote %d bytes\n", size)); |
| if (rc < 0) { |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_write"); |
| } |
| |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_tlstcp_close(netsnmp_transport *t) |
| { |
| _netsnmpTLSBaseData *tlsdata; |
| |
| if (NULL == t || NULL == t->data) |
| return -1; |
| |
| /* RFC5953 Section 5.4. Closing a Session |
| |
| 1) Increment either the snmpTlstmSessionClientCloses or the |
| snmpTlstmSessionServerCloses counter as appropriate. |
| */ |
| if (t->flags & NETSNMP_TLSBASE_IS_CLIENT) |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONCLIENTCLOSES); |
| else |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONSERVERCLOSES); |
| |
| /* RFC5953 Section 5.4. Closing a Session |
| 2) Look up the session using the tmSessionID. |
| */ |
| tlsdata = (_netsnmpTLSBaseData *) t->data; |
| |
| /* RFC5953 Section 5.4. Closing a Session |
| 3) If there is no open session associated with the tmSessionID, then |
| closeSession processing is completed. |
| */ |
| /* Implementation notes: if we have a non-zero tlsdata then it's |
| always true */ |
| |
| /* RFC5953 Section 5.3.1: Establishing a Session as a Client |
| 4) Have (D)TLS close the specified connection. This SHOULD include |
| sending a close_notify TLS Alert to inform the other side that |
| session cleanup may be performed. |
| */ |
| |
| DEBUGMSGTL(("tlstcp", "Shutting down SSL connection\n")); |
| if (tlsdata->ssl) { |
| SSL_shutdown(tlsdata->ssl); |
| } |
| |
| netsnmp_tlsbase_free_tlsdata(tlsdata); |
| |
| t->data = NULL; |
| return netsnmp_socketbase_close(t); |
| } |
| |
| static int |
| netsnmp_tlstcp_accept(netsnmp_transport *t) |
| { |
| BIO *accepted_bio; |
| int rc; |
| SSL_CTX *ctx; |
| SSL *ssl; |
| _netsnmpTLSBaseData *tlsdata = NULL; |
| |
| DEBUGMSGTL(("tlstcp", "netsnmp_tlstcp_accept called\n")); |
| |
| tlsdata = (_netsnmpTLSBaseData *) t->data; |
| |
| rc = BIO_do_accept(tlsdata->accept_bio); |
| |
| if (rc <= 0) { |
| snmp_log(LOG_ERR, "BIO_do_accept failed\n"); |
| _openssl_log_error(rc, NULL, "BIO_do_accept"); |
| /* XXX: need to close the listening connection here? */ |
| return -1; |
| } |
| |
| tlsdata->accepted_bio = accepted_bio = BIO_pop(tlsdata->accept_bio); |
| if (!accepted_bio) { |
| snmp_log(LOG_ERR, "Failed to pop an accepted bio off the bio staack\n"); |
| /* XXX: need to close the listening connection here? */ |
| return -1; |
| } |
| |
| /* create the OpenSSL TLS context */ |
| ctx = tlsdata->ssl_context; |
| |
| /* create the server's main SSL bio */ |
| ssl = tlsdata->ssl = SSL_new(ctx); |
| if (!tlsdata->ssl) { |
| snmp_log(LOG_ERR, "TLSTCP: Failed to create a SSL BIO\n"); |
| BIO_free(accepted_bio); |
| tlsdata->accepted_bio = NULL; |
| return -1; |
| } |
| |
| SSL_set_bio(ssl, accepted_bio, accepted_bio); |
| |
| if ((rc = SSL_accept(ssl)) <= 0) { |
| snmp_log(LOG_ERR, "TLSTCP: Failed SSL_accept\n"); |
| _openssl_log_error(rc, ssl, "SSL_accept"); |
| SSL_shutdown(tlsdata->ssl); |
| SSL_free(tlsdata->ssl); |
| tlsdata->accepted_bio = NULL; /* freed by SSL_free */ |
| tlsdata->ssl = NULL; |
| return -1; |
| } |
| |
| /* |
| * currently netsnmp_tlsbase_wrapup_recv is where we check for |
| * algorithm compliance, but for tls we know the algorithms |
| * at this point, so we could bail earlier... |
| */ |
| #if 0 /* moved checks to netsnmp_tlsbase_wrapup_recv */ |
| netsnmp_openssl_null_checks(tlsdata->ssl, &no_auth, NULL); |
| if (no_auth != 0) { /* null/unknown authentication */ |
| /* xxx-rks: snmp_increment_statistic(STAT_???); */ |
| snmp_log(LOG_ERR, "tlstcp: connection with NULL authentication\n"); |
| SSL_shutdown(tlsdata->ssl); |
| SSL_free(tlsdata->ssl); |
| tlsdata->accepted_bio = NULL; /* freed by SSL_free */ |
| tlsdata->ssl = NULL; |
| return -1; |
| } |
| #endif |
| |
| /* RFC5953 Section 5.3.2: Accepting a Session as a Server |
| 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. |
| |
| 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. |
| |
| Servers that wish to support multiple principals at a particular port |
| SHOULD make use of a (D)TLS extension that allows server-side |
| principal selection like the Server Name Indication extension defined |
| in Section 3.1 of [RFC4366]. Supporting this will allow, for |
| example, sending notifications to a specific principal at a given TCP |
| or UDP port. |
| */ |
| /* Implementation notes: |
| - we expect fingerprints to be stored in the transport config |
| - we do not currently support mulitple principals and only offer one |
| */ |
| if ((rc = netsnmp_tlsbase_verify_client_cert(ssl, tlsdata)) |
| != SNMPERR_SUCCESS) { |
| /* XXX: free needed memory */ |
| snmp_log(LOG_ERR, "TLSTCP: Falied checking client certificate\n"); |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCLIENTCERTIFICATES); |
| SSL_shutdown(tlsdata->ssl); |
| SSL_free(tlsdata->ssl); |
| tlsdata->accepted_bio = NULL; /* freed by SSL_free */ |
| tlsdata->ssl = NULL; |
| return -1; |
| } |
| |
| |
| /* XXX: check acceptance criteria here */ |
| |
| DEBUGMSGTL(("tlstcp", "accept succeeded on sock %d\n", t->sock)); |
| |
| /* RFC5953 Section 5.1.2 step 1, part2:: |
| * If this is the first message received through this session and |
| the session does not have an assigned tlstmSessionID yet then the |
| snmpTlstmSessionAccepts counter is incremented and a |
| tlstmSessionID for the session is created. This will only happen |
| on the server side of a connection because a client would have |
| already assigned a tlstmSessionID during the openSession() |
| invocation. Implementations may have performed the procedures |
| described in Section 5.3.2 prior to this point or they may |
| perform them now, but the procedures described in Section 5.3.2 |
| MUST be performed before continuing beyond this point. |
| */ |
| /* We're taking option 2 and incrementing the session accepts here |
| rather than upon receiving the first packet */ |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONACCEPTS); |
| |
| /* XXX: check that it returns something so we can free stuff? */ |
| return BIO_get_fd(tlsdata->accepted_bio, NULL); |
| } |
| |
| |
| 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"); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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); |
| SNMP_FREE(tlsdata); |
| SNMP_FREE(t); |
| 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 { |
| /* 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_FREE(t); |
| SNMP_FREE(tlsdata); |
| 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_FREE(t); |
| SNMP_FREE(tlsdata); |
| 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; |
| } |
| return t; |
| } |
| |
| /* |
| * Create a TLS-based transport for SNMP. Local is TRUE if addr is the local |
| * address to bind to (i.e. this is a server-type session); otherwise addr is |
| * the remote address to send things to. |
| */ |
| |
| netsnmp_transport * |
| netsnmp_tlstcp_transport(const char *addr_string, int isserver) |
| { |
| netsnmp_transport *t = NULL; |
| _netsnmpTLSBaseData *tlsdata; |
| char *cp; |
| char buf[SPRINT_MAX_LEN]; |
| |
| /* allocate our transport structure */ |
| t = SNMP_MALLOC_TYPEDEF(netsnmp_transport); |
| if (NULL == t) { |
| return NULL; |
| } |
| memset(t, 0, sizeof(netsnmp_transport)); |
| |
| /* allocate our TLS specific data */ |
| if (NULL == (tlsdata = netsnmp_tlsbase_allocate_tlsdata(t, isserver))) |
| return NULL; |
| |
| if (!isserver) |
| t->flags |= NETSNMP_TLSBASE_IS_CLIENT; |
| |
| tlsdata->addr_string = strdup(addr_string); |
| |
| /* see if we can extract the remote hostname */ |
| if (!isserver && tlsdata && addr_string) { |
| /* search for a : */ |
| if (NULL != (cp = strrchr(addr_string, ':'))) { |
| sprintf(buf, "%.*s", |
| (int) SNMP_MIN(cp - addr_string, sizeof(buf) - 1), |
| addr_string); |
| } else { |
| /* else the entire spec is a host name only */ |
| strlcpy(buf, addr_string, sizeof(buf)); |
| } |
| tlsdata->their_hostname = strdup(buf); |
| } |
| |
| t->data = tlsdata; |
| t->data_length = sizeof(_netsnmpTLSBaseData); |
| |
| /* |
| * Set Domain |
| */ |
| t->domain = netsnmpTLSTCPDomain; |
| t->domain_length = netsnmpTLSTCPDomain_len; |
| |
| /* |
| * 16-bit length field, 8 byte TLS header, 20 byte IPv4 header |
| */ |
| |
| t->msgMaxSize = 0xffff - 8 - 20; |
| t->f_recv = netsnmp_tlstcp_recv; |
| t->f_send = netsnmp_tlstcp_send; |
| t->f_open = netsnmp_tlstcp_open; |
| t->f_close = netsnmp_tlstcp_close; |
| t->f_accept = netsnmp_tlstcp_accept; |
| t->f_copy = netsnmp_tlstcp_copy; |
| t->f_config = netsnmp_tlsbase_config; |
| t->f_setup_session = netsnmp_tlsbase_session_init; |
| t->f_fmtaddr = netsnmp_tlstcp_fmtaddr; |
| |
| t->flags |= NETSNMP_TRANSPORT_FLAG_TUNNELED | NETSNMP_TRANSPORT_FLAG_STREAM; |
| |
| return t; |
| } |
| |
| netsnmp_transport * |
| netsnmp_tlstcp_create_tstring(const char *str, int local, |
| const char *default_target) |
| { |
| char buf[SPRINT_MAX_LEN]; |
| |
| if (str == NULL || *str == '\0') |
| str = default_target + 1; /* drop the leading : */ |
| else if (!strchr(str, ':')) { |
| /* it's either :port or :address. Try to guess which. */ |
| const char *cp; |
| int isport = 1; |
| for(cp = str; *cp != '\0'; cp++) { |
| /* if ALL numbers, it must be just a port */ |
| /* if it contains anything else, assume a host or ip address */ |
| if (!isdigit(0xFF & *cp)) { |
| isport = 0; |
| break; |
| } |
| } |
| if (isport) { |
| /* Just :NNN can be passed to openssl */ |
| snprintf(buf, sizeof(buf)-1, "0.0.0.0:%s", str); |
| } else { |
| /* add the default port */ |
| snprintf(buf, sizeof(buf)-1, "%s%s", str, default_target); |
| } |
| str = buf; |
| } |
| return netsnmp_tlstcp_transport(str, local); |
| } |
| |
| |
| netsnmp_transport * |
| netsnmp_tlstcp_create_ostring(const u_char * o, size_t o_len, int local) |
| { |
| char buf[SPRINT_MAX_LEN]; |
| |
| /* ensure buf is big enough */ |
| if (o_len > SPRINT_MAX_LEN - 1) |
| return NULL; |
| |
| memcpy(buf, o, o_len); |
| buf[o_len] = '\0'; |
| |
| return netsnmp_tlstcp_transport(buf, local); |
| } |
| |
| void |
| netsnmp_tlstcp_ctor(void) |
| { |
| DEBUGMSGTL(("tlstcp", "registering TLS constructor\n")); |
| |
| /* config settings */ |
| |
| tlstcpDomain.name = netsnmpTLSTCPDomain; |
| tlstcpDomain.name_length = netsnmpTLSTCPDomain_len; |
| tlstcpDomain.prefix = (const char**)calloc(3, sizeof(char *)); |
| tlstcpDomain.prefix[0] = "tlstcp"; |
| tlstcpDomain.prefix[1] = "tls"; |
| |
| tlstcpDomain.f_create_from_tstring = NULL; |
| tlstcpDomain.f_create_from_tstring_new = netsnmp_tlstcp_create_tstring; |
| tlstcpDomain.f_create_from_ostring = netsnmp_tlstcp_create_ostring; |
| |
| netsnmp_tdomain_register(&tlstcpDomain); |
| } |