Implement TLS 1.3 early exporters.

Bug: 222
Change-Id: I33ee56358a62afcd9c3921026d55efcc543a5c11
Reviewed-on: https://boringssl-review.googlesource.com/23945
Reviewed-by: Steven Valdez <svaldez@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index dec7347..f5c2873 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -52,6 +52,7 @@
 SSL,143,DTLS_MESSAGE_TOO_BIG
 SSL,257,DUPLICATE_EXTENSION
 SSL,264,DUPLICATE_KEY_SHARE
+SSL,283,EARLY_DATA_NOT_IN_USE
 SSL,144,ECC_CERT_NOT_FOR_SIGNING
 SSL,282,EMPTY_HELLO_RETRY_REQUEST
 SSL,145,EMS_STATE_INCONSISTENT
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index c5b95e9..8dad6a4 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2988,6 +2988,13 @@
 // |SSL_ERROR_EARLY_DATA_REJECTED|.
 OPENSSL_EXPORT void SSL_reset_early_data_reject(SSL *ssl);
 
+// SSL_export_early_keying_material behaves like |SSL_export_keying_material|,
+// but it uses the early exporter. The operation will fail if |ssl| did not
+// negotiate TLS 1.3 or 0-RTT.
+OPENSSL_EXPORT int SSL_export_early_keying_material(
+    SSL *ssl, uint8_t *out, size_t out_len, const char *label, size_t label_len,
+    const uint8_t *context, size_t context_len);
+
 
 // Alerts.
 //
@@ -4561,6 +4568,7 @@
 #define SSL_R_NO_SUPPORTED_VERSIONS_ENABLED 280
 #define SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE 281
 #define SSL_R_EMPTY_HELLO_RETRY_REQUEST 282
+#define SSL_R_EARLY_DATA_NOT_IN_USE 283
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/ssl/internal.h b/ssl/internal.h
index 4bef358..5d043a8 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1244,10 +1244,10 @@
 
 // tls13_export_keying_material provides an exporter interface to use the
 // |exporter_secret|.
-int tls13_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len,
-                                 const char *label, size_t label_len,
-                                 const uint8_t *context, size_t context_len,
-                                 int use_context);
+int tls13_export_keying_material(SSL *ssl, Span<uint8_t> out,
+                                 Span<const uint8_t> secret,
+                                 Span<const char> label,
+                                 Span<const uint8_t> context);
 
 // tls13_finished_mac calculates the MAC of the handshake transcript to verify
 // the integrity of the Finished message, and stores the result in |out| and
diff --git a/ssl/t1_enc.cc b/ssl/t1_enc.cc
index 2a09987..4e81fe8 100644
--- a/ssl/t1_enc.cc
+++ b/ssl/t1_enc.cc
@@ -467,8 +467,14 @@
   }
 
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
-    return tls13_export_keying_material(ssl, out, out_len, label, label_len,
-                                        context, context_len, use_context);
+    if (!use_context) {
+      context = nullptr;
+      context_len = 0;
+    }
+    return tls13_export_keying_material(
+        ssl, MakeSpan(out, out_len),
+        MakeConstSpan(ssl->s3->exporter_secret, ssl->s3->exporter_secret_len),
+        MakeConstSpan(label, label_len), MakeConstSpan(context, context_len));
   }
 
   size_t seed_len = 2 * SSL3_RANDOM_SIZE;
@@ -501,3 +507,27 @@
       MakeConstSpan(session->master_key, session->master_key_length),
       MakeConstSpan(label, label_len), seed, {});
 }
+
+int SSL_export_early_keying_material(
+    SSL *ssl, uint8_t *out, size_t out_len, const char *label, size_t label_len,
+    const uint8_t *context, size_t context_len) {
+  if (!SSL_in_early_data(ssl) &&
+      (!ssl->s3->have_version ||
+       ssl_protocol_version(ssl) < TLS1_3_VERSION)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SSL_VERSION);
+    return 0;
+  }
+
+  // The early exporter only exists if we accepted early data or offered it as
+  // a client.
+  if (!SSL_in_early_data(ssl) && !SSL_early_data_accepted(ssl)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_EARLY_DATA_NOT_IN_USE);
+    return 0;
+  }
+
+  return tls13_export_keying_material(
+      ssl, MakeSpan(out, out_len),
+      MakeConstSpan(ssl->s3->early_exporter_secret,
+                    ssl->s3->early_exporter_secret_len),
+      MakeConstSpan(label, label_len), MakeConstSpan(context, context_len));
+}
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 1f43be6..0b4c760 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -2205,6 +2205,22 @@
     GetTestState(ssl)->got_new_session = false;
   }
 
+  if (config->export_early_keying_material > 0) {
+    std::vector<uint8_t> result(
+        static_cast<size_t>(config->export_early_keying_material));
+    if (!SSL_export_early_keying_material(
+            ssl, result.data(), result.size(), config->export_label.data(),
+            config->export_label.size(),
+            reinterpret_cast<const uint8_t *>(config->export_context.data()),
+            config->export_context.size())) {
+      fprintf(stderr, "failed to export keying material\n");
+      return false;
+    }
+    if (WriteAll(ssl, result.data(), result.size()) < 0) {
+      return false;
+    }
+  }
+
   if (config->export_keying_material > 0) {
     std::vector<uint8_t> result(
         static_cast<size_t>(config->export_keying_material));
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 5343346..c6d5c65 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -1289,6 +1289,21 @@
 	// it was accepted.
 	SendEarlyDataExtension bool
 
+	// ExpectEarlyKeyingMaterial, if non-zero, causes a TLS 1.3 server to
+	// read an application data record after the ClientHello before it sends
+	// a ServerHello. The record's contents have the specified length and
+	// match the corresponding early exporter value. This is used to test
+	// the client using the early exporter in the 0-RTT state.
+	ExpectEarlyKeyingMaterial int
+
+	// ExpectEarlyKeyingLabel is the label to use with
+	// ExpectEarlyKeyingMaterial.
+	ExpectEarlyKeyingLabel string
+
+	// ExpectEarlyKeyingContext is the context string to use with
+	// ExpectEarlyKeyingMaterial
+	ExpectEarlyKeyingContext string
+
 	// ExpectEarlyData causes a TLS 1.3 server to read application
 	// data after the ClientHello (assuming the server is able to
 	// derive the key under which the data is encrypted) before it
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index ec320ae..c6ee443 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -44,6 +44,7 @@
 	didResume            bool // whether this connection was a session resumption
 	extendedMasterSecret bool // whether this session used an extended master secret
 	cipherSuite          *cipherSuite
+	earlyCipherSuite     *cipherSuite
 	ocspResponse         []byte // stapled OCSP response
 	sctList              []byte // signed certificate timestamp list
 	peerCertificates     []*x509.Certificate
@@ -63,6 +64,7 @@
 	curveID CurveID
 
 	clientRandom, serverRandom [32]byte
+	earlyExporterSecret        []byte
 	exporterSecret             []byte
 	resumptionSecret           []byte
 
@@ -1847,6 +1849,23 @@
 	return c.peerCertificates[0].VerifyHostname(host)
 }
 
+func (c *Conn) exportKeyingMaterialTLS13(length int, secret, label, context []byte) []byte {
+	cipherSuite := c.cipherSuite
+	if cipherSuite == nil {
+		cipherSuite = c.earlyCipherSuite
+	}
+	if isDraft21(c.wireVersion) {
+		hash := cipherSuite.hash()
+		exporterKeyingLabel := []byte("exporter")
+		contextHash := hash.New()
+		contextHash.Write(context)
+		exporterContext := hash.New().Sum(nil)
+		derivedSecret := hkdfExpandLabel(cipherSuite.hash(), c.wireVersion, secret, label, exporterContext, hash.Size())
+		return hkdfExpandLabel(cipherSuite.hash(), c.wireVersion, derivedSecret, exporterKeyingLabel, contextHash.Sum(nil), length)
+	}
+	return hkdfExpandLabel(cipherSuite.hash(), c.wireVersion, secret, label, context, length)
+}
+
 // ExportKeyingMaterial exports keying material from the current connection
 // state, as per RFC 5705.
 func (c *Conn) ExportKeyingMaterial(length int, label, context []byte, useContext bool) ([]byte, error) {
@@ -1857,16 +1876,7 @@
 	}
 
 	if c.vers >= VersionTLS13 {
-		if isDraft21(c.wireVersion) {
-			hash := c.cipherSuite.hash()
-			exporterKeyingLabel := []byte("exporter")
-			contextHash := hash.New()
-			contextHash.Write(context)
-			exporterContext := hash.New().Sum(nil)
-			derivedSecret := hkdfExpandLabel(c.cipherSuite.hash(), c.wireVersion, c.exporterSecret, label, exporterContext, hash.Size())
-			return hkdfExpandLabel(c.cipherSuite.hash(), c.wireVersion, derivedSecret, exporterKeyingLabel, contextHash.Sum(nil), length), nil
-		}
-		return hkdfExpandLabel(c.cipherSuite.hash(), c.wireVersion, c.exporterSecret, label, context, length), nil
+		return c.exportKeyingMaterialTLS13(length, c.exporterSecret, label, context), nil
 	}
 
 	seedLen := len(c.clientRandom) + len(c.serverRandom)
