Add |SSL_export_traffic_secrets|.

This allows an application to obtain the current TLS 1.3 traffic secrets
for a connection.

Change-Id: I8ad8d0559caba266f74081441dea54b22da3db20
Reviewed-on: https://boringssl-review.googlesource.com/c/33590
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 2f8163a..6898674 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -4716,6 +4716,14 @@
 OPENSSL_EXPORT bool SSL_serialize_handback(const SSL *ssl, CBB *out);
 OPENSSL_EXPORT bool SSL_apply_handback(SSL *ssl, Span<const uint8_t> handback);
 
+// SSL_get_traffic_secrets sets |*out_read_traffic_secret| and
+// |*out_write_traffic_secret| to reference the TLS 1.3 traffic secrets for
+// |ssl|. This function is only valid on TLS 1.3 connections that have
+// completed the handshake. It returns true on success and false on error.
+OPENSSL_EXPORT bool SSL_get_traffic_secrets(
+    const SSL *ssl, Span<const uint8_t> *out_read_traffic_secret,
+    Span<const uint8_t> *out_write_traffic_secret);
+
 BSSL_NAMESPACE_END
 
 }  // extern C++
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index b9c823d..ceeba89 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -506,6 +506,27 @@
   ssl->config->handoff = on;
 }
 
+bool SSL_get_traffic_secrets(const SSL *ssl,
+                             Span<const uint8_t> *out_read_traffic_secret,
+                             Span<const uint8_t> *out_write_traffic_secret) {
+  if (SSL_version(ssl) < TLS1_3_VERSION) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
+    return false;
+  }
+
+  if (!ssl->s3->initial_handshake_complete) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
+    return false;
+  }
+
+  *out_read_traffic_secret = Span<const uint8_t>(
+      ssl->s3->read_traffic_secret, ssl->s3->read_traffic_secret_len);
+  *out_write_traffic_secret = Span<const uint8_t>(
+      ssl->s3->write_traffic_secret, ssl->s3->write_traffic_secret_len);
+
+  return true;
+}
+
 BSSL_NAMESPACE_END
 
 using namespace bssl;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 3632fc5..77ed796 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -836,6 +836,23 @@
     }
   }
 
+  if (config->export_traffic_secrets) {
+    bssl::Span<const uint8_t> read_secret, write_secret;
+    if (!SSL_get_traffic_secrets(ssl, &read_secret, &write_secret)) {
+      fprintf(stderr, "failed to export traffic secrets\n");
+      return false;
+    }
+
+    assert(read_secret.size() <= 0xffff);
+    assert(write_secret.size() == read_secret.size());
+    const uint16_t secret_len = read_secret.size();
+    if (WriteAll(ssl, &secret_len, sizeof(secret_len)) < 0 ||
+        WriteAll(ssl, read_secret.data(), read_secret.size()) < 0 ||
+        WriteAll(ssl, write_secret.data(), write_secret.size()) < 0) {
+      return false;
+    }
+  }
+
   if (config->tls_unique) {
     uint8_t tls_unique[16];
     size_t tls_unique_len;
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 477eae8..b5cc0a7 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -22,6 +22,7 @@
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/base64"
+	"encoding/binary"
 	"encoding/hex"
 	"encoding/json"
 	"encoding/pem"
@@ -490,6 +491,9 @@
 	// expectedQUICTransportParams contains the QUIC transport
 	// parameters that are expected to be sent by the peer.
 	expectedQUICTransportParams []byte
+	// exportTrafficSecrets, if true, configures the test to export the TLS 1.3
+	// traffic secrets and confirms that they match.
+	exportTrafficSecrets bool
 }
 
 var testCases []testCase
@@ -768,6 +772,32 @@
 		}
 	}
 
+	if test.exportTrafficSecrets {
+		secretLenBytes := make([]byte, 2)
+		if _, err := io.ReadFull(tlsConn, secretLenBytes); err != nil {
+			return err
+		}
+		secretLen := binary.LittleEndian.Uint16(secretLenBytes)
+
+		theirReadSecret := make([]byte, secretLen)
+		theirWriteSecret := make([]byte, secretLen)
+		if _, err := io.ReadFull(tlsConn, theirReadSecret); err != nil {
+			return err
+		}
+		if _, err := io.ReadFull(tlsConn, theirWriteSecret); err != nil {
+			return err
+		}
+
+		myReadSecret := tlsConn.in.trafficSecret
+		myWriteSecret := tlsConn.out.trafficSecret
+		if !bytes.Equal(myWriteSecret, theirReadSecret) {
+			return fmt.Errorf("read traffic-secret mismatch; got %x, wanted %x", theirReadSecret, myWriteSecret)
+		}
+		if !bytes.Equal(myReadSecret, theirWriteSecret) {
+			return fmt.Errorf("write traffic-secret mismatch; got %x, wanted %x", theirWriteSecret, myReadSecret)
+		}
+	}
+
 	if test.testTLSUnique {
 		var peersValue [12]byte
 		if _, err := io.ReadFull(tlsConn, peersValue[:]); err != nil {
@@ -1123,6 +1153,10 @@
 		flags = append(flags, "-export-context", test.exportContext)
 	}
 
+	if test.exportTrafficSecrets {
+		flags = append(flags, "-export-traffic-secrets")
+	}
+
 	if test.expectResumeRejected {
 		flags = append(flags, "-expect-session-miss")
 	}
@@ -10521,6 +10555,24 @@
 	})
 }
 
+func addExportTrafficSecretsTests() {
+	for _, cipherSuite := range []testCipherSuite{
+		// Test a SHA-256 and SHA-384 based cipher suite.
+		{"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
+		{"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
+	} {
+
+		testCases = append(testCases, testCase{
+			name: "ExportTrafficSecrets-" + cipherSuite.name,
+			config: Config{
+				MinVersion:   VersionTLS13,
+				CipherSuites: []uint16{cipherSuite.id},
+			},
+			exportTrafficSecrets: true,
+		})
+	}
+}
+
 func addTLSUniqueTests() {
 	for _, isClient := range []bool{false, true} {
 		for _, isResumption := range []bool{false, true} {
@@ -15076,6 +15128,7 @@
 	addSignatureAlgorithmTests()
 	addDTLSRetransmitTests()
 	addExportKeyingMaterialTests()
+	addExportTrafficSecretsTests()
 	addTLSUniqueTests()
 	addCustomExtensionTests()
 	addRSAClientKeyExchangeTests()
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 9a5c9b2..bed0501 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -147,6 +147,7 @@
   { "-reverify-on-resume", &TestConfig::reverify_on_resume },
   { "-jdk11-workaround", &TestConfig::jdk11_workaround },
   { "-server-preference", &TestConfig::server_preference },
+  { "-export-traffic-secrets", &TestConfig::export_traffic_secrets },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 0e842c0..0d0753e 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -171,6 +171,7 @@
   std::string handshaker_path;
   bool jdk11_workaround = false;
   bool server_preference = false;
+  bool export_traffic_secrets = false;
 
   int argc;
   char **argv;