Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Bug 1287711 - Implement SSLKEYLOGFILE for TLS 1.3 (v2)
Summary:
Extend the previous keylogging functionality with TLS 1.3 support.
Verified that a session between nss (selfserv) and boringssl 15868b3bbaa
resulted in the same keys and that it can be used with Wireshark.


Reviewers: mt, ekr

Reviewed By: mt

Bug #: 1287711

Differential Revision: https://phabricator.services.mozilla.com/D82
  • Loading branch information
martinthomson committed Oct 3, 2017
1 parent deeb8aa commit 9f29abe
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 36 deletions.
1 change: 1 addition & 0 deletions gtests/ssl_gtest/manifest.mn
Expand Up @@ -29,6 +29,7 @@ CPPSRCS = \
ssl_gather_unittest.cc \
ssl_gtest.cc \
ssl_hrr_unittest.cc \
ssl_keylog_unittest.cc \
ssl_loopback_unittest.cc \
ssl_misc_unittest.cc \
ssl_record_unittest.cc \
Expand Down
1 change: 1 addition & 0 deletions gtests/ssl_gtest/ssl_gtest.gyp
Expand Up @@ -30,6 +30,7 @@
'ssl_gather_unittest.cc',
'ssl_gtest.cc',
'ssl_hrr_unittest.cc',
'ssl_keylog_unittest.cc',
'ssl_loopback_unittest.cc',
'ssl_misc_unittest.cc',
'ssl_record_unittest.cc',
Expand Down
89 changes: 89 additions & 0 deletions gtests/ssl_gtest/ssl_keylog_unittest.cc
@@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <cstdlib>
#include <fstream>
#include <sstream>

#include "gtest_utils.h"
#include "tls_connect.h"

namespace nss_test {

static const char *keylog_file_path = "keylog.txt";

class KeyLogFileTest : public TlsConnectGeneric {
public:
void SetUp() {
TlsConnectTestBase::SetUp();
remove(keylog_file_path);
setenv("SSLKEYLOGFILE", keylog_file_path, 1);
}

void CheckKeyLog() {
std::ifstream f(keylog_file_path);
std::map<std::string, size_t> labels;
std::string last_client_random;
for (std::string line; std::getline(f, line);) {
if (line[0] == '#') {
continue;
}

std::istringstream iss(line);
std::string label, client_random, secret;
iss >> label >> client_random >> secret;

ASSERT_EQ(1U, client_random.size());
ASSERT_TRUE(last_client_random.empty() ||
last_client_random == client_random);
last_client_random = client_random;
labels[label]++;
}

if (version_ < SSL_LIBRARY_VERSION_TLS_1_3) {
ASSERT_EQ(1U, labels["CLIENT_RANDOM"]);
} else {
ASSERT_EQ(1U, labels["CLIENT_EARLY_TRAFFIC_SECRET"]);
ASSERT_EQ(1U, labels["CLIENT_HANDSHAKE_TRAFFIC_SECRET"]);
ASSERT_EQ(1U, labels["SERVER_HANDSHAKE_TRAFFIC_SECRET"]);
ASSERT_EQ(1U, labels["CLIENT_TRAFFIC_SECRET_0"]);
ASSERT_EQ(1U, labels["SERVER_TRAFFIC_SECRET_0"]);
ASSERT_EQ(1U, labels["EXPORTER_SECRET"]);
}
}

void ConnectAndCheck() {
Connect();
CheckKeyLog();
_exit(0);
}
};

// Tests are run in a separate process to ensure that NSS is not initialized yet
// and can process the SSLKEYLOGFILE environment variable.

TEST_P(KeyLogFileTest, KeyLogFile) {
testing::GTEST_FLAG(death_test_style) = "threadsafe";

ASSERT_EXIT(ConnectAndCheck(), ::testing::ExitedWithCode(0), "");
}

INSTANTIATE_TEST_CASE_P(
KeyLogFileDTLS12, KeyLogFileTest,
::testing::Combine(TlsConnectTestBase::kTlsVariantsDatagram,
TlsConnectTestBase::kTlsV11V12));
INSTANTIATE_TEST_CASE_P(
KeyLogFileTLS12, KeyLogFileTest,
::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
TlsConnectTestBase::kTlsV10ToV12));
#ifndef NSS_DISABLE_TLS_1_3
INSTANTIATE_TEST_CASE_P(
KeyLogFileTLS13, KeyLogFileTest,
::testing::Combine(TlsConnectTestBase::kTlsVariantsStream,
TlsConnectTestBase::kTlsV13));
#endif

} // namespace nss_test
62 changes: 32 additions & 30 deletions lib/ssl/ssl3con.c
Expand Up @@ -11160,64 +11160,66 @@ ssl3_SendNextProto(sslSocket *ss)
return rv;
}

