Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for logging out TLSv1.3 secrets. #2287

Closed
wants to merge 2 commits into from

Conversation

Lukasa
Copy link
Contributor

@Lukasa Lukasa commented Jan 25, 2017

Checklist
  • tests are added or updated
  • CLA is signed
Description of change

Follows on from #1646.

This change adds support for logging out several of the secrets from the TLSv1.3 key schedule for debugging purposes. These secrets are the same as the ones logged by BoringSSL, and are logged in the same format. They are used to continue to allow tools like Wireshark to decrypt TLS traffic when needed.

This change also fixes a bug from the previous code. This slipped past because the tests did not adequately validate the RSA key exchange logging actually occurred: they would pass even if that log never fired. This patch updates the test to correctly exercise that code, and then fixes the underlying bug as well.

I strongly recommend at least one TLSv1.3 expert validates this code. I did my best to read the draft specification and log at the appropriate point, but it is not yet possible to sensibly validate that these logs are actually ok. We should confirm that the TLSv1.3 logs are actually logging the correct secret before we merge this.

ssl/ssl_lib.c Outdated
client_random_len,
secret,
secret_len);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is basically identical to the one above it, except that the one above doesn't fire for TLSv1.3. I suspect we should collapse these two and hoist up the check for TLSv1.3 into the calling scope of the above function, but I wanted a code reviewer to confirm that was a good idea.

An alternative would be to have the function above call this one: either would work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it could be merged with the function above. Have a TLSv1.3 section and a non-TLSv1.3 one.

ssl/ssl_lib.c Outdated
@@ -4413,15 +4413,20 @@ int ssl_log_rsa_client_key_exchange(SSL *ssl,
const uint8_t *premaster,
size_t premaster_len)
{
uint8_t premaster_tag[8];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that you don't use this for anything useful. Isn't it enough that the length parameter for the encrypted premaster was changed to 8 in the call to nss_keylog_int?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh...yes, yes it is. Honestly not sure what I was thinking there.

ssl/ssl_lib.c Outdated
if (client_random_len != 32) {
SSLerr(SSL_F_SSL_LOG_MASTER_SECRET, ERR_R_INTERNAL_ERROR);
return 0;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to think this might do better as an assertion. After all, it is a length that we specify internally and that we should get correctly at all times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

ssl/ssl_lib.c Outdated
client_random_len,
secret,
secret_len);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it could be merged with the function above. Have a TLSv1.3 section and a non-TLSv1.3 one.

ssl/tls13_enc.c Outdated
} else {
insecret = s->session->master_key;
label = server_application_traffic;
labellen = sizeof(server_application_traffic) - 1;
log_label = "SERVER_TRAFFIC_SECRET_0";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe these strings should be made into macros in ssl_locl.h...

@richsalz
Copy link
Contributor

Can you split the bugfix into a separate PR? No need to hold that up for the TLS 1.3 review part. Thanks!

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 25, 2017

@richsalz My gift to you: #2288.

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 25, 2017

Ok, I've updated a bunch of stuff.

Right now the tests will fail because the RSA log doesn't come out correctly, so the tests won't pass until #2288 is merged. At that point, I'll rebase onto a new master and we should be good to go.

s->s3->client_random,
SSL3_RANDOM_SIZE,
s->session->master_key,
s->session->master_key_length))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation of the ssl_log_secret parameters is off by one space. If you used tabs, convert them to spaces, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@levitte
Copy link
Member

levitte commented Jan 25, 2017

#2288 just got merged, so this should build as soon as it's rebased

@Lukasa Lukasa force-pushed the tlsv13-keylogs branch 2 times, most recently from e62f9f6 to ac6a3fd Compare January 25, 2017 21:21
@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 25, 2017

Alrighty, updated.

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 26, 2017

Hrm, what's up with that Windows build? It seems like there's a problem with the linkage on Windows.

@levitte
Copy link
Member

levitte commented Jan 26, 2017

In test/build.info, you need to add ../ssl/ssl_lib.c to the source files for tls13secretstest

@levitte
Copy link
Member

levitte commented Jan 26, 2017

Hold that thought, you did guard it against building when configured shared... I'll have a closer look

@levitte
Copy link
Member

levitte commented Jan 26, 2017

Found it. You got the condition in test/build.info wrong:

diff --git a/test/build.info b/test/build.info
index c11623894..b1a69da24 100644
--- a/test/build.info
+++ b/test/build.info
@@ -371,7 +371,7 @@ IF[{- !$disabled{tests} -}]
   # We disable this test completely in a shared build because it deliberately
   # redefines some internal libssl symbols. This doesn't work in a non-shared
   # build
-  IF[{- !$disabled{shared} -}]
+  IF[{- $disabled{shared} -}]
     PROGRAMS_NO_INST=tls13secretstest
     SOURCE[tls13secretstest]=tls13secretstest.c testutil.c test_main.c
     SOURCE[tls13secretstest]= ../ssl/tls13_enc.c ../ssl/packet.c

@levitte
Copy link
Member

levitte commented Jan 26, 2017

As a matter of fact, you can remove one of the source lines as well:

diff --git a/test/build.info b/test/build.info
index c11623894..579cf7562 100644
--- a/test/build.info
+++ b/test/build.info
@@ -371,10 +371,9 @@ IF[{- !$disabled{tests} -}]
   # We disable this test completely in a shared build because it deliberately
   # redefines some internal libssl symbols. This doesn't work in a non-shared
   # build
-  IF[{- !$disabled{shared} -}]
+  IF[{- $disabled{shared} -}]
     PROGRAMS_NO_INST=tls13secretstest
     SOURCE[tls13secretstest]=tls13secretstest.c testutil.c test_main.c
-    SOURCE[tls13secretstest]= ../ssl/tls13_enc.c ../ssl/packet.c
     INCLUDE[tls13secretstest]=.. ../include
     DEPEND[tls13secretstest]=../libcrypto ../libssl
   ENDIF

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 26, 2017

I mean, I didn't touch test/build.info at all. ;) But I'm certainly happy to add that delta to my patch.

@mattcaswell
Copy link
Member

So why was this working before?

@levitte
Copy link
Member

levitte commented Jan 26, 2017

So why was this working before?

I have no clue. did we fail to pay attention to Windows back in #1646?

@mattcaswell
Copy link
Member

Reading the comment (which I wrote) it doesn't make any sense:

   # We disable this test completely in a shared build because it deliberately
   # redefines some internal libssl symbols. This doesn't work in a non-shared
   # build

We disable the shared build because it doesn't work in non-shared??? Huh? I really have no idea what I was thinking at the time.

Anyway - it would be a shame to lose this from testing in shared builds because that is the default.

@richsalz
Copy link
Contributor

FYI, from a posting in the TLS mailing list:

This is indeed work in progress, the current state can be tracked at:
https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=12779

Note for TLS implementers: Wireshark supports decryption when provided with the master secret (TLS 1.2 and before), but with TLS 1.3 there are more secrets. The current plan is to accept the client/server handshake/application traffic secrets (as opposed to the more sensitive Handshake/Master secrets) following the format proposed by BoringSSL:
https://code.wireshark.org/review/19801

If everything goes well, Wireshark 2.4 should be the first stable version with TLS 1.3 support.

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 26, 2017

No, we didn't support TLSv1.3 in #1646. So I don't think these functions would have needed to be linked in. The change here is that we now log from tls13_enc.c.

@levitte
Copy link
Member

levitte commented Jan 26, 2017

Ok, I'll experiment a bit more...

@mattcaswell
Copy link
Member

No, we didn't support TLSv1.3 in #1646. So I don't think these functions would have needed to be linked in. The change here is that we now log from tls13_enc.c.

Right but the test existed and works fine in master now. So why should we need to change it to work only on non-shared because you added a function call?

@levitte
Copy link
Member

levitte commented Jan 26, 2017

Right but the test existed and works fine in master now. So why should we need to change it to work only on non-shared because you added a function call?

Because ssl/tls13_enc.c, which is mentioned as a specific source module for tls13secretstest, now calls ssl_log_secret, which is a private symbol in ssl/ssl_lib.c

@levitte
Copy link
Member

levitte commented Jan 26, 2017

Unfortunately, ssl/ssl_lib.c has dependencies to a number of libssl modules...

@Lekensteyn
Copy link
Contributor

Matt, this PR (and some other implementations) are mentioned in the commit message of https://code.wireshark.org/review/19801

@levitte
Copy link
Member

levitte commented Jan 26, 2017

@Lukasa, I think that the best you can do to resolve this is to move the key logging functions to a separate source file, so tls13secretstest doesn't have to depend on ssl/ssl_lib.c...

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 26, 2017

