Repository navigation
Conversation
Add a binding for `SSL_CTX_set_client_hello_cb()`. The callback is called with an `SSLSocket` and an `OpenSSL::SSL::ClientHello`, and can return an `SSLContext` to switch to. `OpenSSL::SSL::ClientHello` is only valid while the callback is running and currently provides only `#get_extension`. Unlike the servername callback used by `SSLContext#servername_cb=`, the ClientHello callback is called before the protocol version is negotiated. So the following settings of the returned `SSLContext` can be applied to the connection: * `verify_mode` * `verify_depth` * `options` * `min_version` * `max_version` Apache httpd and nginx use the ClientHello callback for SNI-based virtual host selection for the same reason. OpenSSL may call the servername callback of the original `SSL_CTX` after the `SSL_CTX` is switched by the ClientHello callback. Look up the `SSLContext` from the `SSL_CTX` that registered the servername callback instead of `SSLSocket#context`. This is not supported by LibreSSL. Fixes ruby#1125. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| /* | ||
| * OpenSSL may invoke the callback of the original SSL_CTX after | ||
| * SSL_set_SSL_CTX(), so use the SSLContext that registered it | ||
| */ |
There was a problem hiding this comment.
This happens when both of servername_cb and client_hello_cb are used.
| static const rb_data_type_t ossl_client_hello_type = { | ||
| .wrap_struct_name = "OpenSSL/SSL/ClientHello", | ||
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, | ||
| }; |
There was a problem hiding this comment.
If we add bindings of SSL_client_hello_*() to SSLSocket, we don't need to define this new class. I defined this class for easy to use (I think that client_hello.get_extension is better than ssl_socket.client_hello_get_extension) but we can remove this class.
There was a problem hiding this comment.
I agree a method that is only callable from the callback is not a good fit.
Another possible alternative would be to pass everything (for example in a Hash mapping the type to the raw bytes) to the callback, though I'm not sure which approach is better.
| * See SSL_client_hello_get0_ext(3). | ||
| */ | ||
| static VALUE | ||
| ossl_client_hello_get_extension(VALUE self, VALUE type) |
There was a problem hiding this comment.
OpenSSL provides more SSL_client_hello_*() APIs but I just implement only this as the first step. We can add more methods as follow-up tasks.
| /* SSL_set_SSL_CTX() doesn't update these settings */ | ||
| SSL_set_verify(ssl, | ||
| SSL_CTX_get_verify_mode(ctx2), | ||
| SSL_CTX_get_verify_callback(ctx2)); | ||
| SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth(ctx2)); | ||
| SSL_clear_options(ssl, | ||
| SSL_get_options(ssl) & ~SSL_CTX_get_options(ctx2)); | ||
| SSL_set_options(ssl, SSL_CTX_get_options(ctx2)); |
There was a problem hiding this comment.
We should do them in ossl_call_servername_cb() too but it's out of scope of this PR. I can open a PR for it after this is merged.
| /* SSL_set_SSL_CTX() doesn't update these settings */ | ||
| SSL_set_verify(ssl, | ||
| SSL_CTX_get_verify_mode(ctx2), | ||
| SSL_CTX_get_verify_callback(ctx2)); | ||
| SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth(ctx2)); | ||
| SSL_clear_options(ssl, | ||
| SSL_get_options(ssl) & ~SSL_CTX_get_options(ctx2)); | ||
| SSL_set_options(ssl, SSL_CTX_get_options(ctx2)); | ||
| if (!SSL_set_min_proto_version(ssl, SSL_CTX_get_min_proto_version(ctx2))) | ||
| ossl_raise(eSSLError, "SSL_set_min_proto_version"); | ||
| if (!SSL_set_max_proto_version(ssl, SSL_CTX_get_max_proto_version(ctx2))) | ||
| ossl_raise(eSSLError, "SSL_set_max_proto_version"); |
There was a problem hiding this comment.
Similar code in Apache httpd: https://github.com/apache/httpd/blob/1a0c1b2d791173ec2c9651d11eab52205ed080ad/modules/ssl/ssl_engine_kernel.c#L2490-L2517
It doesn't call SSL_clear_options().
Similar code in nginx: https://github.com/nginx/nginx/blob/3f6f7824d4e2eb1ac37dec76683d525ac0ff521c/src/http/ngx_http_request.c#L983-L1003
It calls SSL_clear_options() but doesn't call SSL_set_{min,max}_proto_version(). nginx uses options for versions: https://github.com/nginx/nginx/blob/d10dd3123a0f1f3c6b9fc836162215a6df7af98b/src/event/ngx_event_openssl.c#L370-L411
| if (!SSL_set_SSL_CTX(ssl, ctx2)) | ||
| ossl_raise(eSSLError, "SSL_set_SSL_CTX"); |
There was a problem hiding this comment.
I commented on #1125, but I don't think we should increase exposure to SSL_set_SSL_CTX().
The following SSL_*() calls below are still not exhaustive, and some fields of SSL can't be changed after SSL_new(), for example session management.
| */ | ||
| static const rb_data_type_t ossl_client_hello_type = { | ||
| .wrap_struct_name = "OpenSSL/SSL/ClientHello", | ||
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, |
There was a problem hiding this comment.
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, | |
| .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED, |
Recently we switched all RUBY_TYPED_FREE_IMMEDIATELY wherever possible.
| static const rb_data_type_t ossl_client_hello_type = { | ||
| .wrap_struct_name = "OpenSSL/SSL/ClientHello", | ||
| .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, | ||
| }; |
There was a problem hiding this comment.
I agree a method that is only callable from the callback is not a good fit.
Another possible alternative would be to pass everything (for example in a Hash mapping the type to the raw bytes) to the callback, though I'm not sure which approach is better.
Add a binding for
SSL_CTX_set_client_hello_cb(). The callback is called with anSSLSocketand anOpenSSL::SSL::ClientHello, and can return anSSLContextto switch to.OpenSSL::SSL::ClientHellois only valid while the callback is running and currently provides only#get_extension.Unlike the servername callback used by
SSLContext#servername_cb=, the ClientHello callback is called before the protocol version is negotiated. So the following settings of the returnedSSLContextcan be applied to the connection:verify_modeverify_depthoptionsmin_versionmax_versionApache httpd and nginx use the ClientHello callback for SNI-based virtual host selection for the same reason.
OpenSSL may call the servername callback of the original
SSL_CTXafter theSSL_CTXis switched by the ClientHello callback. Look up theSSLContextfrom theSSL_CTXthat registered the servername callback instead ofSSLSocket#context.This is not supported by LibreSSL.
Fixes #1125.