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. 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). 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.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.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" diff --git a/ext/openssl/ossl_dtls.c b/ext/openssl/ossl_dtls.c new file mode 100644 index 000000000..91439da20 --- /dev/null +++ b/ext/openssl/ossl_dtls.c @@ -0,0 +1,565 @@ +/* + * '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" + +#include +#include +#include +#include +#include +#include + +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 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]; + +/* + * 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; + } + } +} + +#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) +{ + unsigned int i; + printf("%s cookie: ", label); + for(i=0; isa_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 */ + 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, + cookie, cookie_len); + + PRINT_COOKIE("calculated ", 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; + 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; + } + + cookie1_len = sizeof(cookie1); + cookie_calculate(cookie1, &cookie1_len, peer, + tv.tv_sec); + + for (i = 0; ifd)); + 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 + * + * 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 io, VALUE opts) +{ + int nonblock = opts != Qfalse; + SSL *ssl; + SSL *sslnew; + BIO_ADDR *peer; + 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); + + /* make sure it's all setup */ + ossl_dtls_setup(self); + + 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, 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); + } + } + + 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 */ + 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); + if(peername) { + printf("peername: %s\n", peername); + OPENSSL_free(peername); + } + } +#endif + + /* sslnew contains an initialized SSL, which has a new socket connected to it */ + + /* 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); + + /* connect them up. */ + if (!sslnew) + 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; + + return ret_value; +} + +static VALUE +ossl_dtls_accept(int argc, VALUE *argv, VALUE self) +{ + VALUE io; + + rb_scan_args(argc, argv, "1", &io); + ossl_dtls_setup(self); + + return ossl_dtls_start_accept(self, io, Qfalse); +} + +/* + * 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; + VALUE io; + + rb_scan_args(argc, argv, "1:", &io, &opts); + ossl_dtls_setup(self); + + return ossl_dtls_start_accept(self, io, opts); +} +#endif + +#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); +} + + +#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); +#ifdef HAVE_DTLSV1_ACCEPT + 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); +} 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..b901269d4 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) @@ -34,20 +28,23 @@ 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; + +/* 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, 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; +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) static int ossl_sslctx_ex_store_p; @@ -64,7 +61,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, @@ -715,7 +712,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); @@ -1403,13 +1400,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) { @@ -1424,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); @@ -1517,14 +1507,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) && @@ -1533,7 +1523,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..6811f0543 100644 --- a/ext/openssl/ossl_ssl.h +++ b/ext/openssl/ossl_ssl.h @@ -29,6 +29,31 @@ 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) +#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); + +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); diff --git a/lib/openssl/dtls.rb b/lib/openssl/dtls.rb new file mode 100644 index 000000000..d13bb3297 --- /dev/null +++ b/lib/openssl/dtls.rb @@ -0,0 +1,124 @@ +# 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: + # DTLSContext.new -> ctx + # DTLSContext.new(:TLSv1) -> ctx + # DTLSContext.new("SSLv23") -> ctx + # + # 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= + # 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) + # XXX, unclear this is even useful. + 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 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"]