From d4f56fa5e1286e2dbde3e1c65172812c95ebe33b Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 16 Nov 2017 17:43:05 -0500 Subject: [PATCH 01/17] mark purpose of this branch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2b60b0e2b..138d0c04b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the OpenSSL library. +[DTLS] support is being worked on. + ## Installation The openssl gem is available at [rubygems.org](https://rubygems.org/gems/openssl). From 92d177218b2c773fb3480de3bfb96ba536e24dc5 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 17 Nov 2017 10:47:09 -0500 Subject: [PATCH 02/17] introduce macro to do version tests with version numbers easier to understand --- ext/openssl/ossl.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 5a15839cb..1b1cd11e5 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -41,6 +41,15 @@ #include #include +#ifndef OPENSSL_VERSION_AT_LEAST +/* this will show up in a future version of opensslv.h */ + +#define OPENSSL_MAKE_VERSION(maj,min,fix,patch,status) (((maj&0xf) << 28)+((min&0xff)<<20)+((fix&0xff)<<12)+((patch&0xff)<<4)+status) +/* use this for #if tests, should never depend upon patch/status */ +#define OPENSSL_VERSION_AT_LEAST(maj,min,fix) (OPENSSL_MAKE_VERSION(maj,min,fix, 0, 0) >= OPENSSL_VERSION_NUMBER) + +#endif + /* * Common Module */ @@ -168,6 +177,7 @@ void ossl_debug(const char *, ...); #include "ossl_pkey.h" #include "ossl_rand.h" #include "ossl_ssl.h" +#include "ossl_dtls.h" #include "ossl_version.h" #include "ossl_x509.h" #include "ossl_engine.h" From 44985ecd52212b9c8115863784e35b2f7e3b7c14 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Sat, 18 Nov 2017 18:53:34 -0500 Subject: [PATCH 03/17] the DTLSSocket class must initialize the BIO socket with the dgram methods --- ext/openssl/ossl.c | 1 + ext/openssl/ossl_dtls.c | 251 ++++++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_dtls.h | 18 +++ ext/openssl/ossl_ssl.c | 19 +-- ext/openssl/ossl_ssl.h | 18 +++ lib/openssl/dtls.rb | 121 +++++++++++++++++++ 6 files changed, 412 insertions(+), 16 deletions(-) create mode 100644 ext/openssl/ossl_dtls.c create mode 100644 ext/openssl/ossl_dtls.h create mode 100644 lib/openssl/dtls.rb diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 245385e7d..b48b1ef34 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1204,6 +1204,7 @@ Init_openssl(void) Init_ossl_pkey(); Init_ossl_rand(); Init_ossl_ssl(); + Init_ossl_dtls(); /* must be after _ssl */ Init_ossl_x509(); Init_ossl_ocsp(); Init_ossl_engine(); diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c new file mode 100644 index 000000000..89ead22d6 --- /dev/null +++ b/ext/openssl/ossl_dtls.c @@ -0,0 +1,251 @@ +/* + * 'OpenSSL for Ruby' project + * clone from ossl_ssl.c by Michael Richardson + * Copyright (C) 2017 Michael Richardson + * All rights reserved. + */ +/* + * This program is licensed under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +VALUE cDTLSContext; +VALUE cDTLSSocket; +static VALUE eSSLError; +extern VALUE cSSLContext; +static int ossl_dtlsctx_ex_ptr_idx; /* suspect this should be shared with ssl*/ + +extern const rb_data_type_t ossl_sslctx_type; + +static VALUE +ossl_dtlsctx_s_alloc(VALUE klass) +{ + SSL_CTX *ctx; + long mode = 0 | + SSL_MODE_ENABLE_PARTIAL_WRITE | + SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | + SSL_MODE_RELEASE_BUFFERS; + VALUE obj; + + obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0); + ctx = SSL_CTX_new(DTLS_method()); + if (!ctx) { + ossl_raise(eSSLError, "DTLS_CTX_new"); + } + SSL_CTX_set_mode(ctx, mode); + RTYPEDDATA_DATA(obj) = ctx; + SSL_CTX_set_ex_data(ctx, ossl_dtlsctx_ex_ptr_idx, (void *)obj); + +#if !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_ECDH_AUTO) + /* We use SSL_CTX_set1_curves_list() to specify the curve used in ECDH. It + * allows to specify multiple curve names and OpenSSL will select + * automatically from them. In OpenSSL 1.0.2, the automatic selection has to + * be enabled explicitly. But OpenSSL 1.1.0 removed the knob and it is + * always enabled. To uniform the behavior, we enable the automatic + * selection also in 1.0.2. Users can still disable ECDH by removing ECDH + * cipher suites by SSLContext#ciphers=. */ + if (!SSL_CTX_set_ecdh_auto(ctx, 1)) + ossl_raise(eSSLError, "DTLS_CTX_set_ecdh_auto"); +#endif + + return obj; +} + +#ifndef OPENSSL_NO_SOCK +static VALUE +ossl_dtls_setup(VALUE self) +{ + VALUE io; + SSL *ssl; + rb_io_t *fptr; + BIO *bio = NULL; + + GetSSL(self, ssl); + if (ssl_started(ssl)) + return Qtrue; + + io = rb_attr_get(self, id_i_io); + GetOpenFile(io, fptr); + rb_io_check_readable(fptr); + rb_io_check_writable(fptr); + + printf("dtls setup for fd: %d\n", TO_SOCKET(fptr->fd)); + bio = BIO_new_dgram(TO_SOCKET(fptr->fd), BIO_NOCLOSE); + if(bio == NULL) { + ossl_raise(eSSLError, "ossl_dtls_setup"); + } + SSL_set_bio(ssl, bio, bio); + + return Qtrue; +} + +/* + * call-seq: + * ssl.accept => self + * + * Waits for a SSL/TLS client to initiate a handshake. The handshake may be + * started after unencrypted data has been sent over the socket. + */ +static VALUE +ossl_dtls_accept(VALUE self) +{ + ossl_dtls_setup(self); + + return ossl_start_ssl(self, SSL_accept, "SSL_accept", Qfalse); +} + +#if 0 +/* + * call-seq: + * SSLSocket.new(io) => aSSLSocket + * SSLSocket.new(io, ctx) => aSSLSocket + * + * Creates a new SSL socket from _io_ which must be a real IO object (not an + * IO-like object that responds to read/write). + * + * If _ctx_ is provided the SSL Sockets initial params will be taken from + * the context. + * + * The OpenSSL::Buffering module provides additional IO methods. + * + * This method will freeze the SSLContext if one is provided; + * however, session management is still allowed in the frozen SSLContext. + */ +static VALUE +ossl_dtls_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE io, v_ctx, verify_cb; + SSL *ssl; + SSL_CTX *ctx; + + TypedData_Get_Struct(self, SSL, &ossl_ssl_type, ssl); + if (ssl) + ossl_raise(eSSLError, "SSL already initialized"); + + if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1) + v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); + + GetSSLCTX(v_ctx, ctx); + rb_ivar_set(self, id_i_context, v_ctx); + ossl_sslctx_setup(v_ctx); + + if (rb_respond_to(io, rb_intern("nonblock="))) + rb_funcall(io, rb_intern("nonblock="), 1, Qtrue); + rb_ivar_set(self, id_i_io, io); + + ssl = SSL_new(ctx); + if (!ssl) + ossl_raise(eSSLError, NULL); + RTYPEDDATA_DATA(self) = ssl; + + SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self); + SSL_set_info_callback(ssl, ssl_info_cb); + verify_cb = rb_attr_get(v_ctx, id_i_verify_callback); + SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)verify_cb); + + rb_call_super(0, NULL); + + return self; +} + +/* + * call-seq: + * ssl.connect_nonblock([options]) => self + * + * Initiates the SSL/TLS handshake as a client in non-blocking manner. + * + * # emulates blocking connect + * begin + * ssl.connect_nonblock + * rescue IO::WaitReadable + * IO.select([s2]) + * retry + * rescue IO::WaitWritable + * IO.select(nil, [s2]) + * retry + * end + * + * By specifying a keyword argument _exception_ to +false+, you can indicate + * that connect_nonblock should not raise an IO::WaitReadable or + * IO::WaitWritable exception, but return the symbol +:wait_readable+ or + * +:wait_writable+ instead. + */ +static VALUE +ossl_dtls_connect_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE opts; + rb_scan_args(argc, argv, "0:", &opts); + + ossl_dtls_setup(self); + + return ossl_start_ssl(self, SSL_connect, "SSL_connect", opts); +} + +/* + * call-seq: + * ssl.accept_nonblock([options]) => self + * + * Initiates the SSL/TLS handshake as a server in non-blocking manner. + * + * # emulates blocking accept + * begin + * ssl.accept_nonblock + * rescue IO::WaitReadable + * IO.select([s2]) + * retry + * rescue IO::WaitWritable + * IO.select(nil, [s2]) + * retry + * end + * + * By specifying a keyword argument _exception_ to +false+, you can indicate + * that accept_nonblock should not raise an IO::WaitReadable or + * IO::WaitWritable exception, but return the symbol +:wait_readable+ or + * +:wait_writable+ instead. + */ +static VALUE +ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE opts; + + rb_scan_args(argc, argv, "0:", &opts); + ossl_dtls_setup(self); + + return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts); +} + +#endif /* 0 */ +#endif /* !defined(OPENSSL_NO_SOCK) */ + +#undef rb_intern +#define rb_intern(s) rb_intern_const(s) +void +Init_ossl_dtls(void) +{ + /* Document-module: OpenSSL::SSL + * + * Use SSLContext to set up the parameters for a TLS (former SSL) + * connection. Both client and server TLS connections are supported, + * SSLSocket and SSLServer may be used in conjunction with an instance + * of SSLContext to set up connections. + */ + mSSL = rb_define_module_under(mOSSL, "SSL"); + eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError); + + /* Document-class: OpenSSL::SSL::DTLSContext + * + * A DTLSContext is used to set various options regarding certificates, + * algorithms, verification, session caching, etc. The DTLSContext is + * used to create a DTLSSocket. + * + * All attributes must be set before creating a DTLSSocket as the + * DTLSContext will be frozen afterward. + */ + cDTLSContext = rb_define_class_under(mSSL, "DTLSContext", cSSLContext); + rb_define_alloc_func(cDTLSContext, ossl_dtlsctx_s_alloc); + rb_undef_method(cDTLSContext, "initialize_copy"); + + cDTLSSocket = rb_define_class_under(mSSL, "DTLSSocket", cSSLSocket); + rb_define_method(cDTLSSocket, "accept", ossl_dtls_accept, 0); +} diff --git a/ext/openssl/ossl_dtls.h b/ext/openssl/ossl_dtls.h new file mode 100644 index 000000000..85514f92a --- /dev/null +++ b/ext/openssl/ossl_dtls.h @@ -0,0 +1,18 @@ +/* + * 'OpenSSL for Ruby' project + * Copyright (C) 2017 Michael Richardson + * All rights reserved. + */ +/* + * This program is licensed under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_DTLS_H_) +#define _OSSL_DTLS_H_ + +extern VALUE cDTLSSocket; +extern VALUE cDTLSSession; + +void Init_ossl_dtls(void); + +#endif /* _OSSL_SSL_H_ */ diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index d32a299cb..272473a57 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -13,12 +13,6 @@ #define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) -#ifdef _WIN32 -# define TO_SOCKET(s) _get_osfhandle(s) -#else -# define TO_SOCKET(s) (s) -#endif - #define GetSSLCTX(obj, ctx) do { \ TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx)); \ } while (0) @@ -44,7 +38,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols, id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb, id_i_verify_hostname; -static ID id_i_io, id_i_context, id_i_hostname; +ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_vcb_idx; static int ossl_ssl_ex_ptr_idx; @@ -64,7 +58,7 @@ ossl_sslctx_free(void *ptr) SSL_CTX_free(ctx); } -static const rb_data_type_t ossl_sslctx_type = { +const rb_data_type_t ossl_sslctx_type = { "OpenSSL/SSL/CTX", { 0, ossl_sslctx_free, @@ -1403,13 +1397,6 @@ ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self) * SSLSocket class */ #ifndef OPENSSL_NO_SOCK -static inline int -ssl_started(SSL *ssl) -{ - /* the FD is set in ossl_ssl_setup(), called by #connect or #accept */ - return SSL_get_fd(ssl) >= 0; -} - static void ossl_ssl_free(void *ssl) { @@ -1533,7 +1520,7 @@ no_exception_p(VALUE opts) return 0; } -static VALUE +VALUE ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts) { SSL *ssl; diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h index 535c56097..77b96a2f4 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -30,6 +30,24 @@ extern VALUE mSSL; extern VALUE cSSLSocket; extern VALUE cSSLSession; +#ifdef _WIN32 +# define TO_SOCKET(s) _get_osfhandle(s) +#else +# define TO_SOCKET(s) (s) +#endif + +static inline int +ssl_started(SSL *ssl) +{ + /* the FD is set in ossl_ssl_setup(), called by #connect or #accept */ + return SSL_get_fd(ssl) >= 0; +} + +extern ID id_i_io, id_i_context, id_i_hostname; + +extern VALUE ossl_start_ssl(VALUE self, int (*func)(), + const char *funcname, VALUE opts); + void Init_ossl_ssl(void); void Init_ossl_ssl_session(void); diff --git a/lib/openssl/dtls.rb b/lib/openssl/dtls.rb new file mode 100644 index 000000000..7f91327a5 --- /dev/null +++ b/lib/openssl/dtls.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: false +=begin += Info + 'OpenSSL for Ruby 2' project + Copyright (C) 2001 GOTOU YUUZOU + All rights reserved. + += Licence + This program is licensed under the same licence as Ruby. + (See the file 'LICENCE'.) +=end + +require "openssl/buffering" +require "io/nonblock" + +module OpenSSL + module SSL + class DTLSContext < SSLContext + DEFAULT_PARAMS = { # :nodoc: + :min_version => OpenSSL::SSL::TLS12_VERSION, + :verify_mode => OpenSSL::SSL::VERIFY_PEER, + :verify_hostname => true, + :options => -> { + opts = OpenSSL::SSL::OP_ALL + opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS + opts |= OpenSSL::SSL::OP_NO_COMPRESSION + opts + }.call + } + + # call-seq: + # SSLContext.new -> ctx + # SSLContext.new(:TLSv1) -> ctx + # SSLContext.new("SSLv23") -> ctx + # + # Creates a new SSL context. + # + # If an argument is given, #ssl_version= is called with the value. Note + # that this form is deprecated. New applications should use #min_version= + # and #max_version= as necessary. + def initialize(version = nil) + super(version) + # other stuff? + end + end + + class DTLSSocket < SSLSocket + # parent does: + # attr_reader :hostname + + # The underlying IO object. + #attr_reader :io + #alias :to_io :io + + # call-seq: + # ssl.session -> aSession + # + # Returns the SSLSession object currently used, or nil if the session is + # not established. + def session + SSL::Session.new(self) + rescue SSL::Session::SessionError + nil + end + + private + end + + ## + # DTLSServer represents a TCP/IP server socket with Datagram TLS (DTLS) + # + class DTLSServer < SSLServer + # Creates a new instance of SSLServer. + # * _srv_ is an instance of TCPServer. + # * _ctx_ is an instance of OpenSSL::SSL::SSLContext. + def initialize(svr, ctx) + super(svr, ctx) + @start_immediately = true # not sure. + end + + # See TCPServer#listen for details. + def listen(backlog=5) + # UDP sockets have no backlog configuration. + # do nothing + true + end + + # See BasicSocket#shutdown for details. + def shutdown(how=Socket::SHUT_RDWR) + # UDP sockets do not have shutdown semantics, but TLS does + @svr.shutdown(how) + end + + # Works similar to TCPServer#accept. + def accept + # Socket#accept returns [socket, addrinfo]. + # TCPServer#accept returns a socket. + # The following comma strips addrinfo. + sock, = @svr.accept + begin + ssl = OpenSSL::SSL::DTLSSocket.new(sock, @ctx) + ssl.sync_close = true + ssl.accept if @start_immediately + ssl + rescue Exception => ex + if ssl + ssl.close + else + sock.close + end + raise ex + end + end + + # See IO#close for details. + def close + @svr.close + end + end + end +end From deb37d74c26df757d210a5a3ec0443f152ca092e Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 4 Dec 2017 08:21:24 -0500 Subject: [PATCH 04/17] dtls_accept needs to clone new socket and set up new SSL object --- ext/openssl/ossl_dtls.c | 184 ++++++++++++++++++++++++++++++++-------- ext/openssl/ossl_ssl.c | 4 +- ext/openssl/ossl_ssl.h | 3 + lib/openssl/dtls.rb | 13 +-- 4 files changed, 161 insertions(+), 43 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 89ead22d6..417a8bdd7 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -10,6 +10,10 @@ */ #include "ossl.h" +#include +#include +#include + VALUE cDTLSContext; VALUE cDTLSSocket; static VALUE eSSLError; @@ -70,7 +74,7 @@ ossl_dtls_setup(VALUE self) rb_io_check_readable(fptr); rb_io_check_writable(fptr); - printf("dtls setup for fd: %d\n", TO_SOCKET(fptr->fd)); + //printf("dtls setup for fd: %d\n", TO_SOCKET(fptr->fd)); bio = BIO_new_dgram(TO_SOCKET(fptr->fd), BIO_NOCLOSE); if(bio == NULL) { ossl_raise(eSSLError, "ossl_dtls_setup"); @@ -84,15 +88,153 @@ ossl_dtls_setup(VALUE self) * call-seq: * ssl.accept => self * - * Waits for a SSL/TLS client to initiate a handshake. The handshake may be - * started after unencrypted data has been sent over the socket. + * Looks at the incoming (bind(), but not connect()) socket for new incoming + * DTLS connections, and return a new SSL context for the resulting connection. */ static VALUE ossl_dtls_accept(VALUE self) { + SSL *ssl; + SSL *sslnew; + BIO_ADDR *peer; + int oldsock; + int new_sock; + VALUE dtls_child; + + /* make sure it's all setup */ + ossl_dtls_setup(self); + + GetSSL(self, ssl); + + /* allocate a new BIO_ADDR */ + peer = BIO_ADDR_new(); + + if(DTLSv1_listen(ssl, peer) == -1) { + return self; + } + + { + char *peername= BIO_ADDR_hostname_string(peer, 1); + if(peername) { + printf("peername: %s\n", peername); + OPENSSL_free(peername); + } + } + + /* now create a new socket of the same type */ + { + int socket_type = SOCK_DGRAM; + int family = BIO_ADDR_family(peer); + int protocol = 0; /* UDP has nothing here */ + unsigned char *addrbuf, *sockname; + size_t addrlen; + + /* find out size of addrbuf needed */ + if(BIO_ADDR_rawaddress(peer, NULL, &addrlen) == 0) { + perror("rawaddress size bad"); + goto error; + } + addrbuf = alloca(addrlen); + sockname= alloca(addrlen); /* allocate space for sockname */ + if(!addrbuf) { + goto error; + } + + if(BIO_ADDR_rawaddress(peer, addrbuf, &addrlen)==0) { + perror("rawaddress size bad"); + goto error; + } + + { + /* get the local address from the original socket */ + VALUE io; + rb_io_t *fptr; + + io = rb_attr_get(self, id_i_io); + GetOpenFile(io, fptr); + + oldsock = TO_SOCKET(fptr->fd); + if(getsockname(oldsock, (struct sockaddr *)sockname, (socklen_t *)&addrlen) != 0) { + perror("bad getsockname"); + goto error; + } + } + + /* + * got the address of peer, so set up new socket. First connect(2) + * the socket, and then bind(2) it, so that socket is unique. + */ + new_sock = socket(family, socket_type, protocol); + if(connect(new_sock, (struct sockaddr *)sockname, addrlen) != 0) { + perror("bad connect"); + goto error; + } + if(bind(new_sock, (struct sockaddr *)addrbuf, addrlen) != 0) { + perror("dtls_accept"); + goto error; + } + + } + + /* new_sock is now setup, need to allocate new SSL context and insert socket into new bio */ + sslnew = SSL_new(SSL_get_SSL_CTX(ssl)); + SSL_set_fd(sslnew, new_sock); + + /* create a new ruby object */ + dtls_child = TypedData_Wrap_Struct(cSSLSocket, &ossl_ssl_type, NULL); + + /* connect them up. */ + if (!sslnew) + ossl_raise(eSSLError, NULL); + RTYPEDDATA_DATA(self) = sslnew; + + SSL_set_ex_data(sslnew, ossl_ssl_ex_ptr_idx, (void *)dtls_child); + SSL_set_info_callback(sslnew, ssl_info_cb); + + if(peer) BIO_ADDR_free(peer); + peer = NULL; + + /* start the DTLS on it */ + return ossl_start_ssl(dtls_child, SSL_accept, "SSL_accept", Qfalse); + + error: + if(peer) BIO_ADDR_free(peer); + peer = NULL; + + return Qnil; +} + +/* + * call-seq: + * ssl.accept_nonblock([options]) => self + * + * Initiates the SSL/TLS handshake as a server in non-blocking manner. + * + * # emulates blocking accept + * begin + * ssl.accept_nonblock + * rescue IO::WaitReadable + * IO.select([s2]) + * retry + * rescue IO::WaitWritable + * IO.select(nil, [s2]) + * retry + * end + * + * By specifying a keyword argument _exception_ to +false+, you can indicate + * that accept_nonblock should not raise an IO::WaitReadable or + * IO::WaitWritable exception, but return the symbol +:wait_readable+ or + * +:wait_writable+ instead. + */ +static VALUE +ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE opts; + + rb_scan_args(argc, argv, "0:", &opts); ossl_dtls_setup(self); - return ossl_start_ssl(self, SSL_accept, "SSL_accept", Qfalse); + return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts); } #if 0 @@ -182,38 +324,6 @@ ossl_dtls_connect_nonblock(int argc, VALUE *argv, VALUE self) return ossl_start_ssl(self, SSL_connect, "SSL_connect", opts); } -/* - * call-seq: - * ssl.accept_nonblock([options]) => self - * - * Initiates the SSL/TLS handshake as a server in non-blocking manner. - * - * # emulates blocking accept - * begin - * ssl.accept_nonblock - * rescue IO::WaitReadable - * IO.select([s2]) - * retry - * rescue IO::WaitWritable - * IO.select(nil, [s2]) - * retry - * end - * - * By specifying a keyword argument _exception_ to +false+, you can indicate - * that accept_nonblock should not raise an IO::WaitReadable or - * IO::WaitWritable exception, but return the symbol +:wait_readable+ or - * +:wait_writable+ instead. - */ -static VALUE -ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) -{ - VALUE opts; - - rb_scan_args(argc, argv, "0:", &opts); - ossl_dtls_setup(self); - - return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts); -} #endif /* 0 */ #endif /* !defined(OPENSSL_NO_SOCK) */ @@ -248,4 +358,6 @@ Init_ossl_dtls(void) cDTLSSocket = rb_define_class_under(mSSL, "DTLSSocket", cSSLSocket); rb_define_method(cDTLSSocket, "accept", ossl_dtls_accept, 0); + rb_define_method(cDTLSSocket, "accept_nonblock", ossl_dtls_accept_nonblock, -1); + //printf("\n\nsetting cDTLSSocket.accept to %p\n", ossl_dtls_accept); } diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 272473a57..ee851e0c4 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -41,7 +41,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_vcb_idx; -static int ossl_ssl_ex_ptr_idx; +int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; #if !defined(HAVE_X509_STORE_UP_REF) static int ossl_sslctx_ex_store_p; @@ -709,7 +709,7 @@ ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, #endif /* This function may serve as the entry point to support further callbacks. */ -static void +void ssl_info_cb(const SSL *ssl, int where, int val) { int is_server = SSL_is_server((SSL *)ssl); diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h index 77b96a2f4..51c51f592 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -29,6 +29,9 @@ extern const rb_data_type_t ossl_ssl_session_type; extern VALUE mSSL; extern VALUE cSSLSocket; extern VALUE cSSLSession; +extern int ossl_ssl_ex_ptr_idx; +extern void ssl_info_cb(const SSL *ssl, int where, int val); + #ifdef _WIN32 # define TO_SOCKET(s) _get_osfhandle(s) diff --git a/lib/openssl/dtls.rb b/lib/openssl/dtls.rb index 7f91327a5..d13bb3297 100644 --- a/lib/openssl/dtls.rb +++ b/lib/openssl/dtls.rb @@ -29,11 +29,14 @@ class DTLSContext < SSLContext } # call-seq: - # SSLContext.new -> ctx - # SSLContext.new(:TLSv1) -> ctx - # SSLContext.new("SSLv23") -> ctx + # DTLSContext.new -> ctx + # DTLSContext.new(:TLSv1) -> ctx + # DTLSContext.new("SSLv23") -> ctx # - # Creates a new SSL context. + # Creates a new DTLS context. + # This differs from an SSL context because the DTLS_method() is setup. + # This arranges to do the right UDP things which involve recvfrom()/sendto() rather than + # read/write() down at the BIO layer. # # If an argument is given, #ssl_version= is called with the value. Note # that this form is deprecated. New applications should use #min_version= @@ -68,7 +71,7 @@ def session ## # DTLSServer represents a TCP/IP server socket with Datagram TLS (DTLS) - # + # XXX, unclear this is even useful. class DTLSServer < SSLServer # Creates a new instance of SSLServer. # * _srv_ is an instance of TCPServer. From 8e39f70547594de55d442982f7eecf045d5c3997 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 5 Dec 2017 12:01:18 -0500 Subject: [PATCH 05/17] attempt to deal with having accept call DTLSv1_listen and return a new SSL object --- ext/openssl/ossl_dtls.c | 36 +++++++++++++++++++++++++++++++++--- ext/openssl/ossl_ssl.c | 6 +++--- ext/openssl/ossl_ssl.h | 4 ++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 417a8bdd7..cfd277c45 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -92,27 +92,51 @@ ossl_dtls_setup(VALUE self) * DTLS connections, and return a new SSL context for the resulting connection. */ static VALUE -ossl_dtls_accept(VALUE self) +ossl_dtls_start_accept(VALUE self, VALUE opts) { + int nonblock = opts != Qfalse; SSL *ssl; SSL *sslnew; BIO_ADDR *peer; int oldsock; int new_sock; + rb_io_t *fptr; VALUE dtls_child; + int ret; /* make sure it's all setup */ ossl_dtls_setup(self); GetSSL(self, ssl); + GetOpenFile(rb_attr_get(self, id_i_io), fptr); /* allocate a new BIO_ADDR */ peer = BIO_ADDR_new(); - if(DTLSv1_listen(ssl, peer) == -1) { + ret = -1; + while(ret != 0) { + ret = DTLSv1_listen(ssl, peer); + + if(ret == 0) { + if (no_exception_p(opts)) { return sym_wait_readable; } + read_would_block(nonblock); + rb_io_wait_readable(fptr->fd); + } + } + + if(ret == -1) { + /* this is an error */ + ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", "DTLSv1_listen", ret, errno, SSL_state_string_long(ssl)); return self; } + if(ret != 1) { + /* this is no data present, would block */ + printf("DTLSv1_listen returned: %d\n", ret); + return Qnil; + } + + /* a return of 1 means that a connection is present */ { char *peername= BIO_ADDR_hostname_string(peer, 1); if(peername) { @@ -204,6 +228,12 @@ ossl_dtls_accept(VALUE self) return Qnil; } +static VALUE +ossl_dtls_accept(VALUE self) +{ + return ossl_dtls_start_accept(self, Qfalse); +} + /* * call-seq: * ssl.accept_nonblock([options]) => self @@ -234,7 +264,7 @@ ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) rb_scan_args(argc, argv, "0:", &opts); ossl_dtls_setup(self); - return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts); + return ossl_dtls_start_accept(self, opts); } #if 0 diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index ee851e0c4..8da814a8d 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -28,7 +28,7 @@ static VALUE eSSLErrorWaitWritable; static ID id_call, ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback, id_npn_protocols_encoded; -static VALUE sym_exception, sym_wait_readable, sym_wait_writable; +VALUE sym_exception, sym_wait_readable, sym_wait_writable; static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_verify_depth, id_i_verify_callback, id_i_client_ca, @@ -1504,14 +1504,14 @@ write_would_block(int nonblock) ossl_raise(eSSLErrorWaitWritable, "write would block"); } -static void +void read_would_block(int nonblock) { if (nonblock) ossl_raise(eSSLErrorWaitReadable, "read would block"); } -static int +int no_exception_p(VALUE opts) { if (RB_TYPE_P(opts, T_HASH) && diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h index 51c51f592..6811f0543 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -51,6 +51,10 @@ extern ID id_i_io, id_i_context, id_i_hostname; extern VALUE ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts); +extern int no_exception_p(VALUE opts); +extern void read_would_block(int nonblock); +extern VALUE sym_exception, sym_wait_readable, sym_wait_writable; + void Init_ossl_ssl(void); void Init_ossl_ssl_session(void); From 74c69c21754b7122fb2cdbf94324b05480ed0331 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 10 Jan 2018 10:03:35 -0500 Subject: [PATCH 06/17] updated dtls_start_accept to use new DTLSv1_accept() routine --- ext/openssl/extconf.rb | 4 + ext/openssl/ossl_dtls.c | 169 +++++++++++++--------------------------- 2 files changed, 59 insertions(+), 114 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 5212903b9..c63a8b998 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -149,6 +149,10 @@ def find_openssl_library have_func("X509_STORE_up_ref") have_func("SSL_SESSION_up_ref") have_func("EVP_PKEY_up_ref") + +# added after 1.1.0 +have_func("DTLSv1_accept") + OpenSSL.check_func_or_macro("SSL_CTX_set_tmp_ecdh_callback", "openssl/ssl.h") # removed OpenSSL.check_func_or_macro("SSL_CTX_set_min_proto_version", "openssl/ssl.h") have_func("SSL_CTX_get_security_level") diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index cfd277c45..37889cdec 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -57,6 +57,7 @@ ossl_dtlsctx_s_alloc(VALUE klass) } #ifndef OPENSSL_NO_SOCK +#if HAVE_DTLSV1_ACCEPT static VALUE ossl_dtls_setup(VALUE self) { @@ -95,137 +96,74 @@ static VALUE ossl_dtls_start_accept(VALUE self, VALUE opts) { int nonblock = opts != Qfalse; - SSL *ssl; - SSL *sslnew; - BIO_ADDR *peer; - int oldsock; - int new_sock; - rb_io_t *fptr; - VALUE dtls_child; - int ret; - - /* make sure it's all setup */ - ossl_dtls_setup(self); - - GetSSL(self, ssl); - GetOpenFile(rb_attr_get(self, id_i_io), fptr); - - /* allocate a new BIO_ADDR */ - peer = BIO_ADDR_new(); - - ret = -1; - while(ret != 0) { - ret = DTLSv1_listen(ssl, peer); - - if(ret == 0) { - if (no_exception_p(opts)) { return sym_wait_readable; } - read_would_block(nonblock); - rb_io_wait_readable(fptr->fd); - } - } + SSL *ssl; + SSL *sslnew; + BIO_ADDR *peer; + rb_io_t *fptr; + VALUE dtls_child; + int ret; - if(ret == -1) { - /* this is an error */ - ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", "DTLSv1_listen", ret, errno, SSL_state_string_long(ssl)); - return self; - } - - if(ret != 1) { - /* this is no data present, would block */ - printf("DTLSv1_listen returned: %d\n", ret); - return Qnil; - } - - /* a return of 1 means that a connection is present */ - { - char *peername= BIO_ADDR_hostname_string(peer, 1); - if(peername) { - printf("peername: %s\n", peername); - OPENSSL_free(peername); - } - } - - /* now create a new socket of the same type */ - { - int socket_type = SOCK_DGRAM; - int family = BIO_ADDR_family(peer); - int protocol = 0; /* UDP has nothing here */ - unsigned char *addrbuf, *sockname; - size_t addrlen; - - /* find out size of addrbuf needed */ - if(BIO_ADDR_rawaddress(peer, NULL, &addrlen) == 0) { - perror("rawaddress size bad"); - goto error; - } - addrbuf = alloca(addrlen); - sockname= alloca(addrlen); /* allocate space for sockname */ - if(!addrbuf) { - goto error; - } + /* make sure it's all setup */ + ossl_dtls_setup(self); - if(BIO_ADDR_rawaddress(peer, addrbuf, &addrlen)==0) { - perror("rawaddress size bad"); - goto error; - } + GetSSL(self, ssl); + GetOpenFile(rb_attr_get(self, id_i_io), fptr); - { - /* get the local address from the original socket */ - VALUE io; - rb_io_t *fptr; + /* allocate a new BIO_ADDR */ + sslnew = SSL_new(SSL_get_SSL_CTX(ssl)); - io = rb_attr_get(self, id_i_io); - GetOpenFile(io, fptr); + peer = BIO_ADDR_new(); - oldsock = TO_SOCKET(fptr->fd); - if(getsockname(oldsock, (struct sockaddr *)sockname, (socklen_t *)&addrlen) != 0) { - perror("bad getsockname"); - goto error; + while(ret != 0) { + ret = DTLSv1_accept(ssl, sslnew, peer); + + if(ret == 0) { + if (no_exception_p(opts)) { return sym_wait_readable; } + read_would_block(nonblock); + rb_io_wait_readable(fptr->fd); } } - /* - * got the address of peer, so set up new socket. First connect(2) - * the socket, and then bind(2) it, so that socket is unique. - */ - new_sock = socket(family, socket_type, protocol); - if(connect(new_sock, (struct sockaddr *)sockname, addrlen) != 0) { - perror("bad connect"); - goto error; - } - if(bind(new_sock, (struct sockaddr *)addrbuf, addrlen) != 0) { - perror("dtls_accept"); - goto error; + if(ret == -1) { + /* this is an error */ + ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", "DTLSv1_listen", ret, errno, SSL_state_string_long(ssl)); + return self; } - } - - /* new_sock is now setup, need to allocate new SSL context and insert socket into new bio */ - sslnew = SSL_new(SSL_get_SSL_CTX(ssl)); - SSL_set_fd(sslnew, new_sock); + if(ret != 1) { + /* this is no data present, would block */ + printf("DTLSv1_listen returned: %d\n", ret); + return Qnil; + } - /* create a new ruby object */ - dtls_child = TypedData_Wrap_Struct(cSSLSocket, &ossl_ssl_type, NULL); + /* a return of 1 means that a connection is present */ + { + char *peername= BIO_ADDR_hostname_string(peer, 1); + if(peername) { + printf("peername: %s\n", peername); + OPENSSL_free(peername); + } + } - /* connect them up. */ - if (!sslnew) - ossl_raise(eSSLError, NULL); - RTYPEDDATA_DATA(self) = sslnew; + /* sslnew contains an initialized SSL, which has a new socket connected to it */ - SSL_set_ex_data(sslnew, ossl_ssl_ex_ptr_idx, (void *)dtls_child); - SSL_set_info_callback(sslnew, ssl_info_cb); + /* new_sock is now setup, need to allocate new SSL context and insert socket into new bio */ + /* create a new ruby object */ + dtls_child = TypedData_Wrap_Struct(cSSLSocket, &ossl_ssl_type, NULL); - if(peer) BIO_ADDR_free(peer); - peer = NULL; + /* connect them up. */ + if (!sslnew) + ossl_raise(eSSLError, NULL); + RTYPEDDATA_DATA(self) = sslnew; - /* start the DTLS on it */ - return ossl_start_ssl(dtls_child, SSL_accept, "SSL_accept", Qfalse); + SSL_set_ex_data(sslnew, ossl_ssl_ex_ptr_idx, (void *)dtls_child); + SSL_set_info_callback(sslnew, ssl_info_cb); - error: - if(peer) BIO_ADDR_free(peer); - peer = NULL; + if(peer) BIO_ADDR_free(peer); + peer = NULL; - return Qnil; + /* start the DTLS on it */ + return ossl_start_ssl(dtls_child, SSL_accept, "SSL_accept", Qfalse); } static VALUE @@ -266,6 +204,7 @@ ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) return ossl_dtls_start_accept(self, opts); } +#endif #if 0 /* @@ -387,7 +326,9 @@ Init_ossl_dtls(void) rb_undef_method(cDTLSContext, "initialize_copy"); cDTLSSocket = rb_define_class_under(mSSL, "DTLSSocket", cSSLSocket); +#ifdef HAVE_DTLSV1_ACCEPT rb_define_method(cDTLSSocket, "accept", ossl_dtls_accept, 0); rb_define_method(cDTLSSocket, "accept_nonblock", ossl_dtls_accept_nonblock, -1); +#endif //printf("\n\nsetting cDTLSSocket.accept to %p\n", ossl_dtls_accept); } From 99739f676f5c01b4ca71edd838530e5e8e19fa19 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 10 Jan 2018 10:51:22 -0500 Subject: [PATCH 07/17] bump version and point at correct version of openssl --- openssl.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openssl.gemspec b/openssl.gemspec index 7440e928f..53a54a324 100644 --- a/openssl.gemspec +++ b/openssl.gemspec @@ -1,11 +1,11 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "2.1.0.beta1" - spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] - spec.email = ["ruby-core@ruby-lang.org"] + spec.version = "2.2.0-mcr1" + spec.authors = ["Michael Richardson", "Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] + spec.email = ["mcr@sandelman.ca","ruby-core@ruby-lang.org"] spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} - spec.description = %q{It wraps the OpenSSL library.} - spec.homepage = "https://github.com/ruby/openssl" + spec.description = %q{It wraps the OpenSSL library. Note this version depends upon an as-yet-unreleased version of OpenSSL. Built it from https://github.com/mcr/openssl/tree/dtls-listen-refactor.} + spec.homepage = "https://github.com/mcr/ruby-openssl" spec.license = "Ruby" spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] From 7167019f23949c138515736462299bbbb78256c9 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 11 Jan 2018 12:30:13 -0500 Subject: [PATCH 08/17] added notes about building with DTLS support --- DTLS.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 DTLS.md diff --git a/DTLS.md b/DTLS.md new file mode 100644 index 000000000..0e9e2d7f7 --- /dev/null +++ b/DTLS.md @@ -0,0 +1,43 @@ + +In order to get DTLS to work, you need a patched copy of Openssl. +Get it here: + https://github.com/mcr/openssl/tree/dtls-listen-refactor + +build and install it. You might want to compile without DSO support, as that will +make it easier for the ruby-openssl module to link in the right code. To do +that you can do: + ./Configure no-shared --prefix=/sandel/3rd/openssl --debug linux-x86_64 + +(--debug being optional) + +The resulting openssl.so will be significantly bigger, btw: + %size tmp/x86_64-linux/openssl/2.4.1/openssl.so + text data bss dec hex filename + 3889567 261788 16856 4168211 3f9a13 tmp/x86_64-linux/openssl/2.4.1/openssl.so + + +Pick a --prefix which is not on your regular paths. Probably gem can be +persuaded to do all of this, but hopefully the code will upstreamed sooner +and the problem will go away. + +If DTLSv1_accept() is not available, then the DTLS support will not include +server side code, only client side code. No patches are necessary to make +client-side DTLS work. To be sure that the patch has been found is enabled +check for: + + checking for DTLSv1_accept()... yes + + +Then build with: + + rake compile -- --with-openssl-dir=/sandel/3rd/openssl + +I don't know how to add the extra arguments required to your Gemfile so that +it will be built properly during bundle processing. I'm sure that there is a way, +patches welcome. I do: + gem build openssl + gem install ./openssl-2.2.0.pre.mcr1.gem + +BTW: the pull request is at: + https://github.com/openssl/openssl/pull/5024 +and comments would be welcome. From 25fe42aa816670047bc06e2bbe64318d5a2daefb Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Sat, 20 Jan 2018 21:06:02 -0500 Subject: [PATCH 09/17] added the cookie_gen callback function --- ext/openssl/ossl_dtls.c | 99 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 37889cdec..f268baf9a 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include VALUE cDTLSContext; VALUE cDTLSSocket; @@ -22,6 +24,100 @@ static int ossl_dtlsctx_ex_ptr_idx; /* suspect this should be shared with ssl*/ extern const rb_data_type_t ossl_sslctx_type; +unsigned int cookie_secret_set = 0; +unsigned char cookie_secret[16]; + +/* + * generate a stateless cookie by creating a keyed HMAC-SHA256 for the cookie. + * 1) The key is randomly generated if not already set and is kept constant. + * 2) The contents of the hash come from: + * a) the current time in seconds since epoch with the low + * byte forced to zero, so new cookies occur every 2.5 minutes. + * b) the originating IP address and port number, taken from + * SSL in network byte order. + */ + +static void cookie_secret_setup(void) +{ + if(!cookie_secret_set) { + if(RAND_bytes(cookie_secret, sizeof(cookie_secret)) == 1) { + cookie_secret_set = 1; + } + } +} + +static int cookie_gen(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) +{ + unsigned int i; + unsigned char cookie1[EVP_MAX_MD_SIZE]; + unsigned int cookie1_len; + unsigned char things_to_crunch[256]; + int things_len = 0; + unsigned short peerport; + struct timeval tv; + BIO_ADDR *peer; + BIO *rbio; + int ret; + + cookie_secret_setup(); + gettimeofday(&tv, NULL); + + rbio = SSL_get_rbio(ssl); + peer = BIO_ADDR_new(); + if(rbio == NULL || BIO_dgram_get_peer(rbio, peer) <= 0) { + ret = 0; + goto err; + } + + /* 24 bits of time is enough */ + things_to_crunch[0] = (tv.tv_sec >> 24) & 0xff; + things_to_crunch[1] = (tv.tv_sec >> 16) & 0xff; + things_to_crunch[2] = (tv.tv_sec >> 8) & 0xff; + things_to_crunch[3] = 0; + + peerport = BIO_ADDR_rawport(peer); + things_to_crunch[4] = (peerport >> 8) & 0xff; + things_to_crunch[5] = (peerport >> 0) & 0xff; + things_len = 6; + memcpy(things_to_crunch + things_len, BIO_ADDR_sockaddr(peer), + BIO_ADDR_sockaddr_size(peer)); + things_len += BIO_ADDR_sockaddr_size(peer); + + HMAC(EVP_sha256(), + cookie_secret, sizeof(cookie_secret), + things_to_crunch, things_len, + cookie1, &cookie1_len); + + for (i = 0; i < *cookie_len && i Date: Sun, 21 Jan 2018 14:53:54 -0500 Subject: [PATCH 10/17] cookie validation code --- ext/openssl/ossl_dtls.c | 116 +++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 32 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index f268baf9a..f5724a52e 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -46,14 +46,38 @@ static void cookie_secret_setup(void) } } +static void cookie_calculate(unsigned char cookie[], + unsigned int *cookie_len, + const unsigned short peerport, + const unsigned char *addrdata, + const unsigned int addrlen, + const time_t curtime) +{ + unsigned char things_to_crunch[256]; + int things_len = 0; + + /* 24 bits of time is enough */ + things_to_crunch[0] = (curtime >> 24) & 0xff; + things_to_crunch[1] = (curtime >> 16) & 0xff; + things_to_crunch[2] = (curtime >> 8) & 0xff; + things_to_crunch[3] = 0; + things_to_crunch[4] = (peerport >> 8) & 0xff; + things_to_crunch[5] = (peerport >> 0) & 0xff; + things_len = 6; + memcpy(things_to_crunch + things_len, addrdata, addrlen); + things_len += addrlen; + + HMAC(EVP_sha256(), + cookie_secret, sizeof(cookie_secret), + things_to_crunch, things_len, + cookie, cookie_len); +} + static int cookie_gen(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) { unsigned int i; unsigned char cookie1[EVP_MAX_MD_SIZE]; unsigned int cookie1_len; - unsigned char things_to_crunch[256]; - int things_len = 0; - unsigned short peerport; struct timeval tv; BIO_ADDR *peer; BIO *rbio; @@ -69,24 +93,11 @@ static int cookie_gen(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) goto err; } - /* 24 bits of time is enough */ - things_to_crunch[0] = (tv.tv_sec >> 24) & 0xff; - things_to_crunch[1] = (tv.tv_sec >> 16) & 0xff; - things_to_crunch[2] = (tv.tv_sec >> 8) & 0xff; - things_to_crunch[3] = 0; - - peerport = BIO_ADDR_rawport(peer); - things_to_crunch[4] = (peerport >> 8) & 0xff; - things_to_crunch[5] = (peerport >> 0) & 0xff; - things_len = 6; - memcpy(things_to_crunch + things_len, BIO_ADDR_sockaddr(peer), - BIO_ADDR_sockaddr_size(peer)); - things_len += BIO_ADDR_sockaddr_size(peer); - - HMAC(EVP_sha256(), - cookie_secret, sizeof(cookie_secret), - things_to_crunch, things_len, - cookie1, &cookie1_len); + cookie1_len = sizeof(cookie1); + cookie_calculate(cookie1, &cookie1_len, BIO_ADDR_rawport(peer), + (const unsigned char *)BIO_ADDR_sockaddr(peer), + BIO_ADDR_sockaddr_size(peer), + tv.tv_sec); for (i = 0; i < *cookie_len && i Date: Sun, 21 Jan 2018 14:58:05 -0500 Subject: [PATCH 11/17] added debugging for cookie calculation and verification code --- ext/openssl/ossl_dtls.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index f5724a52e..9ed9cdc40 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -46,6 +46,23 @@ static void cookie_secret_setup(void) } } +#define DTLS_COOKIE_DEBUG 1 + +#ifdef DTLS_COOKIE_DEBUG +static void print_cookie(const char *label, const unsigned char cookie[], const unsigned int cookie_len) +{ + unsigned int i; + printf("%s cookie: ", label); + for(i=0; i Date: Tue, 23 Jan 2018 14:27:37 -0500 Subject: [PATCH 12/17] fixup! 65b5c867345a01ef8912180297f1baf49c3d567b --- ext/openssl/ossl_dtls.c | 45 ++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 9ed9cdc40..b7f68b39f 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -65,25 +65,49 @@ static void print_cookie(const char *label, const unsigned char cookie[], const static void cookie_calculate(unsigned char cookie[], unsigned int *cookie_len, - const unsigned short peerport, - const unsigned char *addrdata, - const unsigned int addrlen, + BIO_ADDR *peer, const time_t curtime) { unsigned char things_to_crunch[256]; int things_len = 0; + const struct sockaddr *peersock = BIO_ADDR_sockaddr(peer); + const unsigned char *addrdata; + unsigned int addrlen; + unsigned short peerport; + + switch(peersock->sa_family) { + case AF_INET: + addrdata = (unsigned char *)&((struct sockaddr_in *)peersock)->sin_addr; + addrlen = 4; + break; + + case AF_INET6: + addrdata = ((struct sockaddr_in6 *)peersock)->sin6_addr.s6_addr; + addrlen = 16; + break; + + default: + addrdata = (unsigned char *)""; + addrlen = 0; + } + + peerport = BIO_ADDR_rawport(peer); - /* 24 bits of time is enough */ + /* 24 bits of time is enough */ + PRINT_COOKIE("time", (unsigned char *)&curtime, 4); things_to_crunch[0] = (curtime >> 24) & 0xff; things_to_crunch[1] = (curtime >> 16) & 0xff; things_to_crunch[2] = (curtime >> 8) & 0xff; things_to_crunch[3] = 0; + PRINT_COOKIE("port", (unsigned char *)&peerport, 2); things_to_crunch[4] = (peerport >> 8) & 0xff; things_to_crunch[5] = (peerport >> 0) & 0xff; things_len = 6; + PRINT_COOKIE("addr", addrdata, addrlen); memcpy(things_to_crunch + things_len, addrdata, addrlen); things_len += addrlen; + PRINT_COOKIE("scrt", cookie_secret, sizeof(cookie_secret)); HMAC(EVP_sha256(), cookie_secret, sizeof(cookie_secret), things_to_crunch, things_len, @@ -113,9 +137,7 @@ static int cookie_gen(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) } cookie1_len = sizeof(cookie1); - cookie_calculate(cookie1, &cookie1_len, BIO_ADDR_rawport(peer), - (const unsigned char *)BIO_ADDR_sockaddr(peer), - BIO_ADDR_sockaddr_size(peer), + cookie_calculate(cookie1, &cookie1_len, peer, tv.tv_sec); for (i = 0; i Date: Mon, 22 Jan 2018 16:43:42 -0500 Subject: [PATCH 13/17] loop on DTLSv1_accept() until there is a connection, breaking if data would block --- ext/openssl/ossl_dtls.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index b7f68b39f..01625dd0f 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -161,7 +161,7 @@ static int cookie_verify(SSL *ssl, const unsigned char *peer_cookie, struct timeval tv; BIO_ADDR *peer; BIO *rbio; - int ret; + int ret = 0; PRINT_COOKIE("peer cookie", peer_cookie, peer_cookie_len); @@ -172,7 +172,7 @@ static int cookie_verify(SSL *ssl, const unsigned char *peer_cookie, peer = BIO_ADDR_new(); if(rbio == NULL || BIO_dgram_get_peer(rbio, peer) <= 0) { ret = 0; - goto err; + goto out; } cookie1_len = sizeof(cookie1); @@ -180,12 +180,13 @@ static int cookie_verify(SSL *ssl, const unsigned char *peer_cookie, if(cookie1_len != peer_cookie_len) { /* cookies lengths must match! */ - return 0; + goto out; } if(memcmp(cookie1, peer_cookie, cookie1_len) == 0) { /* matches exactly */ - return 1; + ret = 1; + goto out; } /* if clock&0xff < 128, then try previous period */ @@ -198,13 +199,11 @@ static int cookie_verify(SSL *ssl, const unsigned char *peer_cookie, if(memcmp(cookie1, peer_cookie, cookie1_len) == 0) { /* matches exactly */ - return 1; + ret = 1; } } - return 0; - - err: + out: if(peer) BIO_ADDR_free(peer); return ret; } @@ -282,6 +281,9 @@ ossl_dtls_setup(VALUE self) * * Looks at the incoming (bind(), but not connect()) socket for new incoming * DTLS connections, and return a new SSL context for the resulting connection. + * + * This uses an OpenSSL extension DTLSv1_accept(), which handles cloning the + * the file descriptor and creating a new SSL context. */ static VALUE ossl_dtls_start_accept(VALUE self, VALUE opts) @@ -305,7 +307,8 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) peer = BIO_ADDR_new(); - while(ret != 0) { + ret = 0; + while(ret == 0) { ret = DTLSv1_accept(ssl, sslnew, peer); if(ret == 0) { From 2a774835a282ceedc832f943f68182b75a2c8a17 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 23 Jan 2018 14:27:10 -0500 Subject: [PATCH 14/17] initialize the dtls_child/connection object better --- ext/openssl/ossl_dtls.c | 14 ++++++++++++-- ext/openssl/ossl_ssl.c | 9 ++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 01625dd0f..c604cfa78 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,12 @@ VALUE cDTLSSocket; static VALUE eSSLError; extern VALUE cSSLContext; static int ossl_dtlsctx_ex_ptr_idx; /* suspect this should be shared with ssl*/ +extern int ossl_ssl_ex_vcb_idx; extern const rb_data_type_t ossl_sslctx_type; +extern ID id_i_verify_callback; + unsigned int cookie_secret_set = 0; unsigned char cookie_secret[16]; @@ -295,6 +299,10 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) rb_io_t *fptr; VALUE dtls_child; int ret; + VALUE v_ctx, verify_cb; + + /* extract the Ruby wrapper for the context for later on */ + v_ctx = rb_ivar_get(self, id_i_context); /* make sure it's all setup */ ossl_dtls_setup(self); @@ -302,7 +310,7 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) GetSSL(self, ssl); GetOpenFile(rb_attr_get(self, id_i_io), fptr); - /* allocate a new BIO_ADDR */ + /* allocate a new SSL* for the connection */ sslnew = SSL_new(SSL_get_SSL_CTX(ssl)); peer = BIO_ADDR_new(); @@ -348,10 +356,12 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) /* connect them up. */ if (!sslnew) ossl_raise(eSSLError, NULL); - RTYPEDDATA_DATA(self) = sslnew; + RTYPEDDATA_DATA(dtls_child) = sslnew; SSL_set_ex_data(sslnew, ossl_ssl_ex_ptr_idx, (void *)dtls_child); SSL_set_info_callback(sslnew, ssl_info_cb); + verify_cb = rb_attr_get(v_ctx, id_i_verify_callback); + SSL_set_ex_data(sslnew, ossl_ssl_ex_vcb_idx, (void *)verify_cb); if(peer) BIO_ADDR_free(peer); peer = NULL; diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 8da814a8d..b901269d4 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -30,8 +30,11 @@ static ID id_call, ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback, id_npn_protocols_encoded; VALUE sym_exception, sym_wait_readable, sym_wait_writable; +/* used by dtls code */ +ID id_i_verify_callback; + static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, - id_i_verify_depth, id_i_verify_callback, id_i_client_ca, + id_i_verify_depth, id_i_client_ca, id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert, id_i_client_cert_cb, id_i_tmp_ecdh_callback, id_i_timeout, id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb, @@ -40,7 +43,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_verify_hostname; ID id_i_io, id_i_context, id_i_hostname; -static int ossl_ssl_ex_vcb_idx; +int ossl_ssl_ex_vcb_idx; int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; #if !defined(HAVE_X509_STORE_UP_REF) @@ -1411,7 +1414,7 @@ const rb_data_type_t ossl_ssl_type = { 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; -static VALUE +VALUE ossl_ssl_s_alloc(VALUE klass) { return TypedData_Wrap_Struct(klass, &ossl_ssl_type, NULL); From a44c3cd060fbbc94d2f2e788b890344ae2a39bb2 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 25 Jan 2018 17:02:15 -0500 Subject: [PATCH 15/17] pass io down to DTLSv1_accept so that ruby can create the object rather than underlying code --- ext/openssl/ossl_dtls.c | 45 +++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index c604cfa78..bf865812e 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -290,16 +290,17 @@ ossl_dtls_setup(VALUE self) * the file descriptor and creating a new SSL context. */ static VALUE -ossl_dtls_start_accept(VALUE self, VALUE opts) +ossl_dtls_start_accept(VALUE self, VALUE io, VALUE opts) { int nonblock = opts != Qfalse; SSL *ssl; SSL *sslnew; BIO_ADDR *peer; - rb_io_t *fptr; - VALUE dtls_child; + rb_io_t *fptr, *nsock; + VALUE dtls_child, ret_value = Qnil; int ret; VALUE v_ctx, verify_cb; + int one = 1; /* extract the Ruby wrapper for the context for later on */ v_ctx = rb_ivar_get(self, id_i_context); @@ -309,15 +310,23 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) GetSSL(self, ssl); GetOpenFile(rb_attr_get(self, id_i_io), fptr); + GetOpenFile(io, nsock); /* allocate a new SSL* for the connection */ sslnew = SSL_new(SSL_get_SSL_CTX(ssl)); peer = BIO_ADDR_new(); + if(setsockopt(fptr->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) { + goto end; + } + if(setsockopt(nsock->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) { + goto end; + } + ret = 0; while(ret == 0) { - ret = DTLSv1_accept(ssl, sslnew, peer); + ret = DTLSv1_accept(ssl, sslnew, peer, nsock->fd); if(ret == 0) { if (no_exception_p(opts)) { return sym_wait_readable; } @@ -358,22 +367,35 @@ ossl_dtls_start_accept(VALUE self, VALUE opts) ossl_raise(eSSLError, NULL); RTYPEDDATA_DATA(dtls_child) = sslnew; + /* setup a new IO object for the FD attached to the dtls_child */ + rb_ivar_set(dtls_child, id_i_io, io); + SSL_set_ex_data(sslnew, ossl_ssl_ex_ptr_idx, (void *)dtls_child); SSL_set_info_callback(sslnew, ssl_info_cb); verify_cb = rb_attr_get(v_ctx, id_i_verify_callback); SSL_set_ex_data(sslnew, ossl_ssl_ex_vcb_idx, (void *)verify_cb); + /* start the DTLS on it */ + ret_value = ossl_start_ssl(dtls_child, SSL_accept, "SSL_accept", Qfalse); + + printf("completed ossl_start_ssl\n"); + + end: if(peer) BIO_ADDR_free(peer); peer = NULL; - /* start the DTLS on it */ - return ossl_start_ssl(dtls_child, SSL_accept, "SSL_accept", Qfalse); + return ret_value; } static VALUE -ossl_dtls_accept(VALUE self) +ossl_dtls_accept(int argc, VALUE *argv, VALUE self) { - return ossl_dtls_start_accept(self, Qfalse); + VALUE io; + + rb_scan_args(argc, argv, "1", &io); + ossl_dtls_setup(self); + + return ossl_dtls_start_accept(self, io, Qfalse); } /* @@ -402,11 +424,12 @@ static VALUE ossl_dtls_accept_nonblock(int argc, VALUE *argv, VALUE self) { VALUE opts; + VALUE io; - rb_scan_args(argc, argv, "0:", &opts); + rb_scan_args(argc, argv, "1:", &io, &opts); ossl_dtls_setup(self); - return ossl_dtls_start_accept(self, opts); + return ossl_dtls_start_accept(self, io, opts); } #endif @@ -531,7 +554,7 @@ Init_ossl_dtls(void) cDTLSSocket = rb_define_class_under(mSSL, "DTLSSocket", cSSLSocket); #ifdef HAVE_DTLSV1_ACCEPT - rb_define_method(cDTLSSocket, "accept", ossl_dtls_accept, 0); + rb_define_method(cDTLSSocket, "accept", ossl_dtls_accept, -1); rb_define_method(cDTLSSocket, "accept_nonblock", ossl_dtls_accept_nonblock, -1); #endif //printf("\n\nsetting cDTLSSocket.accept to %p\n", ossl_dtls_accept); From 019a780bda3723c6eb961251fa78e6d5179ef8f2 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 25 Jan 2018 17:04:46 -0500 Subject: [PATCH 16/17] turn off DTLS_COOKIE_DEBUG --- ext/openssl/ossl_dtls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index bf865812e..1c9d0dd3d 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -50,9 +50,9 @@ static void cookie_secret_setup(void) } } -#define DTLS_COOKIE_DEBUG 1 +#define DTLS_COOKIE_DEBUG 0 -#ifdef DTLS_COOKIE_DEBUG +#if DTLS_COOKIE_DEBUG static void print_cookie(const char *label, const unsigned char cookie[], const unsigned int cookie_len) { unsigned int i; From d6d01de35fe827ac73215bd7966d5f4014c7cf0f Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 25 Jan 2018 17:05:00 -0500 Subject: [PATCH 17/17] turn off other DTLS debugging --- ext/openssl/ossl_dtls.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c index 1c9d0dd3d..91439da20 100644 --- a/ext/openssl/ossl_dtls.c +++ b/ext/openssl/ossl_dtls.c @@ -51,6 +51,7 @@ static void cookie_secret_setup(void) } #define DTLS_COOKIE_DEBUG 0 +#define DTLS_DEBUG 0 #if DTLS_COOKIE_DEBUG static void print_cookie(const char *label, const unsigned char cookie[], const unsigned int cookie_len) @@ -329,6 +330,7 @@ ossl_dtls_start_accept(VALUE self, VALUE io, VALUE opts) ret = DTLSv1_accept(ssl, sslnew, peer, nsock->fd); if(ret == 0) { + if(DTLS_DEBUG) printf("returned 0, waiting for more data\n"); if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); rb_io_wait_readable(fptr->fd); @@ -343,10 +345,11 @@ ossl_dtls_start_accept(VALUE self, VALUE io, VALUE opts) if(ret != 1) { /* this is no data present, would block */ - printf("DTLSv1_listen returned: %d\n", ret); + if(DTLS_DEBUG) printf("DTLSv1_accept returned: %d (needs data, block)\n", ret); return Qnil; } +#if DTLS_DEBUG /* a return of 1 means that a connection is present */ { char *peername= BIO_ADDR_hostname_string(peer, 1); @@ -355,6 +358,7 @@ ossl_dtls_start_accept(VALUE self, VALUE io, VALUE opts) OPENSSL_free(peername); } } +#endif /* sslnew contains an initialized SSL, which has a new socket connected to it */