Yeah, we can do that. ssl_log.c acceptable? (I considered calling it ssl_keylog.c but I figured that'd give the tinfoil-hat brigade heart attacks.)

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 31, 2017

Ok, I've squashed this all down to two commits. I'm pretty happy with this at the moment, though as always better testing would be better. Should be good for another round of code review.

ssl/tls13_enc.c Outdated
@@ -370,6 +375,11 @@ int tls13_change_cipher_state(SSL *s, int which)
keylen = EVP_CIPHER_key_length(ciph);
ivlen = EVP_CIPHER_iv_length(ciph);

if (!ssl_log_secret(s, log_label, secret, hashlen)) {
SSLerr(SSL_F_TLS13_CHANGE_CIPHER_STATE, ERR_R_INTERNAL_ERROR);
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "goto err" to make sure secret is cleansed.

return 0;
}

/* TODO: Can I get this key out? */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this means?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially it means: can I not just confirm that the structure is right, but actually validate the secret? Is it hung off an object that I can use to compare it against, or can I derive it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only way to do that would be to reach inside the SSL object. That might not be an unreasonable thing to do in a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So all four of the derived secrets we're logging out here are stored on the SSL object after the connection is established?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well not exactly. We store the handshake_secret and the master_secret. We also store the server finished hash. That should be enough to derive the two application traffic secrets. We don't store the hash of ClientHello..ServerHello which is necessary for the handshake traffic secrets. It would be possible to calculate the hash using a custom BIO to watch the communication between client and server and pick out the first two messages, because those messages are sent in the clear....but that is definitely harder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm.

This is ultimately a balance between the effort needed to write this test code and risk of this code breaking or being wrong. BoringSSL has no tests for this functionality because ultimately it's not that important to them.

The breakage we're risking here is a breakage where the secret we log out is not right. This kind of breakage is of low likelihood once the code is written, but unless we test that it's right we don't really stand a chance of preventing it. Of course, the amount of code required to get it right is...high.

Ultimately this is a question for the maintenance team: all patches should be written with the assumption that the contributor will be hit by a bus 20 seconds after it's merged and the maintenance team will have to live with it with no assistance for the rest of time. So it's your call, not mine. 😁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking it might be useful to do at least the application traffic secrets bit - it has value as a test beyond the logging functionality. However, thinking about it more you would need to have access to the internal tls13_hkdf_expand() function which, wile doable, probably would mean moving the test to the tls13secretstest.c file. Perhaps a separate PR? For now just leave the TODO in, make it something like TODO(TLS1.3): test that application traffic secrets are what we expect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's clarify the TODO and consider approaching it in a further PR.

*/
if (!SSL_IS_TLS13(s) && !ssl_log_secret(s, MASTER_SECRET_LABEL,
s->session->master_key,
s->session->master_key_length))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is not aligned

ssl/tls13_enc.c Outdated
@@ -261,6 +261,7 @@ int tls13_change_cipher_state(SSL *s, int which)
unsigned char *hash = hashval;
unsigned char *insecret;
unsigned char *finsecret = NULL;
char *log_label = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be const char?

}
token = strtok(NULL, " \n");
if (!token) {
printf("Unexpectedly short premaster secret log.\n");
return -1;
return 0;
}
/* TODO: Can I check this sensibly? */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this old comment refer to? maybe it can be removed now after you check the count?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a similar question about whether I can extract the RSA premaster secret once the test has run to validate the secret. An OpenSSL core dev would probably be able to tell me this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the pre-master is held only transiently. Once we've generated the master secret it is removed. In theory you could probably derive the master secret independently from the logged pre-master and check that it is equal to the master in the session...but that is non-trivial.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then, in that case I'll remove the comment as written and replace it with one that notes that we can't easily validate the log. =)

@Lekensteyn
Copy link
Contributor

@Lukasa Without early data the secret would not be very useful (nothing to test). BoringSSL also does not implement early data as far as I could see, so that did not help either. I should probably look into Tris (Go implementation from Cloudflare) for a more complete 0-RTT implementation for testing.

@levitte levitte dismissed their stale review January 31, 2017 19:19

My change requests have been answered

@Lukasa
Copy link
Contributor Author

Lukasa commented Feb 1, 2017

Ok, I've updated in response to code review.

Copy link
Member