@@ -1885,6 +1895,18 @@
 	return result, nil
 }
 
+func (c *Conn) ExportEarlyKeyingMaterial(length int, label, context []byte) ([]byte, error) {
+	if c.vers < VersionTLS13 {
+		return nil, errors.New("tls: early exporters not defined before TLS 1.3")
+	}
+
+	if c.earlyExporterSecret == nil {
+		return nil, errors.New("tls: no early exporter secret")
+	}
+
+	return c.exportKeyingMaterialTLS13(length, c.earlyExporterSecret, label, context), nil
+}
+
 // noRenegotiationInfo returns true if the renegotiation info extension
 // should be supported in the current handshake.
 func (c *Conn) noRenegotiationInfo() bool {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 5f22ecd..55d21c9 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -414,18 +414,21 @@
 		finishedHash.addEntropy(session.masterSecret)
 		finishedHash.Write(helloBytes)
 
-		earlyLabel := earlyTrafficLabel
-		if isDraft21(session.wireVersion) {
-			earlyLabel = earlyTrafficLabelDraft21
-		}
-
 		if !c.config.Bugs.SkipChangeCipherSpec && isDraft22(session.wireVersion) {
 			c.wireVersion = session.wireVersion
 			c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
 			c.wireVersion = 0
 		}
 
-		earlyTrafficSecret := finishedHash.deriveSecret(earlyLabel)
+		var earlyTrafficSecret []byte
+		if isDraft21(session.wireVersion) {
+			earlyTrafficSecret = finishedHash.deriveSecret(earlyTrafficLabelDraft21)
+			c.earlyExporterSecret = finishedHash.deriveSecret(earlyExporterLabelDraft21)
+		} else {
+			earlyTrafficSecret = finishedHash.deriveSecret(earlyTrafficLabel)
+			c.earlyExporterSecret = finishedHash.deriveSecret(earlyExporterLabel)
+		}
+
 		c.useOutTrafficSecret(session.wireVersion, pskCipherSuite, earlyTrafficSecret)
 		for _, earlyData := range c.config.Bugs.SendEarlyData {
 			if _, err := c.writeRecord(recordTypeApplicationData, earlyData); err != nil {
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 595f8da..9ba6c2c 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -694,22 +694,36 @@
 			}
 		}
 		if encryptedExtensions.extensions.hasEarlyData {
-			earlyLabel := earlyTrafficLabel
+			var earlyTrafficSecret []byte
 			if isDraft21(c.wireVersion) {
-				earlyLabel = earlyTrafficLabelDraft21
+				earlyTrafficSecret = hs.finishedHash.deriveSecret(earlyTrafficLabelDraft21)
+				c.earlyExporterSecret = hs.finishedHash.deriveSecret(earlyExporterLabelDraft21)
+			} else {
+				earlyTrafficSecret = hs.finishedHash.deriveSecret(earlyTrafficLabel)
+				c.earlyExporterSecret = hs.finishedHash.deriveSecret(earlyExporterLabel)
 			}
 
-			earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyLabel)
 			if err := c.useInTrafficSecret(c.wireVersion, hs.suite, earlyTrafficSecret); err != nil {
 				return err
 			}
 
-			for _, expectedMsg := range config.Bugs.ExpectEarlyData {
+			c.earlyCipherSuite = hs.suite
+			expectEarlyData := config.Bugs.ExpectEarlyData
+			if n := config.Bugs.ExpectEarlyKeyingMaterial; n > 0 {
+				exporter, err := c.ExportEarlyKeyingMaterial(n, []byte(config.Bugs.ExpectEarlyKeyingLabel), []byte(config.Bugs.ExpectEarlyKeyingContext))
+				if err != nil {
+					return err
+				}
+				expectEarlyData = append([][]byte{exporter}, expectEarlyData...)
+			}
+
+			for _, expectedMsg := range expectEarlyData {
 				if err := c.readRecord(recordTypeApplicationData); err != nil {
 					return err
 				}
-				if !bytes.Equal(c.input.data[c.input.off:], expectedMsg) {
-					return errors.New("ExpectEarlyData: did not get expected message")
+				msg := c.input.data[c.input.off:]
+				if !bytes.Equal(msg, expectedMsg) {
+					return fmt.Errorf("tls: got early data record %x, wanted %x", msg, expectedMsg)
 				}
 				c.in.freeBlock(c.input)
 				c.input = nil
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index 6fa3c4c..54e18cb 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -446,6 +446,7 @@
 	clientApplicationTrafficLabel = []byte("client application traffic secret")
 	serverApplicationTrafficLabel = []byte("server application traffic secret")
 	applicationTrafficLabel       = []byte("application traffic secret")
+	earlyExporterLabel            = []byte("early exporter master secret")
 	exporterLabel                 = []byte("exporter master secret")
 	resumptionLabel               = []byte("resumption master secret")
 
@@ -457,6 +458,7 @@
 	clientApplicationTrafficLabelDraft21 = []byte("c ap traffic")
 	serverApplicationTrafficLabelDraft21 = []byte("s ap traffic")
 	applicationTrafficLabelDraft21       = []byte("traffic upd")
+	earlyExporterLabelDraft21            = []byte("e exp master")
 	exporterLabelDraft21                 = []byte("exp master")
 	resumptionLabelDraft21               = []byte("res master")
 
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 362a7a5..0e2d011 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -431,6 +431,9 @@
 	exportLabel          string
 	exportContext        string
 	useExportContext     bool
+	// exportEarlyKeyingMaterial, if non-zero, behaves like
+	// exportKeyingMaterial, but for the early exporter.
+	exportEarlyKeyingMaterial int
 	// flags, if not empty, contains a list of command-line flags that will
 	// be passed to the shim program.
 	flags []string
@@ -694,6 +697,20 @@
 		}
 	}
 
+	if isResume && test.exportEarlyKeyingMaterial > 0 {
+		actual := make([]byte, test.exportEarlyKeyingMaterial)
+		if _, err := io.ReadFull(tlsConn, actual); err != nil {
+			return err
+		}
+		expected, err := tlsConn.ExportEarlyKeyingMaterial(test.exportEarlyKeyingMaterial, []byte(test.exportLabel), []byte(test.exportContext))
+		if err != nil {
+			return err
+		}
+		if !bytes.Equal(actual, expected) {
+			return fmt.Errorf("early keying material mismatch; got %x, wanted %x", actual, expected)
+		}
+	}
+
 	if test.exportKeyingMaterial > 0 {
 		actual := make([]byte, test.exportKeyingMaterial)
 		if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -704,7 +721,7 @@
 			return err
 		}
 		if !bytes.Equal(actual, expected) {
-			return fmt.Errorf("keying material mismatch")
+			return fmt.Errorf("keying material mismatch; got %x, wanted %x", actual, expected)
 		}
 	}
 
@@ -1037,12 +1054,18 @@
 
 	if test.exportKeyingMaterial > 0 {
 		flags = append(flags, "-export-keying-material", strconv.Itoa(test.exportKeyingMaterial))
-		flags = append(flags, "-export-label", test.exportLabel)
-		flags = append(flags, "-export-context", test.exportContext)
 		if test.useExportContext {
 			flags = append(flags, "-use-export-context")
 		}
 	}
+	if test.exportEarlyKeyingMaterial > 0 {
+		flags = append(flags, "-on-resume-export-early-keying-material", strconv.Itoa(test.exportEarlyKeyingMaterial))
+	}
+	if test.exportKeyingMaterial > 0 || test.exportEarlyKeyingMaterial > 0 {
+		flags = append(flags, "-export-label", test.exportLabel)
+		flags = append(flags, "-export-context", test.exportContext)
+	}
+
 	if test.expectResumeRejected {
 		flags = append(flags, "-expect-session-miss")
 	}
@@ -9081,6 +9104,187 @@
 			exportContext:        "context",
 			useExportContext:     true,
 		})
