diff options
-rw-r--r-- | ChangeLog | 17 | ||||
-rw-r--r-- | ext/openssl/ossl_ssl.c | 149 | ||||
-rw-r--r-- | test/openssl/test_ssl.rb | 44 |
3 files changed, 173 insertions, 37 deletions
@@ -1,3 +1,20 @@ +Sat May 26 09:54:53 2012 Martin Bosslet <Martin.Bosslet@googlemail.com> + + * ext/openssl/ossl_ssl.c: Allow disabling client-side renegotiation. + * test/openssl/test_ssl.rb: Simple tests for this. + + Client-side renegotiation is still considered problematic, even + when used in the context of secure renegotiation (RI, RFC 5746). + The changes allow users to either completely disable client + renegotiation on the server, or to specify a maximum number of + handshakes allowed in total. The number of total handshakes is + counted in a callback set as SSL_set_info_callback. If the + maximum number of handshakes is exceeded an error will be raised + We do not support renegotiation in the OpenSSL extension, therefore + this feature can only be tested externally. + The feature is opt-in, the default setting will be to allow + unlimited client renegotiation, as was the case before. + Fri May 25 23:38:58 2012 Martin Bosslet <Martin.Bosslet@googlemail.com> * test/openssl/test_ssl.rb: Clarify the intention of errors to be diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index d375b0ff67..0eaa3c8f4b 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -29,41 +29,43 @@ VALUE eSSLError; VALUE cSSLContext; VALUE cSSLSocket; -#define ossl_sslctx_set_cert(o,v) rb_iv_set((o),"@cert",(v)) -#define ossl_sslctx_set_key(o,v) rb_iv_set((o),"@key",(v)) -#define ossl_sslctx_set_client_ca(o,v) rb_iv_set((o),"@client_ca",(v)) -#define ossl_sslctx_set_ca_file(o,v) rb_iv_set((o),"@ca_file",(v)) -#define ossl_sslctx_set_ca_path(o,v) rb_iv_set((o),"@ca_path",(v)) -#define ossl_sslctx_set_timeout(o,v) rb_iv_set((o),"@timeout",(v)) -#define ossl_sslctx_set_verify_mode(o,v) rb_iv_set((o),"@verify_mode",(v)) -#define ossl_sslctx_set_verify_dep(o,v) rb_iv_set((o),"@verify_depth",(v)) -#define ossl_sslctx_set_verify_cb(o,v) rb_iv_set((o),"@verify_callback",(v)) -#define ossl_sslctx_set_options(o,v) rb_iv_set((o),"@options",(v)) -#define ossl_sslctx_set_cert_store(o,v) rb_iv_set((o),"@cert_store",(v)) -#define ossl_sslctx_set_extra_cert(o,v) rb_iv_set((o),"@extra_chain_cert",(v)) -#define ossl_sslctx_set_client_cert_cb(o,v) rb_iv_set((o),"@client_cert_cb",(v)) -#define ossl_sslctx_set_tmp_dh_cb(o,v) rb_iv_set((o),"@tmp_dh_callback",(v)) -#define ossl_sslctx_set_sess_id_ctx(o, v) rb_iv_get((o),"@session_id_context"(v)) - -#define ossl_sslctx_get_cert(o) rb_iv_get((o),"@cert") -#define ossl_sslctx_get_key(o) rb_iv_get((o),"@key") -#define ossl_sslctx_get_client_ca(o) rb_iv_get((o),"@client_ca") -#define ossl_sslctx_get_ca_file(o) rb_iv_get((o),"@ca_file") -#define ossl_sslctx_get_ca_path(o) rb_iv_get((o),"@ca_path") -#define ossl_sslctx_get_timeout(o) rb_iv_get((o),"@timeout") -#define ossl_sslctx_get_verify_mode(o) rb_iv_get((o),"@verify_mode") -#define ossl_sslctx_get_verify_dep(o) rb_iv_get((o),"@verify_depth") -#define ossl_sslctx_get_verify_cb(o) rb_iv_get((o),"@verify_callback") -#define ossl_sslctx_get_options(o) rb_iv_get((o),"@options") -#define ossl_sslctx_get_cert_store(o) rb_iv_get((o),"@cert_store") -#define ossl_sslctx_get_extra_cert(o) rb_iv_get((o),"@extra_chain_cert") -#define ossl_sslctx_get_client_cert_cb(o) rb_iv_get((o),"@client_cert_cb") -#define ossl_sslctx_get_tmp_dh_cb(o) rb_iv_get((o),"@tmp_dh_callback") -#define ossl_sslctx_get_sess_id_ctx(o) rb_iv_get((o),"@session_id_context") +#define ossl_sslctx_set_cert(o,v) rb_iv_set((o),"@cert",(v)) +#define ossl_sslctx_set_key(o,v) rb_iv_set((o),"@key",(v)) +#define ossl_sslctx_set_client_ca(o,v) rb_iv_set((o),"@client_ca",(v)) +#define ossl_sslctx_set_ca_file(o,v) rb_iv_set((o),"@ca_file",(v)) +#define ossl_sslctx_set_ca_path(o,v) rb_iv_set((o),"@ca_path",(v)) +#define ossl_sslctx_set_timeout(o,v) rb_iv_set((o),"@timeout",(v)) +#define ossl_sslctx_set_verify_mode(o,v) rb_iv_set((o),"@verify_mode",(v)) +#define ossl_sslctx_set_verify_dep(o,v) rb_iv_set((o),"@verify_depth",(v)) +#define ossl_sslctx_set_verify_cb(o,v) rb_iv_set((o),"@verify_callback",(v)) +#define ossl_sslctx_set_options(o,v) rb_iv_set((o),"@options",(v)) +#define ossl_sslctx_set_cert_store(o,v) rb_iv_set((o),"@cert_store",(v)) +#define ossl_sslctx_set_extra_cert(o,v) rb_iv_set((o),"@extra_chain_cert",(v)) +#define ossl_sslctx_set_client_cert_cb(o,v) rb_iv_set((o),"@client_cert_cb",(v)) +#define ossl_sslctx_set_tmp_dh_cb(o,v) rb_iv_set((o),"@tmp_dh_callback",(v)) +#define ossl_sslctx_set_sess_id_ctx(o, v) rb_iv_set((o),"@session_id_context",(v)) +#define ossl_sslctx_set_max_handshake(o, v) rb_iv_set((o),"@max_handshake",(v)) + +#define ossl_sslctx_get_cert(o) rb_iv_get((o),"@cert") +#define ossl_sslctx_get_key(o) rb_iv_get((o),"@key") +#define ossl_sslctx_get_client_ca(o) rb_iv_get((o),"@client_ca") +#define ossl_sslctx_get_ca_file(o) rb_iv_get((o),"@ca_file") +#define ossl_sslctx_get_ca_path(o) rb_iv_get((o),"@ca_path") +#define ossl_sslctx_get_timeout(o) rb_iv_get((o),"@timeout") +#define ossl_sslctx_get_verify_mode(o) rb_iv_get((o),"@verify_mode") +#define ossl_sslctx_get_verify_dep(o) rb_iv_get((o),"@verify_depth") +#define ossl_sslctx_get_verify_cb(o) rb_iv_get((o),"@verify_callback") +#define ossl_sslctx_get_options(o) rb_iv_get((o),"@options") +#define ossl_sslctx_get_cert_store(o) rb_iv_get((o),"@cert_store") +#define ossl_sslctx_get_extra_cert(o) rb_iv_get((o),"@extra_chain_cert") +#define ossl_sslctx_get_client_cert_cb(o) rb_iv_get((o),"@client_cert_cb") +#define ossl_sslctx_get_tmp_dh_cb(o) rb_iv_get((o),"@tmp_dh_callback") +#define ossl_sslctx_get_sess_id_ctx(o) rb_iv_get((o),"@session_id_context") +#define ossl_sslctx_get_max_handshake(o) rb_iv_get((o),"@max_handshake") static const char *ossl_sslctx_attrs[] = { "cert", "key", "client_ca", "ca_file", "ca_path", - "timeout", "verify_mode", "verify_depth", + "timeout", "verify_mode", "verify_depth", "max_handshake", "verify_callback", "options", "cert_store", "extra_chain_cert", "client_cert_cb", "tmp_dh_callback", "session_id_context", "session_get_cb", "session_new_cb", "session_remove_cb", @@ -78,6 +80,7 @@ static const char *ossl_sslctx_attrs[] = { #define ossl_ssl_get_x509(o) rb_iv_get((o),"@x509") #define ossl_ssl_get_key(o) rb_iv_get((o),"@key") #define ossl_ssl_get_tmp_dh(o) rb_iv_get((o),"@tmp_dh") +#define ossl_ssl_get_handshake(o) rb_iv_get((o),"@handshake") #define ossl_ssl_set_io(o,v) rb_iv_set((o),"@io",(v)) #define ossl_ssl_set_ctx(o,v) rb_iv_set((o),"@context",(v)) @@ -85,6 +88,7 @@ static const char *ossl_sslctx_attrs[] = { #define ossl_ssl_set_x509(o,v) rb_iv_set((o),"@x509",(v)) #define ossl_ssl_set_key(o,v) rb_iv_set((o),"@key",(v)) #define ossl_ssl_set_tmp_dh(o,v) rb_iv_set((o),"@tmp_dh",(v)) +#define ossl_ssl_set_handshake(o,v) rb_iv_set((o),"@handshake",(v)) static const char *ossl_ssl_attr_readers[] = { "io", "context", }; static const char *ossl_ssl_attrs[] = { @@ -542,6 +546,33 @@ ssl_servername_cb(SSL *ssl, int *ad, void *arg) } #endif +static int +ssl_get_max_handshake(VALUE ssl) +{ + VALUE rb_ctx = ossl_ssl_get_ctx(ssl); + VALUE max_handshake = ossl_sslctx_get_max_handshake(rb_ctx); + return NIL_P(max_handshake) ? -1 : NUM2INT(max_handshake); +} + +static void +ssl_renegotiation_cb(const SSL *ssl, int where, int val) +{ + int state = SSL_state(ssl); + + /* Count handshakes on the server */ + if ((where & SSL_CB_HANDSHAKE_START) && + (state & SSL_ST_ACCEPT)) { + VALUE rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + int max = ssl_get_max_handshake(rb_ssl); + int cur = NUM2INT(ossl_ssl_get_handshake(rb_ssl)); + + if (max != -1 && cur == max) + ossl_raise(eSSLError, "Client renegotations exceeded maximum"); + + ossl_ssl_set_handshake(rb_ssl, INT2NUM(cur + 1)); + } +} + /* * call-seq: * ctx.setup => Qtrue # first time @@ -792,7 +823,6 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v) return v; } - /* * call-seq: * ctx.session_add(session) -> true | false @@ -980,6 +1010,51 @@ ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self) } /* + * call-seq: + * ctx.disable_client_renegotiation -> self + * + * Completely disables client-side renegotiation. Will only affect the + * behavior of a server. A server with client renegotation disabled + * will reject any client-side attempts to renegotiate the session. + */ +static VALUE +ossl_sslctx_disable_client_renegotation(VALUE self) +{ + ossl_sslctx_set_max_handshake(self, INT2NUM(1)); + return self; +} + +/* + * call-seq: + * ctx.allow_client_renegotiation[(num_handshakes)] -> self + * + * Affects only server connections. If no argument is provided, there is no + * restriction on the number of client-side renegotiation attempts, which is + * also the default setting. If an Integer +num_handshakes+ is provided, this + * specifies the maximum number of total handshakes that are allowed before + * further attempts will be rejected. So to allow exactly one renegotiation, + * an argument of 2 would be needed (the initial handshake plus one + * renegotiation attempt). An ArgumentError will be raised for negative + * arguments or a value of 0. + */ +static VALUE +ossl_sslctx_allow_client_renegotiation(int argc, VALUE *argv, VALUE self) +{ + VALUE max = Qnil; + + rb_scan_args(argc, argv, "01", &max); + + if (NIL_P(max)) { + ossl_sslctx_set_max_handshake(self, INT2NUM(-1)); + } else { + if (NUM2INT(max) <= 0) + ossl_raise(rb_eArgError, "Maximum handshakes must be positive and non-zero"); + ossl_sslctx_set_max_handshake(self, max); + } + return self; +} + +/* * SSLSocket class */ static void @@ -998,8 +1073,8 @@ ossl_ssl_shutdown(SSL *ssl) if (rc = SSL_shutdown(ssl)) break; } - ERR_clear_error(); SSL_clear(ssl); + ERR_clear_error(); } } @@ -1045,6 +1120,7 @@ ossl_ssl_initialize(int argc, VALUE *argv, VALUE self) ossl_ssl_set_io(self, io); ossl_ssl_set_ctx(self, ctx); ossl_ssl_set_sync_close(self, Qfalse); + ossl_ssl_set_handshake(self, INT2NUM(0)); ossl_sslctx_setup(ctx); rb_iv_set(self, "@hostname", Qnil); @@ -1095,6 +1171,7 @@ ossl_ssl_setup(VALUE self) SSL_set_ex_data(ssl, ossl_ssl_ex_client_cert_cb_idx, (void*)cb); cb = ossl_sslctx_get_tmp_dh_cb(v_ctx); SSL_set_ex_data(ssl, ossl_ssl_ex_tmp_dh_callback_idx, (void*)cb); + SSL_set_info_callback(ssl, ssl_renegotiation_cb); } return Qtrue; @@ -1305,7 +1382,6 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) return str; } - /* * call-seq: * ssl.sysread(length) => string @@ -1415,6 +1491,7 @@ ossl_ssl_close(VALUE self) { SSL *ssl; + ossl_ssl_set_handshake(self, INT2NUM(0)); Data_Get_Struct(self, SSL, ssl); ossl_ssl_shutdown(ssl); if (RTEST(ossl_ssl_get_sync_close(self))) @@ -1894,6 +1971,8 @@ Init_ossl_ssl() rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1); rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); + rb_define_method(cSSLContext, "disable_client_renegotiation", ossl_sslctx_disable_client_renegotation, 0); + rb_define_method(cSSLContext, "allow_client_renegotiation", ossl_sslctx_allow_client_renegotiation, -1); rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0); diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index de4bd34c5f..97b2c22472 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -505,14 +505,54 @@ if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_2 end + def test_disable_client_renegotiation + ctx_proc = Proc.new { |ctx| ctx.disable_client_renegotiation } + start_server_version(:SSLv23, ctx_proc) { |server, port| + server_connect(port) { |ssl| + assert(ssl.ssl_version) + } + } + end + + def test_allow_client_renegotiation_args + ctx = OpenSSL::SSL::SSLContext.new + assert_raise(ArgumentError) { ctx.allow_client_renegotiation(0) } + assert_raise(ArgumentError) { ctx.allow_client_renegotiation(-1) } + end + + def test_allow_client_renegotiation_once + ctx_proc = Proc.new { |ctx| ctx.allow_client_renegotiation(2) } + start_server_version(:SSLv23, ctx_proc) { |server, port| + server_connect(port) { |ssl| + assert(ssl.ssl_version) + } + } + end + + def test_allow_arbitrary_client_renegotiation + ctx_proc = Proc.new { |ctx| ctx.allow_client_renegotiation } + start_server_version(:SSLv23, ctx_proc) { |server, port| + server_connect(port) { |ssl| + assert(ssl.ssl_version) + } + } + end + private - def start_server_version(version, ctx_proc=nil, &blk) + def start_server_version(version, ctx_proc=nil, server_proc=nil, &blk) ctx_wrap = Proc.new { |ctx| ctx.ssl_version = version ctx_proc.call(ctx) if ctx_proc } - start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_wrap, &blk) + start_server( + PORT, + OpenSSL::SSL::VERIFY_NONE, + true, + :ctx_proc => ctx_wrap, + :server_proc => server_proc, + &blk + ) end def server_connect(port, ctx=nil) |