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/include/openssl/ssl.h b/include/openssl/ssl.h index 1a0e052..c230f8c 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -2913,6 +2913,11 @@ * peformed by |ssl|. This includes the pending renegotiation, if any. */ OPENSSL_EXPORT int SSL_total_renegotiations(const SSL *ssl); +/* SSL_CTX_set_early_data_enabled sets whether early data is allowed to be used + * with resumptions using |ctx|. WARNING: This is experimental and may cause + * interop failures until fully implemented. */ +OPENSSL_EXPORT void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled); + /* SSL_MAX_CERT_LIST_DEFAULT is the default maximum length, in bytes, of a peer * certificate chain. */ #define SSL_MAX_CERT_LIST_DEFAULT (1024 * 100) @@ -3758,6 +3763,10 @@ uint32_t ticket_age_add; + /* ticket_max_early_data is the maximum amount of data allowed to be sent as + * early data. If zero, 0-RTT is disallowed. */ + uint32_t ticket_max_early_data; + /* extended_master_secret is true if the master secret in this session was * generated using EMS and thus isn't vulnerable to the Triple Handshake * attack. */ @@ -4033,6 +4042,10 @@ * shutdown. */ unsigned quiet_shutdown:1; + /* If enable_early_data is non-zero, early data can be sent and accepted over + * new connections. */ + unsigned enable_early_data:1; + /* ocsp_stapling_enabled is only used by client connections and indicates * whether OCSP stapling will be requested. */ unsigned ocsp_stapling_enabled:1;
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h index 28828d1..7d9f2ac 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h
@@ -213,6 +213,7 @@ #define TLSEXT_TYPE_supported_versions 43 #define TLSEXT_TYPE_cookie 44 #define TLSEXT_TYPE_psk_key_exchange_modes 45 +#define TLSEXT_TYPE_ticket_early_data_info 46 /* ExtensionType value from RFC5746 */ #define TLSEXT_TYPE_renegotiate 0xff01
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