/* called from ssl3_SendFinished
/* called from ssl3_SendFinished and tls13_DeriveSecret.
*
* This function is simply a debugging aid and therefore does not return a
* SECStatus. */
static void
ssl3_RecordKeyLog(sslSocket *ss)
void
ssl3_RecordKeyLog(sslSocket *ss, const char *label, PK11SymKey *secret)
{
#ifdef NSS_ALLOW_SSLKEYLOGFILE
SECStatus rv;
SECItem *keyData;
char buf[14 /* "CLIENT_RANDOM " */ +
SSL3_RANDOM_LENGTH * 2 /* client_random */ +
1 /* " " */ +
48 * 2 /* master secret */ +
1 /* new line */];
unsigned int j;
/* Longest label is "CLIENT_HANDSHAKE_TRAFFIC_SECRET", master secret is 48
* bytes which happens to be the largest in TLS 1.3 as well (SHA384).
* Maximum line length: "CLIENT_HANDSHAKE_TRAFFIC_SECRET" (31) + " " (1) +
* client_random (32*2) + " " (1) +
* traffic_secret (48*2) + "\n" (1) = 194. */
char buf[200];
unsigned int offset, len;

PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));

if (!ssl_keylog_iob)
return;

rv = PK11_ExtractKeyValue(ss->ssl3.cwSpec->master_secret);
rv = PK11_ExtractKeyValue(secret);
if (rv != SECSuccess)
return;

ssl_GetSpecReadLock(ss);

/* keyData does not need to be freed. */
keyData = PK11_GetKeyData(ss->ssl3.cwSpec->master_secret);
if (!keyData || !keyData->data || keyData->len != 48) {
ssl_ReleaseSpecReadLock(ss);
keyData = PK11_GetKeyData(secret);
if (!keyData || !keyData->data)
return;

len = strlen(label) + 1 + /* label + space */
SSL3_RANDOM_LENGTH * 2 + 1 + /* client random (hex) + space */
keyData->len * 2 + 1; /* secret (hex) + newline */
PORT_Assert(len <= sizeof(buf));
if (len > sizeof(buf))
return;
}

/* https://developer.mozilla.org/en/NSS_Key_Log_Format */

/* There could be multiple, concurrent writers to the
* keylog, so we have to do everything in a single call to
* fwrite. */

memcpy(buf, "CLIENT_RANDOM ", 14);
j = 14;
hexEncode(buf + j, ss->ssl3.hs.client_random.rand, SSL3_RANDOM_LENGTH);
j += SSL3_RANDOM_LENGTH * 2;
buf[j++] = ' ';
hexEncode(buf + j, keyData->data, 48);
j += 48 * 2;
buf[j++] = '\n';
strcpy(buf, label);
offset = strlen(label);
buf[offset++] += ' ';
hexEncode(buf + offset, ss->ssl3.hs.client_random.rand, SSL3_RANDOM_LENGTH);
offset += SSL3_RANDOM_LENGTH * 2;
buf[offset++] = ' ';
hexEncode(buf + offset, keyData->data, keyData->len);
offset += keyData->len * 2;
buf[offset++] = '\n';

PORT_Assert(j == sizeof(buf));

ssl_ReleaseSpecReadLock(ss);
PORT_Assert(offset == len);

if (fwrite(buf, sizeof(buf), 1, ssl_keylog_iob) != 1)
if (fwrite(buf, len, 1, ssl_keylog_iob) != 1)
return;
fflush(ssl_keylog_iob);
return;
#endif
}

Expand Down Expand Up @@ -11284,7 +11286,7 @@ ssl3_SendFinished(sslSocket *ss, PRInt32 flags)
goto fail; /* error code set by ssl3_FlushHandshake */
}

ssl3_RecordKeyLog(ss);
ssl3_RecordKeyLog(ss, "CLIENT_RANDOM", ss->ssl3.cwSpec->master_secret);

return SECSuccess;

Expand Down
3 changes: 3 additions & 0 deletions lib/ssl/sslimpl.h
Expand Up @@ -1879,6 +1879,9 @@ ssl3_TLSPRFWithMasterSecret(sslSocket *ss, ssl3CipherSpec *spec,
const unsigned char *val, unsigned int valLen,
unsigned char *out, unsigned int outLen);

extern void
ssl3_RecordKeyLog(sslSocket *ss, const char *label, PK11SymKey *secret);

PRBool ssl_AlpnTagAllowed(const sslSocket *ss, const SECItem *tag);

#ifdef TRACE
Expand Down
34 changes: 28 additions & 6 deletions lib/ssl/tls13con.c
Expand Up @@ -75,6 +75,7 @@ static SECStatus
tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key,
const char *prefix,
const char *suffix,
const char *keylogLabel,
const SSL3Hashes *hashes,
PK11SymKey **dest);
static SECStatus tls13_SendEndOfEarlyData(sslSocket *ss);
Expand Down Expand Up @@ -120,6 +121,13 @@ const char kHkdfLabelExporterMasterSecret[] = "exporter master secret";
const char kHkdfPurposeKey[] = "key";
const char kHkdfPurposeIv[] = "iv";

