Bug 1287711 - Implement SSLKEYLOGFILE for TLS 1.3 (v2)
authorMartin Thomson <martin.thomson@gmail.com>
Tue, 03 Oct 2017 10:24:36 -0700
changeset 13616 bc6d9e5391daa9807599dffee447ec4368210bf7
parent 13613 2564805e0d532454eef99fa52403fccc35163fd9
child 13617 ffe09c48221c90d66411834e7da1f852fd2af24a
push id2397
push usermartin.thomson@gmail.com
push dateTue, 03 Oct 2017 17:32:01 +0000
bugs1287711
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
gtests/ssl_gtest/manifest.mn
gtests/ssl_gtest/ssl_gtest.gyp
gtests/ssl_gtest/ssl_keylog_unittest.cc
lib/ssl/ssl3con.c
lib/ssl/sslimpl.h
lib/ssl/tls13con.c
--- a/gtests/ssl_gtest/manifest.mn
+++ b/gtests/ssl_gtest/manifest.mn
@@ -24,16 +24,17 @@ CPPSRCS = \
       ssl_ems_unittest.cc \
       ssl_exporter_unittest.cc \
       ssl_extension_unittest.cc \
       ssl_fragment_unittest.cc \
       ssl_fuzz_unittest.cc \
       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 \
       ssl_resumption_unittest.cc \
       ssl_renegotiation_unittest.cc \
       ssl_skip_unittest.cc \
       ssl_staticrsa_unittest.cc \
       ssl_v2_client_hello_unittest.cc \
--- a/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/gtests/ssl_gtest/ssl_gtest.gyp
@@ -25,16 +25,17 @@
         'ssl_ems_unittest.cc',
         'ssl_exporter_unittest.cc',
         'ssl_extension_unittest.cc',
         'ssl_fuzz_unittest.cc',
         'ssl_fragment_unittest.cc',
         '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',
         'ssl_resumption_unittest.cc',
         'ssl_renegotiation_unittest.cc',
         'ssl_skip_unittest.cc',
         'ssl_staticrsa_unittest.cc',
         'ssl_v2_client_hello_unittest.cc',
new file mode 100644
--- /dev/null
+++ b/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
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -11155,74 +11155,76 @@ ssl3_SendNextProto(sslSocket *ss)
     }
     rv = ssl3_AppendHandshakeVariable(ss, padding, padding_len, 1);
     if (rv != SECSuccess) {
         return rv; /* error code set by AppendHandshake */
     }
     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';
-
-    PORT_Assert(j == sizeof(buf));
-
-    ssl_ReleaseSpecReadLock(ss);
-
-    if (fwrite(buf, sizeof(buf), 1, ssl_keylog_iob) != 1)
+    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(offset == len);
+
+    if (fwrite(buf, len, 1, ssl_keylog_iob) != 1)
         return;
     fflush(ssl_keylog_iob);
-    return;
 #endif
 }
 
 /* called from ssl3_SendClientSecondRound
  *             ssl3_HandleClientHello
  *             ssl3_HandleFinished
  */
 static SECStatus
@@ -11279,17 +11281,17 @@ ssl3_SendFinished(sslSocket *ss, PRInt32
         if (rv != SECSuccess)
             goto fail; /* err set by AppendHandshake. */
     }
     rv = ssl3_FlushHandshake(ss, flags);
     if (rv != SECSuccess) {
         goto fail; /* error code set by ssl3_FlushHandshake */
     }
 
