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>
This commit is contained in:
Steven Valdez 2016-12-07 15:29:45 -05:00 committed by CQ bot account: commit-bot@chromium.org
parent c0c7019282
commit 08b65f4e31
18 changed files with 179 additions and 31 deletions

View File

@ -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;

View File

@ -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

View File

@ -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. */

View File

@ -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;

View File

@ -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;

View File

@ -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:

View File

@ -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;
}

View File

@ -1045,6 +1045,10 @@ static bssl::UniquePtr<SSL_CTX> 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<SSL_SESSION> *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) {

View File

@ -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

View File

@ -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,
}

View File

@ -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:]
}
}

View File

@ -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() {

View File

@ -81,8 +81,10 @@ const Flag<bool> 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",

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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)) ||

View File

@ -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