Enabling 0-RTT on new Session Tickets.
This adds support for setting 0-RTT mode on tickets minted by
BoringSSL, allowing for testing of the initial handshake knowledge.
BUG=76
Change-Id: Ic199842c03b5401ef122a537fdb7ed9e9a5c635a
Reviewed-on: https://boringssl-review.googlesource.com/12740
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/internal.h b/ssl/internal.h
index e26fa13..919f5aa 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1148,10 +1148,10 @@
* it. It writes the parsed extensions to pointers denoted by |ext_types|. On
* success, it fills in the |out_present| and |out_data| fields and returns one.
* Otherwise, it sets |*out_alert| to an alert to send and returns zero. Unknown
- * extensions are rejected. */
+ * extensions are rejected unless |ignore_unknown| is 1. */
int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
const SSL_EXTENSION_TYPE *ext_types,
- size_t num_ext_types);
+ size_t num_ext_types, int ignore_unknown);
/* SSLKEYLOGFILE functions. */
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index a0594f4..492884f 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -780,7 +780,7 @@
int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
const SSL_EXTENSION_TYPE *ext_types,
- size_t num_ext_types) {
+ size_t num_ext_types, int ignore_unknown) {
/* Reset everything. */
for (size_t i = 0; i < num_ext_types; i++) {
*ext_types[i].out_present = 0;
@@ -807,6 +807,9 @@
}
if (ext_type == NULL) {
+ if (ignore_unknown) {
+ continue;
+ }
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
return 0;
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index d8a7d8f..c9dfdcc 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -128,6 +128,7 @@
* ticketAgeAdd [21] OCTET STRING OPTIONAL,
* isServer [22] BOOLEAN DEFAULT TRUE,
* peerSignatureAlgorithm [23] INTEGER OPTIONAL,
+ * ticketMaxEarlyData [24] INTEGER OPTIONAL,
* }
*
* Note: historically this serialization has included other optional
@@ -180,6 +181,8 @@
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 22;
static const int kPeerSignatureAlgorithmTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 23;
+static const int kTicketMaxEarlyDataTag =
+ CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 24;
static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
size_t *out_len, int for_ticket) {
@@ -392,6 +395,13 @@
goto err;
}
+ if (in->ticket_max_early_data != 0 &&
+ (!CBB_add_asn1(&session, &child, kTicketMaxEarlyDataTag) ||
+ !CBB_add_asn1_uint64(&child, in->ticket_max_early_data))) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+ goto err;
+ }
+
if (!CBB_finish(&cbb, out_data, out_len)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
goto err;
@@ -775,6 +785,8 @@
if (!SSL_SESSION_parse_u16(&session, &ret->peer_signature_algorithm,
kPeerSignatureAlgorithmTag, 0) ||
+ !SSL_SESSION_parse_u32(&session, &ret->ticket_max_early_data,
+ kTicketMaxEarlyDataTag, 0) ||
CBS_len(&session) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
goto err;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 2e9f4a6..a60bf81 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -874,6 +874,10 @@
return ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
}
+void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled) {
+ ctx->enable_early_data = !!enabled;
+}
+
static int bio_retry_reason_to_error(int reason) {
switch (reason) {
case BIO_RR_CONNECT:
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 62ee28e..7adef1a 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -276,6 +276,7 @@
session->original_handshake_hash_len;
new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
new_session->ticket_age_add = session->ticket_age_add;
+ new_session->ticket_max_early_data = session->ticket_max_early_data;
new_session->extended_master_secret = session->extended_master_secret;
}
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index d46a027..a98ff43 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1045,6 +1045,10 @@
SSL_CTX_set_short_header_enabled(ssl_ctx.get(), 1);
}
+ if (config->enable_early_data) {
+ SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
+ }
+
return ssl_ctx;
}
@@ -1844,6 +1848,19 @@
GetTestState(ssl.get())->got_new_session ? "" : " not");
return false;
}
+
+ if (expect_new_session) {
+ bool got_early_data_info =
+ GetTestState(ssl.get())->new_session->ticket_max_early_data != 0;
+ if (config->expect_early_data_info != got_early_data_info) {
+ fprintf(
+ stderr,
+ "new session did%s include ticket_early_data_info, but we expected "
+ "the opposite\n",
+ got_early_data_info ? "" : " not");
+ return false;
+ }
+ }
}
if (out_session) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index e527185..029fd4a 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -95,6 +95,7 @@
extensionSupportedVersions uint16 = 43 // draft-ietf-tls-tls13-16
extensionCookie uint16 = 44 // draft-ietf-tls-tls13-16
extensionPSKKeyExchangeModes uint16 = 45 // draft-ietf-tls-tls13-18
+ extensionTicketEarlyDataInfo uint16 = 46 // draft-ietf-tls-tls13-18
extensionCustom uint16 = 1234 // not IANA assigned
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
extensionRenegotiationInfo uint16 = 0xff01
@@ -102,11 +103,6 @@
extensionShortHeader uint16 = 27463 // not IANA assigned
)
-// TLS ticket extension numbers
-const (
- ticketExtensionCustom uint16 = 1234 // not IANA assigned
-)
-
// TLS signaling cipher suite values
const (
scsvRenegotiation uint16 = 0x00ff
@@ -959,6 +955,15 @@
// receipt of a NewSessionTicket message.
ExpectNoNewSessionTicket bool
+ // SendTicketEarlyDataInfo, if non-zero, is the maximum amount of data that we
+ // will accept as early data, and gets sent in the ticket_early_data_info
+ // extension of the NewSessionTicket message.
+ SendTicketEarlyDataInfo uint32
+
+ // ExpectTicketEarlyDataInfo, if true, means that the client will fail upon
+ // absence of the ticket_early_data_info extension.
+ ExpectTicketEarlyDataInfo bool
+
// ExpectTicketAge, if non-zero, is the expected age of the ticket that the
// server receives from the client.
ExpectTicketAge time.Duration
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 3e22465..62a46bf 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1451,6 +1451,10 @@
return errors.New("tls: no GREASE ticket extension found")
}
+ if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.earlyDataInfo == 0 {
+ return errors.New("tls: no ticket_early_data_info extension found")
+ }
+
if c.config.Bugs.ExpectNoNewSessionTicket {
return errors.New("tls: received unexpected NewSessionTicket")
}
@@ -1765,6 +1769,7 @@
m := &newSessionTicketMsg{
version: c.vers,
ticketLifetime: uint32(24 * time.Hour / time.Second),
+ earlyDataInfo: c.config.Bugs.SendTicketEarlyDataInfo,
customExtension: c.config.Bugs.CustomTicketExtension,
ticketAgeAdd: ticketAgeAdd,
}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 01f673b..a431cb5 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -2008,6 +2008,7 @@
ticketLifetime uint32
ticketAgeAdd uint32
ticket []byte
+ earlyDataInfo uint32
customExtension string
hasGREASEExtension bool
}
@@ -2031,8 +2032,12 @@
if m.version >= VersionTLS13 {
extensions := body.addU16LengthPrefixed()
+ if m.earlyDataInfo > 0 {
+ extensions.addU16(extensionTicketEarlyDataInfo)
+ extensions.addU16LengthPrefixed().addU32(m.earlyDataInfo)
+ }
if len(m.customExtension) > 0 {
- extensions.addU16(ticketExtensionCustom)
+ extensions.addU16(extensionCustom)
extensions.addU16LengthPrefixed().addBytes([]byte(m.customExtension))
}
}
@@ -2078,28 +2083,37 @@
if len(data) < 2 {
return false
}
- extsLength := int(data[0])<<8 + int(data[1])
+
+ extensionsLength := int(data[0])<<8 | int(data[1])
data = data[2:]
- if len(data) < extsLength {
+ if extensionsLength != len(data) {
return false
}
- extensions := data[:extsLength]
- data = data[extsLength:]
- for len(extensions) > 0 {
- if len(extensions) < 4 {
+ for len(data) != 0 {
+ if len(data) < 4 {
return false
}
- extValue := uint16(extensions[0])<<8 | uint16(extensions[1])
- extLength := int(extensions[2])<<8 | int(extensions[3])
- if len(extensions) < 4+extLength {
+ extension := uint16(data[0])<<8 | uint16(data[1])
+ length := int(data[2])<<8 | int(data[3])
+ data = data[4:]
+ if len(data) < length {
return false
}
- extensions = extensions[4+extLength:]
- if isGREASEValue(extValue) {
- m.hasGREASEExtension = true
+ switch extension {
+ case extensionTicketEarlyDataInfo:
+ if length != 4 {
+ return false
+ }
+ m.earlyDataInfo = uint32(data[0])<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3])
+ default:
+ if isGREASEValue(extension) {
+ m.hasGREASEExtension = true
+ }
}
+
+ data = data[length:]
}
}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 56026b2..5c3d914 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -8379,6 +8379,33 @@
expectedLocalError: "tls: invalid ticket age",
})
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TLS13-SendTicketEarlyDataInfo",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ SendTicketEarlyDataInfo: 16384,
+ },
+ },
+ flags: []string{
+ "-expect-early-data-info",
+ },
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TLS13-ExpectTicketEarlyDataInfo",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ ExpectTicketEarlyDataInfo: true,
+ },
+ },
+ flags: []string{
+ "-enable-early-data",
+ },
+ })
}
func addChangeCipherSpecTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 22e4c9c..a06b5e5 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -81,8 +81,10 @@
{ "-tls-unique", &TestConfig::tls_unique },
{ "-expect-ticket-renewal", &TestConfig::expect_ticket_renewal },
{ "-expect-no-session", &TestConfig::expect_no_session },
+ { "-expect-early-data-info", &TestConfig::expect_early_data_info },
{ "-use-ticket-callback", &TestConfig::use_ticket_callback },
{ "-renew-ticket", &TestConfig::renew_ticket },
+ { "-enable-early-data", &TestConfig::enable_early_data },
{ "-enable-client-custom-extension",
&TestConfig::enable_client_custom_extension },
{ "-enable-server-custom-extension",
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 882cddc..1307d56 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -83,8 +83,10 @@
bool tls_unique = false;
bool expect_ticket_renewal = false;
bool expect_no_session = false;
+ bool expect_early_data_info = false;
bool use_ticket_callback = false;
bool renew_ticket = false;
+ bool enable_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 5a058b1..7347fc4 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -243,7 +243,8 @@
uint8_t alert;
if (!ssl_parse_extensions(&extensions, &alert, ext_types,
- OPENSSL_ARRAY_SIZE(ext_types))) {
+ OPENSSL_ARRAY_SIZE(ext_types),
+ 0 /* reject unknown */)) {
ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
goto err;
}
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 20b80ed..1d8bc54 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -78,7 +78,8 @@
uint8_t alert;
if (!ssl_parse_extensions(&extensions, &alert, ext_types,
- OPENSSL_ARRAY_SIZE(ext_types))) {
+ OPENSSL_ARRAY_SIZE(ext_types),
+ 0 /* reject unknown */)) {
ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
@@ -211,7 +212,8 @@
uint8_t alert;
if (!ssl_parse_extensions(&extensions, &alert, ext_types,
- OPENSSL_ARRAY_SIZE(ext_types))) {
+ OPENSSL_ARRAY_SIZE(ext_types),
+ 0 /* reject unknown */)) {
ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
return ssl_hs_error;
}
@@ -659,6 +661,30 @@
return 0;
}
+ /* Parse out the extensions. */
+ int have_early_data_info = 0;
+ CBS early_data_info;
+ const SSL_EXTENSION_TYPE ext_types[] = {
+ {TLSEXT_TYPE_ticket_early_data_info, &have_early_data_info,
+ &early_data_info},
+ };
+
+ uint8_t alert;
+ if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+ OPENSSL_ARRAY_SIZE(ext_types),
+ 1 /* ignore unknown */)) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+ return ssl_hs_error;
+ }
+
+ if (have_early_data_info) {
+ if (!CBS_get_u32(&early_data_info, &session->ticket_max_early_data) ||
+ CBS_len(&early_data_info) != 0) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ return ssl_hs_error;
+ }
+ }
+
session->ticket_age_add_valid = 1;
session->not_resumable = 0;
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index cdf78e6..750e47f 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -29,6 +29,11 @@
#include "internal.h"
+/* kMaxEarlyDataAccepted is the advertised number of plaintext bytes of early
+ * data that will be accepted. This value should be slightly below
+ * kMaxEarlyDataSkipped in tls_record.c, which is measured in ciphertext. */
+static const size_t kMaxEarlyDataAccepted = 14336;
+
enum server_hs_state_t {
state_process_client_hello = 0,
state_select_parameters,
@@ -657,9 +662,6 @@
goto err;
}
- /* TODO(svaldez): Add support for sending 0RTT through TicketEarlyDataInfo
- * extension. */
-
CBB cbb, body, ticket, extensions;
if (!ssl->method->init_message(ssl, &cbb, &body,
SSL3_MT_NEW_SESSION_TICKET) ||
@@ -671,6 +673,18 @@
goto err;
}
+ if (ssl->ctx->enable_early_data) {
+ session->ticket_max_early_data = kMaxEarlyDataAccepted;
+
+ CBB early_data_info;
+ if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) ||
+ !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
+ !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
+ !CBB_flush(&extensions)) {
+ goto err;
+ }
+ }
+
/* Add a fake extension. See draft-davidben-tls-grease-01. */
if (!CBB_add_u16(&extensions,
ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) ||
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index 0039a02..362b0c2 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -125,10 +125,11 @@
* forever. */
static const uint8_t kMaxEmptyRecords = 32;
-/* kMaxEarlyDataSkipped is the maximum amount of data processed when skipping
- * over early data. Without this limit an attacker could send records at a
- * faster rate than we can process and cause trial decryption to loop
- * forever. */
+/* kMaxEarlyDataSkipped is the maximum number of rejected early data bytes that
+ * will be skipped. Without this limit an attacker could send records at a
+ * faster rate than we can process and cause trial decryption to loop forever.
+ * This value should be slightly above kMaxEarlyDataAccepted in tls13_server.c,
+ * which is measured in plaintext. */
static const size_t kMaxEarlyDataSkipped = 16384;
/* kMaxWarningAlerts is the number of consecutive warning alerts that will be