diff options
| -rw-r--r-- | lib/curl_setup.h | 3 | ||||
| -rw-r--r-- | lib/vtls/openssl.c | 168 | 
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"  | 