+
+		if vers.version >= VersionTLS13 {
+			// Test the early exporter works while the client is
+			// sending 0-RTT data. This data arrives during the
+			// server handshake, so we test it with ProtocolBugs.
+			testCases = append(testCases, testCase{
+				name: "ExportEarlyKeyingMaterial-Client-InEarlyData-" + vers.name,
+				config: Config{
+					MaxVersion:       vers.version,
+					MaxEarlyDataSize: 16384,
+				},
+				resumeConfig: &Config{
+					MaxVersion:       vers.version,
+					MaxEarlyDataSize: 16384,
+					Bugs: ProtocolBugs{
+						ExpectEarlyKeyingMaterial: 1024,
+						ExpectEarlyKeyingLabel:    "label",
+						ExpectEarlyKeyingContext:  "context",
+					},
+				},
+				resumeSession: true,
+				tls13Variant:  vers.tls13Variant,
+				flags: []string{
+					"-enable-early-data",
+					"-expect-ticket-supports-early-data",
+					"-expect-accept-early-data",
+					"-on-resume-export-early-keying-material", "1024",
+					"-on-resume-export-label", "label",
+					"-on-resume-export-context", "context",
+				},
+			})
+
+			// Test the early exporter still works on the client
+			// after the handshake is confirmed. This arrives after
+			// the server handshake, so the normal hooks work.
+			testCases = append(testCases, testCase{
+				name: "ExportEarlyKeyingMaterial-Client-EarlyDataAccept-" + vers.name,
+				config: Config{
+					MaxVersion:       vers.version,
+					MaxEarlyDataSize: 16384,
+				},
+				resumeConfig: &Config{
+					MaxVersion:       vers.version,
+					MaxEarlyDataSize: 16384,
+				},
+				resumeSession:             true,
+				tls13Variant:              vers.tls13Variant,
+				exportEarlyKeyingMaterial: 1024,
+				exportLabel:               "label",
+				exportContext:             "context",
+				flags: []string{
+					"-enable-early-data",
+					"-expect-ticket-supports-early-data",
+					"-expect-accept-early-data",
+					// Handshake twice on the client to force
+					// handshake confirmation.
+					"-handshake-twice",
+				},
+			})
+
+			// Test the early exporter does not work on the client
+			// if 0-RTT was not offered.
+			testCases = append(testCases, testCase{
+				name: "NoExportEarlyKeyingMaterial-Client-Initial-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				tls13Variant:  vers.tls13Variant,
+				flags:         []string{"-export-early-keying-material", "1024"},
+				shouldFail:    true,
+				expectedError: ":EARLY_DATA_NOT_IN_USE:",
+			})
+			testCases = append(testCases, testCase{
+				name: "NoExportEarlyKeyingMaterial-Client-Resume-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				resumeSession: true,
+				tls13Variant:  vers.tls13Variant,
+				flags:         []string{"-on-resume-export-early-keying-material", "1024"},
+				shouldFail:    true,
+				expectedError: ":EARLY_DATA_NOT_IN_USE:",
+			})
+
+			// Test the early exporter does not work on the client
+			// after a 0-RTT reject.
+			testCases = append(testCases, testCase{
+				name: "NoExportEarlyKeyingMaterial-Client-EarlyDataReject-" + vers.name,
+				config: Config{
+					MaxVersion:       vers.version,
+					MaxEarlyDataSize: 16384,
+					Bugs: ProtocolBugs{
+						AlwaysRejectEarlyData: true,
+					},
+				},
+				resumeSession: true,
+				tls13Variant:  vers.tls13Variant,
+				flags: []string{
+					"-enable-early-data",
+					"-expect-ticket-supports-early-data",
+					"-expect-reject-early-data",
+					"-on-retry-export-early-keying-material", "1024",
+				},
+				shouldFail:    true,
+				expectedError: ":EARLY_DATA_NOT_IN_USE:",
+			})
+
+			// Test the early exporter works on the server.
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "ExportEarlyKeyingMaterial-Server-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+					Bugs: ProtocolBugs{
+						SendEarlyData:           [][]byte{},
+						ExpectEarlyDataAccepted: true,
+					},
+				},
+				tls13Variant:              vers.tls13Variant,
+				resumeSession:             true,
+				exportEarlyKeyingMaterial: 1024,
+				exportLabel:               "label",
+				exportContext:             "context",
+				flags:                     []string{"-enable-early-data"},
+			})
+
+			// Test the early exporter does not work on the server
+			// if 0-RTT was not offered.
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "NoExportEarlyKeyingMaterial-Server-Initial-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				tls13Variant:  vers.tls13Variant,
+				flags:         []string{"-export-early-keying-material", "1024"},
+				shouldFail:    true,
+				expectedError: ":EARLY_DATA_NOT_IN_USE:",
+			})
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "NoExportEarlyKeyingMaterial-Server-Resume-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				resumeSession: true,
+				tls13Variant:  vers.tls13Variant,
+				flags:         []string{"-on-resume-export-early-keying-material", "1024"},
+				shouldFail:    true,
+				expectedError: ":EARLY_DATA_NOT_IN_USE:",
+			})
+		} else {
+			// Test the early exporter fails before TLS 1.3.
+			testCases = append(testCases, testCase{
+				name: "NoExportEarlyKeyingMaterial-Client-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				resumeSession:             true,
+				tls13Variant:              vers.tls13Variant,
+				exportEarlyKeyingMaterial: 1024,
+				exportLabel:               "label",
+				exportContext:             "context",
+				shouldFail:                true,
+				expectedError:             ":WRONG_SSL_VERSION:",
+			})
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "NoExportEarlyKeyingMaterial-Server-" + vers.name,
+				config: Config{
+					MaxVersion: vers.version,
+				},
+				resumeSession:             true,
+				tls13Variant:              vers.tls13Variant,
+				exportEarlyKeyingMaterial: 1024,
+				exportLabel:               "label",
+				exportContext:             "context",
+				shouldFail:                true,
+				expectedError:             ":WRONG_SSL_VERSION:",
+			})
+		}
 	}
 
 	testCases = append(testCases, testCase{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index efe5acb..579cf89 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -177,6 +177,8 @@
   { "-max-version", &TestConfig::max_version },
   { "-expect-version", &TestConfig::expect_version },
   { "-mtu", &TestConfig::mtu },
+  { "-export-early-keying-material",
+    &TestConfig::export_early_keying_material },
   { "-export-keying-material", &TestConfig::export_keying_material },
   { "-expect-total-renegotiations", &TestConfig::expect_total_renegotiations },
   { "-expect-peer-signature-algorithm",
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 783471a..49b86ed 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -79,6 +79,7 @@
   bool fail_cert_callback = false;
   std::string cipher;
   bool handshake_never_done = false;
+  int export_early_keying_material = 0;
   int export_keying_material = 0;
   std::string export_label;
   std::string export_context;
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index 14f4a78..9dcd071 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -68,7 +68,7 @@
 
 static int hkdf_expand_label(uint8_t *out, uint16_t version,
                              const EVP_MD *digest, const uint8_t *secret,
-                             size_t secret_len, const uint8_t *label,
+                             size_t secret_len, const char *label,
                              size_t label_len, const uint8_t *hash,
                              size_t hash_len, size_t len) {
   const char *kTLS13LabelVersion =
@@ -84,7 +84,7 @@
       !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, (const uint8_t *)kTLS13LabelVersion,
                      strlen(kTLS13LabelVersion)) ||
-      !CBB_add_bytes(&child, label, label_len) ||
+      !CBB_add_bytes(&child, (const uint8_t *)label, label_len) ||
       !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, hash, hash_len) ||
       !CBB_finish(cbb.get(), &hkdf_label, &hkdf_label_len)) {
@@ -113,8 +113,7 @@
     }
 
     if (!hkdf_expand_label(hs->secret, ssl->version, hs->transcript.Digest(),
-                           hs->secret, hs->hash_len,
-                           (const uint8_t *)kTLS13LabelDerived,
+                           hs->secret, hs->hash_len, kTLS13LabelDerived,
                            strlen(kTLS13LabelDerived), derive_context,
                            derive_context_len, hs->hash_len)) {
       return 0;
@@ -129,7 +128,7 @@
 // with the given label and the current base secret and most recently-saved
 // handshake context. It returns one on success and zero on error.
 static int derive_secret(SSL_HANDSHAKE *hs, uint8_t *out, size_t len,
-                         const uint8_t *label, size_t label_len) {
+                         const char *label, size_t label_len) {
   uint8_t context_hash[EVP_MAX_MD_SIZE];
   size_t context_hash_len;
   if (!hs->transcript.GetHash(context_hash, &context_hash_len)) {
@@ -167,8 +166,7 @@
   size_t key_len = EVP_AEAD_key_length(aead);
   uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
   if (!hkdf_expand_label(key, session->ssl_version, digest, traffic_secret,
-                         traffic_secret_len, (const uint8_t *)"key", 3, NULL, 0,
-                         key_len)) {
+                         traffic_secret_len, "key", 3, NULL, 0, key_len)) {
     return 0;
   }
 
@@ -176,8 +174,7 @@
   size_t iv_len = EVP_AEAD_nonce_length(aead);
   uint8_t iv[EVP_AEAD_MAX_NONCE_LENGTH];
   if (!hkdf_expand_label(iv, session->ssl_version, digest, traffic_secret,
-                         traffic_secret_len, (const uint8_t *)"iv", 2, NULL, 0,
-                         iv_len)) {
+                         traffic_secret_len, "iv", 2, NULL, 0, iv_len)) {
     return 0;
   }
 
@@ -246,14 +243,16 @@
   const char *early_exporter_label = ssl_is_draft21(version)
                                          ? kTLS13Draft21LabelEarlyExporter
                                          : kTLS13LabelEarlyExporter;
-  return derive_secret(hs, hs->early_traffic_secret, hs->hash_len,
-                       (const uint8_t *)early_traffic_label,
-                       strlen(early_traffic_label)) &&
-         ssl_log_secret(ssl, "CLIENT_EARLY_TRAFFIC_SECRET",
-                        hs->early_traffic_secret, hs->hash_len) &&
-         derive_secret(hs, ssl->s3->early_exporter_secret, hs->hash_len,
-                       (const uint8_t *)early_exporter_label,
-                       strlen(early_exporter_label));
+  if (!derive_secret(hs, hs->early_traffic_secret, hs->hash_len,
+                     early_traffic_label, strlen(early_traffic_label)) ||
+      !ssl_log_secret(ssl, "CLIENT_EARLY_TRAFFIC_SECRET",
+                      hs->early_traffic_secret, hs->hash_len) ||
+      !derive_secret(hs, ssl->s3->early_exporter_secret, hs->hash_len,
+                     early_exporter_label, strlen(early_exporter_label))) {
+    return 0;
+  }
+  ssl->s3->early_exporter_secret_len = hs->hash_len;
+  return 1;
 }
 
 int tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) {
@@ -265,11 +264,11 @@
                                  ? kTLS13Draft21LabelServerHandshakeTraffic
                                  : kTLS13LabelServerHandshakeTraffic;
   return derive_secret(hs, hs->client_handshake_secret, hs->hash_len,
-                       (const uint8_t *)client_label, strlen(client_label)) &&
+                       client_label, strlen(client_label)) &&
          ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET",
                         hs->client_handshake_secret, hs->hash_len) &&
          derive_secret(hs, hs->server_handshake_secret, hs->hash_len,
-                       (const uint8_t *)server_label, strlen(server_label)) &&
+                       server_label, strlen(server_label)) &&
          ssl_log_secret(ssl, "SERVER_HANDSHAKE_TRAFFIC_SECRET",
                         hs->server_handshake_secret, hs->hash_len);
 }
