| /* 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: |
| */ |
| /* |
| * Portions of this file are copyrighted by: |
| * Copyright Copyright 2003 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms specified in the COPYING file |
| * distributed with the Net-SNMP package. |
| */ |
| /* |
| * 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> |
| |
| #ifdef HAVE_LIBSSL_DTLS |
| |
| #include <net-snmp/library/snmpDTLSUDPDomain.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_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_transport.h> |
| #include <net-snmp/library/system.h> |
| #include <net-snmp/library/tools.h> |
| #include <net-snmp/library/callback.h> |
| |
| #include "openssl/bio.h" |
| #include "openssl/ssl.h" |
| #include "openssl/err.h" |
| #include "openssl/rand.h" |
| |
| #include <net-snmp/library/snmpSocketBaseDomain.h> |
| #include <net-snmp/library/snmpTLSBaseDomain.h> |
| #include <net-snmp/library/snmpUDPDomain.h> |
| #include <net-snmp/library/cert_util.h> |
| #include <net-snmp/library/snmp_openssl.h> |
| |
| #ifndef INADDR_NONE |
| #define INADDR_NONE -1 |
| #endif |
| |
| #define WE_ARE_SERVER 0 |
| #define WE_ARE_CLIENT 1 |
| |
| oid netsnmpDTLSUDPDomain[] = { TRANSPORT_DOMAIN_DTLS_UDP_IP }; |
| size_t netsnmpDTLSUDPDomain_len = OID_LENGTH(netsnmpDTLSUDPDomain); |
| |
| static netsnmp_tdomain dtlsudpDomain; |
| |
| /* this stores openssl credentials for each connection since openssl |
| can't do it for us at the moment; hopefully future versions will |
| change */ |
| typedef struct bio_cache_s { |
| BIO *read_bio; /* OpenSSL will read its incoming SSL packets from here */ |
| BIO *write_bio; /* OpenSSL will write its outgoing SSL packets to here */ |
| struct sockaddr_in sockaddr; |
| uint32_t ipv4addr; |
| u_short portnum; |
| u_int flags; |
| struct bio_cache_s *next; |
| int msgnum; |
| char *write_cache; |
| size_t write_cache_len; |
| _netsnmpTLSBaseData *tlsdata; |
| } bio_cache; |
| |
| /** bio_cache flags */ |
| #define NETSNMP_BIO_HAVE_COOKIE 0x0001 /* verified cookie */ |
| #define NETSNMP_BIO_CONNECTED 0x0002 /* recieved decoded data */ |
| #define NETSNMP_BIO_DISCONNECTED 0x0004 /* peer shutdown */ |
| |
| static bio_cache *biocache = NULL; |
| |
| static int openssl_addr_index = 0; |
| |
| static int netsnmp_dtls_verify_cookie(SSL *ssl, unsigned char *cookie, |
| unsigned int cookie_len); |
| static int netsnmp_dtls_gen_cookie(SSL *ssl, unsigned char *cookie, |
| unsigned int *cookie_len); |
| |
| |
| /* this stores remote connections in a list to search through */ |
| /* XXX: optimize for searching */ |
| /* XXX: handle state issues for new connections to reduce DOS issues */ |
| /* (TLS should do this, but openssl can't do more than one ctx per sock */ |
| /* XXX: put a timer on the cache for expirary purposes */ |
| static bio_cache *find_bio_cache(struct sockaddr_in *from_addr) { |
| bio_cache *cachep = NULL; |
| cachep = biocache; |
| while(cachep) { |
| |
| if (cachep->ipv4addr == from_addr->sin_addr.s_addr && |
| cachep->portnum == from_addr->sin_port) { |
| /* found an existing connection */ |
| break; |
| } |
| |
| cachep = cachep->next; |
| } |
| return cachep; |
| } |
| |
| /* removes a single cache entry and returns SUCCESS on finding and |
| removing it. */ |
| static int remove_bio_cache(bio_cache *thiscache) { |
| bio_cache *cachep = NULL, *prevcache = NULL; |
| cachep = biocache; |
| while(cachep) { |
| if (cachep == thiscache) { |
| |
| /* remove it from the list */ |
| if (NULL == prevcache) { |
| /* at the first cache in the list */ |
| biocache = thiscache->next; |
| } else { |
| prevcache->next = thiscache->next; |
| } |
| |
| return SNMPERR_SUCCESS; |
| } |
| prevcache = cachep; |
| cachep = cachep->next; |
| } |
| return SNMPERR_GENERR; |
| } |
| |
| /* frees the contents of a bio_cache */ |
| static void free_bio_cache(bio_cache *cachep) { |
| /* These are freed by the SSL_free() call */ |
| /* |
| BIO_free(cachep->read_bio); |
| BIO_free(cachep->write_bio); |
| */ |
| DEBUGMSGTL(("9:dtlsudp:bio_cache", "releasing %p\n", cachep)); |
| SNMP_FREE(cachep->write_cache); |
| netsnmp_tlsbase_free_tlsdata(cachep->tlsdata); |
| } |
| |
| static void remove_and_free_bio_cache(bio_cache *cachep) { |
| /** no debug, remove_bio_cache does it */ |
| remove_bio_cache(cachep); |
| free_bio_cache(cachep); |
| } |
| |
| |
| /* XXX: lots of malloc/state cleanup needed */ |
| #define DIEHERE(msg) do { snmp_log(LOG_ERR, "%s\n", msg); return NULL; } while(0) |
| |
| static bio_cache * |
| start_new_cached_connection(netsnmp_transport *t, |
| struct sockaddr_in *remote_addr, |
| int we_are_client) { |
| bio_cache *cachep = NULL; |
| _netsnmpTLSBaseData *tlsdata; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| /* RFC5953: section 5.3.1, step 1: |
| 1) The snmpTlstmSessionOpens counter is incremented. |
| */ |
| if (we_are_client) |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENS); |
| |
| if (!t->sock) |
| DIEHERE("no socket passed in to start_new_cached_connection\n"); |
| if (!remote_addr) |
| DIEHERE("no remote_addr passed in to start_new_cached_connection\n"); |
| |
| cachep = SNMP_MALLOC_TYPEDEF(bio_cache); |
| if (!cachep) |
| return NULL; |
| |
| /* allocate our TLS specific data */ |
| if (NULL == (tlsdata = netsnmp_tlsbase_allocate_tlsdata(t, !we_are_client))) { |
| SNMP_FREE(cachep); |
| return NULL; |
| } |
| cachep->tlsdata = tlsdata; |
| |
| /* RFC5953: section 5.3.1, step 1: |
| 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: |
| + This Information is passed in via the transport and default |
| paremeters |
| */ |
| /* see if we have base configuration to copy in to this new one */ |
| if (NULL != t->data && t->data_length == sizeof(_netsnmpTLSBaseData)) { |
| _netsnmpTLSBaseData *parentdata = t->data; |
| if (parentdata->our_identity) |
| tlsdata->our_identity = strdup(parentdata->our_identity); |
| if (parentdata->their_identity) |
| tlsdata->their_identity = strdup(parentdata->their_identity); |
| if (parentdata->their_fingerprint) |
| tlsdata->their_fingerprint = strdup(parentdata->their_fingerprint); |
| if (parentdata->trust_cert) |
| tlsdata->trust_cert = strdup(parentdata->trust_cert); |
| if (parentdata->their_hostname) |
| tlsdata->their_hostname = strdup(parentdata->their_hostname); |
| } |
| |
| DEBUGMSGTL(("dtlsudp", "starting a new connection\n")); |
| cachep->next = biocache; |
| biocache = cachep; |
| |
| cachep->ipv4addr = remote_addr->sin_addr.s_addr; |
| cachep->portnum = remote_addr->sin_port; |
| memcpy(&cachep->sockaddr, remote_addr, sizeof(*remote_addr)); |
| |
| /* create caching memory bios for OpenSSL to read and write to */ |
| |
| cachep->read_bio = BIO_new(BIO_s_mem()); /* openssl reads from */ |
| if (!cachep->read_bio) |
| DIEHERE("failed to create the openssl read_bio"); |
| |
| cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */ |
| if (!cachep->write_bio) { |
| BIO_free(cachep->read_bio); |
| cachep->read_bio = NULL; |
| DIEHERE("failed to create the openssl write_bio"); |
| } |
| |
| BIO_set_mem_eof_return(cachep->read_bio, -1); |
| BIO_set_mem_eof_return(cachep->write_bio, -1); |
| |
| if (we_are_client) { |
| /* we're the client */ |
| DEBUGMSGTL(("dtlsudp", |
| "starting a new connection as a client to sock: %d\n", |
| t->sock)); |
| tlsdata->ssl = SSL_new(sslctx_client_setup(DTLSv1_method(), tlsdata)); |
| |
| /* XXX: session setting 735 */ |
| } else { |
| /* we're the server */ |
| SSL_CTX *ctx = sslctx_server_setup(DTLSv1_method()); |
| if (!ctx) { |
| BIO_free(cachep->read_bio); |
| BIO_free(cachep->write_bio); |
| cachep->read_bio = NULL; |
| cachep->write_bio = NULL; |
| DIEHERE("failed to create the SSL Context"); |
| } |
| |
| /* turn on cookie exchange */ |
| /* Set DTLS cookie generation and verification callbacks */ |
| SSL_CTX_set_cookie_generate_cb(ctx, netsnmp_dtls_gen_cookie); |
| SSL_CTX_set_cookie_verify_cb(ctx, netsnmp_dtls_verify_cookie); |
| |
| tlsdata->ssl = SSL_new(ctx); |
| } |
| |
| if (!tlsdata->ssl) { |
| BIO_free(cachep->read_bio); |
| BIO_free(cachep->write_bio); |
| cachep->read_bio = NULL; |
| cachep->write_bio = NULL; |
| DIEHERE("failed to create the SSL session structure"); |
| } |
| |
| SSL_set_mode(tlsdata->ssl, SSL_MODE_AUTO_RETRY); |
| |
| /* set the bios that openssl should read from and write to */ |
| /* (and we'll do the opposite) */ |
| SSL_set_bio(tlsdata->ssl, cachep->read_bio, cachep->write_bio); |
| |
| /* RFC5953: section 5.3.1, step 1: |
| 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. |
| |
| If the attempt to establish a session is unsuccessful, then |
| snmpTlstmSessionOpenErrors is incremented, an error indication is |
| returned, and processing stops. 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. |
| */ |
| /* Implementation notes: |
| + Because we're working asyncronously the real "end" point of |
| opening a connection doesn't occur here as certificate |
| verification and other things needs to happen first in the |
| verify callback, etc. See the netsnmp_dtlsudp_recv() |
| function for the final processing. |
| */ |
| /* set the SSL notion of we_are_client/server */ |
| if (we_are_client) |
| SSL_set_connect_state(tlsdata->ssl); |
| else { |
| /* XXX: we need to only create cache entries when cookies succeed */ |
| |
| SSL_set_options(tlsdata->ssl, SSL_OP_COOKIE_EXCHANGE); |
| |
| SSL_set_ex_data(tlsdata->ssl, openssl_addr_index, cachep); |
| |
| SSL_set_accept_state(tlsdata->ssl); |
| } |
| |
| /* RFC5953: section 5.3.1, step 1: |
| 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: |
| + our sessionID is stored as the transport's data pointer member |
| */ |
| DEBUGMSGT(("9:dtlsudp:bio_cache:created", "%p\n", cachep)); |
| |
| return cachep; |
| } |
| |
| static bio_cache * |
| find_or_create_bio_cache(netsnmp_transport *t, struct sockaddr_in *from_addr, |
| int we_are_client) { |
| bio_cache *cachep = find_bio_cache(from_addr); |
| if (NULL == cachep) { |
| /* none found; need to start a new context */ |
| cachep = start_new_cached_connection(t, from_addr, we_are_client); |
| if (NULL == cachep) { |
| snmp_log(LOG_ERR, "failed to open a new dtls connection\n"); |
| } |
| } else { |
| DEBUGMSGT(("9:dtlsudp:bio_cache:found", "%p\n", cachep)); |
| } |
| return cachep; |
| } |
| |
| /* |
| * Reads data from our internal openssl outgoing BIO and sends any |
| * queued packets out the UDP port |
| */ |
| static int |
| _netsnmp_send_queued_dtls_pkts(netsnmp_transport *t, bio_cache *cachep) { |
| int outsize, rc2; |
| u_char outbuf[65535]; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| /* for memory bios, we now read from openssl's write |
| buffer (ie, the packet to go out) and send it out |
| the udp port manually */ |
| |
| outsize = BIO_read(cachep->write_bio, outbuf, sizeof(outbuf)); |
| if (outsize > 0) { |
| DEBUGMSGTL(("dtlsudp", "have %d bytes to send\n", outsize)); |
| |
| /* should always be true. */ |
| #if defined(XXXFIXME) && defined(linux) && defined(IP_PKTINFO) |
| /* XXX: before this can work, we need to remember address we |
| received it from (addr_pair) */ |
| rc2 = netsnmp_udp_sendto(t->sock, addr_pair->local_addr, |
| addr_pair->if_index, addr_pair->remote_addr, |
| outbuf, outsize); |
| #else |
| rc2 = sendto(t->sock, outbuf, outsize, 0, |
| (struct sockaddr *)&cachep->sockaddr, sizeof(struct sockaddr)); |
| #endif /* linux && IP_PKTINFO */ |
| |
| if (rc2 == -1) { |
| snmp_log(LOG_ERR, "failed to send a DTLS specific packet\n"); |
| } |
| } else |
| DEBUGMSGTL(("9:dtlsudp", "have 0 bytes to send\n")); |
| |
| return outsize; |
| } |
| |
| /* |
| * If we have any outgoing SNMP data queued that OpenSSL/DTLS couldn't send |
| * (likely due to DTLS control packets needing to go out first) |
| * then this function attempts to send them. |
| */ |
| /* returns SNMPERR_SUCCESS if we succeeded in getting the data out */ |
| /* returns SNMPERR_GENERR if we still need more time */ |
| static int |
| _netsnmp_bio_try_and_write_buffered(netsnmp_transport *t, bio_cache *cachep) { |
| int rc; |
| _netsnmpTLSBaseData *tlsdata; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| tlsdata = cachep->tlsdata; |
| |
| /* make sure we have something to write */ |
| if (!cachep->write_cache || cachep->write_cache_len == 0) |
| return SNMPERR_SUCCESS; |
| |
| DEBUGMSGTL(("dtlsudp", "Trying to write %" NETSNMP_PRIz "d of buffered data\n", |
| cachep->write_cache_len)); |
| |
| /* try and write out the cached data */ |
| rc = SSL_write(tlsdata->ssl, cachep->write_cache, cachep->write_cache_len); |
| |
| while (rc == -1) { |
| int errnum = SSL_get_error(tlsdata->ssl, rc); |
| int bytesout; |
| |
| /* don't treat want_read/write errors as real errors */ |
| if (errnum != SSL_ERROR_WANT_READ && |
| errnum != SSL_ERROR_WANT_WRITE) { |
| DEBUGMSGTL(("dtlsudp", "ssl_write error (of buffered data)\n")); |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_write"); |
| return SNMPERR_GENERR; |
| } |
| |
| /* check to see if we have outgoing DTLS packets to send */ |
| /* (SSL_write could have created DTLS control packets) */ |
| bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); |
| |
| /* If want_read/write but failed to actually send anything |
| then we need to wait for the other side, so quit */ |
| if ((errnum == SSL_ERROR_WANT_READ || |
| errnum == SSL_ERROR_WANT_WRITE) && |
| bytesout <= 0) { |
| /* we've failed; must need to wait longer */ |
| return SNMPERR_GENERR; |
| } |
| |
| /* retry writing */ |
| DEBUGMSGTL(("9:dtlsudp", "recalling ssl_write\n")); |
| rc = SSL_write(tlsdata->ssl, cachep->write_cache, |
| cachep->write_cache_len); |
| } |
| |
| if (rc > 0) |
| cachep->msgnum++; |
| |
| if (_netsnmp_send_queued_dtls_pkts(t, cachep) > 0) { |
| SNMP_FREE(cachep->write_cache); |
| cachep->write_cache_len = 0; |
| DEBUGMSGTL(("dtlsudp", " Write was successful\n")); |
| return SNMPERR_SUCCESS; |
| } |
| DEBUGMSGTL(("dtlsudp", " failed to send over UDP socket\n")); |
| return SNMPERR_GENERR; |
| } |
| |
| static int |
| _netsnmp_add_buffered_data(bio_cache *cachep, char *buf, size_t size) { |
| if (cachep->write_cache && cachep->write_cache_len > 0) { |
| size_t newsize = cachep->write_cache_len + size; |
| |
| char *newbuf = realloc(cachep->write_cache, newsize); |
| if (NULL == newbuf) { |
| /* ack! malloc failure */ |
| /* XXX: free and close */ |
| return SNMPERR_GENERR; |
| } |
| cachep->write_cache = newbuf; |
| |
| /* write the new packet to the end */ |
| memcpy(cachep->write_cache + cachep->write_cache_len, |
| buf, size); |
| cachep->write_cache_len = newsize; |
| } else { |
| cachep->write_cache = netsnmp_memdup(buf, size); |
| if (!cachep->write_cache) { |
| /* ack! malloc failure */ |
| /* XXX: free and close */ |
| return SNMPERR_GENERR; |
| } |
| cachep->write_cache_len = size; |
| } |
| return SNMPERR_SUCCESS; |
| } |
| |
| static int |
| netsnmp_dtlsudp_recv(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| socklen_t fromlen = sizeof(struct sockaddr); |
| netsnmp_indexed_addr_pair *addr_pair = NULL; |
| struct sockaddr *from; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| _netsnmpTLSBaseData *tlsdata; |
| bio_cache *cachep; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| if (NULL == t || t->sock == 0) |
| return -1; |
| |
| /* 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, |
| netsnmpDTLSUDPDomain, sizeof(netsnmpDTLSUDPDomain[0]) * |
| netsnmpDTLSUDPDomain_len); |
| tmStateRef->transportDomainLen = netsnmpDTLSUDPDomain_len; |
| |
| addr_pair = &tmStateRef->addresses; |
| tmStateRef->have_addresses = 1; |
| from = (struct sockaddr *) &(addr_pair->remote_addr); |
| |
| while (rc < 0) { |
| #if defined(linux) && defined(IP_PKTINFO) |
| socklen_t local_addr_len = sizeof(addr_pair->local_addr); |
| rc = netsnmp_udp_recvfrom(t->sock, buf, size, from, &fromlen, |
| (struct sockaddr*)&(addr_pair->local_addr), |
| &local_addr_len, &(addr_pair->if_index)); |
| #else |
| rc = recvfrom(t->sock, buf, size, NETSNMP_DONTWAIT, from, &fromlen); |
| #endif /* linux && IP_PKTINFO */ |
| if (rc < 0 && errno != EINTR) { |
| break; |
| } |
| } |
| |
| DEBUGMSGTL(("dtlsudp", "received %d raw bytes on way to dtls\n", rc)); |
| if (rc < 0) { |
| DEBUGMSGTL(("dtlsudp", "recvfrom fd %d err %d (\"%s\")\n", |
| t->sock, errno, strerror(errno))); |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| |
| /* now that we have the from address filled in, we can look up |
| the openssl context and have openssl read and process |
| appropriately */ |
| |
| /* RFC5953: section 5.1, 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. |
| |
| 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. |
| */ |
| |
| /* RFC5953: section 5.1, step 2: |
| 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 derived 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. |
| */ |
| |
| /* if we don't have a cachep for this connection then |
| we're receiving something new and are the server |
| side */ |
| cachep = |
| find_or_create_bio_cache(t, &addr_pair->remote_addr, WE_ARE_SERVER); |
| if (NULL == cachep) { |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONACCEPTS); |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| tlsdata = cachep->tlsdata; |
| if (NULL == tlsdata->ssl) { |
| /* |
| * this happens when the server starts but doesn't have an |
| * identity and a client connects... |
| */ |
| snmp_log(LOG_ERR, |
| "DTLSUDP: missing tlsdata!\n"); |
| /*snmp_increment_statistic( XXX-rks ??? );*/ |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| |
| /* Implementation notes: |
| - we use the t->data memory pointer as the session ID |
| - the transport domain is already the correct type if we got here |
| - if we don't have a session yet (eg, no tmSessionID from the |
| specs) then we create one automatically here. |
| */ |
| |
| /* write the received buffer to the memory-based input bio */ |
| BIO_write(cachep->read_bio, buf, rc); |
| |
| /* RFC5953: section 5.1, step 3: |
| 3) The incomingMessage and incomingMessageLength are assigned values |
| from the (D)TLS processing. |
| */ |
| /* Implementation notes: |
| + rc = incomingMessageLength |
| + buf = IncomingMessage |
| */ |
| |
| /* XXX: in Wes' other example we do a SSL_pending() call |
| too to ensure we're ready to read... it's possible |
| that buffered stuff in openssl won't be caught by the |
| net-snmp select loop because it's already been pulled |
| out; need to deal with this) */ |
| rc = SSL_read(tlsdata->ssl, buf, size); |
| |
| /* |
| * moved netsnmp_openssl_null_checks to netsnmp_tlsbase_wrapup_recv. |
| * currently netsnmp_tlsbase_wrapup_recv is where we check for |
| * algorithm compliance, but we (sometimes) know the algorithms |
| * at this point, so we could bail earlier (here)... |
| */ |
| |
| while (rc == -1) { |
| int errnum = SSL_get_error(tlsdata->ssl, rc); |
| int bytesout; |
| |
| /* don't treat want_read/write errors as real errors */ |
| if (errnum != SSL_ERROR_WANT_READ && |
| errnum != SSL_ERROR_WANT_WRITE) { |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_read"); |
| break; |
| } |
| |
| /* check to see if we have outgoing DTLS packets to send */ |
| /* (SSL_read could have created DTLS control packets) */ |
| bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); |
| |
| /* If want_read/write but failed to actually send |
| anything then we need to wait for the other side, |
| so quit */ |
| if ((errnum == SSL_ERROR_WANT_READ || |
| errnum == SSL_ERROR_WANT_WRITE) && |
| bytesout <= 0) |
| break; |
| |
| /* retry reading */ |
| DEBUGMSGTL(("9:dtlsudp", "recalling ssl_read\n")); |
| rc = SSL_read(tlsdata->ssl, buf, size); |
| } |
| |
| if (rc == -1) { |
| SNMP_FREE(tmStateRef); |
| |
| DEBUGMSGTL(("9:dtlsudp", "no decoded data from dtls\n")); |
| |
| if (SSL_get_error(tlsdata->ssl, rc) == SSL_ERROR_WANT_READ) { |
| DEBUGMSGTL(("9dtlsudp","here: want read!\n")); |
| |
| /* see if we have buffered write date to send out first */ |
| if (cachep->write_cache) { |
| _netsnmp_bio_try_and_write_buffered(t, cachep); |
| /* XXX: check error or not here? */ |
| /* (what would we do differently?) */ |
| } |
| |
| rc = -1; /* XXX: it's ok, but what's the right return? */ |
| } |
| else |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_read"); |
| |
| #if 0 /* to dump cache if we don't have a cookie, this is where to do it */ |
| if (!(cachep->flags & NETSNMP_BIO_HAVE_COOKIE)) |
| remove_and_free_bio_cache(cachep); |
| #endif |
| return rc; |
| } |
| |
| DEBUGMSGTL(("dtlsudp", "received %d decoded bytes from dtls\n", rc)); |
| |
| if ((0 == rc) && (SSL_get_shutdown(tlsdata->ssl) & SSL_RECEIVED_SHUTDOWN)) { |
| DEBUGMSGTL(("dtlsudp", "peer disconnected\n")); |
| cachep->flags |= NETSNMP_BIO_DISCONNECTED; |
| remove_and_free_bio_cache(cachep); |
| SNMP_FREE(tmStateRef); |
| return rc; |
| } |
| cachep->flags |= NETSNMP_BIO_CONNECTED; |
| |
| /* Until we've locally assured ourselves that all is well in |
| certificate-verification-land we need to be prepared to stop |
| here and ensure all our required checks have been done. */ |
| if (0 == (tlsdata->flags & NETSNMP_TLSBASE_CERT_FP_VERIFIED)) { |
| int verifyresult; |
| |
| if (tlsdata->flags & NETSNMP_TLSBASE_IS_CLIENT) { |
| |
| /* verify that the server's certificate is the correct one */ |
| |
| /* RFC5953: section 5.3.1, step 1: |
| 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. |
| |
| If the attempt to establish a session is |
| unsuccessful, then snmpTlstmSessionOpenErrors is |
| incremented, an error indication is returned, and |
| processing stops. 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, step 1: |
| 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. |
| */ |
| /* RFC5953: section 5.3.1, step 1: |
| 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 and snmpTlstmCertToTSNTable |
| lookup failures, then the |
| snmpTlstmSessionOpenErrors counter is incremented |
| and processing stops. |
| */ |
| |
| /* Implementation notes: |
| + in the following function the server's certificate and |
| presented commonname or subjectAltName is checked |
| according to the rules in the snmpTlstmAddrTable. |
| */ |
| if ((verifyresult = netsnmp_tlsbase_verify_server_cert(tlsdata->ssl, tlsdata)) |
| != SNMPERR_SUCCESS) { |
| if (verifyresult == SNMPERR_TLS_NO_CERTIFICATE) { |
| /* assume we simply haven't received it yet and there |
| is more data to wait-for or send */ |
| /* XXX: probably need to check for whether we should |
| send stuff from our end to continue the transaction |
| */ |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } else { |
| /* XXX: free needed memory */ |
| snmp_log(LOG_ERR, |
| "DTLSUDP: failed to verify ssl certificate (of the server)\n"); |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONUNKNOWNSERVERCERTIFICATE); |
| /* Step 5 says these are always incremented */ |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDSERVERCERTIFICATES); |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| } |
| tlsdata->flags |= NETSNMP_TLSBASE_CERT_FP_VERIFIED; |
| DEBUGMSGTL(("dtlsudp", "Verified the server's certificate\n")); |
| } else { |
| /* verify that the client's certificate is the correct one */ |
| |
| if ((verifyresult = netsnmp_tlsbase_verify_client_cert(tlsdata->ssl, tlsdata)) |
| != SNMPERR_SUCCESS) { |
| if (verifyresult == SNMPERR_TLS_NO_CERTIFICATE) { |
| /* assume we simply haven't received it yet and there |
| is more data to wait-for or send */ |
| /* XXX: probably need to check for whether we should |
| send stuff from our end to continue the transaction |
| */ |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } else { |
| /* XXX: free needed memory */ |
| snmp_log(LOG_ERR, |
| "DTLSUDP: failed to verify ssl certificate (of the client)\n"); |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCLIENTCERTIFICATES); |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| } |
| tlsdata->flags |= NETSNMP_TLSBASE_CERT_FP_VERIFIED; |
| DEBUGMSGTL(("dtlsudp", "Verified the client's certificate\n")); |
| } |
| } |
| |
| if (rc > 0) |
| cachep->msgnum++; |
| |
| if (BIO_ctrl_pending(cachep->write_bio) > 0) { |
| _netsnmp_send_queued_dtls_pkts(t, cachep); |
| } |
| |
| DEBUGIF ("9:dtlsudp") { |
| char *str = netsnmp_udp_fmtaddr(NULL, addr_pair, sizeof(netsnmp_indexed_addr_pair)); |
| DEBUGMSGTL(("9:dtlsudp", |
| "recvfrom fd %d got %d bytes (from %s)\n", |
| t->sock, rc, str)); |
| free(str); |
| } |
| |
| /* see if we have buffered write date to send out first */ |
| if (cachep->write_cache) { |
| if (SNMPERR_GENERR == |
| _netsnmp_bio_try_and_write_buffered(t, cachep)) { |
| /* we still have data that can't get out in the buffer */ |
| /* XXX: nothing to do here? */ |
| } |
| } |
| |
| if (netsnmp_tlsbase_wrapup_recv(tmStateRef, tlsdata, opaque, olength) != |
| SNMPERR_SUCCESS) |
| return SNMPERR_GENERR; |
| |
| /* RFC5953: section 5.1, step 4: |
| 4) The TLS Transport Model passes the transportDomain, |
| transportAddress, incomingMessage, and incomingMessageLength to |
| the Dispatcher using the receiveMessage ASI: |
| |
| statusInformation = |
| receiveMessage( |
| IN transportDomain -- snmpTLSTCPDomain or snmpDTLSUDPDomain, |
| IN transportAddress -- address for the received message |
| IN incomingMessage -- the whole SNMP message from (D)TLS |
| IN incomingMessageLength -- the length of the SNMP message |
| IN tmStateReference -- transport info |
| ) |
| */ |
| /* Implementation notes: those pamateres are all passed outward |
| using the functions arguments and the return code below (the length) */ |
| |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_dtlsudp_send(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| netsnmp_indexed_addr_pair *addr_pair = NULL; |
| struct sockaddr *to = NULL; |
| bio_cache *cachep = NULL; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| u_char outbuf[65535]; |
| _netsnmpTLSBaseData *tlsdata = NULL; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| DEBUGMSGTL(("dtlsudp", "sending %d bytes\n", size)); |
| |
| /* determine remote addresses */ |
| if (opaque != NULL && *opaque != NULL && |
| *olength == sizeof(netsnmp_tmStateReference)) { |
| tmStateRef = (netsnmp_tmStateReference *) *opaque; |
| |
| if (tmStateRef->have_addresses) |
| addr_pair = &(tmStateRef->addresses); |
| else if (t != NULL && t->data != NULL && |
| t->data_length == sizeof(netsnmp_indexed_addr_pair)) |
| addr_pair = (netsnmp_indexed_addr_pair *) (t->data); |
| else if (t != NULL && t->data != NULL && |
| t->data_length == sizeof(_netsnmpTLSBaseData)) { |
| tlsdata = (_netsnmpTLSBaseData *) t->data; |
| addr_pair = (netsnmp_indexed_addr_pair *) (tlsdata->remote_addr); |
| } |
| } else if (t != NULL && t->data != NULL && |
| t->data_length == sizeof(netsnmp_indexed_addr_pair)) { |
| addr_pair = (netsnmp_indexed_addr_pair *) (t->data); |
| } else if (t != NULL && t->data != NULL && |
| t->data_length == sizeof(_netsnmpTLSBaseData)) { |
| tlsdata = (_netsnmpTLSBaseData *) t->data; |
| addr_pair = (netsnmp_indexed_addr_pair *) (tlsdata->remote_addr); |
| } |
| |
| if (NULL == addr_pair) { |
| /* RFC5953: section 5.2, step 1: |
| 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. |
| */ |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES); |
| snmp_log(LOG_ERR, "dtlsudp_send: can't get address to send to\n"); |
| return -1; |
| } |
| |
| to = (struct sockaddr *) &(addr_pair->remote_addr); |
| |
| if (NULL == to || NULL == t || t->sock <= 0) { |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES); |
| snmp_log(LOG_ERR, "invalid netsnmp_dtlsudp_send usage\n"); |
| return -1; |
| } |
| |
| /* RFC5953: section 5.2, step 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: |
| - we use the t->data memory pointer as the session ID |
| - the transport domain is already the correct type if we got here |
| - if we don't have a session yet (eg, no tmSessionID from the |
| specs) then we create one automatically here. |
| */ |
| |
| |
| /* RFC5953: section 5.2, step 3: |
| 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. |
| */ |
| /* RFC5953: section 5.2, step 4: |
| 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 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, step 5: |
| 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 no 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. |
| */ |
| |
| /* we're always a client if we're sending to something unknown yet */ |
| if (NULL == |
| (cachep = find_or_create_bio_cache(t, &addr_pair->remote_addr, |
| WE_ARE_CLIENT))) { |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); |
| return -1; |
| } |
| |
| tlsdata = cachep->tlsdata; |
| if (NULL == tlsdata || NULL == tlsdata->ssl) { |
| /** xxx mem lean? free created bio cache? */ |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONNOSESSIONS); |
| snmp_log(LOG_ERR, "bad tls data or ssl ptr in netsnmp_dtlsudp_send\n"); |
| return -1; |
| } |
| |
| if (!tlsdata->securityName && tmStateRef && |
| tmStateRef->securityNameLen > 0) { |
| tlsdata->securityName = strdup(tmStateRef->securityName); |
| } |
| |
| /* see if we have previous outgoing data to send */ |
| if (cachep->write_cache) { |
| if (SNMPERR_GENERR == _netsnmp_bio_try_and_write_buffered(t, cachep)) { |
| /* we still have data that can't get out in the buffer */ |
| |
| /* add the new data to the end of the existing cache */ |
| if (_netsnmp_add_buffered_data(cachep, buf, size) != |
| SNMPERR_SUCCESS) { |
| /* XXX: free and close */ |
| } |
| return -1; |
| } |
| } |
| |
| DEBUGIF ("9:dtlsudp") { |
| char *str = netsnmp_udp_fmtaddr(NULL, (void *) addr_pair, |
| sizeof(netsnmp_indexed_addr_pair)); |
| DEBUGMSGTL(("9:dtlsudp", "send %d bytes from %p to %s on fd %d\n", |
| size, buf, str, t->sock)); |
| free(str); |
| } |
| |
| /* RFC5953: section 5.2, step 6: |
| 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); |
| |
| while (rc == -1) { |
| int bytesout; |
| int errnum = SSL_get_error(tlsdata->ssl, rc); |
| |
| /* don't treat want_read/write errors as real errors */ |
| if (errnum != SSL_ERROR_WANT_READ && |
| errnum != SSL_ERROR_WANT_WRITE) { |
| DEBUGMSGTL(("dtlsudp", "ssl_write error\n")); |
| _openssl_log_error(rc, tlsdata->ssl, "SSL_write"); |
| break; |
| } |
| |
| /* check to see if we have outgoing DTLS packets to send */ |
| /* (SSL_read could have created DTLS control packets) */ |
| bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); |
| |
| /* If want_read/write but failed to actually send |
| anything then we need to wait for the other side, |
| so quit */ |
| if ((errnum == SSL_ERROR_WANT_READ || |
| errnum == SSL_ERROR_WANT_WRITE) && |
| bytesout <= 0) { |
| /* We need more data written to or read from the socket |
| but we're failing to do so and need to wait till the |
| socket is ready again; unfortunately this means we need |
| to buffer the SNMP data temporarily in the mean time */ |
| |
| /* remember the packet */ |
| if (_netsnmp_add_buffered_data(cachep, buf, size) != |
| SNMPERR_SUCCESS) { |
| |
| /* XXX: free and close */ |
| return -1; |
| } |
| |
| /* exit out of the loop until we get caled again from |
| socket data */ |
| break; |
| } |
| DEBUGMSGTL(("9:dtlsudp", "recalling ssl_write\n")); |
| rc = SSL_write(tlsdata->ssl, buf, size); |
| } |
| |
| if (rc > 0) |
| cachep->msgnum++; |
| |
| /* for memory bios, we now read from openssl's write buffer (ie, |
| the packet to go out) and send it out the udp port manually */ |
| rc = BIO_read(cachep->write_bio, outbuf, sizeof(outbuf)); |
| if (rc <= 0) { |
| /* in theory an ok thing */ |
| return 0; |
| } |
| #if defined(FIXME) && defined(linux) && defined(IP_PKTINFO) |
| /* XXX: before this can work, we need to remember address we |
| received it from (addr_pair) */ |
| rc = netsnmp_udpbase_sendto(t->sock, &cachep->sockaddr remote addr_pair ? &(addr_pair->local_addr) : NULL, to, outbuf, rc); |
| #else |
| rc = sendto(t->sock, outbuf, rc, 0, (struct sockaddr *)&cachep->sockaddr, |
| sizeof(struct sockaddr)); |
| #endif /* linux && IP_PKTINFO */ |
| |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_dtlsudp_close(netsnmp_transport *t) |
| { |
| /* XXX: issue a proper dtls closure notification(s) */ |
| |
| bio_cache *cachep = NULL; |
| _netsnmpTLSBaseData *tlsbase = NULL; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| DEBUGMSGTL(("dtlsudp:close", "closing dtlsudp transport %p\n", t)); |
| |
| /* RFC5953: section 5.4, step 1: |
| 1) Increment either the snmpTlstmSessionClientCloses or the |
| snmpTlstmSessionServerCloses counter as appropriate. |
| */ |
| snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONCLIENTCLOSES); |
| |
| /* RFC5953: section 5.4, step 2: |
| 2) Look up the session using the tmSessionID. |
| */ |
| /* Implementation notes: |
| + Our session id is stored as the t->data pointer |
| */ |
| if (NULL != t->data && t->data_length == sizeof(_netsnmpTLSBaseData)) { |
| tlsbase = (_netsnmpTLSBaseData *) t->data; |
| |
| if (tlsbase->remote_addr) |
| cachep = find_bio_cache((struct sockaddr_in *)tlsbase->remote_addr); |
| } |
| |
| /* RFC5953: section 5.4, step 3: |
| 3) If there is no open session associated with the tmSessionID, then |
| closeSession processing is completed. |
| */ |
| if (NULL == cachep) |
| return netsnmp_socketbase_close(t); |
| |
| /* if we have any remaining packtes to send, try to send them */ |
| if (cachep->write_cache_len > 0) { |
| int i = 0; |
| char buf[8192]; |
| int rc; |
| void *opaque = NULL; |
| int opaque_len = 0; |
| fd_set readfs; |
| struct timeval tv; |
| |
| DEBUGMSGTL(("dtlsudp:close", |
| "%" NETSNMP_PRIz "d bytes remain in write_cache\n", |
| cachep->write_cache_len)); |
| |
| /* |
| * if negotiations have completed and we've received data, try and |
| * send any queued packets. |
| */ |
| if (cachep->flags & NETSNMP_BIO_CONNECTED) { |
| /* make configurable: |
| - do this at all? |
| - retries |
| - timeout |
| */ |
| for (i = 0; i < 6 && cachep->write_cache_len != 0; ++i) { |
| |
| /* first see if we can send out what we have */ |
| _netsnmp_bio_try_and_write_buffered(t, cachep); |
| if (cachep->write_cache_len == 0) |
| break; |
| |
| /* if we've failed that, we probably need to wait for packets */ |
| FD_ZERO(&readfs); |
| FD_SET(t->sock, &readfs); |
| tv.tv_sec = 0; |
| tv.tv_usec = 50000; |
| rc = select(t->sock+1, &readfs, NULL, NULL, &tv); |
| if (rc > 0) { |
| /* junk recv for catching negotations still in play */ |
| opaque_len = 0; |
| netsnmp_dtlsudp_recv(t, buf, sizeof(buf), |
| &opaque, &opaque_len); |
| SNMP_FREE(opaque); |
| } |
| } /* for loop */ |
| } |
| |
| /** dump anything that wasn't sent */ |
| if (cachep->write_cache_len > 0) { |
| DEBUGMSGTL(("dtlsudp:close", |
| "dumping %" NETSNMP_PRIz "d bytes from write_cache\n", |
| cachep->write_cache_len)); |
| SNMP_FREE(cachep->write_cache); |
| cachep->write_cache_len = 0; |
| } |
| } |
| |
| /* RFC5953: section 5.4, step 4: |
| 4) Have (D)TLS close the specified connection. This MUST include |
| sending a close_notify TLS Alert to inform the other side that |
| session cleanup may be performed. |
| */ |
| if (NULL != cachep->tlsdata && NULL != cachep->tlsdata->ssl) { |
| |
| DEBUGMSGTL(("dtlsudp:close", "closing SSL socket\n")); |
| SSL_shutdown(cachep->tlsdata->ssl); |
| |
| /* send the close_notify we maybe generated in step 4 */ |
| if (BIO_ctrl_pending(cachep->write_bio) > 0) |
| _netsnmp_send_queued_dtls_pkts(t, cachep); |
| } |
| |
| remove_and_free_bio_cache(cachep); |
| |
| return netsnmp_socketbase_close(t); |
| } |
| |
| char * |
| netsnmp_dtlsudp_fmtaddr(netsnmp_transport *t, void *data, int len) |
| { |
| return netsnmp_ipv4_fmtaddr("DTLSUDP", t, data, len); |
| } |
| |
| /* |
| * Open a DTLS-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_dtlsudp_transport(struct sockaddr_in *addr, int local) |
| { |
| netsnmp_transport *t = NULL; |
| |
| DEBUGTRACETOK("9:dtlsudp"); |
| |
| t = netsnmp_udpipv4base_transport(addr, local); |
| if (NULL == t) |
| return NULL; |
| |
| if (NULL != t->data && |
| t->data_length == sizeof(netsnmp_indexed_addr_pair)) { |
| _netsnmpTLSBaseData *tlsdata = |
| netsnmp_tlsbase_allocate_tlsdata(t, local); |
| tlsdata->remote_addr = t->data; |
| t->data = tlsdata; |
| t->data_length = sizeof(_netsnmpTLSBaseData); |
| } |
| |
| if (!local) { |
| /* dtls needs to bind the socket for SSL_write to work */ |
| if (connect(t->sock, (struct sockaddr *) addr, sizeof(*addr)) == -1) |
| snmp_log(LOG_ERR, "dtls: failed to connect\n"); |
| } |
| |
| /* XXX: Potentially set sock opts here (SO_SNDBUF/SO_RCV_BUF) */ |
| /* XXX: and buf size */ |
| |
| /* |
| * Set Domain |
| */ |
| t->domain = netsnmpDTLSUDPDomain; |
| t->domain_length = netsnmpDTLSUDPDomain_len; |
| |
| /* |
| * 16-bit length field, 8 byte DTLS header, 20 byte IPv4 header |
| */ |
| t->msgMaxSize = 0xffff - 8 - 20; |
| |
| t->f_recv = netsnmp_dtlsudp_recv; |
| t->f_send = netsnmp_dtlsudp_send; |
| t->f_close = netsnmp_dtlsudp_close; |
| t->f_config = netsnmp_tlsbase_config; |
| t->f_setup_session = netsnmp_tlsbase_session_init; |
| t->f_accept = NULL; |
| t->f_fmtaddr = netsnmp_dtlsudp_fmtaddr; |
| |
| t->flags = NETSNMP_TRANSPORT_FLAG_TUNNELED; |
| |
| return t; |
| } |
| |
| |
| void |
| netsnmp_dtlsudp_agent_config_tokens_register(void) |
| { |
| } |
| |
| |
| |
| |
| netsnmp_transport * |
| netsnmp_dtlsudp_create_tstring(const char *str, int isserver, |
| const char *default_target) |
| { |
| struct sockaddr_in addr; |
| netsnmp_transport *t; |
| _netsnmpTLSBaseData *tlsdata; |
| char buf[SPRINT_MAX_LEN], *cp; |
| |
| if (netsnmp_sockaddr_in2(&addr, str, default_target)) { |
| t = netsnmp_dtlsudp_transport(&addr, isserver); |
| } else { |
| return NULL; |
| } |
| |
| /* see if we can extract the remote hostname */ |
| if (!isserver && t && t->data && str) { |
| tlsdata = (_netsnmpTLSBaseData *) t->data; |
| /* search for a : */ |
| if (NULL != (cp = strrchr(str, ':'))) { |
| sprintf(buf, "%.*s", (int) SNMP_MIN(cp - str, sizeof(buf) - 1), |
| str); |
| } else { |
| /* else the entire spec is a host name only */ |
| strlcpy(buf, str, sizeof(buf)); |
| } |
| tlsdata->their_hostname = strdup(buf); |
| } |
| return t; |
| } |
| |
| |
| netsnmp_transport * |
| netsnmp_dtlsudp_create_ostring(const u_char * o, size_t o_len, int local) |
| { |
| struct sockaddr_in addr; |
| |
| if (o_len == 6) { |
| unsigned short porttmp = (o[4] << 8) + o[5]; |
| addr.sin_family = AF_INET; |
| memcpy((u_char *) & (addr.sin_addr.s_addr), o, 4); |
| addr.sin_port = htons(porttmp); |
| return netsnmp_dtlsudp_transport(&addr, local); |
| } |
| return NULL; |
| } |
| |
| void |
| netsnmp_dtlsudp_ctor(void) |
| { |
| char indexname[] = "_netsnmp_addr_info"; |
| |
| DEBUGMSGTL(("dtlsudp", "registering DTLS constructor\n")); |
| |
| /* config settings */ |
| |
| dtlsudpDomain.name = netsnmpDTLSUDPDomain; |
| dtlsudpDomain.name_length = netsnmpDTLSUDPDomain_len; |
| dtlsudpDomain.prefix = (const char**)calloc(3, sizeof(char *)); |
| dtlsudpDomain.prefix[0] = "dtlsudp"; |
| dtlsudpDomain.prefix[1] = "dtls"; |
| |
| dtlsudpDomain.f_create_from_tstring = NULL; |
| dtlsudpDomain.f_create_from_tstring_new = netsnmp_dtlsudp_create_tstring; |
| dtlsudpDomain.f_create_from_ostring = netsnmp_dtlsudp_create_ostring; |
| |
| if (!openssl_addr_index) |
| openssl_addr_index = |
| SSL_get_ex_new_index(0, indexname, NULL, NULL, NULL); |
| |
| netsnmp_tdomain_register(&dtlsudpDomain); |
| } |
| |
| /* |
| * Much of the code below was taken from the OpenSSL example code |
| * and is subject to the OpenSSL copyright. |
| */ |
| #define NETSNMP_COOKIE_SECRET_LENGTH 16 |
| int cookie_initialized=0; |
| unsigned char cookie_secret[NETSNMP_COOKIE_SECRET_LENGTH]; |
| |
| typedef union { |
| struct sockaddr sa; |
| struct sockaddr_in s4; |
| } _peer_union; |
| |
| int netsnmp_dtls_gen_cookie(SSL *ssl, unsigned char *cookie, |
| unsigned int *cookie_len) |
| { |
| unsigned char *buffer, result[EVP_MAX_MD_SIZE]; |
| unsigned int length, resultlength; |
| bio_cache *cachep = NULL; |
| _peer_union *peer; |
| |
| /* Initialize a random secret */ |
| if (!cookie_initialized) { |
| if (!RAND_bytes(cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH)) { |
| snmp_log(LOG_ERR, "dtls: error setting random cookie secret\n"); |
| return 0; |
| } |
| cookie_initialized = 1; |
| } |
| |
| DEBUGMSGT(("dtlsudp:cookie", "generating cookie...\n")); |
| |
| /* Read peer information */ |
| cachep = SSL_get_ex_data(ssl, openssl_addr_index); |
| if (!cachep) { |
| snmp_log(LOG_ERR, "dtls: failed to get the peer address\n"); |
| return 0; |
| } |
| peer = (_peer_union *)&cachep->sockaddr; |
| |
| /* Create buffer with peer's address and port */ |
| length = 0; |
| switch (peer->sa.sa_family) { |
| case AF_INET: |
| length += sizeof(struct in_addr); |
| length += sizeof(peer->s4.sin_port); |
| break; |
| default: |
| snmp_log(LOG_ERR, "dtls generating cookie: unknown family: %d\n", |
| peer->sa.sa_family); |
| return 0; |
| } |
| buffer = malloc(length); |
| if (buffer == NULL) { |
| snmp_log(LOG_ERR,"dtls: out of memory\n"); |
| return 0; |
| } |
| |
| switch (peer->sa.sa_family) { |
| case AF_INET: |
| memcpy(buffer, |
| &peer->s4.sin_port, |
| sizeof(peer->s4.sin_port)); |
| memcpy(buffer + sizeof(peer->s4.sin_port), |
| &peer->s4.sin_addr, |
| sizeof(struct in_addr)); |
| break; |
| default: |
| snmp_log(LOG_ERR, "dtls: unknown address family generating a cookie\n"); |
| return 0; |
| } |
| |
| /* Calculate HMAC of buffer using the secret */ |
| HMAC(EVP_sha1(), cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH, |
| buffer, length, result, &resultlength); |
| OPENSSL_free(buffer); |
| |
| memcpy(cookie, result, resultlength); |
| *cookie_len = resultlength; |
| |
| DEBUGMSGT(("9:dtlsudp:cookie", "generated %d byte cookie\n", *cookie_len)); |
| |
| return 1; |
| } |
| |
| int netsnmp_dtls_verify_cookie(SSL *ssl, unsigned char *cookie, |
| unsigned int cookie_len) |
| { |
| unsigned char *buffer, result[EVP_MAX_MD_SIZE]; |
| unsigned int length, resultlength, rc; |
| bio_cache *cachep = NULL; |
| _peer_union *peer; |
| |
| /* If secret isn't initialized yet, the cookie can't be valid */ |
| if (!cookie_initialized) |
| return 0; |
| |
| DEBUGMSGT(("9:dtlsudp:cookie", "verifying %d byte cookie\n", cookie_len)); |
| |
| cachep = SSL_get_ex_data(ssl, openssl_addr_index); |
| if (!cachep) { |
| snmp_log(LOG_ERR, "dtls: failed to get the peer address\n"); |
| return 0; |
| } |
| peer = (_peer_union *)&cachep->sockaddr; |
| |
| /* Create buffer with peer's address and port */ |
| length = 0; |
| switch (peer->sa.sa_family) { |
| case AF_INET: |
| length += sizeof(struct in_addr); |
| length += sizeof(peer->s4.sin_port); |
| break; |
| default: |
| snmp_log(LOG_ERR, |
| "dtls: unknown address family %d generating a cookie\n", |
| peer->sa.sa_family); |
| return 0; |
| } |
| buffer = malloc(length); |
| if (buffer == NULL) { |
| snmp_log(LOG_ERR, "dtls: unknown address family generating a cookie\n"); |
| return 0; |
| } |
| |
| switch (peer->sa.sa_family) { |
| case AF_INET: |
| memcpy(buffer, |
| &peer->s4.sin_port, |
| sizeof(peer->s4.sin_port)); |
| memcpy(buffer + sizeof(peer->s4.sin_port), |
| &peer->s4.sin_addr, |
| sizeof(struct in_addr)); |
| break; |
| default: |
| snmp_log(LOG_ERR, |
| "dtls: unknown address family %d generating a cookie\n", |
| peer->sa.sa_family); |
| return 0; |
| } |
| |
| /* Calculate HMAC of buffer using the secret */ |
| HMAC(EVP_sha1(), cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH, |
| buffer, length, result, &resultlength); |
| OPENSSL_free(buffer); |
| |
| if (cookie_len != resultlength || memcmp(result, cookie, resultlength) != 0) |
| rc = 0; |
| else { |
| rc = 1; |
| cachep->flags |= NETSNMP_BIO_HAVE_COOKIE; |
| } |
| |
| DEBUGMSGT(("dtlsudp:cookie", "verify cookie: %d\n", rc)); |
| |
| return rc; |
| } |
| |
| #endif /* HAVE_LIBSSL_DTLS */ |