From 6cdba64e13f9599db49e507418ab5571a2f42d4f Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Tue, 5 Sep 2017 15:27:22 -0400 Subject: [PATCH] openssl: Integrate Peter Wu's SSLKEYLOGFILE implementation This is an adaptation of 2 of Peter Wu's SSLKEYLOGFILE implementations. The first one, written for old OpenSSL versions: https://git.lekensteyn.nl/peter/wireshark-notes/tree/src/sslkeylog.c The second one, written for BoringSSL and new OpenSSL versions: https://github.com/curl/curl/pull/1346 Note the first one is GPL licensed but the author gave permission to waive that license for libcurl. As of right now this feature is disabled by default, and does not have a configure option to enable it. To enable this feature define ENABLE_SSLKEYLOGFILE when building libcurl and set environment variable SSLKEYLOGFILE to a pathname that will receive the keys. And in Wireshark change your preferences to point to that key file: Edit > Preferences > Protocols > SSL > Master-Secret Co-authored-by: Peter Wu Ref: https://github.com/curl/curl/pull/1030 Ref: https://github.com/curl/curl/pull/1346 Closes https://github.com/curl/curl/pull/1866 --- lib/curl_setup.h | 3 + lib/vtls/openssl.c | 168 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 36d1e42bc..402ebc03d 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -717,6 +717,7 @@ Therefore we specify it explicitly. https://github.com/curl/curl/pull/258 #if defined(WIN32) || defined(MSDOS) #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "wt" +#define FOPEN_APPENDTEXT "at" #elif defined(__CYGWIN__) /* Cygwin has specific behavior we need to address when WIN32 is not defined. https://cygwin.com/cygwin-ug-net/using-textbinary.html @@ -726,9 +727,11 @@ endings either CRLF or LF so 't' is appropriate. */ #define FOPEN_READTEXT "rt" #define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" #else #define FOPEN_READTEXT "r" #define FOPEN_WRITETEXT "w" +#define FOPEN_APPENDTEXT "a" #endif /* WinSock destroys recv() buffer when send() failed. diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index c42143a85..a05c994fd 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -146,6 +146,20 @@ static unsigned long OpenSSL_version_num(void) #define OPENSSL_load_builtin_modules(x) #endif +/* + * Whether SSL_CTX_set_keylog_callback is available. + * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 + * BoringSSL: supported since d28f59c27bac (committed 2015-11-19), the + * BORINGSSL_201512 macro from 2016-01-21 should be close enough. + * LibreSSL: unsupported in at least 2.5.1 (explicitly check for it since it + * lies and pretends to be OpenSSL 2.0.0). + */ +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + defined(BORINGSSL_201512) +#define HAVE_KEYLOG_CALLBACK +#endif + #if defined(LIBRESSL_VERSION_NUMBER) #define OSSL_PACKAGE "LibreSSL" #elif defined(OPENSSL_IS_BORINGSSL) @@ -165,11 +179,23 @@ static unsigned long OpenSSL_version_num(void) "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" #endif +#ifdef ENABLE_SSLKEYLOGFILE +typedef struct ssl_tap_state { + int master_key_length; + unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; + unsigned char client_random[SSL3_RANDOM_SIZE]; +} ssl_tap_state_t; +#endif /* ENABLE_SSLKEYLOGFILE */ + struct ssl_backend_data { /* these ones requires specific SSL-types */ SSL_CTX* ctx; SSL* handle; X509* server_cert; +#ifdef ENABLE_SSLKEYLOGFILE + /* tap_state holds the last seen master key if we're logging them */ + ssl_tap_state_t tap_state; +#endif }; #define BACKEND connssl->backend @@ -182,6 +208,112 @@ struct ssl_backend_data { */ #define RAND_LOAD_LENGTH 1024 +#ifdef ENABLE_SSLKEYLOGFILE +/* The fp for the open SSLKEYLOGFILE, or NULL if not open */ +static FILE *keylog_file_fp; + +#ifdef HAVE_KEYLOG_CALLBACK +static void ossl_keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + + /* Using fputs here instead of fprintf since libcurl's fprintf replacement + may not be thread-safe. */ + if(keylog_file_fp && line && *line) { + char stackbuf[256]; + char *buf; + size_t linelen = strlen(line); + + if(linelen <= sizeof(stackbuf) - 2) + buf = stackbuf; + else { + buf = malloc(linelen + 2); + if(!buf) + return; + } + strncpy(buf, line, linelen); + buf[linelen] = '\n'; + buf[linelen + 1] = '\0'; + + fputs(buf, keylog_file_fp); + if(buf != stackbuf) + free(buf); + } +} +#else +#define KEYLOG_PREFIX "CLIENT_RANDOM " +#define KEYLOG_PREFIX_LEN (sizeof(KEYLOG_PREFIX) - 1) +/* + * tap_ssl_key is called by libcurl to make the CLIENT_RANDOMs if the OpenSSL + * being used doesn't have native support for doing that. + */ +static void tap_ssl_key(const SSL *ssl, ssl_tap_state_t *state) +{ + const char *hex = "0123456789ABCDEF"; + int pos, i; + char line[KEYLOG_PREFIX_LEN + 2 * SSL3_RANDOM_SIZE + 1 + + 2 * SSL_MAX_MASTER_KEY_LENGTH + 1 + 1]; + const SSL_SESSION *session = SSL_get_session(ssl); + unsigned char client_random[SSL3_RANDOM_SIZE]; + unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; + int master_key_length = 0; + + if(!session || !keylog_file_fp) + return; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that + * we have a valid SSL context if we have a non-NULL session. */ + SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE); + master_key_length = + SSL_SESSION_get_master_key(session, master_key, SSL_MAX_MASTER_KEY_LENGTH); +#else + if(ssl->s3 && session->master_key_length > 0) { + master_key_length = session->master_key_length; + memcpy(master_key, session->master_key, session->master_key_length); + memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE); + } +#endif + + if(master_key_length <= 0) + return; + + /* Skip writing keys if there is no key or it did not change. */ + if(state->master_key_length == master_key_length && + !memcmp(state->master_key, master_key, master_key_length) && + !memcmp(state->client_random, client_random, SSL3_RANDOM_SIZE)) { + return; + } + + state->master_key_length = master_key_length; + memcpy(state->master_key, master_key, master_key_length); + memcpy(state->client_random, client_random, SSL3_RANDOM_SIZE); + + memcpy(line, KEYLOG_PREFIX, KEYLOG_PREFIX_LEN); + pos = KEYLOG_PREFIX_LEN; + + /* Client Random for SSLv3/TLS */ + for(i = 0; i < SSL3_RANDOM_SIZE; i++) { + line[pos++] = hex[client_random[i] >> 4]; + line[pos++] = hex[client_random[i] & 0xF]; + } + line[pos++] = ' '; + + /* Master Secret (size is at most SSL_MAX_MASTER_KEY_LENGTH) */ + for(i = 0; i < master_key_length; i++) { + line[pos++] = hex[master_key[i] >> 4]; + line[pos++] = hex[master_key[i] & 0xF]; + } + line[pos++] = '\n'; + line[pos] = '\0'; + + /* Using fputs here instead of fprintf since libcurl's fprintf replacement + may not be thread-safe. */ + fputs(line, keylog_file_fp); +} +#endif /* !HAVE_KEYLOG_CALLBACK */ +#endif /* ENABLE_SSLKEYLOGFILE */ + static const char *SSL_ERROR_to_str(int err) { switch(err) { @@ -756,6 +888,10 @@ static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) */ static int Curl_ossl_init(void) { +#ifdef ENABLE_SSLKEYLOGFILE + const char *keylog_file_name; +#endif + OPENSSL_load_builtin_modules(); #ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES @@ -792,6 +928,19 @@ static int Curl_ossl_init(void) OpenSSL_add_all_algorithms(); #endif +#ifdef ENABLE_SSLKEYLOGFILE + keylog_file_name = curl_getenv("SSLKEYLOGFILE"); + if(keylog_file_name && !keylog_file_fp) { + keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT); + if(keylog_file_fp) { + if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096)) { + fclose(keylog_file_fp); + keylog_file_fp = NULL; + } + } + } +#endif + return 1; } @@ -828,6 +977,13 @@ static void Curl_ossl_cleanup(void) SSL_COMP_free_compression_methods(); #endif #endif + +#ifdef ENABLE_SSLKEYLOGFILE + if(keylog_file_fp) { + fclose(keylog_file_fp); + keylog_file_fp = NULL; + } +#endif } /* @@ -2231,6 +2387,13 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) SSL_CTX_set_verify(BACKEND->ctx, verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ +#if defined(ENABLE_SSLKEYLOGFILE) && defined(HAVE_KEYLOG_CALLBACK) + if(keylog_file) { + SSL_CTX_set_keylog_callback(connssl->ctx, ossl_keylog_callback); + } +#endif + /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { result = (*data->set.ssl.fsslctx)(data, BACKEND->ctx, @@ -2325,6 +2488,11 @@ static CURLcode ossl_connect_step2(struct connectdata *conn, int sockindex) ERR_clear_error(); err = SSL_connect(BACKEND->handle); + /* If keylogging is enabled but the keylog callback is not supported then log + secrets here, immediately after SSL_connect by using tap_ssl_key. */ +#if defined(ENABLE_SSLKEYLOGFILE) && !defined(HAVE_KEYLOG_CALLBACK) + tap_ssl_key(BACKEND->handle, &BACKEND->tap_state); +#endif /* 1 is fine 0 is "not successful but was shut down controlled"