Add SSL_CTX_set_keylog_bio.

Configures the SSL stack to log session information to a BIO. The intent is to
support NSS's SSLKEYLOGFILE environment variable. Add support for the same
environment variable to tool/client.cc.

Tested against Wireshark 1.12.0.

BUG=393477

Change-Id: I4c231f9abebf194eb2df4aaeeafa337516774c95
Reviewed-on: https://boringssl-review.googlesource.com/1699
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 900fae7..da8e55e 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -689,6 +689,14 @@
 #define SSL_CTX_set_msg_callback_arg(ctx, arg) SSL_CTX_ctrl((ctx), SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, (arg))
 #define SSL_set_msg_callback_arg(ssl, arg) SSL_ctrl((ssl), SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, (arg))
 
+/* SSL_CTX_set_keylog_bio sets configures all SSL objects attached to |ctx| to
+ * log session material to |keylog_bio|. This is intended for debugging use with
+ * tools like Wireshark. |ctx| takes ownership of |keylog_bio|.
+ *
+ * The format is described in
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. */
+void SSL_CTX_set_keylog_bio(SSL_CTX *ctx, BIO *keylog_bio);
+
 
 struct ssl_aead_ctx_st;
 typedef struct ssl_aead_ctx_st SSL_AEAD_CTX;
@@ -1033,6 +1041,11 @@
 
 	/* If true, a client will request a stapled OCSP response. */
 	char ocsp_stapling_enabled;
+
+	/* If not NULL, session key material will be logged to this BIO for
+	 * debugging purposes. The format matches NSS's and is readable by
+	 * Wireshark. */
+	BIO *keylog_bio;
 	};
 
 #endif
@@ -2474,6 +2487,8 @@
 #define SSL_F_ssl3_expect_change_cipher_spec 282
 #define SSL_F_ssl23_get_v2_client_hello 283
 #define SSL_F_ssl3_cert_verify_hash 284
+#define SSL_F_ssl_ctx_log_rsa_client_key_exchange 285
+#define SSL_F_ssl_ctx_log_master_secret 286
 #define SSL_R_UNABLE_TO_FIND_ECDH_PARAMETERS 100
 #define SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC 101
 #define SSL_R_INVALID_NULL_CMD_NAME 102
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index f5bd284..c494a4a 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -168,6 +168,14 @@
 		memcpy(p, s->s3->tmp.finish_md, i);
 		l=i;
 
+                /* Log the master secret, if logging is enabled. */
+                if (!ssl_ctx_log_master_secret(s->ctx,
+				s->s3->client_random, SSL3_RANDOM_SIZE,
+				s->session->master_key, s->session->master_key_length))
+			{
+			return 0;
+			}
+
                 /* Copy the finished so we can use it for
                    renegotiation checks */
                 if(s->type == SSL_ST_CONNECT)
diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c
index 5f8c5db..cb9f95f 100644
--- a/ssl/s3_clnt.c
+++ b/ssl/s3_clnt.c
@@ -2015,6 +2015,13 @@
 				goto err;
 				}
 
+			/* Log the premaster secret, if logging is enabled. */
+			if (!ssl_ctx_log_rsa_client_key_exchange(s->ctx,
+					p, n, tmp_buf, sizeof(tmp_buf)))
+				{
+				goto err;
+				}
+
 			/* Fix buf for TLS and beyond */
 			if (s->version > SSL3_VERSION)
 				{
diff --git a/ssl/ssl_error.c b/ssl/ssl_error.c
index 20737a5..f6120dc 100644
--- a/ssl/ssl_error.c
+++ b/ssl/ssl_error.c
@@ -163,6 +163,8 @@
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_cipher_process_rulestr, 0), "ssl_cipher_process_rulestr"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_cipher_strength_sort, 0), "ssl_cipher_strength_sort"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_create_cipher_list, 0), "ssl_create_cipher_list"},
+  {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_ctx_log_master_secret, 0), "ssl_ctx_log_master_secret"},
+  {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_ctx_log_rsa_client_key_exchange, 0), "ssl_ctx_log_rsa_client_key_exchange"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_ctx_make_profiles, 0), "ssl_ctx_make_profiles"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_get_new_session, 0), "ssl_get_new_session"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_ssl_get_prev_session, 0), "ssl_get_prev_session"},
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index b8df1bf..1ad825a 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2130,6 +2130,9 @@
 	if (a->tlsext_channel_id_private)
 		EVP_PKEY_free(a->tlsext_channel_id_private);
 
+	if (a->keylog_bio)
+		BIO_free(a->keylog_bio);
+
 	OPENSSL_free(a);
 	}
 
@@ -3088,6 +3091,122 @@
 	SSL_callback_ctrl(ssl, SSL_CTRL_SET_MSG_CALLBACK, (void (*)(void))cb);
 	}
 