@mattcaswell mattcaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I spotted some style nits for multi-line comments that I hadn't noticed before. Approved subject to those being fixed.

s->session->master_key_length))
/* Log the master secret, if logging is enabled. We don't log it for
* TLSv1.3: there's a different key schedule for that.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Multi-line comments should look like this:

 /*
 * Log the master secret, if logging is enabled. We don't log it for
 * TLSv1.3: there's a different key schedule for that.
 */

@@ -41,6 +41,18 @@ static X509 *ocspcert = NULL;

#define NUM_EXTRA_CERTS 40

/* This structure is used to validate that the correct number of log messages
* of various types are emitted when emitting secret logs.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

/* TODO: Can I check this sensibly? */
/* We can't sensibly check the log because the premaster secret is
* transient, and OpenSSL doesn't keep hold of it once the master
* secret is generated. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

* client random, and then the hex-encoded secret. In this case,
* we treat all of these secrets identically and then just
* distinguish between them when counting what we saw.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

}

/* TODO(TLS1.3): test that application traffic secrets are what
* we expect */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

@@ -271,15 +348,20 @@ static int test_keylog(void) {

/* Now we want to test that our output data was vaguely sensible. We
* do that by using strtok and confirming that we have more or less the
* data we expect.
* data we expect. For both client and server, we expect to see one master
* secret. The client should also see a RSA key exchange.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

@@ -355,15 +438,20 @@ static int test_keylog_no_master_key(void) {
}

/* Now we want to test that our output data was vaguely sensible. For this
* test, we expect no CLIENT_RANDOM entry.
* test, we expect no CLIENT_RANDOM entry becuase it doesn't make sense for
* TLSv1.3, but we do expect both client and server to emit keys.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: multi-line comment style

@Lukasa
Copy link
Contributor Author

Lukasa commented Feb 1, 2017

Updated the multiline comments.

Copy link
Member

@mattcaswell mattcaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great...still missed a couple though. Also needs a second team review...@levitte?

ssl/ssl_locl.h Outdated
/* ssl_log_master_secret logs |master| to the SSL_CTX associated with |ssl|, if
* logging is enabled. It returns one on success and zero on failure. The entry
* is identified by |client_random|.
/* ssl_log_secret logs |secret| to the SSL_CTX associated with |ssl|, if
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed one.

/*
* We can't sensibly check the log because the premaster secret is
* transient, and OpenSSL doesn't keep hold of it once the master
* secret is generated. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this one.

Copy link
Member

@levitte levitte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup

Copy link
Contributor

@Lekensteyn Lekensteyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I have tested it with a modified s_client version and am able to decrypt the handshake and application data in both direction with Wireshark. Will put a PR for that as well.

@mattcaswell
Copy link
Member

Pushed. Thanks!!

@mattcaswell mattcaswell closed this Feb 2, 2017
levitte pushed a commit that referenced this pull request Feb 2, 2017
Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from #2287)
levitte pushed a commit that referenced this pull request Feb 2, 2017
Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from #2287)
@Lekensteyn
Copy link
Contributor

What is the first public version where support for this API can be expected? 1.1.1, 1.2.0 or something else?

What is the suggested method to detect the API availability? There is no macro, so either there will be ifdef checks against the version or there needs to be a configure-time check.

Lekensteyn added a commit to Lekensteyn/curl that referenced this pull request Mar 22, 2017
NSS and GnuTLS support the SSLKEYLOGFILE method to configure the TLS
keylog file, but OpenSSL requires application to configure it
explicitly.

This patch adds an opt-in feature that enables keylogging for OpenSSL.
libcurl applications linked with OpenSSL that would like to disable this
feature can add this to their CURLOPT_SSL_CTX_FUNCTION callback:

    SSL_CTX_set_keylog_callback((SSL_CTX *)sslctx, NULL)

TODO: feature detection, openssl/openssl#2287 (comment)
TODO: update docs? Where to mention this?

Ref curl#1030
@levitte
Copy link
Member

levitte commented Mar 22, 2017

Our aim is to release a 1.1.1 that includes TLS 1.3. Either way, the following would be safe to check for the availability:

#if OPENSSL_VERSION_NUMBER >= 0x10101000L

The reasoning is that TLSv1.3 can only appear in the next feature release, which must at least be 1.1.1.

@mattcaswell
Copy link
Member

In addition to @levitte's comment, the key logging API will also be in 1.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants