From fdfc36778b88c5bceaea6e83c1f2dfa97203f016 Mon Sep 17 00:00:00 2001 From: dmiller Date: Sat, 29 Jul 2017 05:55:30 +0000 Subject: [PATCH] Add DTLS and ALPN support to Ncat. Closes #446 --- CHANGELOG | 4 + ncat/docs/ncat.xml | 16 ++++ ncat/docs/ncatguide.xml | 4 +- ncat/ncat_connect.c | 160 +++++++++++++++++++------------- ncat/ncat_core.c | 1 + ncat/ncat_core.h | 1 + ncat/ncat_listen.c | 7 ++ ncat/ncat_main.c | 20 +++- ncat/util.c | 36 +++++++ ncat/util.h | 2 + nsock/include/nsock.h | 7 ++ nsock/include/nsock_config.h.in | 2 + nsock/src/configure | 60 +++++++++++- nsock/src/configure.ac | 8 ++ nsock/src/nsock_connect.c | 29 ++++-- nsock/src/nsock_core.c | 4 +- nsock/src/nsock_ssl.c | 63 +++++++++++-- 17 files changed, 336 insertions(+), 88 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fefa210b0..5519fc2de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [Ncat][GH#446] Added Datagram TLS (DTLS) support to Ncat in connect (client) + mode with --udp --ssl. Also added Application Layer Protocol Negotiation + (ALPN) support with the --ssl-alpn option. [denandz, Daniel Miller] + o Updated the default ciphers list for Ncat and the secure ciphers list for Nsock to use "!aNULL:!eNULL" instead of "!ADH". With the addition of ECDH ciphersuites, anonymous ECDH suites were being allowed. [Daniel Miller] diff --git a/ncat/docs/ncat.xml b/ncat/docs/ncat.xml index 1dd88ee00..cc87dc332 100644 --- a/ncat/docs/ncat.xml +++ b/ncat/docs/ncat.xml @@ -314,6 +314,8 @@ particularly handy for talking to SSL enabled HTTP servers, etc. In server mode, this option listens for incoming SSL connections, rather than plain untunneled traffic. + In UDP connect mode, this option enables Datagram TLS (DTLS). + This is not supported in server mode. @@ -395,6 +397,20 @@ ALL:!aNULL:!eNULL:!LOW:!EXP:!MD5:@STRENGTH + + + + (Specify ALPN protocol list) + (Ncat option) + + + This option allows you to specify a comma-separated list of + protocols to send via the Application-Layer Protocol Negotiation + (ALPN) TLS extension. Not supported by all versions of OpenSSL. + + + + diff --git a/ncat/docs/ncatguide.xml b/ncat/docs/ncatguide.xml index ec401a5fa..fa8ec2cd3 100644 --- a/ncat/docs/ncatguide.xml +++ b/ncat/docs/ncatguide.xml @@ -308,8 +308,8 @@ Content-Type: text/html; charset=UTF-8 with only one client, and the (Ncat option)not supported with UDP option doesn't work, the reason for this being that UDP has no notion - of a connection. UDP may not be combined with - SSL.SSLnot supported with UDP + of a connection. UDP may be secured by a form of SSL called Datagram TLS (DTLS)DTLSDatagram TLS. + This is currently only supported in connect (client) mode.SSLnot supported with UDP in server mode diff --git a/ncat/ncat_connect.c b/ncat/ncat_connect.c index 51f061ee3..d8c73ab1b 100644 --- a/ncat/ncat_connect.c +++ b/ncat/ncat_connect.c @@ -256,6 +256,30 @@ static void set_ssl_ctx_options(SSL_CTX *ctx) if (!SSL_CTX_set_cipher_list(ctx, o.sslciphers)) bye("Unable to set OpenSSL cipher list: %s", ERR_error_string(ERR_get_error(), NULL)); } + +#ifdef HAVE_ALPN_SUPPORT + + if (o.sslalpn) { + size_t alpn_len; + unsigned char *alpn = next_protos_parse(&alpn_len, o.sslalpn); + + if (alpn == NULL) + bye("Could not parse ALPN string"); + + if (o.debug) + logdebug("Using ALPN String %s\n", o.sslalpn); + + /* SSL_CTX_set_alpn_protos returns 0 on success */ + if (SSL_CTX_set_alpn_protos(ctx, alpn, alpn_len) != 0){ + free(alpn); + bye("SSL_CTX_set_alpn_protos: %s.", ERR_error_string(ERR_get_error(), NULL)); + } + + free(alpn); + } + +#endif + } #endif @@ -878,6 +902,51 @@ static int do_proxy_socks5(void) return(sd); } +static nsock_iod new_iod(nsock_pool mypool) { + nsock_iod nsi = nsock_iod_new(mypool, NULL); + if (nsi == NULL) + bye("Failed to create nsock_iod."); + if (nsock_iod_set_hostname(nsi, o.target) == -1) + bye("Failed to set hostname on iod."); + + switch (srcaddr.storage.ss_family) { + case AF_UNSPEC: + break; + case AF_INET: + nsock_iod_set_localaddr(nsi, &srcaddr.storage, + sizeof(srcaddr.in)); + break; +#ifdef AF_INET6 + case AF_INET6: + nsock_iod_set_localaddr(nsi, &srcaddr.storage, + sizeof(srcaddr.in6)); + break; +#endif +#if HAVE_SYS_UN_H + case AF_UNIX: + nsock_iod_set_localaddr(nsi, &srcaddr.storage, + SUN_LEN((struct sockaddr_un *)&srcaddr.storage)); + break; +#endif + default: + nsock_iod_set_localaddr(nsi, &srcaddr.storage, + sizeof(srcaddr.storage)); + break; + } + + if (o.numsrcrtes) { + unsigned char *ipopts = NULL; + size_t ipoptslen = 0; + + if (o.af != AF_INET) + bye("Sorry, -g can only currently be used with IPv4."); + ipopts = buildsrcrte(targetaddrs->addr.in.sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen); + + nsock_iod_set_ipoptions(nsi, ipopts, ipoptslen); + free(ipopts); /* Nsock has its own copy */ + } + return nsi; +} int ncat_connect(void) { @@ -908,18 +977,15 @@ int ncat_connect(void) nsock_pool_set_broadcast(mypool, 1); #ifdef HAVE_OPENSSL - set_ssl_ctx_options((SSL_CTX *) nsock_pool_ssl_init(mypool, 0)); +#ifdef HAVE_DTLS_CLIENT_METHOD + if(o.proto == IPPROTO_UDP) + set_ssl_ctx_options((SSL_CTX *) nsock_pool_dtls_init(mypool, 0)); + else +#endif + set_ssl_ctx_options((SSL_CTX *) nsock_pool_ssl_init(mypool, 0)); #endif if (!o.proxytype) { - /* A non-proxy connection. Create an iod for a new socket. */ - cs.sock_nsi = nsock_iod_new(mypool, NULL); - if (cs.sock_nsi == NULL) - bye("Failed to create nsock_iod."); - - if (nsock_iod_set_hostname(cs.sock_nsi, o.target) == -1) - bye("Failed to set hostname on iod."); - #if HAVE_SYS_UN_H /* For DGRAM UNIX socket we have to use source socket */ if (o.af == AF_UNIX && o.proto == IPPROTO_UDP) @@ -945,50 +1011,13 @@ int ncat_connect(void) strncpy(srcaddr.un.sun_path, tmp_name, sizeof(srcaddr.un.sun_path)); free (tmp_name); } - nsock_iod_set_localaddr(cs.sock_nsi, &srcaddr.storage, - SUN_LEN((struct sockaddr_un *)&srcaddr.storage)); if (o.verbose) loguser("[%s] used as source DGRAM Unix domain socket.\n", srcaddr.un.sun_path); } - else #endif - switch (srcaddr.storage.ss_family) { - case AF_UNSPEC: - break; - case AF_INET: - nsock_iod_set_localaddr(cs.sock_nsi, &srcaddr.storage, - sizeof(srcaddr.in)); - break; -#ifdef AF_INET6 - case AF_INET6: - nsock_iod_set_localaddr(cs.sock_nsi, &srcaddr.storage, - sizeof(srcaddr.in6)); - break; -#endif -#if HAVE_SYS_UN_H - case AF_UNIX: - nsock_iod_set_localaddr(cs.sock_nsi, &srcaddr.storage, - SUN_LEN((struct sockaddr_un *)&srcaddr.storage)); - break; -#endif - default: - nsock_iod_set_localaddr(cs.sock_nsi, &srcaddr.storage, - sizeof(srcaddr.storage)); - break; - } - - if (o.numsrcrtes) { - unsigned char *ipopts = NULL; - size_t ipoptslen = 0; - - if (o.af != AF_INET) - bye("Sorry, -g can only currently be used with IPv4."); - ipopts = buildsrcrte(targetaddrs->addr.in.sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen); - - nsock_iod_set_ipoptions(cs.sock_nsi, ipopts, ipoptslen); - free(ipopts); /* Nsock has its own copy */ - } + /* A non-proxy connection. Create an iod for a new socket. */ + cs.sock_nsi = new_iod(mypool); #if HAVE_SYS_UN_H if (o.af == AF_UNIX) { @@ -1068,35 +1097,27 @@ int ncat_connect(void) static void try_nsock_connect(nsock_pool nsp, struct sockaddr_list *conn_addr) { +#ifdef HAVE_OPENSSL + if (o.ssl) { + nsock_connect_ssl(nsp, cs.sock_nsi, connect_handler, + o.conntimeout, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + o.proto, inet_port(&conn_addr->addr), + NULL); + } + else +#endif if (o.proto == IPPROTO_UDP) { nsock_connect_udp(nsp, cs.sock_nsi, connect_handler, (void *)conn_addr->next, &conn_addr->addr.sockaddr, conn_addr->addrlen, inet_port(&conn_addr->addr)); } -#ifdef HAVE_OPENSSL - else if (o.proto == IPPROTO_SCTP && o.ssl) { - nsock_connect_ssl(nsp, cs.sock_nsi, connect_handler, - o.conntimeout, (void *)conn_addr->next, - &conn_addr->addr.sockaddr, conn_addr->addrlen, - IPPROTO_SCTP, inet_port(&conn_addr->addr), - NULL); - } -#endif else if (o.proto == IPPROTO_SCTP) { nsock_connect_sctp(nsp, cs.sock_nsi, connect_handler, o.conntimeout, (void *)conn_addr->next, &conn_addr->addr.sockaddr, conn_addr->addrlen, inet_port(&conn_addr->addr)); } -#ifdef HAVE_OPENSSL - else if (o.ssl) { - nsock_connect_ssl(nsp, cs.sock_nsi, connect_handler, - o.conntimeout, (void *)conn_addr->next, - &conn_addr->addr.sockaddr, conn_addr->addrlen, - IPPROTO_TCP, inet_port(&conn_addr->addr), - NULL); - } -#endif else { nsock_connect_tcp(nsp, cs.sock_nsi, connect_handler, o.conntimeout, (void *)conn_addr->next, @@ -1132,6 +1153,13 @@ static void connect_handler(nsock_pool nsp, nsock_event evt, void *data) loguser("Connection to %s failed: %s.\n", inet_socktop(&peer), socket_strerror(errcode)); loguser("Trying next address...\n"); } +#ifdef HAVE_OPENSSL + /* If it's an SSL reconnect, clear out any old session info */ + if (nsock_iod_check_ssl(cs.sock_nsi)) { + nsock_iod_delete(cs.sock_nsi, NSOCK_PENDING_NOTIFY); + cs.sock_nsi = new_iod(nsp); + } +#endif try_nsock_connect(nsp, next_addr); return; } diff --git a/ncat/ncat_core.c b/ncat/ncat_core.c index e5c983bfc..ef6ffdb80 100644 --- a/ncat/ncat_core.c +++ b/ncat/ncat_core.c @@ -215,6 +215,7 @@ void options_init(void) o.sslverify = 0; o.ssltrustfile = NULL; o.sslciphers = NULL; + o.sslalpn = NULL; #endif } diff --git a/ncat/ncat_core.h b/ncat/ncat_core.h index a9a774517..3075a2155 100644 --- a/ncat/ncat_core.h +++ b/ncat/ncat_core.h @@ -218,6 +218,7 @@ struct options { int sslverify; char *ssltrustfile; char *sslciphers; + char *sslalpn; int zerobyte; }; diff --git a/ncat/ncat_listen.c b/ncat/ncat_listen.c index 6dc9c73ff..6d39ce4c4 100644 --- a/ncat/ncat_listen.c +++ b/ncat/ncat_listen.c @@ -264,6 +264,8 @@ static int ncat_listen_stream(int proto) #ifdef HAVE_OPENSSL if (o.ssl) + if (o.sslalpn) + bye("ALPN is not supported in listen mode\n"); setup_ssl_listen(); #endif @@ -712,6 +714,11 @@ static int ncat_listen_dgram(int proto) struct timeval *tvp = NULL; unsigned int num_sockets; +#ifdef HAVE_OPENSSL + if(o.ssl) + bye("DTLS is not supported in listen mode\n"); +#endif + for (i = 0; i < NUM_LISTEN_ADDRS; i++) { sockfd[i].fd = -1; sockfd[i].addr.storage.ss_family = AF_UNSPEC; diff --git a/ncat/ncat_main.c b/ncat/ncat_main.c index e045fc0a6..b4aa40532 100644 --- a/ncat/ncat_main.c +++ b/ncat/ncat_main.c @@ -320,12 +320,14 @@ int main(int argc, char *argv[]) {"ssl-verify", no_argument, NULL, 0}, {"ssl-trustfile", required_argument, NULL, 0}, {"ssl-ciphers", required_argument, NULL, 0}, + {"ssl-alpn", required_argument, NULL, 0}, #else {"ssl-cert", optional_argument, NULL, 0}, {"ssl-key", optional_argument, NULL, 0}, {"ssl-verify", no_argument, NULL, 0}, {"ssl-trustfile", optional_argument, NULL, 0}, {"ssl-ciphers", optional_argument, NULL, 0}, + {"ssl-alpn", optional_argument, NULL, 0}, #endif {0, 0, 0, 0} }; @@ -536,7 +538,16 @@ int main(int argc, char *argv[]) } else if (strcmp(long_options[option_index].name, "ssl-ciphers") == 0) { o.ssl = 1; o.sslciphers = Strdup(optarg); +#ifdef HAVE_ALPN_SUPPORT + } else if (strcmp(long_options[option_index].name, "ssl-alpn") == 0) { + o.ssl = 1; + o.sslalpn = Strdup(optarg); } +#else + } else if (strcmp(long_options[option_index].name, "ssl-alpn") == 0) { + bye("OpenSSL does not have ALPN support compiled in. The --ssl-alpn option cannot be chosen."); + } +#endif #else else if (strcmp(long_options[option_index].name, "ssl-cert") == 0) { bye("OpenSSL isn't compiled in. The --ssl-cert option cannot be chosen."); @@ -548,6 +559,8 @@ int main(int argc, char *argv[]) bye("OpenSSL isn't compiled in. The --ssl-trustfile option cannot be chosen."); } else if (strcmp(long_options[option_index].name, "ssl-ciphers") == 0) { bye("OpenSSL isn't compiled in. The --ssl-ciphers option cannot be chosen."); + } else if (strcmp(long_options[option_index].name, "ssl-alpn") == 0) { + bye("OpenSSL isn't compiled in. The --ssl-alpn option cannot be chosen."); } #endif #ifdef HAVE_LUA @@ -637,6 +650,7 @@ int main(int argc, char *argv[]) " --ssl-verify Verify trust and domain name of certificates\n" " --ssl-trustfile PEM file containing trusted SSL certificates\n" " --ssl-ciphers Cipherlist containing SSL ciphers to use\n" +" --ssl-alpn ALPN protocol list to use.\n" #endif " --version Display Ncat's version information and exit\n" "\n" @@ -903,9 +917,11 @@ int main(int argc, char *argv[]) } if (o.proto == IPPROTO_UDP) { - /* Don't allow a false sense of security if someone tries SSL over UDP. */ + +#ifndef HAVE_DTLS_CLIENT_METHOD if (o.ssl) - bye("UDP mode does not support SSL."); + bye("OpenSSL does not have DTLS support compiled in."); +#endif if (o.keepopen && o.cmdexec == NULL) bye("UDP mode does not support the -k or --keep-open options, except with --exec or --sh-exec."); if (o.broker) diff --git a/ncat/util.c b/ncat/util.c index 795b57caa..fa1398534 100644 --- a/ncat/util.c +++ b/ncat/util.c @@ -750,3 +750,39 @@ int fix_line_endings(char *src, int *len, char **dst, int *state) return 1; } + +/*- + * next_protos_parse parses a comma separated list of strings into a string + * in a format suitable for passing to SSL_CTX_set_next_protos_advertised. + * outlen: (output) set to the length of the resulting buffer on success. + * err: NULL on failure + * in: a NULL terminated string like "abc,def,ghi" + * + * returns: a malloc'd buffer or NULL on failure. + */ +unsigned char *next_protos_parse(size_t *outlen, const char *in) +{ + size_t len; + unsigned char *out; + size_t i, start = 0; + + len = strlen(in); + if (len >= 65535) + return NULL; + + out = safe_malloc(strlen(in) + 1); + for (i = 0; i <= len; ++i) { + if (i == len || in[i] == ',') { + if (i - start > 255) { + free(out); + return NULL; + } + out[start] = i - start; + start = i + 1; + } else + out[i + 1] = in[i]; + } + + *outlen = len + 1; + return out; +} diff --git a/ncat/util.h b/ncat/util.h index fbbd40a34..e6bc2dc08 100644 --- a/ncat/util.h +++ b/ncat/util.h @@ -230,4 +230,6 @@ struct fdinfo *get_fdinfo(const fd_list_t *, int); int fix_line_endings(char *src, int *len, char **dst, int *state); +unsigned char *next_protos_parse(size_t *outlen, const char *in); + #endif diff --git a/nsock/include/nsock.h b/nsock/include/nsock.h index 396424aad..7ad4775c2 100644 --- a/nsock/include/nsock.h +++ b/nsock/include/nsock.h @@ -248,6 +248,13 @@ void nsock_pool_set_device(nsock_pool nsp, const char *device); #define NSOCK_SSL_MAX_SPEED (1 << 0) nsock_ssl_ctx nsock_pool_ssl_init(nsock_pool ms_pool, int flags); +/* Initializes an Nsock pool to create a DTLS connect. This sets and internal + * SSL_CTX, which is like a template that sets options for all connections that + * are made from it. Returns the SSL_CTX so tyou can set your own options. + * + * Functionally similar to nsock_pool_ssl_init, just for the DTLS */ +nsock_ssl_ctx nsock_pool_dtls_init(nsock_pool ms_pool, int flags); + /* Enforce use of a given IO engine. * The engine parameter is a zero-terminated string that will be * strup()'ed by the library. No validity check is performed by this function, diff --git a/nsock/include/nsock_config.h.in b/nsock/include/nsock_config.h.in index 76615849f..8d21eba4a 100644 --- a/nsock/include/nsock_config.h.in +++ b/nsock/include/nsock_config.h.in @@ -82,6 +82,8 @@ #undef HAVE_OPENSSL #undef HAVE_SSL_SET_TLSEXT_HOST_NAME +#undef HAVE_DTLS_CLIENT_METHOD +#undef HAVE_ALPN_SUPPORT #undef HAVE_EPOLL #undef HAVE_POLL diff --git a/nsock/src/configure b/nsock/src/configure index c2ccc3dc7..4763c4b3d 100755 --- a/nsock/src/configure +++ b/nsock/src/configure @@ -664,6 +664,7 @@ infodir docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -738,6 +739,7 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -990,6 +992,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1127,7 +1138,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1280,6 +1291,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -4908,6 +4920,52 @@ if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; }; $as_echo "#define HAVE_SSL_SET_TLSEXT_HOST_NAME 1" >>confdefs.h +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for DTLS_client_method" >&5 +$as_echo_n "checking for DTLS_client_method... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +DTLS_client_method() + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; }; $as_echo "#define HAVE_DTLS_CLIENT_METHOD 1" >>confdefs.h + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_set_alpn_protos" >&5 +$as_echo_n "checking for SSL_set_alpn_protos... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +SSL_set_alpn_protos(NULL, NULL, 0) + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; }; $as_echo "#define HAVE_ALPN_SUPPORT 1" >>confdefs.h + else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } diff --git a/nsock/src/configure.ac b/nsock/src/configure.ac index c446f79b3..a170d00d7 100644 --- a/nsock/src/configure.ac +++ b/nsock/src/configure.ac @@ -263,6 +263,14 @@ if test "$use_openssl" = "yes"; then AC_TRY_LINK([#include ], [SSL_set_tlsext_host_name(NULL, NULL)], [AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_SSL_SET_TLSEXT_HOST_NAME)], [AC_MSG_RESULT([no])]) + AC_MSG_CHECKING([for DTLS_client_method]) + AC_TRY_LINK([#include ], [DTLS_client_method()], + [AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_DTLS_CLIENT_METHOD)], + [AC_MSG_RESULT([no])]) + AC_MSG_CHECKING([for SSL_set_alpn_protos]) + AC_TRY_LINK([#include ], [SSL_set_alpn_protos(NULL, NULL, 0)], + [AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_ALPN_SUPPORT)], + [AC_MSG_RESULT([no])]) LIBS="$LIBS_TMP" fi diff --git a/nsock/src/nsock_connect.c b/nsock/src/nsock_connect.c index f5a1a59af..b6a9c2089 100644 --- a/nsock/src/nsock_connect.c +++ b/nsock/src/nsock_connect.c @@ -184,8 +184,8 @@ int nsock_setup_udp(nsock_pool nsp, nsock_iod ms_iod, int af) { return nsi->sd; } -/* This does the actual logistics of requesting a TCP connection. It is shared - * by nsock_connect_tcp and nsock_connect_ssl */ +/* This does the actual logistics of requesting a connection. It is shared + * by nsock_connect_tcp and nsock_connect_ssl, among others */ void nsock_connect_internal(struct npool *ms, struct nevent *nse, int type, int proto, struct sockaddr_storage *ss, size_t sslen, unsigned short port) { @@ -256,7 +256,7 @@ void nsock_connect_internal(struct npool *ms, struct nevent *nse, int type, int if (ms->engine->io_operations->iod_connect(ms, iod->sd, (struct sockaddr *)ss, sslen) == -1) { int err = socket_errno(); - if (proto == IPPROTO_UDP || (err != EINPROGRESS && err != EAGAIN)) { + if ((proto == IPPROTO_UDP) || (err != EINPROGRESS && err != EAGAIN)) { nse->event_done = 1; nse->status = NSE_STATUS_ERROR; nse->errnum = err; @@ -376,7 +376,7 @@ nsock_event_id nsock_connect_sctp(nsock_pool nsp, nsock_iod ms_iod, nsock_ev_han return nse->id; } -/* Request an SSL over TCP/SCTP connection to another system (by IP address). +/* Request an SSL over TCP/SCTP/UDP connection to another system (by IP address). * The in_addr is normal network byte order, but the port number should be given * in HOST BYTE ORDER. This function will call back only after it has made the * connection AND done the initial SSL negotiation. From that point on, you use @@ -397,7 +397,12 @@ nsock_event_id nsock_connect_ssl(nsock_pool nsp, nsock_iod nsiod, nsock_ev_handl struct nevent *nse; if (!ms->sslctx) - nsock_pool_ssl_init(ms, 0); + { + if (proto == IPPROTO_UDP) + nsock_pool_dtls_init(ms, 0); + else + nsock_pool_ssl_init(ms, 0); + } assert(nsi->state == NSIOD_STATE_INITIAL || nsi->state == NSIOD_STATE_UNKNOWN); @@ -405,14 +410,22 @@ nsock_event_id nsock_connect_ssl(nsock_pool nsp, nsock_iod nsiod, nsock_ev_handl assert(nse); /* Set our SSL_SESSION so we can benefit from session-id reuse. */ - nsi_set_ssl_session(nsi, (SSL_SESSION *)ssl_session); + /* but not with DTLS; save space in ClientHello message */ + if (proto != IPPROTO_UDP) + nsi_set_ssl_session(nsi, (SSL_SESSION *)ssl_session); - nsock_log_info("SSL connection requested to %s:%hu/%s (IOD #%li) EID %li", + if (proto == IPPROTO_UDP) + nsock_log_info("DTLS connection requested to %s:%hu/udp (IOD #%li) EID %li", + + inet_ntop_ez(ss, sslen), port, nsi->id, nse->id); + else + nsock_log_info("SSL connection requested to %s:%hu/%s (IOD #%li) EID %li", inet_ntop_ez(ss, sslen), port, (proto == IPPROTO_TCP ? "tcp" : "sctp"), nsi->id, nse->id); /* Do the actual connect() */ - nsock_connect_internal(ms, nse, SOCK_STREAM, proto, ss, sslen, port); + nsock_connect_internal(ms, nse, (proto == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM), proto, ss, sslen, port); + nsock_pool_add_event(ms, nse); return nse->id; diff --git a/nsock/src/nsock_core.c b/nsock/src/nsock_core.c index e4faf8a80..23b9c9f94 100644 --- a/nsock/src/nsock_core.c +++ b/nsock/src/nsock_core.c @@ -377,7 +377,9 @@ void handle_connect_result(struct npool *ms, struct nevent *nse, enum nse_status } #if HAVE_SSL_SET_TLSEXT_HOST_NAME - if (iod->hostname != NULL) { + /* Avoid sending SNI extension with DTLS because many servers don't allow + * fragmented ClientHello messages. */ + if (iod->hostname != NULL && iod->lastproto != IPPROTO_UDP) { if (SSL_set_tlsext_host_name(iod->ssl, iod->hostname) != 1) fatal("SSL_set_tlsext_host_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); } diff --git a/nsock/src/nsock_ssl.c b/nsock/src/nsock_ssl.c index ba7b2dd77..7683afff4 100644 --- a/nsock/src/nsock_ssl.c +++ b/nsock/src/nsock_ssl.c @@ -80,8 +80,7 @@ extern struct timeval nsock_tod; -/* Create an SSL_CTX and do initialization that is common to all init modes. */ -static SSL_CTX *ssl_init_common() { +static SSL_CTX *ssl_init_helper(const SSL_METHOD *method) { SSL_CTX *ctx; #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined LIBRESSL_VERSION_NUMBER @@ -89,7 +88,7 @@ static SSL_CTX *ssl_init_common() { SSL_library_init(); #endif - ctx = SSL_CTX_new(SSLv23_client_method()); + ctx = SSL_CTX_new(method); if (!ctx) { fatal("OpenSSL failed to create a new SSL_CTX: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -105,18 +104,19 @@ static SSL_CTX *ssl_init_common() { return ctx; } +/* Create an SSL_CTX and do initialization that is common to all init modes. */ +static SSL_CTX *ssl_init_common() { + return ssl_init_helper(SSLv23_client_method()); +} + /* Initializes an Nsock pool to create SSL connections. This sets an internal * SSL_CTX, which is like a template that sets options for all connections that * are made from it. The connections made from this context will use only secure * ciphers but no server certificate verification is done. Returns the SSL_CTX * so you can set your own options. */ -nsock_ssl_ctx nsock_pool_ssl_init(nsock_pool ms_pool, int flags) { - struct npool *ms = (struct npool *)ms_pool; +static nsock_ssl_ctx nsock_pool_ssl_init_helper(struct npool *ms, int flags) { char rndbuf[128]; - if (ms->sslctx == NULL) - ms->sslctx = ssl_init_common(); - /* Get_random_bytes may or may not provide high-quality randomness. Add it to * the entropy pool without increasing the entropy estimate (third argument of * RAND_add is 0). We rely on OpenSSL's entropy gathering, called implicitly @@ -146,6 +146,49 @@ nsock_ssl_ctx nsock_pool_ssl_init(nsock_pool ms_pool, int flags) { return ms->sslctx; } +nsock_ssl_ctx nsock_pool_ssl_init(nsock_pool ms_pool, int flags) { + struct npool *ms = (struct npool *)ms_pool; + + if (ms->sslctx == NULL) + ms->sslctx = ssl_init_common(); + return nsock_pool_ssl_init_helper(ms, flags); +} + +#ifdef HAVE_DTLS_CLIENT_METHOD + +/* Create an SSL_CTX and do initialisation, creating a DTLS client */ +static SSL_CTX *dtls_init_common() { + return ssl_init_helper(DTLS_client_method()); +} + +/* Initializes an Nsock pool to create DTLS connections. Very much similar to + * nsock_pool_ssl_init, just with DTLS. */ +nsock_ssl_ctx nsock_pool_dtls_init(nsock_pool ms_pool, int flags) { + SSL_CTX *dtls_ctx = NULL; + struct npool *ms = (struct npool *)ms_pool; + + if (ms->sslctx == NULL) + ms->sslctx = dtls_init_common(); + dtls_ctx = (SSL_CTX *) nsock_pool_ssl_init_helper(ms, flags); + + /* Don't add padding or the ClientHello will fragment and not connect properly. */ + SSL_CTX_clear_options(dtls_ctx, SSL_OP_TLSEXT_PADDING); + + if (!SSL_CTX_set_cipher_list(dtls_ctx, "DEFAULT")) + fatal("Unable to set OpenSSL cipher list: %s", + ERR_error_string(ERR_get_error(), NULL)); + + return dtls_ctx; +} + +#else /* OpenSSL Version does not support DTLS */ + +nsock_ssl_ctx nsock_pool_dtls_init(nsock_pool ms_pool, int flags) { + fatal("%s called with no OpenSSL DTLS support", __func__); +} + +#endif + /* Check server certificate verification, after a connection is established. We * check first that a certificate was even offered, then call * SSL_get_verify_result to get the overall status of verification. (Just @@ -180,6 +223,10 @@ nsock_ssl_ctx nsock_pool_ssl_init(nsock_pool ms_pool, int flags) { fatal("%s called with no OpenSSL support", __func__); } +nsock_ssl_ctx nsock_pool_dtls_init(nsock_pool ms_pool, int flags) { + fatal("%s called with no OpenSSL support", __func__); +} + int nsi_ssl_post_connect_verify(const nsock_iod nsockiod) { return 1; }