+void SSL_CTX_set_keylog_bio(SSL_CTX *ctx, BIO *keylog_bio)
+	{
+	if (ctx->keylog_bio != NULL)
+		BIO_free(ctx->keylog_bio);
+	ctx->keylog_bio = keylog_bio;
+	}
+
+static int cbb_add_hex(CBB *cbb, const uint8_t *in, size_t in_len)
+	{
+	static const char hextable[] = "0123456789abcdef";
+	uint8_t *out;
+	size_t i;
+
+	if (!CBB_add_space(cbb, &out, in_len * 2))
+		{
+		return 0;
+		}
+
+	for (i = 0; i < in_len; i++)
+		{
+		*(out++) = (uint8_t)hextable[in[i] >> 4];
+		*(out++) = (uint8_t)hextable[in[i] & 0xf];
+		}
+	return 1;
+	}
+
+int ssl_ctx_log_rsa_client_key_exchange(SSL_CTX *ctx,
+	const uint8_t *encrypted_premaster, size_t encrypted_premaster_len,
+	const uint8_t *premaster, size_t premaster_len)
+	{
+	BIO *bio = ctx->keylog_bio;
+	CBB cbb;
+	uint8_t *out;
+	size_t out_len;
+	int ret;
+
+	if (bio == NULL)
+		{
+		return 1;
+		}
+
+	if (encrypted_premaster_len < 8)
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl_ctx_log_rsa_client_key_exchange, ERR_R_INTERNAL_ERROR);
+		return 0;
+		}
+
+	if (!CBB_init(&cbb, 4 + 16 + 1 + premaster_len*2 + 1))
+		{
+		return 0;
+		}
+	if (!CBB_add_bytes(&cbb, (const uint8_t*)"RSA ", 4) ||
+		/* Only the first 8 bytes of the encrypted premaster secret are
+		 * logged. */
+		!cbb_add_hex(&cbb, encrypted_premaster, 8) ||
+		!CBB_add_bytes(&cbb, (const uint8_t*)" ", 1) ||
+		!cbb_add_hex(&cbb, premaster, premaster_len) ||
+		!CBB_add_bytes(&cbb, (const uint8_t*)"\n", 1) ||
+		!CBB_finish(&cbb, &out, &out_len))
+		{
+		CBB_cleanup(&cbb);
+		return 0;
+		}
+
+	CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+	ret = BIO_write(bio, out, out_len) >= 0 && BIO_flush(bio);
+	CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+
+	OPENSSL_free(out);
+	return ret;
+	}
+
+int ssl_ctx_log_master_secret(SSL_CTX *ctx,
+	const uint8_t *client_random, size_t client_random_len,
+	const uint8_t *master, size_t master_len)
+	{
+	BIO *bio = ctx->keylog_bio;
+	CBB cbb;
+	uint8_t *out;
+	size_t out_len;
+	int ret;
+
+	if (bio == NULL)
+		{
+		return 1;
+		}
+
+	if (client_random_len != 32)
+		{
+		OPENSSL_PUT_ERROR(SSL, ssl_ctx_log_master_secret, ERR_R_INTERNAL_ERROR);
+		return 0;
+		}
+
+	if (!CBB_init(&cbb, 14 + 64 + 1 + master_len*2 + 1))
+		{
+		return 0;
+		}
+	if (!CBB_add_bytes(&cbb, (const uint8_t*)"CLIENT_RANDOM ", 14) ||
+		!cbb_add_hex(&cbb, client_random, 32) ||
+		!CBB_add_bytes(&cbb, (const uint8_t*)" ", 1) ||
+		!cbb_add_hex(&cbb, master, master_len) ||
+		!CBB_add_bytes(&cbb, (const uint8_t*)"\n", 1) ||
+		!CBB_finish(&cbb, &out, &out_len))
+		{
+		CBB_cleanup(&cbb);
+		return 0;
+		}
+
+	CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+	ret = BIO_write(bio, out, out_len) >= 0 && BIO_flush(bio);
+	CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+
+	OPENSSL_free(out);
+	return ret;
+	}
+
 int SSL_cutthrough_complete(const SSL *s)
 	{
 	return (!s->server &&                 /* cutthrough only applies to clients */
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index ad2d843..2d10650 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -1091,6 +1091,19 @@
 								int idx);
 void tls1_set_cert_validity(SSL *s);
 
+/* ssl_ctx_log_rsa_client_key_exchange logs |premaster| to |ctx|, if logging is
+ * enabled. It returns one on success and zero on failure. The entry is
+ * identified by the first 8 bytes of |encrypted_premaster|. */
+int ssl_ctx_log_rsa_client_key_exchange(SSL_CTX *ctx,
+	const uint8_t *encrypted_premaster, size_t encrypted_premaster_len,
+	const uint8_t *premaster, size_t premaster_len);
+
+/* ssl_ctx_log_master_secret logs |master| to |ctx|, if logging is enabled. It
+ * returns one on success and zero on failure. The entry is identified by
+ * |client_random|. */
+int ssl_ctx_log_master_secret(SSL_CTX *ctx,
+	const uint8_t *client_random, size_t client_random_len,
+	const uint8_t *master, size_t master_len);
 
 int ssl3_can_cutthrough(const SSL *s);
 int ssl_get_max_version(const SSL *s);
diff --git a/tool/client.cc b/tool/client.cc
index 5acc8b1..8aef559 100644
--- a/tool/client.cc
+++ b/tool/client.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include <errno.h>
+#include <stdlib.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 
@@ -253,6 +254,16 @@
 
   SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
 
+  const char *keylog_file = getenv("SSLKEYLOGFILE");
+  if (keylog_file) {
+    BIO *keylog_bio = BIO_new_file(keylog_file, "a");
+    if (!keylog_bio) {
+      ERR_print_errors_cb(PrintErrorCallback, stderr);
+      return false;
+    }
+    SSL_CTX_set_keylog_bio(ctx, keylog_bio);
+  }
+
   int sock = -1;
   if (!Connect(&sock, args_map["-connect"])) {
     return false;