Created
July 13, 2015 13:38
-
-
Save rhenium/b1711edcc903e8887a51 to your computer and use it in GitHub Desktop.
Ruby の openssl で ALPN をつかえるようにする
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb | |
index b1a527c..a11d40e 100644 | |
--- a/ext/openssl/extconf.rb | |
+++ b/ext/openssl/extconf.rb | |
@@ -111,6 +111,7 @@ have_func("TLSv1_2_method") | |
have_func("TLSv1_2_server_method") | |
have_func("TLSv1_2_client_method") | |
have_macro("OPENSSL_NPN_NEGOTIATED", ['openssl/ssl.h']) && $defs.push("-DHAVE_OPENSSL_NPN_NEGOTIATED") | |
+have_func("SSL_CTX_set_alpn_protos") && $defs.push("-DHAVE_OPENSSL_ALPN") | |
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 | |
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c | |
index 143189e..e044388 100644 | |
--- a/ext/openssl/ossl_ssl.c | |
+++ b/ext/openssl/ossl_ssl.c | |
@@ -81,6 +81,10 @@ static const char *ossl_sslctx_attrs[] = { | |
"npn_protocols", | |
"npn_select_cb", | |
#endif | |
+#ifdef HAVE_OPENSSL_ALPN | |
+ "alpn_protocols", | |
+ "alpn_select_cb", | |
+#endif | |
}; | |
#define ossl_ssl_get_io(o) rb_iv_get((o),"@io") | |
@@ -647,6 +651,55 @@ ssl_npn_select_cb(SSL *s, unsigned char **out, unsigned char *outlen, const unsi | |
} | |
#endif | |
+#ifdef HAVE_OPENSSL_ALPN | |
+static VALUE | |
+ssl_alpn_encode_protocol_i(VALUE cur, VALUE encoded) | |
+{ | |
+ int len = RSTRING_LENINT(cur); | |
+ char len_byte; | |
+ if (len < 1 || len > 255) | |
+ ossl_raise(eSSLError, "ALPN advertising protocol must have length 1..255"); | |
+ /* Encode the length byte */ | |
+ len_byte = len; | |
+ rb_str_buf_cat(encoded, &len_byte, 1); | |
+ rb_str_buf_cat(encoded, RSTRING_PTR(cur), len); | |
+ return Qnil; | |
+} | |
+ | |
+static VALUE | |
+ssl_alpn_encode_protocols(VALUE sslctx, VALUE protocols) | |
+{ | |
+ VALUE encoded = rb_str_new2(""); | |
+ rb_iterate(rb_each, protocols, ssl_alpn_encode_protocol_i, encoded); | |
+ return encoded; | |
+} | |
+ | |
+static int | |
+ssl_alpn_select_cb(SSL *s, 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); | |
+ StringValue(selected); | |
+ *out = (unsigned char *) StringValuePtr(selected); | |
+ *outlen = RSTRING_LENINT(selected); | |
+ | |
+ return SSL_TLSEXT_ERR_OK; | |
+} | |
+#endif | |
+ | |
/* This function may serve as the entry point to support further | |
* callbacks. */ | |
static void | |
@@ -791,6 +844,19 @@ ossl_sslctx_setup(VALUE self) | |
} | |
#endif | |
+#ifdef HAVE_OPENSSL_ALPN | |
+ val = rb_iv_get(self, "@alpn_protocols"); | |
+ if (!NIL_P(val)) { | |
+ VALUE encoded = ssl_alpn_encode_protocols(self, val); | |
+ SSL_CTX_set_alpn_protos(ctx, (const unsigned char *) RSTRING_PTR(encoded), RSTRING_LENINT(encoded)); | |
+ OSSL_Debug("TLS ALPN advertise protocols 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("TLS ALPN protocol select callback added"); | |
+ } | |
+#endif | |
+ | |
rb_obj_freeze(self); | |
val = ossl_sslctx_get_sess_id_ctx(self); | |
@@ -1905,6 +1971,30 @@ ossl_ssl_npn_protocol(VALUE self) | |
return rb_str_new((const char *) out, outlen); | |
} | |
# endif | |
+# ifdef HAVE_OPENSSL_ALPN | |
+/* | |
+ * call-seq: | |
+ * ssl.alpn_protocol => String | |
+ * | |
+ * Returns the protocol string that was finally selected by the server | |
+ * 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) */ | |
void | |
@@ -2154,6 +2244,27 @@ Init_ossl_ssl(void) | |
*/ | |
rb_attr(cSSLContext, rb_intern("npn_select_cb"), 1, 1, Qfalse); | |
#endif | |
+#ifdef HAVE_OPENSSL_ALPN | |
+ /* | |
+ * ALPN protocols | |
+ * | |
+ * === Example | |
+ * | |
+ * ctx.alpn_protocols = ["http/1.1", "h2", "h2-14"] | |
+ */ | |
+ rb_attr(cSSLContext, rb_intern("alpn_protocols"), 1, 1, Qfalse); | |
+ /* | |
+ * ALPN select callback | |
+ * | |
+ * === 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="); | |
@@ -2270,6 +2381,9 @@ Init_ossl_ssl(void) | |
# ifdef HAVE_OPENSSL_NPN_NEGOTIATED | |
rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); | |
# endif | |
+# ifdef HAVE_OPENSSL_ALPN | |
+ rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); | |
+# endif | |
#endif | |
#define ossl_ssl_def_const(x) rb_define_const(mSSL, #x, INT2NUM(SSL_##x)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment