-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
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
Conversation
001b5ec
to
2c321a5
Compare
ssl/ssl_lib.c
Outdated
client_random_len, | ||
secret, | ||
secret_len); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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
...
Can you split the bugfix into a separate PR? No need to hold that up for the TLS 1.3 review part. Thanks! |
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. |
ssl/statem/statem_lib.c
Outdated
s->s3->client_random, | ||
SSL3_RANDOM_SIZE, | ||
s->session->master_key, | ||
s->session->master_key_length)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
#2288 just got merged, so this should build as soon as it's rebased |
e62f9f6
to
ac6a3fd
Compare
Alrighty, updated. |
Hrm, what's up with that Windows build? It seems like there's a problem with the linkage on Windows. |
In |
Hold that thought, you did guard it against building when configured |
Found it. You got the condition in 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 |
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 |
I mean, I didn't touch test/build.info at all. ;) But I'm certainly happy to add that delta to my patch. |
So why was this working before? |
I have no clue. did we fail to pay attention to Windows back in #1646? |
Reading the comment (which I wrote) it doesn't make any sense:
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. |
FYI, from a posting in the TLS mailing list:
|
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. |
Ok, I'll experiment a bit more... |
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 |
Unfortunately, |
Matt, this PR (and some other implementations) are mentioned in the commit message of https://code.wireshark.org/review/19801 |
@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 |
Yeah, we can do that. |
75f8c2a
to
c4fcf1b
Compare
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; |
There was a problem hiding this comment.
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.
test/sslapitest.c
Outdated
return 0; | ||
} | ||
|
||
/* TODO: Can I get this key out? */ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. 😁
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
ssl/statem/statem_lib.c
Outdated
*/ | ||
if (!SSL_IS_TLS13(s) && !ssl_log_secret(s, MASTER_SECRET_LABEL, | ||
s->session->master_key, | ||
s->session->master_key_length)) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
test/sslapitest.c
Outdated
} | ||
token = strtok(NULL, " \n"); | ||
if (!token) { | ||
printf("Unexpectedly short premaster secret log.\n"); | ||
return -1; | ||
return 0; | ||
} | ||
/* TODO: Can I check this sensibly? */ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. =)
@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. |
c4fcf1b
to
daa317c
Compare
Ok, I've updated in response to code review. |
daa317c
to
f906a5b
Compare
There was a problem hiding this 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.
ssl/statem/statem_lib.c
Outdated
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. | ||
*/ |
There was a problem hiding this comment.
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.
*/
test/sslapitest.c
Outdated
@@ -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. | |||
*/ |
There was a problem hiding this comment.
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
test/sslapitest.c
Outdated
/* 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. */ |
There was a problem hiding this comment.
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
test/sslapitest.c
Outdated
* 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. | ||
*/ |
There was a problem hiding this comment.
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
test/sslapitest.c
Outdated
} | ||
|
||
/* TODO(TLS1.3): test that application traffic secrets are what | ||
* we expect */ |
There was a problem hiding this comment.
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
test/sslapitest.c
Outdated
@@ -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. |
There was a problem hiding this comment.
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
test/sslapitest.c
Outdated
@@ -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. |
There was a problem hiding this comment.
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
f906a5b
to
0e042db
Compare
Updated the multiline comments. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed one.
test/sslapitest.c
Outdated
/* | ||
* 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. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this one.
0e042db
to
b64db11
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup
There was a problem hiding this 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.
Pushed. Thanks!! |
Reviewed-by: Richard Levitte <levitte@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from #2287)
Reviewed-by: Richard Levitte <levitte@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from #2287)
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. |
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
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. |
In addition to @levitte's comment, the key logging API will also be in 1.1.1 |
Checklist
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.