Add Data-less Zero-RTT support.
This adds support on the server and client to accept data-less early
data. The server will still fail to parse early data with any
contents, so this should remain disabled.
BUG=76
Change-Id: Id85d192d8e0360b8de4b6971511b5e8a0e8012f7
Reviewed-on: https://boringssl-review.googlesource.com/12921
Reviewed-by: David Benjamin <davidben@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/ssl/dtls_method.c b/ssl/dtls_method.c
index 6084789..806212d 100644
--- a/ssl/dtls_method.c
+++ b/ssl/dtls_method.c
@@ -145,6 +145,7 @@
dtls1_release_current_message,
dtls1_read_app_data,
dtls1_read_change_cipher_spec,
+ NULL,
dtls1_read_close_notify,
dtls1_write_app_data,
dtls1_dispatch_alert,
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 1feb7d8..3898c1b 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -208,6 +208,18 @@
}
if (!SSL_is_dtls(ssl) || ssl->d1->send_cookie) {
+ if (hs->early_data_offered) {
+ if (!tls13_init_early_key_schedule(hs) ||
+ !tls13_advance_key_schedule(hs, ssl->session->master_key,
+ ssl->session->master_key_length) ||
+ !tls13_derive_early_secrets(hs) ||
+ !tls13_set_traffic_key(ssl, evp_aead_seal,
+ hs->early_traffic_secret,
+ hs->hash_len)) {
+ ret = -1;
+ goto end;
+ }
+ }
hs->next_state = SSL3_ST_CR_SRVR_HELLO_A;
} else {
hs->next_state = DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A;
@@ -875,6 +887,12 @@
return 1;
}
+ if (hs->early_data_offered) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_ON_EARLY_DATA);
+ al = SSL_AD_PROTOCOL_VERSION;
+ goto f_err;
+ }
+
ssl_clear_tls13_state(hs);
if (!ssl_check_message_type(ssl, SSL3_MT_SERVER_HELLO)) {
diff --git a/ssl/internal.h b/ssl/internal.h
index 99980d8..ed5172c 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -865,6 +865,11 @@
* It returns one on success and zero on error. */
int tls13_init_key_schedule(SSL_HANDSHAKE *hs);
+/* tls13_init_early_key_schedule initializes the handshake hash and key
+ * derivation state from the resumption secret to derive the early secrets. It
+ * returns one on success and zero on error. */
+int tls13_init_early_key_schedule(SSL_HANDSHAKE *hs);
+
/* tls13_advance_key_schedule incorporates |in| into the key schedule with
* HKDF-Extract. It returns one on success and zero on error. */
int tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in,
@@ -876,6 +881,10 @@
const uint8_t *traffic_secret,
size_t traffic_secret_len);
+/* tls13_derive_early_secrets derives the early traffic secret. It returns one
+ * on success and zero on error. */
+int tls13_derive_early_secrets(SSL_HANDSHAKE *hs);
+
/* tls13_derive_handshake_secrets derives the handshake traffic secret. It
* returns one on success and zero on error. */
int tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs);
@@ -930,6 +939,7 @@
ssl_hs_channel_id_lookup,
ssl_hs_private_key_operation,
ssl_hs_pending_ticket,
+ ssl_hs_read_end_of_early_data,
};
struct ssl_handshake_st {
@@ -957,6 +967,7 @@
size_t hash_len;
uint8_t secret[EVP_MAX_MD_SIZE];
+ uint8_t early_traffic_secret[EVP_MAX_MD_SIZE];
uint8_t client_handshake_secret[EVP_MAX_MD_SIZE];
uint8_t server_handshake_secret[EVP_MAX_MD_SIZE];
uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE];
@@ -1100,6 +1111,9 @@
* Start. The client may write data at this point. */
unsigned in_false_start:1;
+ /* early_data_offered is one if the client sent the early_data extension. */
+ unsigned early_data_offered:1;
+
/* next_proto_neg_seen is one of NPN was negotiated. */
unsigned next_proto_neg_seen:1;
@@ -1398,6 +1412,7 @@
int (*read_app_data)(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
int peek);
int (*read_change_cipher_spec)(SSL *ssl);
+ int (*read_end_of_early_data)(SSL *ssl);
void (*read_close_notify)(SSL *ssl);
int (*write_app_data)(SSL *ssl, const uint8_t *buf, int len);
int (*dispatch_alert)(SSL *ssl);
@@ -1629,9 +1644,11 @@
uint8_t write_traffic_secret[EVP_MAX_MD_SIZE];
uint8_t read_traffic_secret[EVP_MAX_MD_SIZE];
uint8_t exporter_secret[EVP_MAX_MD_SIZE];
+ uint8_t early_exporter_secret[EVP_MAX_MD_SIZE];
uint8_t write_traffic_secret_len;
uint8_t read_traffic_secret_len;
uint8_t exporter_secret_len;
+ uint8_t early_exporter_secret_len;
/* Connection binding to prevent renegotiation attacks */
uint8_t previous_client_finished[12];
@@ -1933,6 +1950,9 @@
* hash of the peer's certificate and then discard it to save memory and
* session space. Only effective on the server side. */
unsigned retain_only_sha256_of_client_certs:1;
+
+ /* early_data_accepted is true if early data was accepted by the server. */
+ unsigned early_data_accepted:1;
};
/* From draft-ietf-tls-tls13-18, used in determining PSK modes. */
@@ -2049,6 +2069,7 @@
int ssl3_read_app_data(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
int peek);
int ssl3_read_change_cipher_spec(SSL *ssl);
+int ssl3_read_end_of_early_data(SSL *ssl);
void ssl3_read_close_notify(SSL *ssl);
int ssl3_read_handshake_bytes(SSL *ssl, uint8_t *buf, int len);
int ssl3_write_app_data(SSL *ssl, const uint8_t *buf, int len);
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index 8fa51e9..7ef400a 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -153,6 +153,7 @@
}
OPENSSL_cleanse(hs->secret, sizeof(hs->secret));
+ OPENSSL_cleanse(hs->early_traffic_secret, sizeof(hs->early_traffic_secret));
OPENSSL_cleanse(hs->client_handshake_secret,
sizeof(hs->client_handshake_secret));
OPENSSL_cleanse(hs->server_handshake_secret,
diff --git a/ssl/s3_pkt.c b/ssl/s3_pkt.c
index 2f919ca..69696ed 100644
--- a/ssl/s3_pkt.c
+++ b/ssl/s3_pkt.c
@@ -407,6 +407,30 @@
return 1;
}
+int ssl3_read_end_of_early_data(SSL *ssl) {
+ SSL3_RECORD *rr = &ssl->s3->rrec;
+
+ if (rr->length == 0) {
+ int ret = ssl3_get_record(ssl);
+ if (ret <= 0) {
+ return ret;
+ }
+ }
+
+ if (rr->type != SSL3_RT_ALERT ||
+ rr->length != 2 ||
+ rr->data[0] != SSL3_AL_WARNING ||
+ rr->data[1] != TLS1_AD_END_OF_EARLY_DATA) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+ return -1;
+ }
+
+ rr->length = 0;
+ ssl_read_buffer_discard(ssl);
+ return 1;
+}
+
void ssl3_read_close_notify(SSL *ssl) {
/* Read records until an error or close_notify. */
while (ssl3_get_record(ssl) > 0) {
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index e3bcb91..88d5f29 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -821,6 +821,10 @@
ctx->enable_early_data = !!enabled;
}
+int SSL_early_data_accepted(const SSL *ssl) {
+ return ssl->early_data_accepted;
+}
+
static int bio_retry_reason_to_error(int reason) {
switch (reason) {
case BIO_RR_CONNECT:
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 759d87b..ef4a889 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2086,11 +2086,30 @@
* https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8 */
static int ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
- /* TODO(svaldez): Support 0RTT. */
+ SSL *const ssl = hs->ssl;
+ uint16_t session_version;
+ if (ssl->session == NULL ||
+ !ssl->method->version_from_wire(&session_version,
+ ssl->session->ssl_version) ||
+ session_version < TLS1_3_VERSION ||
+ ssl->session->ticket_max_early_data == 0 ||
+ hs->received_hello_retry_request ||
+ !ssl->ctx->enable_early_data) {
+ return 1;
+ }
+
+ hs->early_data_offered = 1;
+
+ if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) ||
+ !CBB_add_u16(out, 0) ||
+ !CBB_flush(out)) {
+ return 0;
+ }
+
return 1;
}
-static int ext_early_data_parse_clienthello(SSL_HANDSHAKE *hs,
+static int ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs,
uint8_t *out_alert, CBS *contents) {
SSL *const ssl = hs->ssl;
if (contents == NULL) {
@@ -2102,11 +2121,44 @@
return 0;
}
- /* Since we don't currently accept 0-RTT, we have to skip past any early data
- * the client might have sent. */
- if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
- ssl->s3->skip_early_data = 1;
+ if (!ssl->s3->session_reused) {
+ *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+ OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+ return 0;
}
+
+ ssl->early_data_accepted = 1;
+ return 1;
+}
+
+static int ext_early_data_parse_clienthello(SSL_HANDSHAKE *hs,
+ uint8_t *out_alert, CBS *contents) {
+ SSL *const ssl = hs->ssl;
+ if (contents == NULL ||
+ ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
+ return 1;
+ }
+
+ if (CBS_len(contents) != 0) {
+ *out_alert = SSL_AD_DECODE_ERROR;
+ return 0;
+ }
+
+ hs->early_data_offered = 1;
+ return 1;
+}
+
+static int ext_early_data_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
+ if (!hs->ssl->early_data_accepted) {
+ return 1;
+ }
+
+ if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) ||
+ !CBB_add_u16(out, 0) ||
+ !CBB_flush(out)) {
+ return 0;
+ }
+
return 1;
}
@@ -2597,9 +2649,9 @@
TLSEXT_TYPE_early_data,
NULL,
ext_early_data_add_clienthello,
- forbid_parse_serverhello,
+ ext_early_data_parse_serverhello,
ext_early_data_parse_clienthello,
- dont_add_serverhello,
+ ext_early_data_add_serverhello,
},
{
TLSEXT_TYPE_supported_versions,
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 2c70361..11d0d19 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -108,6 +108,7 @@
bssl::UniquePtr<SSL_SESSION> new_session;
bool ticket_decrypt_done = false;
bool alpn_select_done = false;
+ bool is_resume = false;
bool early_callback_ready = false;
};
@@ -764,6 +765,10 @@
*out = (const uint8_t*)config->select_alpn.data();
*outlen = config->select_alpn.size();
+ if (GetTestState(ssl)->is_resume && config->select_resume_alpn.size() > 0) {
+ *out = (const uint8_t*)config->select_resume_alpn.data();
+ *outlen = config->select_resume_alpn.size();
+ }
return SSL_TLSEXT_ERR_OK;
}
@@ -1109,7 +1114,8 @@
NULL);
}
- if (!config->select_alpn.empty() || config->decline_alpn) {
+ if (!config->select_alpn.empty() || !config->select_resume_alpn.empty() ||
+ config->decline_alpn) {
SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, NULL);
}
@@ -1422,13 +1428,22 @@
}
}
- if (!config->expected_alpn.empty()) {
+ std::string expected_alpn = config->expected_alpn;
+ if (is_resume && !config->expected_resume_alpn.empty()) {
+ expected_alpn = config->expected_resume_alpn;
+ }
+ bool expect_no_alpn = (!is_resume && config->expect_no_alpn) ||
+ (is_resume && config->expect_no_resume_alpn);
+ if (expect_no_alpn) {
+ expected_alpn.clear();
+ }
+
+ if (!expected_alpn.empty() || expect_no_alpn) {
const uint8_t *alpn_proto;
unsigned alpn_proto_len;
SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len);
- if (alpn_proto_len != config->expected_alpn.size() ||
- OPENSSL_memcmp(alpn_proto, config->expected_alpn.data(),
- alpn_proto_len) != 0) {
+ if (alpn_proto_len != expected_alpn.size() ||
+ OPENSSL_memcmp(alpn_proto, expected_alpn.data(), alpn_proto_len) != 0) {
fprintf(stderr, "negotiated alpn proto mismatch\n");
return false;
}
@@ -1540,6 +1555,15 @@
return false;
}
+ if (is_resume) {
+ if ((config->expect_accept_early_data && !SSL_early_data_accepted(ssl)) ||
+ (config->expect_reject_early_data && SSL_early_data_accepted(ssl))) {
+ fprintf(stderr,
+ "Early data was%s accepted, but we expected the opposite\n",
+ SSL_early_data_accepted(ssl) ? "" : " not");
+ return false;
+ }
+ }
if (!config->psk.empty()) {
if (SSL_get_peer_cert_chain(ssl) != nullptr) {
@@ -1629,6 +1653,10 @@
static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
SSL_CTX *ssl_ctx, const TestConfig *config,
bool is_resume, SSL_SESSION *session) {
+ if (is_resume && config->enable_resume_early_data) {
+ SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
+ }
+
bssl::UniquePtr<SSL> ssl(SSL_new(ssl_ctx));
if (!ssl) {
return false;
@@ -1639,6 +1667,8 @@
return false;
}
+ GetTestState(ssl.get())->is_resume = is_resume;
+
if (config->fallback_scsv &&
!SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) {
return false;
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 167e872..65a8797 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -249,6 +249,7 @@
extendedMasterSecret bool // Whether an extended master secret was used to generate the session
sctList []byte
ocspResponse []byte
+ earlyALPN string
ticketCreationTime time.Time
ticketExpiration time.Time
ticketAgeAdd uint32
@@ -1146,10 +1147,26 @@
// SendEarlyData causes a TLS 1.3 client to send the provided data
// in application data records immediately after the ClientHello,
- // provided that the client has a PSK that is appropriate for sending
- // early data and includes that PSK in its ClientHello.
+ // provided that the client offers a TLS 1.3 session. It will do this
+ // whether or not the server advertised early data for the ticket.
SendEarlyData [][]byte
+ // ExpectEarlyDataAccepted causes a TLS 1.3 client to check that early data
+ // was accepted by the server.
+ ExpectEarlyDataAccepted bool
+
+ // AlwaysAcceptEarlyData causes a TLS 1.3 server to always accept early data
+ // regardless of ALPN mismatch.
+ AlwaysAcceptEarlyData bool
+
+ // AlwaysRejectEarlyData causes a TLS 1.3 server to always reject early data.
+ AlwaysRejectEarlyData bool
+
+ // SendEarlyDataExtension, if true, causes a TLS 1.3 server to send the
+ // early_data extension in EncryptedExtensions, independent of whether
+ // it was accepted.
+ SendEarlyDataExtension bool
+
// 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 1bdca84..ffe4f34 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1477,6 +1477,7 @@
ticketExpiration: c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
ticketAgeAdd: newSessionTicket.ticketAgeAdd,
maxEarlyDataSize: newSessionTicket.maxEarlyDataSize,
+ earlyALPN: c.clientProtocol,
}
cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
@@ -1789,6 +1790,7 @@
ticketCreationTime: c.config.time(),
ticketExpiration: c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
ticketAgeAdd: uint32(addBuffer[3])<<24 | uint32(addBuffer[2])<<16 | uint32(addBuffer[1])<<8 | uint32(addBuffer[0]),
+ earlyALPN: []byte(c.clientProtocol),
}
if !c.config.Bugs.SendEmptySessionTicket {
@@ -1798,7 +1800,6 @@
return err
}
}
-
c.out.Lock()
defer c.out.Unlock()
_, err = c.writeRecord(recordTypeHandshake, m.marshal())
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index bf38c1a..a3fc14a 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -330,7 +330,7 @@
}
var sendEarlyData bool
- if len(hello.pskIdentities) > 0 && session.maxEarlyDataSize > 0 && c.config.Bugs.SendEarlyData != nil {
+ if len(hello.pskIdentities) > 0 && c.config.Bugs.SendEarlyData != nil {
hello.hasEarlyData = true
sendEarlyData = true
}
@@ -1336,6 +1336,18 @@
c.srtpProtectionProfile = serverExtensions.srtpProtectionProfile
}
+ if c.vers >= VersionTLS13 && c.didResume {
+ if c.config.Bugs.ExpectEarlyDataAccepted && !serverExtensions.hasEarlyData {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: server did not accept early data when expected")
+ }
+
+ if !c.config.Bugs.ExpectEarlyDataAccepted && serverExtensions.hasEarlyData {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: server accepted early data when not expected")
+ }
+ }
+
return nil
}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 64edd01..b1afc03 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -451,7 +451,7 @@
var pskIndex int
foundKEMode := bytes.IndexByte(pskKEModes, pskDHEKEMode) >= 0
- if foundKEMode {
+ if foundKEMode && !config.SessionTicketsDisabled {
for i, pskIdentity := range pskIdentities {
// TODO(svaldez): Check the obfuscatedTicketAge before accepting 0-RTT.
sessionState, ok := c.decryptTicket(pskIdentity.ticket)
@@ -579,6 +579,10 @@
c.writeRecord(recordTypeHandshake, helloRetryRequest.marshal())
c.flushHandshake()
+ if hs.clientHello.hasEarlyData {
+ c.skipEarlyData = true
+ }
+
// Read new ClientHello.
newMsg, err := c.readHandshake()
if err != nil {
@@ -591,6 +595,10 @@
}
hs.writeClientHash(newClientHello.marshal())
+ if newClientHello.hasEarlyData {
+ return errors.New("tls: EarlyData sent in new ClientHello")
+ }
+
applyBugsToClientHello(newClientHello, config)
// Check that the new ClientHello matches the old ClientHello,
@@ -628,6 +636,7 @@
newClientHelloCopy.pskIdentities[i].obfuscatedTicketAge = identity.obfuscatedTicketAge
}
newClientHelloCopy.pskBinders = oldClientHelloCopy.pskBinders
+ newClientHelloCopy.hasEarlyData = oldClientHelloCopy.hasEarlyData
if !oldClientHelloCopy.equal(&newClientHelloCopy) {
return errors.New("tls: new ClientHello does not match")
@@ -650,10 +659,13 @@
}
// Decide whether or not to accept early data.
- // TODO(nharper): This does not check that ALPN or SNI matches.
- if hs.clientHello.hasEarlyData {
- if !sendHelloRetryRequest && hs.sessionState != nil {
- encryptedExtensions.extensions.hasEarlyData = true
+ if !sendHelloRetryRequest && hs.clientHello.hasEarlyData {
+ if !config.Bugs.AlwaysRejectEarlyData && hs.sessionState != nil {
+ if c.clientProtocol == string(hs.sessionState.earlyALPN) || config.Bugs.AlwaysAcceptEarlyData {
+ encryptedExtensions.extensions.hasEarlyData = true
+ }
+ }
+ if encryptedExtensions.extensions.hasEarlyData {
earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyTrafficLabel)
c.in.useTrafficSecret(c.vers, hs.suite, earlyTrafficSecret, clientWrite)
@@ -673,6 +685,10 @@
}
}
+ if config.Bugs.SendEarlyDataExtension {
+ encryptedExtensions.extensions.hasEarlyData = true
+ }
+
// Resolve ECDHE and compute the handshake secret.
if hs.hello.hasKeyShare {
// Once a curve has been selected and a key share identified,
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 563d4b2..262ac28 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -3562,6 +3562,40 @@
// Cover HelloRetryRequest during an ECDHE-PSK resumption.
resumeSession: true,
})
+
+ // TODO(svaldez): Send data on early data once implemented.
+ tests = append(tests, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-accept-early-data",
+ },
+ })
+
+ tests = append(tests, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MinVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{},
+ ExpectEarlyDataAccepted: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ },
+ })
}
// TLS client auth.
@@ -5957,8 +5991,8 @@
// In TLS 1.3, clients may advertise a cipher list which does not
// include the selected cipher. Test that we tolerate this. Servers may
- // resume at another cipher if the PRF matches, but BoringSSL will
- // always decline.
+ // resume at another cipher if the PRF matches and are not doing 0-RTT, but
+ // BoringSSL will always decline.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-UnofferedCipher-TLS13",
@@ -9916,6 +9950,364 @@
},
},
})
+
+ // Test that we accept data-less early data.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-DataLessEarlyData-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{},
+ ExpectEarlyDataAccepted: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-accept-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-accept-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-Reject-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ AlwaysRejectEarlyData: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-HRR-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ },
+ })
+
+ // The client must check the server does not send the early_data
+ // extension while rejecting the session.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyDataWithoutResume-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ SessionTicketsDisabled: true,
+ Bugs: ProtocolBugs{
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ // The client must fail with a dedicated error code if the server
+ // responds with TLS 1.2 when offering 0-RTT.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-EarlyDataVersionDowngrade-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS12,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+ })
+
+ // Test that the client rejects an (unsolicited) early_data extension if
+ // the server sent an HRR.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-ServerAcceptsEarlyDataOnHRR-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+ SendEarlyDataExtension: true,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+
+ fooString := "foo"
+ barString := "bar"
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject
+ // that changed it.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNMismatch-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-alpn", "foo",
+ "-expect-resume-alpn", "bar",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the first connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNOmitted1-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"foo"},
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-no-alpn",
+ "-expect-resume-alpn", "foo",
+ },
+ })
+
+ // Test that the client reports the correct ALPN after a 0-RTT reject if
+ // ALPN was omitted from the second connection.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-ALPNOmitted2-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ "-expect-reject-early-data",
+ "-expect-alpn", "foo",
+ "-expect-no-resume-alpn",
+ },
+ })
+
+ // Test that the client enforces ALPN match on 0-RTT accept.
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-DataLessEarlyData-BadALPNMismatch-Client",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ ALPNProtocol: &fooString,
+ },
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ MaxEarlyDataSize: 16384,
+ Bugs: ProtocolBugs{
+ AlwaysAcceptEarlyData: true,
+ ALPNProtocol: &barString,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-advertise-alpn", "\x03foo\x03bar",
+ "-enable-early-data",
+ "-expect-early-data-info",
+ },
+ shouldFail: true,
+ expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
+ })
+
+ // Test that the server correctly rejects 0-RTT when the previous
+ // session did not allow early data on resumption.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-NonZeroRTTSession-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-resume-early-data",
+ "-expect-reject-early-data",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the first
+ // connection.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNOmitted1-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "",
+ "-select-resume-alpn", "foo",
+ },
+ })
+
+ // Test that we reject early data where ALPN is omitted from the second
+ // connection.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNOmitted2-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "foo",
+ "-select-resume-alpn", "",
+ },
+ })
+
+ // Test that we reject early data with mismatched ALPN.
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-EarlyData-ALPNMismatch-Server",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"foo"},
+ },
+ resumeConfig: &Config{
+ MaxVersion: VersionTLS13,
+ NextProtos: []string{"bar"},
+ Bugs: ProtocolBugs{
+ SendEarlyData: [][]byte{{}},
+ ExpectEarlyDataAccepted: false,
+ },
+ },
+ resumeSession: true,
+ flags: []string{
+ "-enable-early-data",
+ "-select-alpn", "foo",
+ "-select-resume-alpn", "bar",
+ },
+ })
+
}
func addTLS13CipherPreferenceTests() {
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 4a4540c..10ac54f 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -25,6 +25,7 @@
handshakeHash []byte
certificates [][]byte
extendedMasterSecret bool
+ earlyALPN []byte
ticketCreationTime time.Time
ticketExpiration time.Time
ticketFlags uint32
@@ -58,6 +59,9 @@
msg.addU32(s.ticketAgeAdd)
}
+ earlyALPN := msg.addU16LengthPrefixed()
+ earlyALPN.addBytes(s.earlyALPN)
+
return msg.finish()
}
@@ -138,6 +142,14 @@
data = data[4:]
}
+ earlyALPNLen := int(data[0])<<8 | int(data[1])
+ data = data[2:]
+ if len(data) < earlyALPNLen {
+ return false
+ }
+ s.earlyALPN = data[:earlyALPNLen]
+ data = data[earlyALPNLen:]
+
if len(data) > 0 {
return false
}
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index e581581..7e57543 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -85,6 +85,7 @@
{ "-use-ticket-callback", &TestConfig::use_ticket_callback },
{ "-renew-ticket", &TestConfig::renew_ticket },
{ "-enable-early-data", &TestConfig::enable_early_data },
+ { "-enable-resume-early-data", &TestConfig::enable_resume_early_data },
{ "-enable-client-custom-extension",
&TestConfig::enable_client_custom_extension },
{ "-enable-server-custom-extension",
@@ -123,6 +124,10 @@
&TestConfig::expect_no_secure_renegotiation },
{ "-expect-session-id", &TestConfig::expect_session_id },
{ "-expect-no-session-id", &TestConfig::expect_no_session_id },
+ { "-expect-accept-early-data", &TestConfig::expect_accept_early_data },
+ { "-expect-reject-early-data", &TestConfig::expect_reject_early_data },
+ { "-expect-no-alpn", &TestConfig::expect_no_alpn },
+ { "-expect-no-resume-alpn", &TestConfig::expect_no_resume_alpn },
};
const Flag<std::string> kStringFlags[] = {
@@ -137,8 +142,10 @@
{ "-host-name", &TestConfig::host_name },
{ "-advertise-alpn", &TestConfig::advertise_alpn },
{ "-expect-alpn", &TestConfig::expected_alpn },
+ { "-expect-resume-alpn", &TestConfig::expected_resume_alpn },
{ "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn },
{ "-select-alpn", &TestConfig::select_alpn },
+ { "-select-resume-alpn", &TestConfig::select_resume_alpn },
{ "-psk", &TestConfig::psk },
{ "-psk-identity", &TestConfig::psk_identity },
{ "-srtp-profiles", &TestConfig::srtp_profiles },
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 7057b48..fadd05e 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -52,8 +52,12 @@
std::string host_name;
std::string advertise_alpn;
std::string expected_alpn;
+ std::string expected_resume_alpn;
+ bool expect_no_alpn = false;
+ bool expect_no_resume_alpn = false;
std::string expected_advertised_alpn;
std::string select_alpn;
+ std::string select_resume_alpn;
bool decline_alpn = false;
bool expect_session_miss = false;
bool expect_extended_master_secret = false;
@@ -84,9 +88,12 @@
bool expect_ticket_renewal = false;
bool expect_no_session = false;
bool expect_early_data_info = false;
+ bool expect_accept_early_data = false;
+ bool expect_reject_early_data = false;
bool use_ticket_callback = false;
bool renew_ticket = false;
bool enable_early_data = false;
+ bool enable_resume_early_data = false;
bool enable_client_custom_extension = false;
bool enable_server_custom_extension = false;
bool custom_extension_skip = false;
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index 6243923..4a16437 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -64,6 +64,14 @@
break;
}
+ case ssl_hs_read_end_of_early_data: {
+ int ret = ssl->method->read_end_of_early_data(ssl);
+ if (ret <= 0) {
+ return ret;
+ }
+ break;
+ }
+
case ssl_hs_x509_lookup:
ssl->rwstate = SSL_X509_LOOKUP;
hs->wait = ssl_hs_ok;
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index f13a4f7..16aedc6 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -37,6 +37,7 @@
state_process_server_certificate,
state_process_server_certificate_verify,
state_process_server_finished,
+ state_send_end_of_early_data,
state_send_client_certificate,
state_send_client_certificate_verify,
state_complete_client_certificate_verify,
@@ -144,7 +145,11 @@
}
static enum ssl_hs_wait_t do_send_second_client_hello(SSL_HANDSHAKE *hs) {
- if (!ssl_write_client_hello(hs)) {
+ SSL *const ssl = hs->ssl;
+ /* TODO(svaldez): Ensure that we set can_early_write to false since 0-RTT is
+ * rejected if we receive a HelloRetryRequest. */
+ if (!ssl->method->set_write_state(ssl, NULL) ||
+ !ssl_write_client_hello(hs)) {
return ssl_hs_error;
}
@@ -254,7 +259,6 @@
ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
return ssl_hs_error;
}
- ssl_set_session(ssl, NULL);
/* Resumption incorporates fresh key material, so refresh the timeout. */
ssl_session_renew_timeout(ssl, hs->new_session,
@@ -267,17 +271,6 @@
hs->new_session->cipher = cipher;
hs->new_cipher = cipher;
- /* Store the initial negotiated ALPN in the session. */
- if (ssl->s3->alpn_selected != NULL) {
- hs->new_session->early_alpn =
- BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
- if (hs->new_session->early_alpn == NULL) {
- ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
- return ssl_hs_error;
- }
- hs->new_session->early_alpn_len = ssl->s3->alpn_selected_len;
- }
-
/* The PRF hash is now known. Set up the key schedule. */
if (!tls13_init_key_schedule(hs)) {
return ssl_hs_error;
@@ -319,7 +312,13 @@
if (!ssl_hash_current_message(hs) ||
!tls13_derive_handshake_secrets(hs) ||
!tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret,
- hs->hash_len) ||
+ hs->hash_len)) {
+ return ssl_hs_error;
+ }
+
+ /* If not sending early data, set client traffic keys now so that alerts are
+ * encrypted. */
+ if (!hs->early_data_offered &&
!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
hs->hash_len)) {
return ssl_hs_error;
@@ -347,6 +346,32 @@
return ssl_hs_error;
}
+ /* Store the negotiated ALPN in the session. */
+ if (ssl->s3->alpn_selected != NULL) {
+ hs->new_session->early_alpn =
+ BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
+ if (hs->new_session->early_alpn == NULL) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+ return ssl_hs_error;
+ }
+ hs->new_session->early_alpn_len = ssl->s3->alpn_selected_len;
+ }
+
+ if (ssl->early_data_accepted) {
+ if (ssl->session->cipher != hs->new_session->cipher ||
+ ssl->session->early_alpn_len != ssl->s3->alpn_selected_len ||
+ OPENSSL_memcmp(ssl->session->early_alpn, ssl->s3->alpn_selected,
+ ssl->s3->alpn_selected_len) != 0) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_ALPN_MISMATCH_ON_EARLY_DATA);
+ return ssl_hs_error;
+ }
+ }
+
+ /* Release offered session now that it is no longer needed. */
+ if (ssl->s3->session_reused) {
+ ssl_set_session(ssl, NULL);
+ }
+
if (!ssl_hash_current_message(hs)) {
return ssl_hs_error;
}
@@ -450,12 +475,32 @@
}
ssl->method->received_flight(ssl);
+ hs->tls13_state = state_send_end_of_early_data;
+ return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ /* TODO(svaldez): Stop sending early data. */
+ if (ssl->early_data_accepted &&
+ !ssl->method->add_alert(ssl, SSL3_AL_WARNING,
+ TLS1_AD_END_OF_EARLY_DATA)) {
+ return ssl_hs_error;
+ }
+
+ if (hs->early_data_offered &&
+ !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
+ hs->hash_len)) {
+ return ssl_hs_error;
+ }
+
hs->tls13_state = state_send_client_certificate;
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
+
/* The peer didn't request a certificate. */
if (!hs->cert_request) {
hs->tls13_state = state_complete_second_flight;
@@ -581,6 +626,9 @@
case state_process_server_finished:
ret = do_process_server_finished(hs);
break;
+ case state_send_end_of_early_data:
+ ret = do_send_end_of_early_data(hs);
+ break;
case state_send_client_certificate:
ret = do_send_client_certificate(hs);
break;
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c
index 412705d..16efd85 100644
--- a/ssl/tls13_enc.c
+++ b/ssl/tls13_enc.c
@@ -28,22 +28,43 @@
#include "internal.h"
-int tls13_init_key_schedule(SSL_HANDSHAKE *hs) {
- if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, ssl3_protocol_version(hs->ssl),
- hs->new_cipher->algorithm_prf)) {
+static int init_key_schedule(SSL_HANDSHAKE *hs, uint16_t version,
+ int algorithm_prf) {
+ if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, version, algorithm_prf)) {
return 0;
}
-
hs->hash_len = SSL_TRANSCRIPT_digest_len(&hs->transcript);
/* Initialize the secret to the zero key. */
OPENSSL_memset(hs->secret, 0, hs->hash_len);
+ return 1;
+}
+
+int tls13_init_key_schedule(SSL_HANDSHAKE *hs) {
+ if (!init_key_schedule(hs, ssl3_protocol_version(hs->ssl),
+ hs->new_cipher->algorithm_prf)) {
+ return 0;
+ }
+
SSL_TRANSCRIPT_free_buffer(&hs->transcript);
return 1;
}
+int tls13_init_early_key_schedule(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ uint16_t session_version;
+ if (!ssl->method->version_from_wire(&session_version,
+ ssl->session->ssl_version) ||
+ !init_key_schedule(hs, session_version,
+ ssl->session->cipher->algorithm_prf)) {
+ return 0;
+ }
+
+ return 1;
+}
+
int tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in,
size_t len) {
return HKDF_extract(hs->secret, &hs->hash_len,
@@ -100,6 +121,13 @@
int tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction,
const uint8_t *traffic_secret,
size_t traffic_secret_len) {
+ const SSL_SESSION *session = SSL_get_session(ssl);
+ uint16_t version;
+ if (!ssl->method->version_from_wire(&version, session->ssl_version)) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+
if (traffic_secret_len > 0xff) {
OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
return 0;
@@ -108,14 +136,13 @@
/* Look up cipher suite properties. */
const EVP_AEAD *aead;
size_t discard;
- if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard,
- SSL_get_session(ssl)->cipher,
- ssl3_protocol_version(ssl))) {
+ if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher,
+ version)) {
return 0;
}
const EVP_MD *digest = ssl_get_handshake_digest(
- SSL_get_session(ssl)->cipher->algorithm_prf, ssl3_protocol_version(ssl));
+ session->cipher->algorithm_prf, version);
/* Derive the key. */
size_t key_len = EVP_AEAD_key_length(aead);
@@ -134,8 +161,7 @@
}
SSL_AEAD_CTX *traffic_aead = SSL_AEAD_CTX_new(
- direction, ssl3_protocol_version(ssl), SSL_get_session(ssl)->cipher, key,
- key_len, NULL, 0, iv, iv_len);
+ direction, version, session->cipher, key, key_len, NULL, 0, iv, iv_len);
if (traffic_aead == NULL) {
return 0;
}
@@ -164,6 +190,11 @@
return 1;
}
+static const char kTLS13LabelExporter[] = "exporter master secret";
+static const char kTLS13LabelEarlyExporter[] = "early exporter master secret";
+
+static const char kTLS13LabelClientEarlyTraffic[] =
+ "client early traffic secret";
static const char kTLS13LabelClientHandshakeTraffic[] =
"client handshake traffic secret";
static const char kTLS13LabelServerHandshakeTraffic[] =
@@ -173,6 +204,18 @@
static const char kTLS13LabelServerApplicationTraffic[] =
"server application traffic secret";
+int tls13_derive_early_secrets(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ return derive_secret(hs, hs->early_traffic_secret, hs->hash_len,
+ (const uint8_t *)kTLS13LabelClientEarlyTraffic,
+ strlen(kTLS13LabelClientEarlyTraffic)) &&
+ 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 *)kTLS13LabelEarlyExporter,
+ strlen(kTLS13LabelEarlyExporter));
+}
+
int tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
return derive_secret(hs, hs->client_handshake_secret, hs->hash_len,
@@ -187,8 +230,6 @@
hs->server_handshake_secret, hs->hash_len);
}
-static const char kTLS13LabelExporter[] = "exporter master secret";
-
int tls13_derive_application_secrets(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
ssl->s3->exporter_secret_len = hs->hash_len;
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 9c8d1a1..4854002 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -43,6 +43,8 @@
state_send_server_certificate_verify,
state_complete_server_certificate_verify,
state_send_server_finished,
+ state_read_second_client_flight,
+ state_process_end_of_early_data,
state_process_client_certificate,
state_process_client_certificate_verify,
state_process_channel_id,
@@ -289,6 +291,19 @@
/* Carry over authentication information from the previous handshake into
* a fresh session. */
hs->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY);
+
+ if (/* Early data must be acceptable for this ticket. */
+ ssl->ctx->enable_early_data &&
+ session->ticket_max_early_data != 0 &&
+ /* The client must have offered early data. */
+ hs->early_data_offered &&
+ /* The negotiated ALPN must match the one in the ticket. */
+ ssl->s3->alpn_selected_len == session->early_alpn_len &&
+ OPENSSL_memcmp(ssl->s3->alpn_selected, session->early_alpn,
+ ssl->s3->alpn_selected_len) == 0) {
+ ssl->early_data_accepted = 1;
+ }
+
SSL_SESSION_free(session);
if (hs->new_session == NULL) {
ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -323,6 +338,7 @@
}
}
+ /* Store the initial negotiated ALPN in the session. */
if (ssl->s3->alpn_selected != NULL) {
hs->new_session->early_alpn =
BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
@@ -351,12 +367,22 @@
return ssl_hs_error;
}
+ if (ssl->early_data_accepted) {
+ if (!tls13_derive_early_secrets(hs)) {
+ return ssl_hs_error;
+ }
+ } else if (hs->early_data_offered) {
+ ssl->s3->skip_early_data = 1;
+ }
+
ssl->method->received_flight(ssl);
/* Resolve ECDHE and incorporate it into the secret. */
int need_retry;
if (!resolve_ecdhe_secret(hs, &need_retry, &client_hello)) {
if (need_retry) {
+ ssl->early_data_accepted = 0;
+ ssl->s3->skip_early_data = 1;
hs->tls13_state = state_send_hello_retry_request;
return ssl_hs_ok;
}
@@ -440,8 +466,6 @@
/* Derive and enable the handshake traffic secrets. */
if (!tls13_derive_handshake_secrets(hs) ||
- !tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
- hs->hash_len) ||
!tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_handshake_secret,
hs->hash_len)) {
goto err;
@@ -543,8 +567,33 @@
return ssl_hs_error;
}
+ hs->tls13_state = state_read_second_client_flight;
+ return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ if (ssl->early_data_accepted) {
+ if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->early_traffic_secret,
+ hs->hash_len)) {
+ return ssl_hs_error;
+ }
+ hs->tls13_state = state_process_end_of_early_data;
+ return ssl_hs_read_end_of_early_data;
+ }
+
+ hs->tls13_state = state_process_end_of_early_data;
+ return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) {
+ SSL *const ssl = hs->ssl;
+ if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
+ hs->hash_len)) {
+ return ssl_hs_error;
+ }
hs->tls13_state = state_process_client_certificate;
- return ssl_hs_flush_and_read_message;
+ return ssl_hs_read_message;
}
static enum ssl_hs_wait_t do_process_client_certificate(SSL_HANDSHAKE *hs) {
@@ -720,10 +769,16 @@
break;
case state_complete_server_certificate_verify:
ret = do_send_server_certificate_verify(hs, 0 /* complete */);
- break;
+ break;
case state_send_server_finished:
ret = do_send_server_finished(hs);
break;
+ case state_read_second_client_flight:
+ ret = do_read_second_client_flight(hs);
+ break;
+ case state_process_end_of_early_data:
+ ret = do_process_end_of_early_data(hs);
+ break;
case state_process_client_certificate:
ret = do_process_client_certificate(hs);
break;
diff --git a/ssl/tls_method.c b/ssl/tls_method.c
index 6144f86..2af4f2c 100644
--- a/ssl/tls_method.c
+++ b/ssl/tls_method.c
@@ -141,6 +141,7 @@
ssl3_release_current_message,
ssl3_read_app_data,
ssl3_read_change_cipher_spec,
+ ssl3_read_end_of_early_data,
ssl3_read_close_notify,
ssl3_write_app_data,
ssl3_dispatch_alert,
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index aafb6f5..0f9720c 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -327,6 +327,14 @@
}
if (type == SSL3_RT_ALERT) {
+ /* Return end_of_early_data alerts as-is for the caller to process. */
+ if (CBS_len(out) == 2 &&
+ CBS_data(out)[0] == SSL3_AL_WARNING &&
+ CBS_data(out)[1] == TLS1_AD_END_OF_EARLY_DATA) {
+ *out_type = type;
+ return ssl_open_record_success;
+ }
+
return ssl_process_alert(ssl, out_alert, CBS_data(out), CBS_len(out));
}