diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2017-09-29 21:15:25 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2017-09-29 23:28:36 +0900 |
commit | 2d8614ec591801d6fefb57df5d50f561ddc7a50b (patch) | |
tree | 30dc16f6c7ec4c0dfcf0020c980fd075bcf121aa | |
parent | e72d960db2623b21ee001b5a7b9d9e6ff55bdf94 (diff) | |
download | ruby-openssl-ky/ssl-keylog.tar.gz |
ssl: add keylog_callback to SSLContextky/ssl-keylog
Support logging TLS key materials in NSS Key Log Format.
OpenSSL 1.1.1 added SSL_CTX_set_keylog_callback() function that sets a
callback function that is called whenever key material used by a
connection is generated or received.
-rw-r--r-- | ext/openssl/ossl_ssl.c | 65 | ||||
-rw-r--r-- | test/test_ssl.rb | 63 |
2 files changed, 127 insertions, 1 deletions
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 93fc497e..fd0779da 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -43,7 +43,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_callback; static ID id_i_io, id_i_context, id_i_hostname; static int ossl_ssl_ex_vcb_idx; @@ -714,6 +714,43 @@ ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, } #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) +struct ssl_keylog_callback_args { + VALUE ssl; + VALUE callback; + const char *line; +}; + +static VALUE +ssl_keylog_callback_call(VALUE tmp) +{ + struct ssl_keylog_callback_args *args = (void *)tmp; + + rb_funcall(args->callback, rb_intern("call"), 2, args->ssl, + rb_str_new_cstr(args->line)); + return Qnil; +} + +static void +ssl_keylog_callback(const SSL *ssl, const char *line) +{ + VALUE sslctx_obj; + struct ssl_keylog_callback_args args; + int state; + + args.ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); + sslctx_obj = rb_attr_get(args.ssl, id_i_context); + args.callback = rb_attr_get(sslctx_obj, id_i_keylog_callback); + args.line = line; + + rb_protect(ssl_keylog_callback_call, (VALUE)&args, &state); + if (state) { + rb_set_errinfo(Qnil); + OSSL_Debug("Exception raised in keylog_callback suppressed"); + } +} +#endif + /* This function may serve as the entry point to support further callbacks. */ static void ssl_info_cb(const SSL *ssl, int where, int val) @@ -922,6 +959,11 @@ ossl_sslctx_setup(VALUE self) } #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) + if (!NIL_P(rb_attr_get(self, id_i_keylog_callback))) + SSL_CTX_set_keylog_callback(ctx, ssl_keylog_callback); +#endif + rb_obj_freeze(self); val = rb_attr_get(self, id_i_session_id_context); @@ -2548,6 +2590,26 @@ Init_ossl_ssl(void) rb_attr(cSSLContext, rb_intern("alpn_select_cb"), 1, 1, Qfalse); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10101000 && !defined(LIBRESSL_VERSION_NUMBER) + /* + * A callback invoked whenever a TLS key material is generated or received. + * The callback is called with two arguments: the SSLSocket object in use, + * and a String containing the key materials. + * + * This callback was added in OpenSSL 1.1.1. + * + * See also SSL_CTX_set_keylog_callback(3) for details. + * + * === Example + * + * keylog = File.open("sslkey.log", "a") + * ctx.keylog_callback = lambda do |ssl, line| + * keylog << line << "\n" + * end + */ + rb_attr(cSSLContext, rb_intern("keylog_callback"), 1, 1, Qfalse); +#endif + rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); rb_define_private_method(cSSLContext, "set_minmax_proto_version", @@ -2802,6 +2864,7 @@ Init_ossl_ssl(void) DefIVarID(alpn_select_cb); DefIVarID(servername_cb); DefIVarID(verify_hostname); + DefIVarID(keylog_callback); DefIVarID(io); DefIVarID(context); diff --git a/test/test_ssl.rb b/test/test_ssl.rb index ab6382d7..f77c9739 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -1350,6 +1350,69 @@ end } end + def test_keylog_callback + pend "SSL_CTX_set_keylog_callback() is not implemented" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:keylog_callback=) + + # TLS 1.2 + server_lines = [] + ctx_proc = -> ctx { + ctx.min_version = ctx.max_version = :TLS1_2 + ctx.ciphers = "DEFAULT:!kRSA" # Prevent RSA key agreement explicitly + ctx.keylog_callback = -> (ssl, line) { + assert_kind_of OpenSSL::SSL::SSLSocket, ssl + server_lines << line + } + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx = OpenSSL::SSL::SSLContext.new + client_lines = [] + ctx.keylog_callback = -> (ssl, line) { + client_lines << line + } + server_connect(port, ctx) { |ssl| + assert_equal 1, server_lines.size + assert_match (/\ACLIENT_RANDOM \w{64} \w{96}\z/), server_lines.first + assert_equal server_lines, client_lines + } + end + + # TLS 1.3 + server_lines = [] + ctx_proc = -> ctx { + begin + ctx.min_version = ctx.max_version = :TLS1_3 + rescue + pend "TLS 1.3 not supported" + end + ctx.keylog_callback = -> (ssl, line) { + server_lines << line + } + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + begin + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = ctx.max_version = :TLS1_3 + client_lines = [] + ctx.keylog_callback = -> (ssl, line) { + client_lines << line + } + server_connect(port, ctx) { |ssl| + ssl.puts("abc"); ssl.gets + assert_equal 4, server_lines.size + ch, ca, sh, sa = server_lines.sort + assert_match (/\ACLIENT_HANDSHAKE_TRAFFIC_SECRET /), ch + assert_match (/\ACLIENT_TRAFFIC_SECRET_0 /), ca + assert_match (/\ASERVER_HANDSHAKE_TRAFFIC_SECRET /), sh + assert_match (/\ASERVER_TRAFFIC_SECRET_0 /), sa + assert_equal server_lines.sort, client_lines.sort + } + rescue OpenSSL::SSL::SSLError + pend "TLS 1.3 not supported" + end + end + end + private def start_server_version(version, ctx_proc = nil, |