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;