@@ -287,16 +286,15 @@
                                    ? kTLS13Draft21LabelExporter
                                    : kTLS13LabelExporter;
   return derive_secret(hs, hs->client_traffic_secret_0, hs->hash_len,
-                       (const uint8_t *)client_label, strlen(client_label)) &&
+                       client_label, strlen(client_label)) &&
          ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0",
                         hs->client_traffic_secret_0, hs->hash_len) &&
          derive_secret(hs, hs->server_traffic_secret_0, hs->hash_len,
-                       (const uint8_t *)server_label, strlen(server_label)) &&
+                       server_label, strlen(server_label)) &&
          ssl_log_secret(ssl, "SERVER_TRAFFIC_SECRET_0",
                         hs->server_traffic_secret_0, hs->hash_len) &&
          derive_secret(hs, ssl->s3->exporter_secret, hs->hash_len,
-                       (const uint8_t *)exporter_label,
-                       strlen(exporter_label)) &&
+                       exporter_label, strlen(exporter_label)) &&
          ssl_log_secret(ssl, "EXPORTER_SECRET", ssl->s3->exporter_secret,
                         hs->hash_len);
 }
@@ -322,8 +320,8 @@
 
   const EVP_MD *digest = ssl_session_get_digest(SSL_get_session(ssl));
   if (!hkdf_expand_label(secret, ssl->version, digest, secret, secret_len,
-                         (const uint8_t *)traffic_label, strlen(traffic_label),
-                         NULL, 0, secret_len)) {
+                         traffic_label, strlen(traffic_label), NULL, 0,
+                         secret_len)) {
     return 0;
   }
 
