ruby-changes:39266
From: tenderlove <ko1@a...>
Date: Thu, 23 Jul 2015 03:32:10 +0900 (JST)
Subject: [ruby-changes:39266] tenderlove:r51347 (trunk): * ext/openssl/ossl_ssl.c: add ALPN support. [Feature #9390]
tenderlove 2015-07-23 03:31:42 +0900 (Thu, 23 Jul 2015) New Revision: 51347 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=51347 Log: * ext/openssl/ossl_ssl.c: add ALPN support. [Feature #9390] * ext/openssl/extconf.rb: detect ALPN support in OpenSSL * test/openssl/test_ssl.rb: test for ALPN 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 51346) +++ ChangeLog (revision 51347) @@ -1,3 +1,11 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Thu Jul 23 03:29:49 2015 Aaron Patterson <tenderlove@r...> + + * ext/openssl/ossl_ssl.c: add ALPN support. [Feature #9390] + + * ext/openssl/extconf.rb: detect ALPN support in OpenSSL + + * test/openssl/test_ssl.rb: test for ALPN + Wed Jul 22 23:44:17 2015 Nobuyoshi Nakada <nobu@r...> * string.c (rb_str_reverse): reversed string is not a substring, Index: ext/openssl/ossl_ssl.c =================================================================== --- ext/openssl/ossl_ssl.c (revision 51346) +++ ext/openssl/ossl_ssl.c (revision 51347) @@ -600,13 +600,13 @@ ssl_npn_encode_protocol_i(VALUE cur, VAL https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L600 return Qnil; } -static void -ssl_npn_encode_protocols(VALUE sslctx, VALUE protocols) +static VALUE +ssl_encode_npn_protocols(VALUE protocols) { VALUE encoded = rb_str_new2(""); rb_iterate(rb_each, protocols, ssl_npn_encode_protocol_i, encoded); StringValueCStr(encoded); - rb_iv_set(sslctx, "@_protocols", encoded); + return encoded; } static int @@ -645,6 +645,34 @@ ssl_npn_select_cb(SSL *s, unsigned char https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L645 return SSL_TLSEXT_ERR_OK; } + +#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB +static int +ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) +{ + int i = 0; + VALUE sslctx_obj, cb, protocols, selected; + + sslctx_obj = (VALUE) arg; + cb = rb_iv_get(sslctx_obj, "@alpn_select_cb"); + protocols = rb_ary_new(); + + /* The format is len_1|proto_1|...|len_n|proto_n\0 */ + while (in[i]) { + VALUE protocol = rb_str_new((const char *) &in[i + 1], in[i]); + rb_ary_push(protocols, protocol); + i += in[i] + 1; + } + + selected = rb_funcall(cb, rb_intern("call"), 1, protocols); + *out = (unsigned char *) StringValuePtr(selected); + *outlen = RSTRING_LENINT(selected); + rb_iv_set(sslctx_obj, "@_alpn_selected", selected); + + return SSL_TLSEXT_ERR_OK; +} +#endif + #endif /* This function may serve as the entry point to support further @@ -781,7 +809,7 @@ ossl_sslctx_setup(VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L809 #ifdef HAVE_OPENSSL_NPN_NEGOTIATED val = rb_iv_get(self, "@npn_protocols"); if (!NIL_P(val)) { - ssl_npn_encode_protocols(self, val); + rb_iv_set(self, "@_protocols", ssl_encode_npn_protocols(val)); SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *) self); OSSL_Debug("SSL NPN advertise callback added"); } @@ -791,6 +819,19 @@ ossl_sslctx_setup(VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L819 } #endif +#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB + val = rb_iv_get(self, "@alpn_protocols"); + if (!NIL_P(val)) { + VALUE rprotos = ssl_encode_npn_protocols(val); + SSL_CTX_set_alpn_protos(ctx, StringValueCStr(rprotos), RSTRING_LEN(rprotos)); + OSSL_Debug("SSL ALPN values added"); + } + if (RTEST(rb_iv_get(self, "@alpn_select_cb"))) { + SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); + OSSL_Debug("SSL ALPN select callback added"); + } +#endif + rb_obj_freeze(self); val = ossl_sslctx_get_sess_id_ctx(self); @@ -1904,6 +1945,29 @@ ossl_ssl_npn_protocol(VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1945 else return rb_str_new((const char *) out, outlen); } + +/* + * call-seq: + * ssl.alpn_protocol => String + * + * Returns the ALPN protocol string that was finally selected by the client + * during the handshake. + */ +static VALUE +ossl_ssl_alpn_protocol(VALUE self) +{ + SSL *ssl; + const unsigned char *out; + unsigned int outlen; + + ossl_ssl_data_get_struct(self, ssl); + + SSL_get0_alpn_selected(ssl, &out, &outlen); + if (!outlen) + return Qnil; + else + return rb_str_new((const char *) out, outlen); +} # endif #endif /* !defined(OPENSSL_NO_SOCK) */ @@ -2155,6 +2219,38 @@ Init_ossl_ssl(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2219 rb_attr(cSSLContext, rb_intern("npn_select_cb"), 1, 1, Qfalse); #endif +#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB + /* + * An Enumerable of Strings. Each String represents a protocol to be + * advertised as the list of supported protocols for Application-Layer Protocol + * Negotiation. Supported in OpenSSL 1.0.1 and higher. Has no effect + * on the client side. If not set explicitly, the NPN extension will + * not be sent by the server in the handshake. + * + * === Example + * + * ctx.alpn_protocols = ["http/1.1", "spdy/2", "h2"] + */ + rb_attr(cSSLContext, rb_intern("alpn_protocols"), 1, 1, Qfalse); + /* + * A callback invoked on the server side when the server needs to select + * a protocol from the list sent by the client. Supported in OpenSSL 1.0.2 + * and higher. The server MUST select a protocol of those advertised by + * the client. If none is acceptable, raising an error in the callback + * will cause the handshake to fail. Not setting this callback explicitly + * means not supporting the ALPN extension on the client - any protocols + * advertised by the server will be ignored. + * + * === Example + * + * ctx.alpn_select_cb = lambda do |protocols| + * #inspect the protocols and select one + * protocols.first + * end + */ + rb_attr(cSSLContext, rb_intern("alpn_select_cb"), 1, 1, Qfalse); +#endif + rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); rb_define_method(cSSLContext, "initialize", ossl_sslctx_initialize, -1); @@ -2267,6 +2363,9 @@ Init_ossl_ssl(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2363 rb_define_method(cSSLSocket, "session=", ossl_ssl_set_session, 1); rb_define_method(cSSLSocket, "verify_result", ossl_ssl_get_verify_result, 0); rb_define_method(cSSLSocket, "client_ca", ossl_ssl_get_client_ca_list, 0); +# ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB + rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); +# endif # ifdef HAVE_OPENSSL_NPN_NEGOTIATED rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); # endif Index: ext/openssl/extconf.rb =================================================================== --- ext/openssl/extconf.rb (revision 51346) +++ ext/openssl/extconf.rb (revision 51347) @@ -110,6 +110,7 @@ have_func("TLSv1_1_client_method") https://github.com/ruby/ruby/blob/trunk/ext/openssl/extconf.rb#L110 have_func("TLSv1_2_method") have_func("TLSv1_2_server_method") have_func("TLSv1_2_client_method") +have_func("SSL_CTX_set_alpn_select_cb") have_macro("OPENSSL_NPN_NEGOTIATED", ['openssl/ssl.h']) && $defs.push("-DHAVE_OPENSSL_NPN_NEGOTIATED") 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") Index: test/openssl/test_ssl.rb =================================================================== --- test/openssl/test_ssl.rb (revision 51346) +++ test/openssl/test_ssl.rb (revision 51347) @@ -795,6 +795,38 @@ end https://github.com/ruby/ruby/blob/trunk/test/openssl/test_ssl.rb#L795 } end +if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 + def test_alpn_protocol_selection_ary + advertised = ["http/1.1", "spdy/2"] + ctx_proc = Proc.new { |ctx| + ctx.alpn_select_cb = -> (protocols) { + protocols.first + } + ctx.alpn_protocols = advertised + } + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.alpn_protocols = advertised + server_connect(port, ctx) { |ssl| + assert_equal(advertised.first, ssl.alpn_protocol) + } + } + end + + def test_alpn_protocol_selection_cancel + ctx_proc = Proc.new { |ctx| + ctx.alpn_select_cb = -> (protocols) { nil } + } + assert_raises(MiniTest::Assertion) do # minitest/assertion comes from `assert_join_threads` + start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.alpn_protocols = ["http/1.1"] + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } + } + end + end +end + if OpenSSL::OPENSSL_VERSION_NUMBER > 0x10001000 def test_npn_protocol_selection_ary -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/