From 9fa13901cd459ea71543753a68fbf75c4f35c51e Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Tue, 23 Oct 2018 18:39:24 +0200 Subject: sslkeylog.c: support TLS 1.3 and OpenSSL 1.1.1 OpenSSL 1.1.1 adds TLS 1.3 support which uses a new secrets format. Previously it resulted in garbage keylog files, this has been fixed now. OpenSSL 1.1.1 also introduces a new API for secrets extraction. Consumers can use it like this (curl uses this code for example): static void keylog_callback(const SSL *ssl, const char *line) { /* write line and terminating '\n' */ } { SSL_CTX *ctx; ... SSL_CTX_set_keylog_callback(ctx, keylog_callback); SSL *ssl = SSL_new(ctx); } In case you cannot change the source code for an application, you can use sslkeylog.c again. This will basically perform the above step, set the key log callback before calling SSL_new. Since the new OpenSSL 1.1.1 API requires no further interception of SSL_read and other functions, a new NO_OPENSSL_110_SUPPORT macro was to avoid intercepting these. Additionally, a NO_OPENSSL_102_SUPPORT macro avoids the need for OpenSSL development headers. Caveat: when building with OpenSSL <= 1.0.2, libsslkeylog.so will not be compatible with runtime OpenSSL 1.1.0. OpenSSL 1.1.1 still works though. Use of SSL_new and interception via SSL_CTX_set_keylog_callback was initially proposed by Derick Rethans, thanks for the suggestion! --- src/sslkeylog.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/src/sslkeylog.c b/src/sslkeylog.c index fa46291..1617cd6 100644 --- a/src/sslkeylog.c +++ b/src/sslkeylog.c @@ -1,6 +1,7 @@ /* * Dumps master keys for OpenSSL clients to file. The format is documented at * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format + * Supports TLS 1.3 when used with OpenSSL 1.1.1. * * Copyright (C) 2014 Peter Wu * Licensed under the terms of GPLv3 (or any later version) at your choice. @@ -9,9 +10,28 @@ * cc sslkeylog.c -shared -o libsslkeylog.so -fPIC -ldl * SSLKEYLOGFILE=premaster.txt LD_PRELOAD=./libsslkeylog.so openssl ... */ + +/* + * A single libsslkeylog.so supports multiple OpenSSL runtime versions. If you + * would like to build this library without OpenSSL development headers and do + * not require support for older OpenSSL versions, then disable it by defining + * the NO_OPENSSL_102_SUPPORT or NO_OPENSSL_110_SUPPORT macros. + */ +/* Define to drop OpenSSL <= 1.0.2 support and require OpenSSL >= 1.1.0. */ +//#define NO_OPENSSL_102_SUPPORT +/* Define to drop OpenSSL <= 1.1.0 support and require OpenSSL >= 1.1.1. */ +//#define NO_OPENSSL_110_SUPPORT + +/* No OpenSSL 1.1.0 support implies no OpenSSL 1.0.2 support. */ +#ifdef NO_OPENSSL_110_SUPPORT +# define NO_OPENSSL_102_SUPPORT +#endif + #define _GNU_SOURCE /* for RTLD_NEXT */ #include -#include +#ifndef NO_OPENSSL_102_SUPPORT +# include +#endif /* ! NO_OPENSSL_102_SUPPORT */ #include #include #include @@ -20,17 +40,33 @@ #ifndef OPENSSL_SONAME /* fallback library if OpenSSL is not already loaded. Other values to try: - * libssl.so.0.9.8 libssl.so.1.0.0 */ + * libssl.so.0.9.8 libssl.so.1.0.0 libssl.so.1.1 */ # define OPENSSL_SONAME "libssl.so" #endif -#define PREFIX "CLIENT_RANDOM " -#define PREFIX_LEN (sizeof(PREFIX) - 1) #define FIRSTLINE "# SSL key logfile generated by sslkeylog.c\n" #define FIRSTLINE_LEN (sizeof(FIRSTLINE) - 1) +/* When building for OpenSSL 1.1.0 or newer, no headers are required. */ +#ifdef NO_OPENSSL_102_SUPPORT +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; +/* Extra definitions for OpenSSL 1.1.0 support when headers are unavailable. */ +# ifndef NO_OPENSSL_110_SUPPORT +typedef struct ssl_session_st SSL_SESSION; +# define SSL3_RANDOM_SIZE 32 +# define SSL_MAX_MASTER_KEY_LENGTH 48 +# define OPENSSL_VERSION_NUMBER 0x10100000L +# endif /* ! NO_OPENSSL_110_SUPPORT */ +#endif /* ! NO_OPENSSL_102_SUPPORT */ + static int keylog_file_fd = -1; +/* Legacy routines for dumping TLS <= 1.2 secrets on older OpenSSL versions. */ +#ifndef NO_OPENSSL_110_SUPPORT +#define PREFIX "CLIENT_RANDOM " +#define PREFIX_LEN (sizeof(PREFIX) - 1) + static inline void put_hex(char *buffer, int pos, char c) { unsigned char c1 = ((unsigned char) c) >> 4; @@ -64,6 +100,7 @@ static void dump_to_fd(int fd, unsigned char *client_random, * write access so do not write hex values one by one. */ write(fd, line, pos); } +#endif /* ! NO_OPENSSL_110_SUPPORT */ static void init_keylog_file(void) { @@ -102,6 +139,7 @@ static inline void *lookup_symbol(const char *sym) return func; } +#ifndef NO_OPENSSL_110_SUPPORT typedef struct ssl_tap_state { int master_key_length; unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; @@ -156,9 +194,29 @@ static void copy_client_random(const SSL *ssl, unsigned char *client_random) #endif } +/* non-NULL if the new OpenSSL 1.1.1 keylog API is supported. */ +static void *new_keylog_api(void) +{ + static void *set_keylog_cb = (void *)-1; + if (set_keylog_cb == (void *)-1) { + /* + * Assume OpenSSL is already loaded and try to locate the OpenSSL 1.1.1 + * keylog API. Do not fallback to lookup_symbol as that could result in + * loading a different, incompatible version of libssl.so. + */ + set_keylog_cb = dlsym(RTLD_NEXT, "SSL_CTX_set_keylog_callback"); + } + return set_keylog_cb; +} + /* Copies SSL state for later comparison in tap_ssl_key. */ static void ssl_tap_state_init(ssl_tap_state_t *state, const SSL *ssl) { + if (new_keylog_api()) { + /* Favor using the callbacks API to extract secrets. */ + return; + } + const SSL_SESSION *session = ssl_get_session(ssl); memset(state, 0, sizeof(ssl_tap_state_t)); @@ -173,6 +231,11 @@ static void ssl_tap_state_init(ssl_tap_state_t *state, const SSL *ssl) static void tap_ssl_key(const SSL *ssl, ssl_tap_state_t *state) { + if (new_keylog_api()) { + /* Favor using the callbacks API to extract secrets. */ + return; + } + const SSL_SESSION *session = ssl_get_session(ssl); unsigned char client_random[SSL3_RANDOM_SIZE]; unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; @@ -261,3 +324,35 @@ int SSL_write(SSL *ssl, const void *buf, int num) tap_ssl_key(ssl, &state); return ret; } +#endif /* ! NO_OPENSSL_110_SUPPORT */ + +/* Key extraction via the new OpenSSL 1.1.1 API. */ +static void keylog_callback(const SSL *ssl, const char *line) +{ + init_keylog_file(); + if (keylog_file_fd >= 0) { + write(keylog_file_fd, line, strlen(line)); + write(keylog_file_fd, "\n", 1); + } +} + +SSL *SSL_new(SSL_CTX *ctx) +{ + static SSL *(*func)(); + static void (*set_keylog_cb)(); + if (!func) { + func = lookup_symbol(__func__); +#ifdef NO_OPENSSL_110_SUPPORT + /* The new API MUST be available since OpenSSL 1.1.1. */ + set_keylog_cb = lookup_symbol("SSL_CTX_set_keylog_callback"); +#else /* ! NO_OPENSSL_110_SUPPORT */ + /* May be NULL if used with an older OpenSSL runtime library. */ + set_keylog_cb = new_keylog_api(); +#endif /* ! NO_OPENSSL_110_SUPPORT */ + } + if (set_keylog_cb) { + /* Override any previous key log callback. */ + set_keylog_cb(ctx, keylog_callback); + } + return func(ctx); +} -- cgit v1.2.1