Skip to content

Commit 173be66

Browse files
authored
Merge pull request #536 from cdelafuente-r7/add_keylog_cb
Add support to SSL_CTX_set_keylog_callback()
2 parents f5b82e8 + 3b63232 commit 173be66

File tree

2 files changed

+133
-1
lines changed

2 files changed

+133
-1
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode,
4949
id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb,
5050
id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols,
5151
id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb,
52-
id_i_verify_hostname;
52+
id_i_verify_hostname, id_i_keylog_cb;
5353
static ID id_i_io, id_i_context, id_i_hostname;
5454

5555
static int ossl_ssl_ex_vcb_idx;
@@ -441,6 +441,54 @@ ossl_sslctx_session_new_cb(SSL *ssl, SSL_SESSION *sess)
441441
return 0;
442442
}
443443

444+
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
445+
/*
446+
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
447+
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
448+
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
449+
*/
450+
451+
struct ossl_call_keylog_cb_args {
452+
VALUE ssl_obj;
453+
const char * line;
454+
};
455+
456+
static VALUE
457+
ossl_call_keylog_cb(VALUE args_v)
458+
{
459+
VALUE sslctx_obj, cb, line_v;
460+
struct ossl_call_keylog_cb_args *args = (struct ossl_call_keylog_cb_args *) args_v;
461+
462+
sslctx_obj = rb_attr_get(args->ssl_obj, id_i_context);
463+
464+
cb = rb_attr_get(sslctx_obj, id_i_keylog_cb);
465+
if (NIL_P(cb)) return Qnil;
466+
467+
line_v = rb_str_new_cstr(args->line);
468+
469+
return rb_funcall(cb, id_call, 2, args->ssl_obj, line_v);
470+
}
471+
472+
static void
473+
ossl_sslctx_keylog_cb(const SSL *ssl, const char *line)
474+
{
475+
VALUE ssl_obj;
476+
struct ossl_call_keylog_cb_args args;
477+
int state = 0;
478+
479+
OSSL_Debug("SSL keylog callback entered");
480+
481+
ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
482+
args.ssl_obj = ssl_obj;
483+
args.line = line;
484+
485+
rb_protect(ossl_call_keylog_cb, (VALUE)&args, &state);
486+
if (state) {
487+
rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
488+
}
489+
}
490+
#endif
491+
444492
static VALUE
445493
ossl_call_session_remove_cb(VALUE ary)
446494
{
@@ -911,6 +959,18 @@ ossl_sslctx_setup(VALUE self)
911959
OSSL_Debug("SSL TLSEXT servername callback added");
912960
}
913961

962+
#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER)
963+
/*
964+
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
965+
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
966+
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
967+
*/
968+
if (RTEST(rb_attr_get(self, id_i_keylog_cb))) {
969+
SSL_CTX_set_keylog_callback(ctx, ossl_sslctx_keylog_cb);
970+
OSSL_Debug("SSL keylog callback added");
971+
}
972+
#endif
973+
914974
return Qtrue;
915975
}
916976

@@ -2779,6 +2839,29 @@ Init_ossl_ssl(void)
27792839
*/
27802840
rb_attr(cSSLContext, rb_intern_const("alpn_select_cb"), 1, 1, Qfalse);
27812841

2842+
/*
2843+
* A callback invoked when TLS key material is generated or received, in
2844+
* order to allow applications to store this keying material for debugging
2845+
* purposes.
2846+
*
2847+
* The callback is invoked with an SSLSocket and a string containing the
2848+
* key material in the format used by NSS for its SSLKEYLOGFILE debugging
2849+
* output.
2850+
*
2851+
* It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements
2852+
* SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see
2853+
* https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6).
2854+
*
2855+
* === Example
2856+
*
2857+
* context.keylog_cb = proc do |_sock, line|
2858+
* File.open('ssl_keylog_file', "a") do |f|
2859+
* f.write("#{line}\n")
2860+
* end
2861+
* end
2862+
*/
2863+
rb_attr(cSSLContext, rb_intern_const("keylog_cb"), 1, 1, Qfalse);
2864+
27822865
rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
27832866
rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
27842867
rb_define_private_method(cSSLContext, "set_minmax_proto_version",
@@ -3060,6 +3143,7 @@ Init_ossl_ssl(void)
30603143
DefIVarID(alpn_select_cb);
30613144
DefIVarID(servername_cb);
30623145
DefIVarID(verify_hostname);
3146+
DefIVarID(keylog_cb);
30633147

30643148
DefIVarID(io);
30653149
DefIVarID(context);

test/openssl/test_ssl.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,54 @@ def socketpair
804804
end
805805
end
806806

807+
def test_keylog_cb
808+
pend "Keylog callback is not supported" if !openssl?(1, 1, 1) || libressl?
809+
810+
prefix = 'CLIENT_RANDOM'
811+
context = OpenSSL::SSL::SSLContext.new
812+
context.min_version = context.max_version = OpenSSL::SSL::TLS1_2_VERSION
813+
814+
cb_called = false
815+
context.keylog_cb = proc do |_sock, line|
816+
cb_called = true
817+
assert_equal(prefix, line.split.first)
818+
end
819+
820+
start_server do |port|
821+
server_connect(port, context) do |ssl|
822+
ssl.puts "abc"
823+
assert_equal("abc\n", ssl.gets)
824+
assert_equal(true, cb_called)
825+
end
826+
end
827+
828+
if tls13_supported?
829+
prefixes = [
830+
'SERVER_HANDSHAKE_TRAFFIC_SECRET',
831+
'EXPORTER_SECRET',
832+
'SERVER_TRAFFIC_SECRET_0',
833+
'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
834+
'CLIENT_TRAFFIC_SECRET_0',
835+
]
836+
context = OpenSSL::SSL::SSLContext.new
837+
context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION
838+
cb_called = false
839+
context.keylog_cb = proc do |_sock, line|
840+
cb_called = true
841+
assert_not_nil(prefixes.delete(line.split.first))
842+
end
843+
844+
start_server do |port|
845+
server_connect(port, context) do |ssl|
846+
ssl.puts "abc"
847+
assert_equal("abc\n", ssl.gets)
848+
assert_equal(true, cb_called)
849+
end
850+
assert_equal(0, prefixes.size)
851+
end
852+
end
853+
end
854+
807855
def test_tlsext_hostname
808856
fooctx = OpenSSL::SSL::SSLContext.new
809857
fooctx.cert = @cli_cert

0 commit comments

Comments
 (0)