aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2017-09-29 21:15:25 +0900
committerKazuki Yamaguchi <k@rhe.jp>2017-09-29 23:28:36 +0900
commit2d8614ec591801d6fefb57df5d50f561ddc7a50b (patch)
tree30dc16f6c7ec4c0dfcf0020c980fd075bcf121aa
parente72d960db2623b21ee001b5a7b9d9e6ff55bdf94 (diff)
downloadruby-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.c65
-rw-r--r--test/test_ssl.rb63
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,