blob: b7dc767ec7dda3ccab7ca22c0876ac7a208a0a3a [file] [log] [blame]
/* 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/net-snmp-features.h>
netsnmp_feature_require(cert_util)
netsnmp_feature_require(sockaddr_size)
#include <net-snmp/library/snmpDTLSUDPDomain.h>
#include <net-snmp/library/snmpUDPIPv6Domain.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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
static int openssl_addr_index6 = 0;
#endif
/* 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 */
netsnmp_sockaddr_storage sas;
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(netsnmp_sockaddr_storage *from_addr) {
bio_cache *cachep = NULL;
for(cachep = biocache; cachep; cachep = cachep->next) {
if (cachep->sas.sa.sa_family != from_addr->sa.sa_family)
continue;
if ((from_addr->sa.sa_family == AF_INET) &&
((cachep->sas.sin.sin_addr.s_addr !=
from_addr->sin.sin_addr.s_addr) ||
(cachep->sas.sin.sin_port != from_addr->sin.sin_port)))
continue;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
else if ((from_addr->sa.sa_family == AF_INET6) &&
((cachep->sas.sin6.sin6_port != from_addr->sin6.sin6_port) ||
(cachep->sas.sin6.sin6_scope_id !=
from_addr->sin6.sin6_scope_id) ||
(memcmp(cachep->sas.sin6.sin6_addr.s6_addr,
from_addr->sin6.sin6_addr.s6_addr,
sizeof(from_addr->sin6.sin6_addr.s6_addr)) != 0)))
continue;
#endif
/* found an existing connection */
break;
}
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,
netsnmp_sockaddr_storage *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;
if (remote_addr->sa.sa_family == AF_INET)
memcpy(&cachep->sas.sin, &remote_addr->sin, sizeof(remote_addr->sin));
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
else if (remote_addr->sa.sa_family == AF_INET6)
memcpy(&cachep->sas.sin6, &remote_addr->sin6, sizeof(remote_addr->sin6));
#endif
else
DIEHERE("unknown address family");
/* 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,
netsnmp_sockaddr_storage *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;
}
static netsnmp_indexed_addr_pair *
_extract_addr_pair(netsnmp_transport *t, void *opaque, int olen)
{
netsnmp_indexed_addr_pair *addr_pair = NULL;
if (opaque && olen == sizeof(netsnmp_tmStateReference)) {
netsnmp_tmStateReference *tmStateRef =
(netsnmp_tmStateReference *) opaque;
if (tmStateRef->have_addresses)
addr_pair = &(tmStateRef->addresses);
}
if ((NULL == addr_pair) && (NULL != t)) {
if (t->data != NULL &&
t->data_length == sizeof(netsnmp_indexed_addr_pair))
addr_pair = (netsnmp_indexed_addr_pair *) (t->data);
else if (t->data != NULL &&
t->data_length == sizeof(_netsnmpTLSBaseData)) {
_netsnmpTLSBaseData *tlsdata = (_netsnmpTLSBaseData *) t->data;
addr_pair = (netsnmp_indexed_addr_pair *) (tlsdata->addr);
}
}
return addr_pair;
}
static struct sockaddr *
_find_remote_sockaddr(netsnmp_transport *t, void *opaque, int olen, int *socklen)
{
netsnmp_indexed_addr_pair *addr_pair = _extract_addr_pair(t, opaque, olen);
struct sockaddr *sa = NULL;
if (NULL == addr_pair)
return NULL;
sa = &addr_pair->remote_addr.sa;
*socklen = netsnmp_sockaddr_size(sa);
return sa;
}
/*
* 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. */
int socksize;
struct sockaddr *sa;
sa = _find_remote_sockaddr(t, NULL, 0, &socksize);
if (NULL == sa)
sa = &cachep->sas.sa;
socksize = netsnmp_sockaddr_size(sa);
rc2 = t->base_transport->f_send(t, outbuf, outsize, (void**)&sa,
&socksize);
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;
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) {
char *opaque = NULL;
int olen;
rc = t->base_transport->f_recv(t, buf, size, (void**)&opaque, &olen);
if (rc > 0)
memcpy(from, opaque, olen);
SNMP_FREE(opaque);
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 {
#ifndef NETSNMP_NO_LISTEN_SUPPORT
/* 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"));
#else /* NETSNMP_NO_LISTEN_SUPPORT */
return NULL;
#endif /* NETSNMP_NO_LISTEN_SUPPORT */
}
}
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 =
t->base_transport->f_fmtaddr(t, 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;
bio_cache *cachep = NULL;
netsnmp_tmStateReference *tmStateRef = NULL;
u_char outbuf[65535];
_netsnmpTLSBaseData *tlsdata = NULL;
int socksize;
struct sockaddr *sa;
DEBUGTRACETOK("9:dtlsudp");
DEBUGMSGTL(("dtlsudp", "sending %d bytes\n", size));
if (NULL == t || t->sock <= 0) {
snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES);
snmp_log(LOG_ERR, "invalid netsnmp_dtlsudp_send usage\n");
return -1;
}
/* determine remote addresses */
addr_pair = _extract_addr_pair(t, opaque ? *opaque : NULL,
olength ? *olength : 0);
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;
}
/* 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.
*/
if (opaque != NULL && *opaque != NULL &&
olength != NULL && *olength == sizeof(netsnmp_tmStateReference))
tmStateRef = (netsnmp_tmStateReference *) *opaque;
/* 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 = t->base_transport->f_fmtaddr(t, (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;
}
socksize = netsnmp_sockaddr_size(&cachep->sas.sa);
sa = &cachep->sas.sa;
rc = t->base_transport->f_send(t, outbuf, rc, (void**)&sa, &socksize);
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->addr)
cachep = find_bio_cache(&tlsbase->addr->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)
{
int sa_len;
struct sockaddr *sa = _find_remote_sockaddr(t, data, len, &sa_len);
if (sa) {
data = sa;
len = sa_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.
*/
static netsnmp_transport *
_transport_common(netsnmp_transport *t, int local)
{
char *tmp = NULL;
int tmp_len;
DEBUGTRACETOK("9:dtlsudp");
if (NULL == t)
return NULL;
/** save base transport for clients; need in send/recv functions later */
if (t->data) { /* don't copy data */
tmp = t->data;
tmp_len = t->data_length;
t->data = NULL;
}
t->base_transport = netsnmp_transport_copy(t);
if (tmp) {
t->data = tmp;
t->data_length = tmp_len;
}
if (NULL != t->data &&
t->data_length == sizeof(netsnmp_indexed_addr_pair)) {
_netsnmpTLSBaseData *tlsdata =
netsnmp_tlsbase_allocate_tlsdata(t, local);
tlsdata->addr = t->data;
t->data = tlsdata;
t->data_length = sizeof(_netsnmpTLSBaseData);
}
/*
* Set Domain
*/
t->domain = netsnmpDTLSUDPDomain;
t->domain_length = netsnmpDTLSUDPDomain_len;
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;
}
netsnmp_transport *
netsnmp_dtlsudp_transport(struct sockaddr_in *addr, int local)
{
netsnmp_transport *t = NULL;
DEBUGTRACETOK("dtlsudp");
t = netsnmp_udp_transport(addr, local);
if (NULL == t)
return NULL;
_transport_common(t, local);
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");
}
return t;
}
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
char *
netsnmp_dtlsudp6_fmtaddr(netsnmp_transport *t, void *data, int len)
{
int sa_len;
struct sockaddr *sa = _find_remote_sockaddr(t, data, len, &sa_len);
if (sa) {
data = sa;
len = sa_len;
}
return netsnmp_ipv6_fmtaddr("DTLSUDP6", 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_dtlsudp6_transport(struct sockaddr_in6 *addr, int local)
{
netsnmp_transport *t = NULL;
DEBUGTRACETOK("dtlsudp");
t = netsnmp_udp6_transport(addr, local);
if (NULL == t)
return NULL;
_transport_common(t, local);
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 */
t->f_fmtaddr = netsnmp_dtlsudp6_fmtaddr;
return t;
}
#endif
netsnmp_transport *
netsnmp_dtlsudp_create_tstring(const char *str, int isserver,
const char *default_target)
{
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
struct sockaddr_in6 addr6;
#endif
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);
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
else if (netsnmp_sockaddr_in6_2(&addr6, str, default_target))
t = netsnmp_dtlsudp6_transport(&addr6, isserver);
#endif
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);
}
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
else if (o_len == 18) {
struct sockaddr_in6 addr6;
unsigned short porttmp = (o[16] << 8) + o[17];
addr6.sin6_family = AF_INET6;
memcpy((u_char *) & (addr6.sin6_addr.s6_addr), o, 4);
addr6.sin6_port = htons(porttmp);
return netsnmp_dtlsudp6_transport(&addr6, local);
}
#endif
return NULL;
}
void
netsnmp_dtlsudp_ctor(void)
{
char indexname[] = "_netsnmp_addr_info";
static const char *prefixes[] = { "dtlsudp", "dtls"
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
, "dtlsudp6", "dtls6"
#endif
};
int i, num_prefixes = sizeof(prefixes) / sizeof(char *);
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
char indexname6[] = "_netsnmp_addr_info6";
#endif
DEBUGMSGTL(("dtlsudp", "registering DTLS constructor\n"));
/* config settings */
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
if (!openssl_addr_index6)
openssl_addr_index6 =
SSL_get_ex_new_index(0, indexname6, NULL, NULL, NULL);
#endif
dtlsudpDomain.name = netsnmpDTLSUDPDomain;
dtlsudpDomain.name_length = netsnmpDTLSUDPDomain_len;
dtlsudpDomain.prefix = (const char**)calloc(num_prefixes + 1,
sizeof(char *));
for (i = 0; i < num_prefixes; ++ i)
dtlsudpDomain.prefix[i] = prefixes[i];
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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
struct sockaddr_in6 s6;
#endif
} _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->sas;
/* 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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
case AF_INET6:
length += sizeof(struct in6_addr);
length += sizeof(peer->s6.sin6_port);
break;
#endif
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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
case AF_INET6:
memcpy(buffer,
&peer->s6.sin6_port,
sizeof(peer->s6.sin6_port));
memcpy(buffer + sizeof(peer->s6.sin6_port),
&peer->s6.sin6_addr,
sizeof(struct in6_addr));
break;
#endif
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->sas;
/* 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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
case AF_INET6:
length += sizeof(struct in6_addr);
length += sizeof(peer->s6.sin6_port);
break;
#endif
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;
#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN
case AF_INET6:
memcpy(buffer,
&peer->s6.sin6_port,
sizeof(peer->s6.sin6_port));
memcpy(buffer + sizeof(peer->s6.sin6_port),
&peer->s6.sin6_addr,
sizeof(struct in6_addr));
break;
#endif
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 */