diff options
-rw-r--r-- | ext/openssl/ossl_ssl.c | 124 | ||||
-rw-r--r-- | test/test_ssl.rb | 34 |
2 files changed, 60 insertions, 98 deletions
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 0eaa3c8..9233488 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -44,7 +44,6 @@ VALUE cSSLSocket; #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") @@ -61,11 +60,10 @@ VALUE cSSLSocket; #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", "max_handshake", + "timeout", "verify_mode", "verify_depth", "renegotiation_cb", "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", @@ -80,7 +78,6 @@ 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)) @@ -88,7 +85,6 @@ 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[] = { @@ -546,30 +542,34 @@ ssl_servername_cb(SSL *ssl, int *ad, void *arg) } #endif -static int -ssl_get_max_handshake(VALUE ssl) +static void +ssl_renegotiation_cb(const SSL *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); + VALUE ssl_obj, sslctx_obj, cb; + void *ptr; + + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + ossl_raise(eSSLError, "SSL object could not be retrieved"); + ssl_obj = (VALUE)ptr; + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return; + cb = rb_iv_get(sslctx_obj, "@renegotiation_cb"); + if (NIL_P(cb)) return; + + (void) rb_funcall(cb, rb_intern("call"), 1, ssl_obj); } +/* This function may serve as the entry point to support further + * callbacks. */ static void -ssl_renegotiation_cb(const SSL *ssl, int where, int val) +ssl_info_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)); + ssl_renegotiation_cb(ssl); } } @@ -1010,51 +1010,6 @@ 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 @@ -1120,7 +1075,6 @@ 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); @@ -1171,7 +1125,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); + SSL_set_info_callback(ssl, ssl_info_cb); } return Qtrue; @@ -1491,7 +1445,6 @@ 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))) @@ -1805,7 +1758,18 @@ Init_ossl_ssl() ossl_ssl_ex_tmp_dh_callback_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_tmp_dh_callback_idx",0,0,0); + /* 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"); + /* Document-class: OpenSSL::SSL::SSLError + * + * Generic error class raised by SSLSocket and SSLContext. + */ eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError); Init_ossl_ssl_session(); @@ -1964,6 +1928,28 @@ Init_ossl_ssl() */ rb_attr(cSSLContext, rb_intern("servername_cb"), 1, 1, Qfalse); #endif + /* + * A callback invoked whenever a new handshake is initiated. May be used + * to disable renegotiation entirely. + * + * The callback is invoked with the active SSLSocket. The callback's + * return value is irrelevant, normal return indicates "approval" of the + * renegotiation and will continue the process. To forbid renegotiation + * and to cancel the process, an Error may be raised within the callback. + * + * === Disable client renegotiation + * + * When running a server, it is often desirable to disable client + * renegotiation entirely. You may use a callback as follows to implement + * this feature: + * + * num_handshakes = 0 + * ctx.renegotiation_cb = lambda do |ssl| + * num_handshakes += 1 + * raise RuntimeError.new("Client renegotiation disabled") if num_handshakes > 1 + * end + */ + rb_attr(cSSLContext, rb_intern("renegotiation_cb"), 1, 1, Qfalse); rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); @@ -1971,8 +1957,6 @@ 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/test_ssl.rb b/test/test_ssl.rb index 97b2c22..97a3a46 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -505,39 +505,17 @@ if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_2 end - def test_disable_client_renegotiation - ctx_proc = Proc.new { |ctx| ctx.disable_client_renegotiation } + def test_renegotiation_cb + num_handshakes = 0 + renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } + ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } start_server_version(:SSLv23, ctx_proc) { |server, port| server_connect(port) { |ssl| - assert(ssl.ssl_version) + assert_equal(1, num_handshakes) } } 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, server_proc=nil, &blk) |