const char keylogLabelClientEarlyTrafficSecret[] = "CLIENT_EARLY_TRAFFIC_SECRET";
const char keylogLabelClientHsTrafficSecret[] = "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
const char keylogLabelServerHsTrafficSecret[] = "SERVER_HANDSHAKE_TRAFFIC_SECRET";
const char keylogLabelClientTrafficSecret[] = "CLIENT_TRAFFIC_SECRET_0";
const char keylogLabelServerTrafficSecret[] = "SERVER_TRAFFIC_SECRET_0";
const char keylogLabelExporterSecret[] = "EXPORTER_SECRET";

#define TRAFFIC_SECRET(ss, dir, name) ((ss->sec.isServer ^ \
(dir == CipherSpecWrite)) \
? ss->ssl3.hs.client##name \
Expand Down Expand Up @@ -757,14 +765,14 @@ tls13_ComputeEarlySecrets(sslSocket *ss)
hashes.len = tls13_GetHashSize(ss);

rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelPskBinderKey, &hashes,
NULL, kHkdfLabelPskBinderKey, NULL, &hashes,
&ss->ssl3.hs.pskBinderKey);
if (rv != SECSuccess) {
return SECFailure;
}

rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelEarlyExporterSecret,
NULL, kHkdfLabelEarlyExporterSecret, NULL,
&hashes, &ss->ssl3.hs.earlyExporterSecret);
if (rv != SECSuccess) {
return SECFailure;
Expand Down Expand Up @@ -802,15 +810,19 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss)
/* Now compute |*HsTrafficSecret| */
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelClient,
kHkdfLabelHandshakeTrafficSecret, NULL,
kHkdfLabelHandshakeTrafficSecret,
keylogLabelClientHsTrafficSecret,
NULL,
&ss->ssl3.hs.clientHsTrafficSecret);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return rv;
}
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelServer,
kHkdfLabelHandshakeTrafficSecret, NULL,
kHkdfLabelHandshakeTrafficSecret,
keylogLabelServerHsTrafficSecret,
NULL,
&ss->ssl3.hs.serverHsTrafficSecret);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
Expand Down Expand Up @@ -845,6 +857,7 @@ tls13_ComputeApplicationSecrets(sslSocket *ss)
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelClient,
kHkdfLabelApplicationTrafficSecret,
keylogLabelClientTrafficSecret,
NULL,
&ss->ssl3.hs.clientTrafficSecret);
if (rv != SECSuccess) {
Expand All @@ -853,6 +866,7 @@ tls13_ComputeApplicationSecrets(sslSocket *ss)
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelServer,
kHkdfLabelApplicationTrafficSecret,
keylogLabelServerTrafficSecret,
NULL,
&ss->ssl3.hs.serverTrafficSecret);
if (rv != SECSuccess) {
Expand All @@ -861,7 +875,9 @@ tls13_ComputeApplicationSecrets(sslSocket *ss)

rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelExporterMasterSecret,
NULL, &ss->ssl3.hs.exporterSecret);
keylogLabelExporterSecret,
NULL,
&ss->ssl3.hs.exporterSecret);
if (rv != SECSuccess) {
return SECFailure;
}
Expand All @@ -880,7 +896,7 @@ tls13_ComputeFinalSecrets(sslSocket *ss)

rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelResumptionMasterSecret,
NULL, &resumptionMasterSecret);
NULL, NULL, &resumptionMasterSecret);
PK11_FreeSymKey(ss->ssl3.hs.currentSecret);
ss->ssl3.hs.currentSecret = NULL;
if (rv != SECSuccess) {
Expand Down Expand Up @@ -1432,6 +1448,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss,
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelClient,
kHkdfLabelEarlyTrafficSecret,
keylogLabelClientEarlyTrafficSecret,
NULL, /* Current running hash. */
&ss->ssl3.hs.clientEarlyTrafficSecret);
if (rv != SECSuccess) {
Expand Down Expand Up @@ -2543,6 +2560,7 @@ static SECStatus
tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key,
const char *prefix,
const char *suffix,
const char *keylogLabel,
const SSL3Hashes *hashes,
PK11SymKey **dest)
{
Expand Down Expand Up @@ -2585,6 +2603,9 @@ tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key,
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
}
if (keylogLabel) {
ssl3_RecordKeyLog(ss, keylogLabel, *dest);
}
return SECSuccess;
}

Expand Down Expand Up @@ -4351,6 +4372,7 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfLabelClient,
kHkdfLabelEarlyTrafficSecret,
keylogLabelClientEarlyTrafficSecret,
NULL,
&ss->ssl3.hs.clientEarlyTrafficSecret);
if (rv != SECSuccess)
Expand Down

0 comments on commit 9f29abe

Please sign in to comment.