wolfssl: Add SSLKEYLOGFILE support - #5327
Conversation
42ca65a to
9b3883a
Compare
Definitely. The SSLKEYLOGFILE code in ngtcp2 needs to be changed for thread safety, you can see an attempt in #4311 that I abandoned. I suggest a separate file in vtls sslkeylogfile.c and then put everything there. |
9b3883a to
ed3e1aa
Compare
|
I've added a new keylog.c implementation and adapted OpenSSL and WolfSSL to use it. Test instructions can be found in the commit messages. None of the documentation seem to require updates, I could update libcurl-env.3 with version numbers and backends, but maybe that is too much detail? I've a patch to add support for ngtcp2 as well but still have to test it. It could be done as part of this PR or a new PR once the patch is ready. With the multiple TLS backends feature, only one of them can every be loaded it seems. So there is no concern about opening/closing the keylog file multiple times. However with addition of the feature to vquic, |
ed3e1aa to
55d72f8
Compare
|
Rebased on master and tested against ngtcp2 + openssl (built using CMake - patches for this will follow in a separate PR). 👀 are welcome! |
55d72f8 to
3c4ed37
Compare
b3a7991 to
98f0681
Compare
|
Rebased again on master (no changes). The previous tests failed due to ngtcp2 build errors, that should have been fixed in master now. Feature freeze is coming up :) |
Create a set of routines for TLS key log file handling to enable reuse
with other TLS backends. Simplify the OpenSSL backend as follows:
- Drop the ENABLE_SSLKEYLOGFILE macro as it is unconditionally enabled.
- Do not perform dynamic memory allocation when preparing a log entry.
Unless the TLS specifications change we can suffice with a reasonable
fixed-size buffer.
- Simplify state tracking when SSL_CTX_set_keylog_callback is
unavailable. My original sslkeylog.c code included this tracking in
order to handle multiple calls to SSL_connect and detect new keys
after renegotiation (via SSL_read/SSL_write). For curl however we can
be sure that a single master secret eventually becomes available
after SSL_connect, so a simple flag is sufficient. An alternative to
the flag is examining SSL_state(), but this seems more complex and is
not pursued. Capturing keys after server renegotiation was already
unsupported in curl and remains unsupported.
Tested with curl built against OpenSSL 0.9.8zh, 1.0.2u, and 1.1.1f
(`SSLKEYLOGFILE=keys.txt curl -vkso /dev/null https://localhost:4433`)
against an OpenSSL 1.1.1f server configured with:
# Force non-TLSv1.3, use TLSv1.0 since 0.9.8 fails with 1.1 or 1.2
openssl s_server -www -tls1
# Likewise, but fail the server handshake.
openssl s_server -www -tls1 -Verify 2
# TLS 1.3 test. No need to test the failing server handshake.
openssl s_server -www -tls1_3
Verify that all secrets (1 for TLS 1.0, 4 for TLS 1.3) are correctly
written using Wireshark. For the first and third case, expect four
matches per connection (decrypted Server Finished, Client Finished, HTTP
Request, HTTP Response). For the second case where the handshake fails,
expect a decrypted Server Finished only.
tshark -i lo -pf tcp -otls.keylog_file:keys.txt -Tfields \
-eframe.number -eframe.time -etcp.stream -e_ws.col.Info \
-dtls.port==4433,http -ohttp.desegment_body:FALSE \
-Y 'tls.handshake.verify_data or http'
A single connection can easily be identified via the `tcp.stream` field.
|
Hmm, a previous build with Warnings: This is tricky. I see there is Based on the git logs for wolfssl src/tls13.c:
I'll assume it is acceptable not to support wolfSSL 3.12 where TLS 1.3 is enabled. Fixed by using a different API with the same result. After fixing that, I ran into yet another issue. The public header make |
Tested following the same curl and tshark commands as in commit "vtls: Extract and simplify key log file handling from OpenSSL" using WolfSSL v4.4.0-stable-128-g5179503e8 from git master built with `./configure --enable-all --enable-debug CFLAGS=-DHAVE_SECRET_CALLBACK`. Full support for this feature requires certain wolfSSL build options, see "Availability note" in lib/vtls/wolfssl.c for details. Closes curl#5327
Tested with ngtcp2 built against the OpenSSL library. Additionally tested with MultiSSL (NSS for TLS and ngtcp2+OpenSSL for QUIC). The TLS backend (independent of QUIC) may or may not already have opened the keylog file before. Therefore Curl_tls_keylog_open is always called to ensure the file is open.
98f0681 to
19f001d
Compare
|
I amended the existing wolfssl patch with the following and referenced the "Availability note" in the commit message: diff--- a/lib/vtls/wolfssl.c
+++ b/lib/vtls/wolfssl.c
@@ -100,12 +100,15 @@ struct ssl_backend_data {
static Curl_recv wolfssl_recv;
static Curl_send wolfssl_send;
+#ifdef OPENSSL_EXTRA
/*
* Availability note:
* The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in
* WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that
* option is not set, then TLS 1.3 will not be logged.
* For TLS 1.2 and before, we use wolfSSL_get_keys().
+ * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA
+ * (--enable-opensslextra or --enable-all).
*/
#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13)
static int
@@ -162,17 +165,23 @@ wolfssl_log_tls12_secret(SSL *ssl)
unsigned char *ms, *sr, *cr;
unsigned int msLen, srLen, crLen, i, x = 0;
- switch(SSL_version(ssl)) {
- case SSL3_VERSION:
- case TLS1_VERSION:
- case TLS1_1_VERSION:
- case TLS1_2_VERSION:
+#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000 /* >= 3.13.0 */
+ /* wolfSSL_GetVersion is available since 3.13, we use it instead of
+ * SSL_version since the latter relies on OPENSSL_ALL (--enable-opensslall or
+ * --enable-all). Failing to perform this check could result in an unusable
+ * key log line when TLS 1.3 is actually negotiated. */
+ switch(wolfSSL_GetVersion(ssl)) {
+ case WOLFSSL_SSLV3:
+ case WOLFSSL_TLSV1:
+ case WOLFSSL_TLSV1_1:
+ case WOLFSSL_TLSV1_2:
break;
default:
/* TLS 1.3 does not use this mechanism, the "master secret" returned below
* is not directly usable. */
return;
}
+#endif
if(SSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != SSL_SUCCESS) {
return;
@@ -190,6 +199,7 @@ wolfssl_log_tls12_secret(SSL *ssl)
Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen);
}
+#endif /* OPENSSL_EXTRA */
static int do_file_type(const char *type)
{
@@ -477,6 +487,7 @@ wolfssl_connect_step1(struct connectdata *conn,
}
#endif /* HAVE_ALPN */
+#ifdef OPENSSL_EXTRA
if(Curl_tls_keylog_enabled()) {
/* Ensure the Client Random is preserved. */
wolfSSL_KeepArrays(backend->handle);
@@ -485,6 +496,7 @@ wolfssl_connect_step1(struct connectdata *conn,
wolfssl_tls13_secret_callback, NULL);
#endif
}
+#endif /* OPENSSL_EXTRA */
/* Check if there's a cached ID we can/should use here! */
if(SSL_SET_OPTION(primary.sessionid)) {
@@ -546,23 +558,29 @@ wolfssl_connect_step2(struct connectdata *conn,
ret = SSL_connect(backend->handle);
+#ifdef OPENSSL_EXTRA
if(Curl_tls_keylog_enabled()) {
/* If key logging is enabled, wait for the handshake to complete and then
* proceed with logging secrets (for TLS 1.2 or older).
*
- * During the handshake (ret==-1), WolfSSL_want returns SSL_READING when it
- * is waiting for the server response. When the handshake finishes (success
- * or failure), it returns SSL_NOTHING and we may read the master secret.
- * Note that OpenSSL SSL_want always returns SSL_READING at this point.
- * If this ever changes, the worst case is that no key is logged on error.
+ * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits
+ * for the server response. At that point the master secret is not yet
+ * available, so we must not try to read it.
+ * To log the secret on completion with a handshake failure, detect
+ * completion via the observation that there is nothing to read or write.
+ * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever
+ * changes, the worst case is that no key is logged on error.
*/
- if(ret == SSL_SUCCESS || SSL_want(backend->handle) == SSL_NOTHING) {
+ if(ret == SSL_SUCCESS ||
+ (!wolfSSL_want_read(backend->handle) &&
+ !wolfSSL_want_write(backend->handle))) {
wolfssl_log_tls12_secret(backend->handle);
/* Client Random and master secrets are no longer needed, erase these.
* Ignored while the handshake is still in progress. */
wolfSSL_FreeArrays(backend->handle);
}
}
+#endif /* OPENSSL_EXTRA */
if(ret != 1) {
char error_buffer[WOLFSSL_MAX_ERROR_SZ];
@@ -870,7 +888,9 @@ static size_t Curl_wolfssl_version(char *buffer, size_t size)
static int Curl_wolfssl_init(void)
{
+#ifdef OPENSSL_EXTRA
Curl_tls_keylog_open();
+#endif
return (wolfSSL_Init() == SSL_SUCCESS);
}
@@ -878,7 +898,9 @@ static int Curl_wolfssl_init(void)
static void Curl_wolfssl_cleanup(void)
{
wolfSSL_Cleanup();
+#ifdef OPENSSL_EXTRA
Curl_tls_keylog_close();
+#endif
}This was tested with a bunch of wolfSSL v4.4.0-stable-128-g5179503e8 configs: Does this still look fine to you @bagder? |
|
👍 from me! |
Refactored SSLKEYLOGFILE support in OpenSSL and extended support to WolfSSL.
Tested with OpenSSL 0.9.8zh, 1.0.2u, and 1.1.1f and WolfSSL v4.4.0-stable-44-g3944c8eb7 and TLS 1.0/1.3, see individual commit messages for details.
DONE:
Update documentation(not needed)