@@ -342,9 +340,9 @@
                                      ? kTLS13Draft21LabelResumption
                                      : kTLS13LabelResumption;
   hs->new_session->master_key_length = hs->hash_len;
-  return derive_secret(
-      hs, hs->new_session->master_key, hs->new_session->master_key_length,
-      (const uint8_t *)resumption_label, strlen(resumption_label));
+  return derive_secret(hs, hs->new_session->master_key,
+                       hs->new_session->master_key_length, resumption_label,
+                       strlen(resumption_label));
 }
 
 static const char kTLS13LabelFinished[] = "finished";
@@ -358,8 +356,8 @@
   uint8_t key[EVP_MAX_MD_SIZE];
   unsigned len;
   if (!hkdf_expand_label(key, version, digest, secret, hash_len,
-                         (const uint8_t *)kTLS13LabelFinished,
-                         strlen(kTLS13LabelFinished), NULL, 0, hash_len) ||
+                         kTLS13LabelFinished, strlen(kTLS13LabelFinished), NULL,
+                         0, hash_len) ||
       HMAC(digest, key, hash_len, context, context_len, out, &len) == NULL) {
     return 0;
   }
@@ -397,30 +395,29 @@
   const EVP_MD *digest = ssl_session_get_digest(session);
   return hkdf_expand_label(session->master_key, session->ssl_version, digest,
                            session->master_key, session->master_key_length,
-                           (const uint8_t *)kTLS13LabelResumptionPSK,
+                           kTLS13LabelResumptionPSK,
                            strlen(kTLS13LabelResumptionPSK), nonce.data(),
                            nonce.size(), session->master_key_length);
 }
 
 static const char kTLS13LabelExportKeying[] = "exporter";
 
