summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortechnorama <technorama@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-01-24 21:45:42 +0000
committertechnorama <technorama@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-01-24 21:45:42 +0000
commit7361a2ecb3b86e1fea0cc1bb5a3c4351ab50068f (patch)
tree3678b68536e31a42807a60b606e63d9294498fbc
parentb9170351da22956db16ae9ed06975d216d414809 (diff)
* 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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@21761 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog7
-rw-r--r--ext/openssl/extconf.rb3
-rw-r--r--ext/openssl/ossl_ssl.c103
-rw-r--r--test/openssl/test_ssl.rb44
4 files changed, 151 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index 1ccacf002e..0f028c1d2e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Sun Jan 25 06:44:58 2009 Technorama Ltd. <oss-ruby@technorama.net>
+
+ * 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@ruby-lang.org>
* lib/mkmf.rb (configuration): tools under the top source
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index 02e06b7149..457a17de0f 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -96,6 +96,9 @@ have_func("X509_STORE_set_ex_data")
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
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
index 08818ac8ab..4025166fec 100644
--- a/ext/openssl/ossl_ssl.c
+++ b/ext/openssl/ossl_ssl.c
@@ -67,6 +67,9 @@ static const char *ossl_sslctx_attrs[] = {
"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 @@ static const char *ossl_sslctx_attrs[] = {
#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 @@ ossl_sslctx_add_extra_chain_cert_i(VALUE i, VALUE arg)
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 @@ ossl_sslctx_setup(VALUE self)
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 @@ ossl_ssl_setup(VALUE self)
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 @@ ossl_ssl_setup(VALUE self)
}
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 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname)
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 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname)
}
}
- cb_state = rb_ivar_get(self, ID_callback_state);
- if (!NIL_P(cb_state))
- rb_jump_tag(NUM2INT(cb_state));
-
return self;
}
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index 9010fd2e9c..2c5886bb42 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -570,6 +570,50 @@ class OpenSSL::TestSSL < Test::Unit::TestCase
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