Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby の openssl で ALPN をつかえるようにする
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
You can’t perform that action at this time.