summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/curl_setup.h3
-rw-r--r--lib/vtls/openssl.c168
2 files changed, 171 insertions, 0 deletions
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"