[ruby/openssl] Add support to SSL_CTX_set_keylog_callback

- This callback is invoked when TLS key material is generated or
  received, in order to allow applications to store this keying material
  for debugging purposes.
- It is invoked with an `SSLSocket` and a string containing the key
  material in the format used by NSS for its SSLKEYLOGFILE debugging
  output.
- This commit adds the Ruby binding `keylog_cb` and the related tests
- It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
  `SSL_CTX_set_keylog_callback()` from v3.4.2, it does nothing (see
  648d39f0f0)

https://github.com/ruby/openssl/commit/3b63232cf1
This commit is contained in:
Christophe De La Fuente 2022-08-29 20:15:54 +02:00 committed by Kazuki Yamaguchi
parent e4b1627983
commit 17998ad3bb
2 changed files with 133 additions and 1 deletions

View File

@ -49,7 +49,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode,
id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb,
id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols,
id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb,
id_i_verify_hostname;
id_i_verify_hostname, id_i_keylog_cb;
static ID id_i_io, id_i_context, id_i_hostname;
static int ossl_ssl_ex_vcb_idx;
@ -441,6 +441,54 @@ ossl_sslctx_session_new_cb(SSL *ssl, SSL_SESSION *sess)
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
/*
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
*/
struct ossl_call_keylog_cb_args {
VALUE ssl_obj;
const char * line;
};
static VALUE
ossl_call_keylog_cb(VALUE args_v)
{
VALUE sslctx_obj, cb, line_v;
struct ossl_call_keylog_cb_args *args = (struct ossl_call_keylog_cb_args *) args_v;
sslctx_obj = rb_attr_get(args->ssl_obj, id_i_context);
cb = rb_attr_get(sslctx_obj, id_i_keylog_cb);
if (NIL_P(cb)) return Qnil;
line_v = rb_str_new_cstr(args->line);
return rb_funcall(cb, id_call, 2, args->ssl_obj, line_v);
}
static void
ossl_sslctx_keylog_cb(const SSL *ssl, const char *line)
{
VALUE ssl_obj;
struct ossl_call_keylog_cb_args args;
int state = 0;
OSSL_Debug("SSL keylog callback entered");
ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
args.ssl_obj = ssl_obj;
args.line = line;
rb_protect(ossl_call_keylog_cb, (VALUE)&args, &state);
if (state) {
rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
}
}
#endif
static VALUE
ossl_call_session_remove_cb(VALUE ary)
{
@ -911,6 +959,18 @@ ossl_sslctx_setup(VALUE self)
OSSL_Debug("SSL TLSEXT servername callback added");
}
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
/*
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
*/
if (RTEST(rb_attr_get(self, id_i_keylog_cb))) {
SSL_CTX_set_keylog_callback(ctx, ossl_sslctx_keylog_cb);
OSSL_Debug("SSL keylog callback added");
}
#endif
return Qtrue;
}
@ -2783,6 +2843,29 @@ Init_ossl_ssl(void)
*/
rb_attr(cSSLContext, rb_intern_const("alpn_select_cb"), 1, 1, Qfalse);
/*
* A callback invoked when TLS key material is generated or received, in
* order to allow applications to store this keying material for debugging
* purposes.
*
* The callback is invoked with an SSLSocket and a string containing the
* key material in the format used by NSS for its SSLKEYLOGFILE debugging
* output.
*
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
*
* === Example
*
* context.keylog_cb = proc do |_sock, line|
* File.open('ssl_keylog_file', "a") do |f|
* f.write("#{line}\n")
* end
* end
*/
rb_attr(cSSLContext, rb_intern_const("keylog_cb"), 1, 1, Qfalse);
rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
rb_define_private_method(cSSLContext, "set_minmax_proto_version",
@ -3064,6 +3147,7 @@ Init_ossl_ssl(void)
DefIVarID(alpn_select_cb);
DefIVarID(servername_cb);
DefIVarID(verify_hostname);
DefIVarID(keylog_cb);
DefIVarID(io);
DefIVarID(context);

View File

@ -804,6 +804,54 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
def test_keylog_cb
pend "Keylog callback is not supported" if !openssl?(1, 1, 1) || libressl?
prefix = 'CLIENT_RANDOM'
context = OpenSSL::SSL::SSLContext.new
context.min_version = context.max_version = OpenSSL::SSL::TLS1_2_VERSION
cb_called = false
context.keylog_cb = proc do |_sock, line|
cb_called = true
assert_equal(prefix, line.split.first)
end
start_server do |port|
server_connect(port, context) do |ssl|
ssl.puts "abc"
assert_equal("abc\n", ssl.gets)
assert_equal(true, cb_called)
end
end
if tls13_supported?
prefixes = [
'SERVER_HANDSHAKE_TRAFFIC_SECRET',
'EXPORTER_SECRET',
'SERVER_TRAFFIC_SECRET_0',
'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
'CLIENT_TRAFFIC_SECRET_0',
]
context = OpenSSL::SSL::SSLContext.new
context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION
cb_called = false
context.keylog_cb = proc do |_sock, line|
cb_called = true
assert_not_nil(prefixes.delete(line.split.first))
end
start_server do |port|
server_connect(port, context) do |ssl|
ssl.puts "abc"
assert_equal("abc\n", ssl.gets)
assert_equal(true, cb_called)
end
assert_equal(0, prefixes.size)
end
end
end
def test_tlsext_hostname
fooctx = OpenSSL::SSL::SSLContext.new
fooctx.cert = @cli_cert