-int tls13_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len,
-                                 const char *label, size_t label_len,
-                                 const uint8_t *context_in,
-                                 size_t context_in_len, int use_context) {
-  const uint8_t *context = NULL;
-  size_t context_len = 0;
-  if (use_context) {
-    context = context_in;
-    context_len = context_in_len;
+int tls13_export_keying_material(SSL *ssl, Span<uint8_t> out,
+                                 Span<const uint8_t> secret,
+                                 Span<const char> label,
+                                 Span<const uint8_t> context) {
+  if (secret.empty()) {
+    assert(0);
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
   }
 
-  if (!ssl_is_draft21(ssl->version)) {
+  uint16_t version = SSL_get_session(ssl)->ssl_version;
+  if (!ssl_is_draft21(version)) {
     const EVP_MD *digest = ssl_session_get_digest(SSL_get_session(ssl));
-    return hkdf_expand_label(
-        out, ssl->version, digest, ssl->s3->exporter_secret,
-        ssl->s3->exporter_secret_len, (const uint8_t *)label, label_len,
-        context, context_len, out_len);
+    return hkdf_expand_label(out.data(), version, digest, secret.data(),
+                             secret.size(), label.data(), label.size(),
+                             context.data(), context.size(), out.size());
   }
 
   const EVP_MD *digest = ssl_session_get_digest(SSL_get_session(ssl));
@@ -431,18 +428,18 @@
   unsigned hash_len;
   unsigned export_context_len;
   unsigned derived_secret_len = EVP_MD_size(digest);
-  if (!EVP_Digest(context, context_len, hash, &hash_len, digest, NULL) ||
-      !EVP_Digest(NULL, 0, export_context, &export_context_len, digest, NULL)) {
-    return 0;
-  }
-  return hkdf_expand_label(
-             derived_secret, ssl->version, digest, ssl->s3->exporter_secret,
-             ssl->s3->exporter_secret_len, (const uint8_t *)label, label_len,
-             export_context, export_context_len, derived_secret_len) &&
-         hkdf_expand_label(
-             out, ssl->version, digest, derived_secret, derived_secret_len,
-             (const uint8_t *)kTLS13LabelExportKeying,
-             strlen(kTLS13LabelExportKeying), hash, hash_len, out_len);
+  return EVP_Digest(context.data(), context.size(), hash, &hash_len, digest,
+                    nullptr) &&
+         EVP_Digest(nullptr, 0, export_context, &export_context_len, digest,
+                    nullptr) &&
+         hkdf_expand_label(derived_secret, version, digest, secret.data(),
+                           secret.size(), label.data(), label.size(),
+                           export_context, export_context_len,
+                           derived_secret_len) &&
+         hkdf_expand_label(out.data(), version, digest, derived_secret,
+                           derived_secret_len, kTLS13LabelExportKeying,
+                           strlen(kTLS13LabelExportKeying), hash, hash_len,
+                           out.size());
 }
 
 static const char kTLS13LabelPSKBinder[] = "resumption psk binder key";
@@ -471,8 +468,8 @@
   uint8_t binder_key[EVP_MAX_MD_SIZE] = {0};
   size_t len;
   if (!hkdf_expand_label(binder_key, version, digest, early_secret, hash_len,
-                         (const uint8_t *)binder_label, strlen(binder_label),
-                         binder_context, binder_context_len, hash_len) ||
+                         binder_label, strlen(binder_label), binder_context,
+                         binder_context_len, hash_len) ||
       !tls13_verify_data(digest, version, out, &len, binder_key, hash_len,
                          context, context_len)) {
     return 0;