-    ssl3_RecordKeyLog(ss);
+    ssl3_RecordKeyLog(ss, "CLIENT_RANDOM", ss->ssl3.cwSpec->master_secret);
 
     return SECSuccess;
 
 fail:
     return rv;
 }
 
 /* wrap the master secret, and put it into the SID.
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -1874,16 +1874,19 @@ extern HASH_HashType
 ssl3_GetTls12HashType(sslSocket *ss);
 
 extern SECStatus
 ssl3_TLSPRFWithMasterSecret(sslSocket *ss, ssl3CipherSpec *spec,
                             const char *label, unsigned int labelLen,
                             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
 #define SSL_TRACE(msg) ssl_Trace msg
 #else
 #define SSL_TRACE(msg)
 #endif
 
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -70,16 +70,17 @@ static SECStatus tls13_HandleCertificate
     sslSocket *ss, PRUint8 *b, PRUint32 length,
     SSL3Hashes *hashes);
 static SECStatus tls13_RecoverWrappedSharedSecret(sslSocket *ss,
                                                   sslSessionID *sid);
 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);
 static SECStatus tls13_SendFinished(sslSocket *ss, PK11SymKey *baseKey);
 static SECStatus tls13_ComputePskBinderHash(sslSocket *ss,
                                             unsigned long prefixLength,
                                             SSL3Hashes *hashes);
 static SECStatus tls13_VerifyFinished(sslSocket *ss, SSL3HandshakeType message,
@@ -115,16 +116,23 @@ const char kHkdfLabelEarlyExporterSecret
 const char kHkdfLabelHandshakeTrafficSecret[] = "handshake traffic secret";
 const char kHkdfLabelApplicationTrafficSecret[] = "application traffic secret";
 const char kHkdfLabelFinishedSecret[] = "finished";
 const char kHkdfLabelResumptionMasterSecret[] = "resumption master secret";
 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 \
                                            : ss->ssl3.hs.server##name)
 
 const SSL3ProtocolVersion kTlsRecordVersion = SSL_LIBRARY_VERSION_TLS_1_0;
 const SSL3ProtocolVersion kDtlsRecordVersion = SSL_LIBRARY_VERSION_TLS_1_1;
 
@@ -752,24 +760,24 @@ tls13_ComputeEarlySecrets(sslSocket *ss)
                           hashes.u.raw, buf, 0);
         if (rv != SECSuccess) {
             FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
             return SECFailure;
         }
         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;
         }
     } else {
         PORT_Assert(!ss->ssl3.hs.resumptionMasterSecret);
     }
 
@@ -797,25 +805,29 @@ tls13_ComputeHandshakeSecrets(sslSocket 
     PK11_FreeSymKey(ss->ssl3.hs.dheSecret);
     ss->ssl3.hs.dheSecret = NULL;
     PK11_FreeSymKey(ss->ssl3.hs.currentSecret);
     ss->ssl3.hs.currentSecret = newSecret;
 
     /* 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);
         return rv;
     }
 
     SSL_TRC(5, ("%d: TLS13[%d]: compute master secret (%s)",
                 SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
@@ -840,33 +852,37 @@ tls13_ComputeHandshakeSecrets(sslSocket 
 static SECStatus
 tls13_ComputeApplicationSecrets(sslSocket *ss)
 {
     SECStatus rv;
 
     rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
                             kHkdfLabelClient,
                             kHkdfLabelApplicationTrafficSecret,
+                            keylogLabelClientTrafficSecret,
                             NULL,
                             &ss->ssl3.hs.clientTrafficSecret);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
                             kHkdfLabelServer,
                             kHkdfLabelApplicationTrafficSecret,
+                            keylogLabelServerTrafficSecret,
                             NULL,
                             &ss->ssl3.hs.serverTrafficSecret);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     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;
     }
 
     return SECSuccess;
 }
 
 static SECStatus
@@ -875,17 +891,17 @@ tls13_ComputeFinalSecrets(sslSocket *ss)
     SECStatus rv;
     PK11SymKey *resumptionMasterSecret = NULL;
 
     PORT_Assert(!ss->ssl3.crSpec->master_secret);
     PORT_Assert(!ss->ssl3.cwSpec->master_secret);
 
     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) {
         return SECFailure;
     }
 
     /* This is pretty gross. TLS 1.3 uses a number of master secrets:
      * The master secret to generate the keys and then the resumption
@@ -1427,16 +1443,17 @@ tls13_HandleClientHelloPart2(sslSocket *
     /* Take ownership of the session. */
     ss->sec.ci.sid = sid;
     sid = NULL;
 
     if (ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) {
         rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
                                 kHkdfLabelClient,
                                 kHkdfLabelEarlyTrafficSecret,
+                                keylogLabelClientEarlyTrafficSecret,
                                 NULL, /* Current running hash. */
                                 &ss->ssl3.hs.clientEarlyTrafficSecret);
         if (rv != SECSuccess) {
             FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
             return SECFailure;
         }
     }
 
@@ -2538,16 +2555,17 @@ loser:
  *    Derive-Secret(Secret, Label, Messages) =
  *       HKDF-Expand-Label(Secret, Label,
  *                         Hash(Messages) + Hash(resumption_context), L))
  */
 static SECStatus
 tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key,
                    const char *prefix,
                    const char *suffix,
+                   const char *keylogLabel,
                    const SSL3Hashes *hashes,
                    PK11SymKey **dest)
 {
     SECStatus rv;
     SSL3Hashes hashesTmp;
     char buf[100];
     const char *label;
 
@@ -2580,16 +2598,19 @@ tls13_DeriveSecret(sslSocket *ss, PK11Sy
                                hashes->u.raw, hashes->len,
                                label, strlen(label),
                                tls13_GetHkdfMechanism(ss),
                                tls13_GetHashSize(ss), dest);
     if (rv != SECSuccess) {
         LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
         return SECFailure;
     }
+    if (keylogLabel) {
+        ssl3_RecordKeyLog(ss, keylogLabel, *dest);
+    }
     return SECSuccess;
 }
 
 /* Derive traffic keys for the next cipher spec in the queue. */
 static SECStatus
 tls13_DeriveTrafficKeys(sslSocket *ss, ssl3CipherSpec *spec,
                         TrafficKeyType type,
                         CipherSpecDirection direction,
@@ -4346,16 +4367,17 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss
     }
 
     /* Cipher suite already set in tls13_SetupClientHello. */
     ss->ssl3.hs.preliminaryInfo = 0;
 
     rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
                             kHkdfLabelClient,
                             kHkdfLabelEarlyTrafficSecret,
+                            keylogLabelClientEarlyTrafficSecret,
                             NULL,
                             &ss->ssl3.hs.clientEarlyTrafficSecret);
     if (rv != SECSuccess)
         return SECFailure;
 
     rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData,
                              CipherSpecWrite, PR_TRUE);
     if (rv != SECSuccess) {