From 08b65f4e31f7eb63bad0e903e9f191bcc699eaca Mon Sep 17 00:00:00 2001 From: Steven Valdez Date: Wed, 7 Dec 2016 15:29:45 -0500 Subject: [PATCH] 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 Commit-Queue: David Benjamin CQ-Verified: CQ bot account: commit-bot@chromium.org --- include/openssl/ssl.h | 13 +++++++++ include/openssl/tls1.h | 1 + ssl/internal.h | 4 +-- ssl/s3_both.c | 5 +++- ssl/ssl_asn1.c | 12 ++++++++ ssl/ssl_lib.c | 4 +++ ssl/ssl_session.c | 1 + ssl/test/bssl_shim.cc | 17 ++++++++++++ ssl/test/runner/common.go | 15 ++++++---- ssl/test/runner/conn.go | 5 ++++ ssl/test/runner/handshake_messages.go | 40 ++++++++++++++++++--------- ssl/test/runner/runner.go | 27 ++++++++++++++++++ ssl/test/test_config.cc | 2 ++ ssl/test/test_config.h | 2 ++ ssl/tls13_both.c | 3 +- ssl/tls13_client.c | 30 ++++++++++++++++++-- ssl/tls13_server.c | 20 ++++++++++++-- ssl/tls_record.c | 9 +++--- 18 files changed, 179 insertions(+), 31 deletions(-) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 1a0e0529e..c230f8cfa 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -2913,6 +2913,11 @@ OPENSSL_EXPORT int SSL_renegotiate_pending(SSL *ssl); * 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 @@ struct ssl_session_st { 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 @@ struct ssl_ctx_st { * 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 28828d1d1..7d9f2acb2 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h @@ -213,6 +213,7 @@ extern "C" { #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 e26fa13ef..919f5aa23 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -1148,10 +1148,10 @@ typedef struct { * 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 a0594f4c7..492884f78 100644 --- a/ssl/s3_both.c +++ b/ssl/s3_both.c @@ -780,7 +780,7 @@ int ssl_verify_alarm_type(long type) { 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 @@ int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert, } 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 d8a7d8f5e..c9dfdcc1a 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 @@ static const int kIsServerTag = 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 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data, 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 @@ static SSL_SESSION *SSL_SESSION_parse(CBS *cbs) { 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 2e9f4a6e2..a60bf81e0 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -874,6 +874,10 @@ int SSL_send_fatal_alert(SSL *ssl, uint8_t alert) { 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 62ee28ebf..7adef1a32 100644 --- a/ssl/ssl_session.c +++ b/ssl/ssl_session.c @@ -276,6 +276,7 @@ SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) { 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 d46a02722..a98ff4387 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -1045,6 +1045,10 @@ static bssl::UniquePtr SetupCtx(const TestConfig *config) { 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 @@ static bool DoExchange(bssl::UniquePtr *out_session, 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 e527185bb..029fd4a9d 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -95,6 +95,7 @@ const ( 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 @@ const ( 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 @@ type ProtocolBugs struct { // 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 3e22465c2..62a46bf88 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -1451,6 +1451,10 @@ func (c *Conn) handlePostHandshakeMessage() error { 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 @@ func (c *Conn) SendNewSessionTicket() error { 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 01f673b75..a431cb559 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go @@ -2008,6 +2008,7 @@ type newSessionTicketMsg struct { ticketLifetime uint32 ticketAgeAdd uint32 ticket []byte + earlyDataInfo uint32 customExtension string hasGREASEExtension bool } @@ -2031,8 +2032,12 @@ func (m *newSessionTicketMsg) marshal() []byte { 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 @@ func (m *newSessionTicketMsg) unmarshal(data []byte) bool { 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 56026b235..5c3d914e0 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -8379,6 +8379,33 @@ func addSessionTicketTests() { 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 22e4c9ce2..a06b5e549 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -81,8 +81,10 @@ const Flag kBoolFlags[] = { { "-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 882cddcb4..1307d565d 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -83,8 +83,10 @@ struct TestConfig { 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 5a058b114..7347fc441 100644 --- a/ssl/tls13_both.c +++ b/ssl/tls13_both.c @@ -243,7 +243,8 @@ int tls13_process_certificate(SSL_HANDSHAKE *hs, int allow_anonymous) { 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 20b80ed36..1d8bc54d3 100644 --- a/ssl/tls13_client.c +++ b/ssl/tls13_client.c @@ -78,7 +78,8 @@ static enum ssl_hs_wait_t do_process_hello_retry_request(SSL_HANDSHAKE *hs) { 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 @@ static enum ssl_hs_wait_t do_process_server_hello(SSL_HANDSHAKE *hs) { 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 @@ int tls13_process_new_session_ticket(SSL *ssl) { 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 cdf78e666..750e47fa4 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 @@ static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) { 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 @@ static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) { 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 0039a023a..362b0c278 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