| /* 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. |
| */ |
| |
| /* |
| * NOTE: THIS IS AN EXPERIMENTAL IMPLEMENTATION AND NOT YET SUITABLE |
| * FOR PRODUCTION USE |
| * |
| * THERE are KNOWN security ISSUES with THIS code! |
| * (if nothing else, you can't tie certificates to certain hosts/users) |
| */ |
| |
| /* |
| * ---------- Creating Certificates ---------- |
| * |
| * Example pub/priv key creation steps using openssl (replace for-user |
| * with the appropriate name, etc (e.g. for a user you might use their |
| * first.last name and for a server, use it's hostname or something) |
| * |
| * 1) create the CSR file first: |
| * |
| * openssl req -days 365 -new -out for-user.csr -keyout for-user.priv |
| * |
| * 2) Optionally remove the passphrase if you hate that sort of thing |
| * (obviously not recommended; useful on servers without password prompts) |
| * |
| * openssl rsa -in for-user.priv -out for-user.insecure.priv |
| * |
| * 3) Create a self-signed key from the CSR: |
| * |
| * openssl x509 -set_serial `date +%Y%m%d` -in for-user.csr -out for-user.cert -req -signkey for-user.insecure.priv -days 365 |
| * |
| * |
| * These can then be used by the config tokens for both the client and |
| * the server: |
| * |
| * ---------- Creating a CA for issuing certs ---------- |
| * |
| * TBD |
| * |
| * ---------- Configuration ---------- |
| * |
| * In the snmp.conf file, you should specify the following |
| * types of configuration lines: |
| * |
| * To tell the client which keys *it* should use to authenticate with: |
| * |
| * defX509ClientPriv /path/to/for-user.insecure.priv |
| * defX509ClientPub /path/to/for-user.insecure.cert |
| * |
| * To tell the client to only a list of servers: |
| * |
| * defX509ServerCerts /path/to/server-certs.certs |
| * |
| * (server-certs.certs can be created by simply cat'ing multiple |
| * server cert files into ones big file) |
| * |
| * To tell the server it's certs to offer: |
| * |
| * defX509ServerPub /path/to/server1.insecure.cert |
| * defX509ServerPriv /path/to/server1.insecure.priv |
| * |
| * To tell the server which keys it should accept from clients: |
| * |
| * defX509ClientCerts /path/to/client-certs.certs |
| * |
| * To authorize for R/W a particular CommonName from those certs: |
| * |
| * rwuser "John Doe" |
| * |
| */ |
| |
| #include <net-snmp/net-snmp-config.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_WINSOCK_H |
| #include <winsock2.h> |
| #include <ws2tcpip.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/snmpDTLSUDPDomain.h> |
| #include <net-snmp/library/snmpUDPDomain.h> |
| #include <net-snmp/library/system.h> |
| #include <net-snmp/library/tools.h> |
| #include <net-snmp/library/snmp_openssl.h> |
| #include <net-snmp/library/callback.h> |
| |
| #include "openssl/bio.h" |
| #include "openssl/ssl.h" |
| #include "openssl/err.h" |
| |
| #ifndef INADDR_NONE |
| #define INADDR_NONE -1 |
| #endif |
| |
| #ifdef MSG_DONTWAIT |
| #define NETSNMP_DONTWAIT MSG_DONTWAIT |
| #else |
| #define NETSNMP_DONTWAIT 0 |
| #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 *bio; |
| BIO *write_bio; |
| struct sockaddr_in sockaddr; |
| uint32_t ipv4addr; |
| u_short portnum; |
| SSL *con; |
| SSL_CTX *ctx; |
| struct bio_cache_s *next; |
| int msgnum; |
| int sock; |
| char *securityName; |
| } bio_cache; |
| |
| bio_cache *biocache = NULL; |
| |
| /* |
| * cached SSL context information |
| * (in theory we may want more than one per client/server but it's |
| * unlikely and a CPU and memory waste unless we do need more than one) |
| */ |
| SSL_CTX *client_ctx, *server_ctx; |
| |
| /* 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; |
| } |
| |
| static const char * _x509_get_error(int x509failvalue, const char *location) { |
| static const char *reason = NULL; |
| |
| /* XXX: use this instead: X509_verify_cert_error_string(err) */ |
| |
| switch (x509failvalue) { |
| case X509_V_OK: |
| reason = "X509_V_OK"; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: |
| reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_CRL: |
| reason = "X509_V_ERR_UNABLE_TO_GET_CRL"; |
| break; |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: |
| reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"; |
| break; |
| case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: |
| reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"; |
| break; |
| case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: |
| reason = "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"; |
| break; |
| case X509_V_ERR_CERT_SIGNATURE_FAILURE: |
| reason = "X509_V_ERR_CERT_SIGNATURE_FAILURE"; |
| break; |
| case X509_V_ERR_CRL_SIGNATURE_FAILURE: |
| reason = "X509_V_ERR_CRL_SIGNATURE_FAILURE"; |
| break; |
| case X509_V_ERR_CERT_NOT_YET_VALID: |
| reason = "X509_V_ERR_CERT_NOT_YET_VALID"; |
| break; |
| case X509_V_ERR_CERT_HAS_EXPIRED: |
| reason = "X509_V_ERR_CERT_HAS_EXPIRED"; |
| break; |
| case X509_V_ERR_CRL_NOT_YET_VALID: |
| reason = "X509_V_ERR_CRL_NOT_YET_VALID"; |
| break; |
| case X509_V_ERR_CRL_HAS_EXPIRED: |
| reason = "X509_V_ERR_CRL_HAS_EXPIRED"; |
| break; |
| case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: |
| reason = "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"; |
| break; |
| case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: |
| reason = "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"; |
| break; |
| case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: |
| reason = "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"; |
| break; |
| case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: |
| reason = "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"; |
| break; |
| case X509_V_ERR_OUT_OF_MEM: |
| reason = "X509_V_ERR_OUT_OF_MEM"; |
| break; |
| case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: |
| reason = "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"; |
| break; |
| case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: |
| reason = "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: |
| reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"; |
| break; |
| case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: |
| reason = "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"; |
| break; |
| case X509_V_ERR_CERT_CHAIN_TOO_LONG: |
| reason = "X509_V_ERR_CERT_CHAIN_TOO_LONG"; |
| break; |
| case X509_V_ERR_CERT_REVOKED: |
| reason = "X509_V_ERR_CERT_REVOKED"; |
| break; |
| case X509_V_ERR_INVALID_CA: |
| reason = "X509_V_ERR_INVALID_CA"; |
| break; |
| case X509_V_ERR_PATH_LENGTH_EXCEEDED: |
| reason = "X509_V_ERR_PATH_LENGTH_EXCEEDED"; |
| break; |
| case X509_V_ERR_INVALID_PURPOSE: |
| reason = "X509_V_ERR_INVALID_PURPOSE"; |
| break; |
| case X509_V_ERR_CERT_UNTRUSTED: |
| reason = "X509_V_ERR_CERT_UNTRUSTED"; |
| break; |
| case X509_V_ERR_CERT_REJECTED: |
| reason = "X509_V_ERR_CERT_REJECTED"; |
| break; |
| case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: |
| reason = "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"; |
| break; |
| case X509_V_ERR_AKID_SKID_MISMATCH: |
| reason = "X509_V_ERR_AKID_SKID_MISMATCH"; |
| break; |
| case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: |
| reason = "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"; |
| break; |
| case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: |
| reason = "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"; |
| break; |
| case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: |
| reason = "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"; |
| break; |
| case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: |
| reason = "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"; |
| break; |
| case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: |
| reason = "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"; |
| break; |
| case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: |
| reason = "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"; |
| break; |
| case X509_V_ERR_INVALID_NON_CA: |
| reason = "X509_V_ERR_INVALID_NON_CA"; |
| break; |
| case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: |
| reason = "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"; |
| break; |
| case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: |
| reason = "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"; |
| break; |
| case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: |
| reason = "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"; |
| break; |
| case X509_V_ERR_INVALID_EXTENSION: |
| reason = "X509_V_ERR_INVALID_EXTENSION"; |
| break; |
| case X509_V_ERR_INVALID_POLICY_EXTENSION: |
| reason = "X509_V_ERR_INVALID_POLICY_EXTENSION"; |
| break; |
| case X509_V_ERR_NO_EXPLICIT_POLICY: |
| reason = "X509_V_ERR_NO_EXPLICIT_POLICY"; |
| break; |
| case X509_V_ERR_UNNESTED_RESOURCE: |
| reason = "X509_V_ERR_UNNESTED_RESOURCE"; |
| break; |
| case X509_V_ERR_APPLICATION_VERIFICATION: |
| reason = "X509_V_ERR_APPLICATION_VERIFICATION"; |
| default: |
| reason = "unknown failure code"; |
| } |
| |
| return reason; |
| } |
| |
| int verify_callback(int ok, X509_STORE_CTX *ctx) { |
| int err, depth; |
| char buf[1024]; |
| X509 *thecert; |
| |
| thecert = X509_STORE_CTX_get_current_cert(ctx); |
| err = X509_STORE_CTX_get_error(ctx); |
| depth = X509_STORE_CTX_get_error_depth(ctx); |
| |
| /* things to do: */ |
| |
| X509_NAME_oneline(X509_get_subject_name(thecert), buf, sizeof(buf)); |
| DEBUGMSGTL(("dtlsudp_x509", |
| "Cert: %s\n", buf)); |
| |
| |
| DEBUGMSGTL(("dtlsudp_x509", |
| " verify value: %d, depth=%d, error code=%d, error string=%s\n", |
| ok, depth, err, _x509_get_error(err, "verify callback"))); |
| |
| /* check if we allow self-signed certs */ |
| if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_ALLOW_SELF_SIGNED) && |
| (X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT == err || |
| X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN == err)) { |
| DEBUGMSGTL(("dtlsudp_x509", " accepting a self-signed certificate\n")); |
| return 1; |
| } |
| |
| |
| DEBUGMSGTL(("dtlsudp_x509", " returing the passed in value of %d\n", ok)); |
| return(ok); |
| } |
| |
| static void _openssl_log_error(int rc, SSL *con, const char *location) { |
| const char *reason; |
| |
| if (rc == -1) { |
| int sslnum = SSL_get_error(con, rc); |
| |
| switch(sslnum) { |
| case SSL_ERROR_NONE: |
| reason = "SSL_ERROR_NONE"; |
| break; |
| |
| case SSL_ERROR_SSL: |
| reason = "SSL_ERROR_SSL"; |
| break; |
| |
| case SSL_ERROR_WANT_READ: |
| reason = "SSL_ERROR_WANT_READ"; |
| break; |
| |
| case SSL_ERROR_WANT_WRITE: |
| reason = "SSL_ERROR_WANT_WRITE"; |
| break; |
| |
| case SSL_ERROR_WANT_X509_LOOKUP: |
| reason = "SSL_ERROR_WANT_X509_LOOKUP"; |
| break; |
| |
| case SSL_ERROR_SYSCALL: |
| reason = "SSL_ERROR_SYSCALL"; |
| snmp_log(LOG_ERR, "DTLS error: %s: rc=%d, sslerror = %d (%s): system_error=%d (%s)\n", |
| location, rc, sslnum, reason, errno, strerror(errno)); |
| return; |
| |
| case SSL_ERROR_ZERO_RETURN: |
| reason = "SSL_ERROR_ZERO_RETURN"; |
| break; |
| |
| case SSL_ERROR_WANT_CONNECT: |
| reason = "SSL_ERROR_WANT_CONNECT"; |
| break; |
| |
| case SSL_ERROR_WANT_ACCEPT: |
| reason = "SSL_ERROR_WANT_ACCEPT"; |
| break; |
| |
| default: |
| reason = "unknown"; |
| } |
| |
| snmp_log(LOG_ERR, "DTLS error: %s: rc=%d, sslerror = %d (%s)\n", |
| location, rc, sslnum, reason); |
| } |
| } |
| |
| /* XXX: lots of malloc/state cleanup needed */ |
| #define DIEHERE(msg) { snmp_log(LOG_ERR, "%s\n", msg); return NULL; } |
| |
| static bio_cache * |
| start_new_cached_connection(int sock, struct sockaddr_in *remote_addr, |
| int we_are_client) { |
| bio_cache *cachep = NULL; |
| |
| if (!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; |
| |
| 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; |
| cachep->sock = sock; |
| memcpy(&cachep->sockaddr, remote_addr, sizeof(*remote_addr)); |
| |
| if (we_are_client) { |
| DEBUGMSGTL(("dtlsudp", "starting a new connection as a client to sock: %d\n", sock)); |
| cachep->con = SSL_new(client_ctx); |
| |
| /* XXX: session setting 735 */ |
| |
| /* create a bio */ |
| |
| cachep->bio = BIO_new(BIO_s_mem()); /* The one openssl reads from */ |
| cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */ |
| |
| BIO_set_mem_eof_return(cachep->bio, -1); |
| BIO_set_mem_eof_return(cachep->write_bio, -1); |
| |
| SSL_set_bio(cachep->con, cachep->bio, cachep->write_bio); |
| SSL_set_connect_state(cachep->con); |
| |
| } else { |
| /* we're the server */ |
| |
| cachep->bio = BIO_new(BIO_s_mem()); /* The one openssl reads from */ |
| |
| if (!cachep->bio) |
| DIEHERE("failed to create the read bio"); |
| |
| cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */ |
| |
| if (!cachep->write_bio) { |
| DIEHERE("failed to create the write bio"); |
| BIO_free(cachep->bio); |
| } |
| |
| BIO_set_mem_eof_return(cachep->bio, -1); |
| BIO_set_mem_eof_return(cachep->write_bio, -1); |
| |
| cachep->con = SSL_new(server_ctx); |
| |
| if (!cachep->con) { |
| BIO_free(cachep->bio); |
| BIO_free(cachep->write_bio); |
| DIEHERE("failed to create the write bio"); |
| } |
| |
| /* turn on cookie exchange */ |
| /* XXX: we need to only create cache entries when cookies succeed */ |
| SSL_set_options(cachep->con, SSL_OP_COOKIE_EXCHANGE); |
| |
| /* set the bios that openssl should read from and write to */ |
| /* (and we'll do the opposite) */ |
| SSL_set_bio(cachep->con, cachep->bio, cachep->write_bio); |
| SSL_set_accept_state(cachep->con); |
| |
| } |
| |
| return cachep; |
| } |
| |
| static bio_cache * |
| find_or_create_bio_cache(int sock, 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(sock, from_addr, we_are_client); |
| if (NULL == cachep) { |
| snmp_log(LOG_ERR, "failed to open a new dtls connection\n"); |
| } |
| } |
| return cachep; |
| } |
| |
| /* |
| * You can write something into opaque that will subsequently get passed back |
| * to your send function if you like. For instance, you might want to |
| * remember where a PDU came from, so that you can send a reply there... |
| */ |
| |
| static int |
| netsnmp_dtlsudp_recv(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| socklen_t fromlen = sizeof(struct sockaddr); |
| netsnmp_addr_pair *addr_pair = NULL; |
| struct sockaddr *from; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| X509 *peer; |
| |
| if (t != NULL && t->sock >= 0) { |
| /* create a tmStateRef cache for slow fill-in */ |
| tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference); |
| |
| if (tmStateRef == NULL) { |
| *opaque = NULL; |
| *olength = 0; |
| return -1; |
| } |
| |
| addr_pair = &tmStateRef->addresses; |
| tmStateRef->have_addresses = 1; |
| from = (struct sockaddr *) &(addr_pair->remote_addr); |
| |
| while (rc < 0) { |
| #if defined(linux) && defined(IP_PKTINFO) |
| rc = netsnmp_udp_recvfrom(t->sock, buf, size, from, &fromlen, &(addr_pair->local_addr)); |
| #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; |
| } |
| |
| if (rc >= 0) { |
| /* now that we have the from address filled in, we can look up |
| the openssl context and have openssl read and process |
| appropriately */ |
| |
| /* if we don't have a cachep for this connection then |
| we're receiving something new and are the server |
| side */ |
| /* XXX: allow for a SNMP client to never accept new conns? */ |
| bio_cache *cachep = |
| find_or_create_bio_cache(t->sock, &addr_pair->remote_addr, |
| WE_ARE_SERVER); |
| if (NULL == cachep) { |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| |
| /* write the received buffer to the memory-based input bio */ |
| BIO_write(cachep->bio, buf, rc); |
| |
| /* 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(cachep->con, buf, size); |
| |
| DEBUGMSGTL(("dtlsudp", "received %d decoded bytes from dtls\n", rc)); |
| |
| if (BIO_ctrl_pending(cachep->write_bio) > 0) { |
| /* we have outgoing data to send; probably DTLS negotation */ |
| |
| u_char outbuf[65535]; |
| int outsize; |
| int rc2; |
| |
| /* 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) { |
| /* 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(cachep->sock, addr_pair->local_addr, addr_pair->remote_addr, outbuf, outsize); |
| #else |
| rc2 = sendto(t->sock, outbuf, outsize, 0, &cachep->sockaddr, sizeof(struct sockaddr)); |
| #endif /* linux && IP_PKTINFO */ |
| |
| if (rc2 == -1) { |
| snmp_log(LOG_ERR, "failed to send a DTLS specific packet\n"); |
| } |
| } |
| } |
| |
| if (SSL_pending(cachep->con)) { |
| fprintf(stderr, "ack: got here... pending\n"); |
| exit(1); |
| } |
| |
| if (rc == -1) { |
| _openssl_log_error(rc, cachep->con, "SSL_read"); |
| SNMP_FREE(tmStateRef); |
| |
| if (SSL_get_error(cachep->con, rc) == SSL_ERROR_WANT_READ) |
| return -1; /* XXX: it's ok, but what's the right return? */ |
| return rc; |
| } |
| |
| { |
| char *str = netsnmp_udp_fmtaddr(NULL, addr_pair, sizeof(netsnmp_addr_pair)); |
| DEBUGMSGTL(("dtlsudp", |
| "recvfrom fd %d got %d bytes (from %s)\n", |
| t->sock, rc, str)); |
| free(str); |
| } |
| |
| /* XXX: disallow NULL auth/encr algs in our implementations */ |
| tmStateRef->transportSecurityLevel = SNMP_SEC_LEVEL_AUTHPRIV; |
| |
| /* use x509 cert to do lookup to secname if DNE in cachep yet */ |
| if (!cachep->securityName) { |
| if (NULL != (peer = SSL_get_peer_certificate(cachep->con))) { |
| X509_NAME *subname; |
| char namebuf[1024]; |
| |
| /* we have one */ |
| subname = X509_get_subject_name(peer); |
| X509_NAME_get_text_by_NID(subname, NID_commonName, |
| namebuf, sizeof(namebuf)); |
| DEBUGMSGTL(("dtlsudp", "got commonname: %s\n", |
| namebuf)); |
| cachep->securityName = strdup(namebuf); |
| DEBUGMSGTL(("dtlsudp", "set SecName to: %s\n", |
| cachep->securityName)); |
| } else { |
| SNMP_FREE(tmStateRef); |
| return -1; |
| } |
| } |
| |
| /* XXX: detect and throw out overflow secname sizes rather |
| than truncating. */ |
| strlcpy(tmStateRef->securityName, cachep->securityName, |
| sizeof(tmStateRef->securityName)); |
| tmStateRef->securityName[sizeof(tmStateRef->securityName)-1] = '\0'; |
| tmStateRef->securityNameLen = strlen(tmStateRef->securityName); |
| |
| *opaque = tmStateRef; |
| *olength = sizeof(netsnmp_tmStateReference); |
| |
| } else { |
| DEBUGMSGTL(("dtlsudp", "recvfrom fd %d err %d (\"%s\")\n", |
| t->sock, errno, strerror(errno))); |
| } |
| } |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_dtlsudp_send(netsnmp_transport *t, void *buf, int size, |
| void **opaque, int *olength) |
| { |
| int rc = -1; |
| netsnmp_addr_pair *addr_pair = NULL; |
| struct sockaddr *to = NULL; |
| bio_cache *cachep = NULL; |
| netsnmp_tmStateReference *tmStateRef = NULL; |
| u_char outbuf[65535]; |
| |
| 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_addr_pair)) |
| addr_pair = (netsnmp_addr_pair *) (t->data); |
| } else if (t != NULL && t->data != NULL && |
| t->data_length == sizeof(netsnmp_addr_pair)) { |
| addr_pair = (netsnmp_addr_pair *) (t->data); |
| } |
| |
| if (NULL == addr_pair) { |
| 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_log(LOG_ERR, "invalid netsnmp_dtlsudp_send usage\n"); |
| return -1; |
| } |
| |
| /* we're always a client if we're sending to something unknown yet */ |
| if (NULL == |
| (cachep = find_or_create_bio_cache(t->sock, &addr_pair->remote_addr, |
| WE_ARE_CLIENT))) |
| return -1; |
| |
| if (!cachep->securityName && tmStateRef && tmStateRef->securityNameLen > 0) |
| cachep->securityName = strdup(tmStateRef->securityName); |
| |
| |
| { |
| char *str = netsnmp_udp_fmtaddr(NULL, (void *) addr_pair, |
| sizeof(netsnmp_addr_pair)); |
| DEBUGMSGTL(("dtlsudp", "send %d bytes from %p to %s on fd %d\n", |
| size, buf, str, t->sock)); |
| free(str); |
| } |
| rc = SSL_write(cachep->con, buf, size); |
| if (rc < 0) { |
| _openssl_log_error(rc, cachep->con, "SSL_write"); |
| } |
| |
| /* 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_udp_sendto(cachep->sock, &cachep->sockaddr remote addr_pair ? &(addr_pair->local_addr) : NULL, to, outbuf, rc); |
| #else |
| rc = sendto(t->sock, outbuf, rc, 0, &cachep->sockaddr, sizeof(struct sockaddr)); |
| #endif /* linux && IP_PKTINFO */ |
| |
| return rc; |
| } |
| |
| |
| |
| static int |
| netsnmp_dtlsudp_close(netsnmp_transport *t) |
| { |
| int rc = -1; |
| /* XXX: issue a proper dtls closure notification(s) */ |
| if (t->sock >= 0) { |
| #ifndef HAVE_CLOSESOCKET |
| rc = close(t->sock); |
| #else |
| rc = closesocket(t->sock); |
| #endif |
| t->sock = -1; |
| } |
| return rc; |
| } |
| |
| /* |
| * 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; |
| int rc = 0; |
| char *str = NULL; |
| char *client_socket = NULL; |
| netsnmp_addr_pair addr_pair; |
| |
| if (addr == NULL || addr->sin_family != AF_INET) { |
| return NULL; |
| } |
| |
| memset(&addr_pair, 0, sizeof(netsnmp_addr_pair)); |
| memcpy(&(addr_pair.remote_addr), addr, sizeof(struct sockaddr_in)); |
| |
| t = SNMP_MALLOC_TYPEDEF(netsnmp_transport); |
| if (t == NULL) { |
| return NULL; |
| } |
| |
| str = netsnmp_udp_fmtaddr(NULL, (void *)&addr_pair, |
| sizeof(netsnmp_addr_pair)); |
| DEBUGMSGTL(("dtlsudp", "open %s %s\n", local ? "local" : "remote", |
| str)); |
| free(str); |
| |
| t->domain = netsnmpDTLSUDPDomain; |
| t->domain_length = netsnmpDTLSUDPDomain_len; |
| |
| t->sock = socket(PF_INET, SOCK_DGRAM, 0); |
| DEBUGMSGTL(("dtlsudp", "openned socket %d as local=%d\n", t->sock, |
| local)); |
| if (t->sock < 0) { |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| |
| /* XXX: Potentially set sock opts here (SO_SNDBUF/SO_RCV_BUF) */ |
| /* XXX: and buf size */ |
| _netsnmp_udp_sockopt_set(t->sock, local); |
| |
| if (local) { |
| /* |
| * This session is inteneded as a server, so we must bind on to the |
| * given IP address, which may include an interface address, or could |
| * be INADDR_ANY, but certainly includes a port number. |
| */ |
| |
| t->local = (u_char *) malloc(6); |
| if (t->local == NULL) { |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| memcpy(t->local, (u_char *) & (addr->sin_addr.s_addr), 4); |
| t->local[4] = (htons(addr->sin_port) & 0xff00) >> 8; |
| t->local[5] = (htons(addr->sin_port) & 0x00ff) >> 0; |
| t->local_length = 6; |
| |
| #if defined(linux) && defined(IP_PKTINFO) |
| { |
| int sockopt = 1; |
| if (setsockopt(t->sock, SOL_IP, IP_PKTINFO, &sockopt, sizeof sockopt) == -1) { |
| DEBUGMSGTL(("dtlsudp", "couldn't set IP_PKTINFO: %s\n", |
| strerror(errno))); |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| DEBUGMSGTL(("dtlsudp", "set IP_PKTINFO\n")); |
| } |
| #endif |
| rc = bind(t->sock, (struct sockaddr *) addr, |
| sizeof(struct sockaddr)); |
| if (rc != 0) { |
| netsnmp_dtlsudp_close(t); |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| t->data = NULL; |
| t->data_length = 0; |
| } else { |
| /* |
| * This is a client session. If we've been given a |
| * client address to send from, then bind to that. |
| * Otherwise the send will use "something sensible". |
| */ |
| client_socket = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_CLIENT_ADDR); |
| if (client_socket) { |
| struct sockaddr_in client_addr; |
| netsnmp_sockaddr_in2(&client_addr, client_socket, NULL); |
| addr_pair.local_addr = client_addr.sin_addr; |
| client_addr.sin_port = 0; |
| DEBUGMSGTL(("dtlsudp", "binding socket: %d\n", t->sock)); |
| rc = bind(t->sock, (struct sockaddr *)&client_addr, |
| sizeof(struct sockaddr)); |
| if ( rc != 0 ) { |
| DEBUGMSGTL(("dtlsudp", "failed to bind for clientaddr: %d %s\n", |
| errno, strerror(errno))); |
| netsnmp_dtlsudp_close(t); |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| } |
| |
| str = netsnmp_udp_fmtaddr(NULL, (void *)&addr_pair, |
| sizeof(netsnmp_addr_pair)); |
| DEBUGMSGTL(("dtlsudp", "client open %s\n", str)); |
| free(str); |
| |
| /* |
| * Save the (remote) address in the |
| * transport-specific data pointer for later use by netsnmp_dtlsudp_send. |
| */ |
| |
| t->data = SNMP_MALLOC_TYPEDEF(netsnmp_addr_pair); |
| t->remote = (u_char *)malloc(6); |
| if (t->data == NULL || t->remote == NULL) { |
| netsnmp_transport_free(t); |
| return NULL; |
| } |
| memcpy(t->remote, (u_char *) & (addr->sin_addr.s_addr), 4); |
| t->remote[4] = (htons(addr->sin_port) & 0xff00) >> 8; |
| t->remote[5] = (htons(addr->sin_port) & 0x00ff) >> 0; |
| t->remote_length = 6; |
| memcpy(t->data, &addr_pair, sizeof(netsnmp_addr_pair)); |
| t->data_length = sizeof(netsnmp_addr_pair); |
| |
| /* 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"); |
| |
| } |
| |
| /* |
| * 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_accept = NULL; |
| t->f_fmtaddr = netsnmp_udp_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 local, |
| const char *default_target) |
| { |
| struct sockaddr_in addr; |
| |
| if (netsnmp_sockaddr_in2(&addr, str, default_target)) { |
| return netsnmp_dtlsudp_transport(&addr, local); |
| } else { |
| return NULL; |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| #define LOGANDDIE(msg) { snmp_log(LOG_ERR, "%s\n", msg); return 0; } |
| |
| static int have_done_init = 0; |
| |
| static int |
| dtlsudp_bootstrap(int majorid, int minorid, void *serverarg, void *clientarg) { |
| const char *certfile; |
| EVP_PKEY *key = NULL; |
| X509 *cert = NULL; |
| BIO *keybio = NULL; |
| |
| /* don't do this more than once */ |
| if (have_done_init) |
| return 0; |
| have_done_init = 1; |
| |
| /*********************************************************************** |
| * Set up the client context |
| */ |
| client_ctx = SSL_CTX_new(DTLSv1_client_method()); |
| if (!client_ctx) { |
| LOGANDDIE("can't create a new context"); |
| } |
| SSL_CTX_set_read_ahead (client_ctx, 1); /* Required for DTLS */ |
| |
| SSL_CTX_set_verify(client_ctx, |
| SSL_VERIFY_PEER| |
| SSL_VERIFY_FAIL_IF_NO_PEER_CERT| |
| SSL_VERIFY_CLIENT_ONCE, |
| &verify_callback); |
| |
| keybio = BIO_new(BIO_s_file()); |
| if (!keybio) |
| LOGANDDIE ("error creating bio for reading public key"); |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_PUB); |
| |
| DEBUGMSGTL(("dtlsudp", "using public key: %s\n", certfile)); |
| if (BIO_read_filename(keybio, certfile) <=0) |
| LOGANDDIE ("error reading public key"); |
| |
| cert = PEM_read_bio_X509_AUX(keybio, NULL, NULL, NULL); |
| if (!cert) |
| LOGANDDIE("failed to load public key"); |
| |
| /* XXX: mem leak on previous keybio? */ |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_PRIV); |
| |
| keybio = BIO_new(BIO_s_file()); |
| if (!keybio) |
| LOGANDDIE ("error creating bio for reading private key"); |
| |
| DEBUGMSGTL(("dtlsudp", "using private key: %s\n", certfile)); |
| if (!keybio || |
| BIO_read_filename(keybio, certfile) <= 0) |
| LOGANDDIE ("error reading private key"); |
| |
| key = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL); |
| |
| if (!key) |
| LOGANDDIE("failed to load private key"); |
| |
| |
| if (SSL_CTX_use_certificate(client_ctx, cert) <= 0) |
| LOGANDDIE("failed to set the certificate to use"); |
| |
| if (SSL_CTX_use_PrivateKey(client_ctx, key) <= 0) |
| LOGANDDIE("failed to set the private key to use"); |
| |
| if (!SSL_CTX_check_private_key(client_ctx)) |
| LOGANDDIE("public and private keys incompatible"); |
| |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_CERTS); |
| |
| /* XXX: also need to match individual cert to indiv. host */ |
| |
| if(! SSL_CTX_load_verify_locations(client_ctx, certfile, NULL)) { |
| LOGANDDIE("failed to load truststore"); |
| /* Handle failed load here */ |
| } |
| |
| if (!SSL_CTX_set_default_verify_paths(client_ctx)) { |
| LOGANDDIE ("failed to set default verify path"); |
| } |
| |
| /*********************************************************************** |
| * Set up the server context |
| */ |
| /* setting up for ssl */ |
| server_ctx = SSL_CTX_new(DTLSv1_server_method()); |
| if (!server_ctx) { |
| LOGANDDIE("can't create a new context"); |
| } |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_PUB); |
| |
| if (SSL_CTX_use_certificate_file(server_ctx, certfile, |
| SSL_FILETYPE_PEM) < 1) { |
| LOGANDDIE("faild to load cert"); |
| } |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_PRIV); |
| |
| if (SSL_CTX_use_PrivateKey_file(server_ctx, certfile, SSL_FILETYPE_PEM) < 1) { |
| LOGANDDIE("faild to load key"); |
| } |
| |
| SSL_CTX_set_read_ahead(server_ctx, 1); |
| |
| |
| certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_CERTS); |
| if(! SSL_CTX_load_verify_locations(server_ctx, certfile, NULL)) { |
| LOGANDDIE("failed to load truststore"); |
| /* Handle failed load here */ |
| } |
| |
| SSL_CTX_set_verify(server_ctx, |
| SSL_VERIFY_PEER| |
| SSL_VERIFY_FAIL_IF_NO_PEER_CERT| |
| SSL_VERIFY_CLIENT_ONCE, |
| &verify_callback); |
| |
| return 0; |
| } |
| |
| |
| void |
| netsnmp_dtlsudp_ctor(void) |
| { |
| DEBUGMSGTL(("dtlsudp", "registering DTLS constructor\n")); |
| |
| /* config settings */ |
| |
| /* bootstrap ssl since we'll need it */ |
| netsnmp_init_openssl(); |
| |
| /* |
| * for the client |
| */ |
| |
| /* pem file of valid server CERT CAs */ |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerCerts", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_CERTS); |
| |
| /* the public client cert to authenticate with */ |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientPub", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_PUB); |
| |
| /* the private client cert to authenticate with */ |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientPriv", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_PRIV); |
| |
| /* |
| * for the server |
| */ |
| |
| /* The list of valid client keys to accept (or CAs I think) */ |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientCerts", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_CLIENT_CERTS); |
| |
| /* The X509 server key to use */ |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerPub", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_PUB); |
| |
| netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerPriv", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_X509_SERVER_PRIV); |
| |
| netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "AllowSelfSignedX509", |
| NETSNMP_DS_LIBRARY_ID, |
| NETSNMP_DS_LIB_ALLOW_SELF_SIGNED); |
| |
| /* |
| * register our boot-strapping needs |
| */ |
| snmp_register_callback(SNMP_CALLBACK_LIBRARY, |
| SNMP_CALLBACK_POST_READ_CONFIG, |
| dtlsudp_bootstrap, NULL); |
| |
| dtlsudpDomain.name = netsnmpDTLSUDPDomain; |
| dtlsudpDomain.name_length = netsnmpDTLSUDPDomain_len; |
| dtlsudpDomain.prefix = (const char**)calloc(2, sizeof(char *)); |
| dtlsudpDomain.prefix[0] = "dtlsudp"; |
| |
| 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; |
| |
| netsnmp_tdomain_register(&dtlsudpDomain); |
| } |