ruby-changes:10217
From: technorama <ko1@a...>
Date: Sun, 25 Jan 2009 06:47:49 +0900 (JST)
Subject: [ruby-changes:10217] Ruby:r21761 (trunk): * ext/openssl/ossl_ssl.c: Server Name Indication support.
technorama 2009-01-25 06:45:42 +0900 (Sun, 25 Jan 2009) New Revision: 21761 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=21761 Log: * ext/openssl/ossl_ssl.c: Server Name Indication support. new methods SSLContext#server_name_cb=, SSLSocket#hostname=. * test/openssl/test_ssl.rb: Tests for above. Modified files: trunk/ChangeLog trunk/ext/openssl/extconf.rb trunk/ext/openssl/ossl_ssl.c trunk/test/openssl/test_ssl.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 21760) +++ ChangeLog (revision 21761) @@ -1,3 +1,10 @@ +Sun Jan 25 06:44:58 2009 Technorama Ltd. <oss-ruby@t...> + + * ext/openssl/ossl_ssl.c: Server Name Indication support. + new methods SSLContext#server_name_cb=, SSLSocket#hostname=. + + * test/openssl/test_ssl.rb: Tests for above. + Sat Jan 24 08:22:35 2009 Nobuyoshi Nakada <nobu@r...> * lib/mkmf.rb (configuration): tools under the top source Index: ext/openssl/ossl_ssl.c =================================================================== --- ext/openssl/ossl_ssl.c (revision 21760) +++ ext/openssl/ossl_ssl.c (revision 21761) @@ -67,6 +67,9 @@ "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", +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + "servername_cb", +#endif }; #define ossl_ssl_get_io(o) rb_iv_get((o),"@io") @@ -84,7 +87,12 @@ #define ossl_ssl_set_tmp_dh(o,v) rb_iv_set((o),"@tmp_dh",(v)) static const char *ossl_ssl_attr_readers[] = { "io", "context", }; -static const char *ossl_ssl_attrs[] = { "sync_close", }; +static const char *ossl_ssl_attrs[] = { +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + "hostname", +#endif + "sync_close", +}; ID ID_callback_state; @@ -446,6 +454,66 @@ return i; } +static VALUE ossl_sslctx_setup(VALUE self); + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME +static VALUE +ossl_call_servername_cb(VALUE ary) +{ + VALUE ssl_obj, sslctx_obj, cb, ret_obj; + + Check_Type(ary, T_ARRAY); + ssl_obj = rb_ary_entry(ary, 0); + + sslctx_obj = rb_iv_get(ssl_obj, "@context"); + if (NIL_P(sslctx_obj)) return Qnil; + cb = rb_iv_get(sslctx_obj, "@servername_cb"); + if (NIL_P(cb)) return Qnil; + + ret_obj = rb_funcall(cb, rb_intern("call"), 1, ary); + if (rb_obj_is_kind_of(ret_obj, cSSLContext)) { + SSL *ssl; + SSL_CTX *ctx2; + + ossl_sslctx_setup(ret_obj); + Data_Get_Struct(ssl_obj, SSL, ssl); + Data_Get_Struct(ret_obj, SSL_CTX, ctx2); + SSL_set_SSL_CTX(ssl, ctx2); + } else if (!NIL_P(ret_obj)) { + rb_raise(rb_eArgError, "servername_cb must return an OpenSSL::SSL::SSLContext object or nil"); + } + + return ret_obj; +} + +static int +ssl_servername_cb(SSL *ssl, int *ad, void *arg) +{ + VALUE ary, ssl_obj, ret_obj; + void *ptr; + int state = 0; + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + + if (!servername) + return SSL_TLSEXT_ERR_OK; + + if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL) + return SSL_TLSEXT_ERR_ALERT_FATAL; + ssl_obj = (VALUE)ptr; + ary = rb_ary_new2(2); + rb_ary_push(ary, ssl_obj); + rb_ary_push(ary, rb_str_new2(servername)); + + ret_obj = rb_protect((VALUE(*)_((VALUE)))ossl_call_servername_cb, ary, &state); + if (state) { + rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + return SSL_TLSEXT_ERR_OK; +} +#endif + /* * call-seq: * ctx.setup => Qtrue # first time @@ -581,6 +649,15 @@ SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb); OSSL_Debug("SSL SESSION remove callback added"); } + +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + val = rb_iv_get(self, "@servername_cb"); + if (!NIL_P(val)) { + SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); + OSSL_Debug("SSL TLSEXT servername callback added"); + } +#endif + return Qtrue; } @@ -901,6 +978,10 @@ Data_Get_Struct(self, SSL, ssl); if(!ssl){ +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + VALUE hostname = rb_iv_get(self, "@hostname"); +#endif + v_ctx = ossl_ssl_get_ctx(self); Data_Get_Struct(v_ctx, SSL_CTX, ctx); @@ -910,6 +991,12 @@ } DATA_PTR(self) = ssl; +#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + if (!NIL_P(hostname)) { + if (SSL_set_tlsext_host_name(ssl, StringValuePtr(hostname)) != 1) + ossl_raise(eSSLError, "SSL_set_tlsext_host_name:"); + } +#endif io = ossl_ssl_get_io(self); GetOpenFile(io, fptr); rb_io_check_readable(fptr); @@ -946,7 +1033,15 @@ Data_Get_Struct(self, SSL, ssl); GetOpenFile(ossl_ssl_get_io(self), fptr); for(;;){ - if((ret = func(ssl)) > 0) break; + ret = func(ssl); + + cb_state = rb_ivar_get(self, ID_callback_state); + if (!NIL_P(cb_state)) + rb_jump_tag(NUM2INT(cb_state)); + + if (ret > 0) + break; + switch((ret2 = ssl_get_error(ssl, ret))){ case SSL_ERROR_WANT_WRITE: rb_io_wait_writable(FPTR_TO_FD(fptr)); @@ -962,10 +1057,6 @@ } } - cb_state = rb_ivar_get(self, ID_callback_state); - if (!NIL_P(cb_state)) - rb_jump_tag(NUM2INT(cb_state)); - return self; } Index: ext/openssl/extconf.rb =================================================================== --- ext/openssl/extconf.rb (revision 21760) +++ ext/openssl/extconf.rb (revision 21761) @@ -96,6 +96,9 @@ have_func("OBJ_NAME_do_all_sorted") have_func("SSL_SESSION_get_id") have_func("OPENSSL_cleanse") +unless have_func("SSL_set_tlsext_host_name", ['openssl/ssl.h']) + have_macro("SSL_set_tlsext_host_name", ['openssl/ssl.h']) && $defs.push("-DHAVE_SSL_SET_TLSEXT_HOST_NAME") +end if try_compile("#define FOO(a, ...) foo(a, ##__VA_ARGS__)\n int x(){FOO(1);FOO(1,2);FOO(1,2,3);}\n") $defs.push("-DHAVE_VA_ARGS_MACRO") end Index: test/openssl/test_ssl.rb =================================================================== --- test/openssl/test_ssl.rb (revision 21760) +++ test/openssl/test_ssl.rb (revision 21761) @@ -570,6 +570,50 @@ end end end + + def test_tlsext_hostname + return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname) + + ctx_proc = Proc.new do |ctx, ssl| + foo_ctx = ctx.dup + + ctx.servername_cb = Proc.new do |ssl, hostname| + case hostname + when 'foo.example.com' + foo_ctx + when 'bar.example.com' + nil + else + raise "unknown hostname #{hostname.inspect}" + end + end + end + + server_proc = Proc.new do |ctx, ssl| + readwrite_loop(ctx, ssl) + end + + start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc, :server_proc => server_proc) do |server, port| + 2.times do |i| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + if defined?(OpenSSL::SSL::OP_NO_TICKET) + # disable RFC4507 support + ctx.options = OpenSSL::SSL::OP_NO_TICKET + end + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.hostname = (i & 1 == 0) ? 'foo.example.com' : 'bar.example.com' + ssl.connect + + str = "x" * 100 + "\n" + ssl.puts(str) + assert_equal(str, ssl.gets) + + ssl.close + end + end + end end end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/