Skip to content

Commit

Permalink
[ruby/openssl] Add support to SSL_CTX_set_keylog_callback
Browse files Browse the repository at this point in the history
- 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
  libressl/openbsd@648d39f)

ruby/openssl@3b63232cf1
  • Loading branch information
cdelafuente-r7 authored and rhenium committed Oct 17, 2022
1 parent e4b1627 commit 17998ad
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 1 deletion.
86 changes: 85 additions & 1 deletion ext/openssl/ossl_ssl.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand Down
48 changes: 48 additions & 0 deletions test/openssl/test_ssl.rb
Expand Up @@ -804,6 +804,54 @@ def socketpair
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
Expand Down

0 comments on commit 17998ad

Please sign in to comment.