Adding NewSessionTicket.

We will now send tickets as a server and accept them as a
client. Correctly offering and resuming them in the handshake will be
implemented in a follow-up.

Now that we're actually processing draft 14 tickets, bump the draft
version.

Change-Id: I304320a29c4ffe564fa9c00642a4ace96ff8d871
Reviewed-on: https://boringssl-review.googlesource.com/8982
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-07-27 11:10:52 -04:00 committed by CQ bot account: commit-bot@chromium.org
parent e8e84b9008
commit 1e6f11a7ff
10 changed files with 272 additions and 109 deletions

View File

@ -572,7 +572,7 @@ OPENSSL_EXPORT int DTLSv1_handle_timeout(SSL *ssl);
#define DTLS1_VERSION 0xfeff
#define DTLS1_2_VERSION 0xfefd
#define TLS1_3_DRAFT_VERSION 13
#define TLS1_3_DRAFT_VERSION 14
/* SSL_CTX_set_min_version sets the minimum protocol version for |ctx| to
* |version|. */
@ -3702,7 +3702,10 @@ struct ssl_session_st {
uint8_t original_handshake_hash[EVP_MAX_MD_SIZE];
unsigned original_handshake_hash_len;
uint32_t tlsext_tick_lifetime_hint; /* Session lifetime hint in seconds */
uint32_t ticket_lifetime_hint; /* Session lifetime hint in seconds */
uint32_t ticket_flags;
uint32_t ticket_age_add;
/* 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
@ -3714,6 +3717,9 @@ struct ssl_session_st {
/* not_resumable is used to indicate that session resumption is disallowed. */
unsigned not_resumable:1;
/* ticket_age_add_valid is non-zero if |ticket_age_add| is valid. */
unsigned ticket_age_add_valid:1;
};
/* ssl_cipher_preference_list_st contains a list of SSL_CIPHERs with

View File

@ -1953,7 +1953,7 @@ static int ssl3_get_new_session_ticket(SSL *ssl) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
goto err;
}
session->tlsext_tick_lifetime_hint = ticket_lifetime_hint;
session->ticket_lifetime_hint = ticket_lifetime_hint;
/* Generate a session ID for this session based on the session ticket. We use
* the session ID mechanism for detecting ticket resumption. This also fits in

View File

@ -1867,107 +1867,22 @@ static int ssl3_send_new_session_ticket(SSL *ssl) {
return ssl->method->write_message(ssl);
}
/* Serialize the SSL_SESSION to be encoded into the ticket. */
uint8_t *session = NULL;
size_t session_len;
if (!SSL_SESSION_to_bytes_for_ticket(
ssl->session != NULL ? ssl->session : ssl->s3->new_session,
&session, &session_len)) {
return -1;
}
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
HMAC_CTX hctx;
HMAC_CTX_init(&hctx);
int ret = -1;
CBB cbb, body, ticket;
if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_NEW_SESSION_TICKET) ||
if (!ssl->method->init_message(ssl, &cbb, &body,
SSL3_MT_NEW_SESSION_TICKET) ||
/* Ticket lifetime hint (advisory only): We leave this unspecified for
* resumed session (for simplicity), and guess that tickets for new
* sessions will live as long as their sessions. */
!CBB_add_u32(&body, ssl->session != NULL ? 0 :
ssl->s3->new_session->timeout) ||
!CBB_add_u16_length_prefixed(&body, &ticket)) {
goto err;
}
/* If the session is too long, emit a dummy value rather than abort the
* connection. */
const size_t max_ticket_overhead =
16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE;
if (session_len > 0xffff - max_ticket_overhead) {
static const char kTicketPlaceholder[] = "TICKET TOO LARGE";
if (!CBB_add_bytes(&ticket, (const uint8_t *)kTicketPlaceholder,
strlen(kTicketPlaceholder)) ||
!CBB_add_u32(&body,
ssl->session != NULL ? 0 : ssl->s3->new_session->timeout) ||
!CBB_add_u16_length_prefixed(&body, &ticket) ||
!ssl_encrypt_ticket(ssl, &ticket, ssl->session != NULL
? ssl->session
: ssl->s3->new_session) ||
!ssl->method->finish_message(ssl, &cbb)) {
goto err;
return 0;
}
ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
ret = 1;
goto err;
}
/* Initialize HMAC and cipher contexts. If callback present it does all the
* work otherwise use generated values from parent ctx. */
SSL_CTX *tctx = ssl->initial_ctx;
uint8_t iv[EVP_MAX_IV_LENGTH];
uint8_t key_name[16];
if (tctx->tlsext_ticket_key_cb != NULL) {
if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx,
1 /* encrypt */) < 0) {
goto err;
}
} else {
if (!RAND_bytes(iv, 16) ||
!EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL,
tctx->tlsext_tick_aes_key, iv) ||
!HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(),
NULL)) {
goto err;
}
memcpy(key_name, tctx->tlsext_tick_key_name, 16);
}
uint8_t *ptr;
if (!CBB_add_bytes(&ticket, key_name, 16) ||
!CBB_add_bytes(&ticket, iv, EVP_CIPHER_CTX_iv_length(&ctx)) ||
!CBB_reserve(&ticket, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) {
goto err;
}
int len;
size_t total = 0;
if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session, session_len)) {
goto err;
}
total += len;
if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) {
goto err;
}
total += len;
if (!CBB_did_write(&ticket, total)) {
goto err;
}
unsigned hlen;
if (!HMAC_Update(&hctx, CBB_data(&ticket), CBB_len(&ticket)) ||
!CBB_reserve(&ticket, &ptr, EVP_MAX_MD_SIZE) ||
!HMAC_Final(&hctx, ptr, &hlen) ||
!CBB_did_write(&ticket, hlen) ||
!ssl->method->finish_message(ssl, &cbb)) {
goto err;
}
ssl->state = SSL3_ST_SW_SESSION_TICKET_B;
ret = ssl->method->write_message(ssl);
err:
OPENSSL_free(session);
EVP_CIPHER_CTX_cleanup(&ctx);
HMAC_CTX_cleanup(&hctx);
return ret;
return ssl->method->write_message(ssl);
}

View File

@ -891,6 +891,8 @@ struct ssl_handshake_st {
uint8_t *cert_context;
size_t cert_context_len;
uint8_t session_tickets_sent;
} /* SSL_HANDSHAKE */;
SSL_HANDSHAKE *ssl_handshake_new(enum ssl_hs_wait_t (*do_handshake)(SSL *ssl));
@ -925,6 +927,7 @@ int tls13_prepare_certificate(SSL *ssl);
enum ssl_private_key_result_t tls13_prepare_certificate_verify(
SSL *ssl, int is_first_run);
int tls13_prepare_finished(SSL *ssl);
int tls13_process_new_session_ticket(SSL *ssl);
int ssl_ext_key_share_parse_serverhello(SSL *ssl, uint8_t **out_secret,
size_t *out_secret_len,
@ -1202,12 +1205,18 @@ typedef struct dtls1_state_st {
extern const SSL3_ENC_METHOD TLSv1_enc_data;
extern const SSL3_ENC_METHOD SSLv3_enc_data;
/* From draft-ietf-tls-tls13-14, used in determining ticket validity. */
#define SSL_TICKET_ALLOW_EARLY_DATA 1
#define SSL_TICKET_ALLOW_DHE_RESUMPTION 2
#define SSL_TICKET_ALLOW_PSK_RESUMPTION 4
int ssl_clear_bad_session(SSL *ssl);
CERT *ssl_cert_new(void);
CERT *ssl_cert_dup(CERT *cert);
void ssl_cert_clear_certs(CERT *c);
void ssl_cert_free(CERT *c);
int ssl_get_new_session(SSL *ssl, int is_server);
int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session);
enum ssl_session_result_t {
ssl_session_success,

View File

@ -120,6 +120,8 @@
* extendedMasterSecret [17] BOOLEAN OPTIONAL,
* keyExchangeInfo [18] INTEGER OPTIONAL,
* certChain [19] SEQUENCE OF Certificate OPTIONAL,
* ticketFlags [20] INTEGER OPTIONAL,
* ticketAgeAdd [21] OCTET STRING OPTIONAL,
* }
*
* Note: historically this serialization has included other optional
@ -164,6 +166,10 @@ static const int kKeyExchangeInfoTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 18;
static const int kCertChainTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19;
static const int kTicketFlagsTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 20;
static const int kTicketAgeAddTag =
CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 21;
static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
size_t *out_len, int for_ticket) {
@ -255,9 +261,9 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
}
}
if (in->tlsext_tick_lifetime_hint > 0) {
if (in->ticket_lifetime_hint > 0) {
if (!CBB_add_asn1(&session, &child, kTicketLifetimeHintTag) ||
!CBB_add_asn1_uint64(&child, in->tlsext_tick_lifetime_hint)) {
!CBB_add_asn1_uint64(&child, in->ticket_lifetime_hint)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
goto err;
}
@ -341,6 +347,23 @@ static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
}
}
if (in->ticket_flags > 0) {
if (!CBB_add_asn1(&session, &child, kTicketFlagsTag) ||
!CBB_add_asn1_uint64(&child, in->ticket_flags)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
goto err;
}
}
if (in->ticket_age_add_valid) {
if (!CBB_add_asn1(&session, &child, kTicketAgeAddTag) ||
!CBB_add_asn1(&child, &child2, CBS_ASN1_OCTETSTRING) ||
!CBB_add_u32(&child2, in->ticket_age_add)) {
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;
@ -573,7 +596,7 @@ static SSL_SESSION *SSL_SESSION_parse(CBS *cbs) {
kHostNameTag) ||
!SSL_SESSION_parse_string(&session, &ret->psk_identity,
kPSKIdentityTag) ||
!SSL_SESSION_parse_u32(&session, &ret->tlsext_tick_lifetime_hint,
!SSL_SESSION_parse_u32(&session, &ret->ticket_lifetime_hint,
kTicketLifetimeHintTag, 0) ||
!SSL_SESSION_parse_octet_string(&session, &ret->tlsext_tick,
&ret->tlsext_ticklen, kTicketTag)) {
@ -652,6 +675,19 @@ static SSL_SESSION *SSL_SESSION_parse(CBS *cbs) {
}
}
CBS age_add;
int age_add_present;
if (!SSL_SESSION_parse_u32(&session, &ret->ticket_flags,
kTicketFlagsTag, 0) ||
!CBS_get_optional_asn1_octet_string(&session, &age_add, &age_add_present,
kTicketAgeAddTag) ||
(age_add_present &&
!CBS_get_u32(&age_add, &ret->ticket_age_add)) ||
CBS_len(&age_add) != 0) {
goto err;
}
ret->ticket_age_add_valid = age_add_present;
if (CBS_len(&session) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
goto err;

View File

@ -219,8 +219,8 @@ SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) {
}
if (include_ticket) {
if (session->tlsext_tick != NULL) {
new_session->tlsext_tick = BUF_memdup(session->tlsext_tick,
session->tlsext_ticklen);
new_session->tlsext_tick =
BUF_memdup(session->tlsext_tick, session->tlsext_ticklen);
if (new_session->tlsext_tick == NULL) {
goto err;
}
@ -252,7 +252,9 @@ SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) {
session->original_handshake_hash_len);
new_session->original_handshake_hash_len =
session->original_handshake_hash_len;
new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
new_session->ticket_lifetime_hint = session->ticket_lifetime_hint;
new_session->ticket_flags = session->ticket_flags;
new_session->ticket_age_add = session->ticket_age_add;
new_session->extended_master_secret = session->extended_master_secret;
new_session->peer_sha256_valid = session->peer_sha256_valid;
new_session->not_resumable = 1;
@ -468,6 +470,93 @@ err:
return 0;
}
int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
int ret = 0;
/* Serialize the SSL_SESSION to be encoded into the ticket. */
uint8_t *session_buf = NULL;
size_t session_len;
if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) {
return -1;
}
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
HMAC_CTX hctx;
HMAC_CTX_init(&hctx);
/* If the session is too long, emit a dummy value rather than abort the
* connection. */
static const size_t kMaxTicketOverhead =
16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE;
if (session_len > 0xffff - kMaxTicketOverhead) {
static const char kTicketPlaceholder[] = "TICKET TOO LARGE";
if (CBB_add_bytes(out, (const uint8_t *)kTicketPlaceholder,
strlen(kTicketPlaceholder))) {
ret = 1;
}
goto err;
}
/* Initialize HMAC and cipher contexts. If callback present it does all the
* work otherwise use generated values from parent ctx. */
SSL_CTX *tctx = ssl->initial_ctx;
uint8_t iv[EVP_MAX_IV_LENGTH];
uint8_t key_name[16];
if (tctx->tlsext_ticket_key_cb != NULL) {
if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx,
1 /* encrypt */) < 0) {
goto err;
}
} else {
if (!RAND_bytes(iv, 16) ||
!EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL,
tctx->tlsext_tick_aes_key, iv) ||
!HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(),
NULL)) {
goto err;
}
memcpy(key_name, tctx->tlsext_tick_key_name, 16);
}
uint8_t *ptr;
if (!CBB_add_bytes(out, key_name, 16) ||
!CBB_add_bytes(out, iv, EVP_CIPHER_CTX_iv_length(&ctx)) ||
!CBB_reserve(out, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) {
goto err;
}
int len;
size_t total = 0;
if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session_buf, session_len)) {
goto err;
}
total += len;
if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) {
goto err;
}
total += len;
if (!CBB_did_write(out, total)) {
goto err;
}
unsigned hlen;
if (!HMAC_Update(&hctx, CBB_data(out), CBB_len(out)) ||
!CBB_reserve(out, &ptr, EVP_MAX_MD_SIZE) ||
!HMAC_Final(&hctx, ptr, &hlen) ||
!CBB_did_write(out, hlen)) {
goto err;
}
ret = 1;
err:
OPENSSL_free(session_buf);
EVP_CIPHER_CTX_cleanup(&ctx);
HMAC_CTX_cleanup(&hctx);
return ret;
}
/* ssl_lookup_session looks up |session_id| in the session cache and sets
* |*out_session| to an |SSL_SESSION| object if found. The caller takes
* ownership of the result. */

View File

@ -1028,6 +1028,14 @@ static int DoSendFatalAlert(SSL *ssl, uint8_t alert) {
return ret;
}
static uint16_t GetProtocolVersion(const SSL *ssl) {
uint16_t version = SSL_version(ssl);
if (!SSL_is_dtls(ssl)) {
return version;
}
return 0x0201 + ~version;
}
// CheckHandshakeProperties checks, immediately after |ssl| completes its
// initial handshake (or False Starts), whether all the properties are
// consistent with the test configuration and invariants.
@ -1057,8 +1065,8 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume) {
bool expect_new_session =
!config->expect_no_session &&
(!SSL_session_reused(ssl) || config->expect_ticket_renewal) &&
/* TODO(svaldez): Implement Session Resumption. */
SSL_version(ssl) != TLS1_3_VERSION;
// Session tickets are sent post-handshake in TLS 1.3.
GetProtocolVersion(ssl) < TLS1_3_VERSION;
if (expect_new_session != GetTestState(ssl)->got_new_session) {
fprintf(stderr,
"new session was%s cached, but we expected the opposite\n",
@ -1569,11 +1577,24 @@ static bool DoExchange(ScopedSSL_SESSION *out_session, SSL_CTX *ssl_ctx,
if (!config->is_server && !config->false_start &&
!config->implicit_handshake &&
// Session tickets are sent post-handshake in TLS 1.3.
GetProtocolVersion(ssl.get()) < TLS1_3_VERSION &&
GetTestState(ssl.get())->got_new_session) {
fprintf(stderr, "new session was established after the handshake\n");
return false;
}
if (GetProtocolVersion(ssl.get()) >= TLS1_3_VERSION && !config->is_server) {
bool expect_new_session =
!config->expect_no_session && !config->shim_shuts_down;
if (expect_new_session != GetTestState(ssl.get())->got_new_session) {
fprintf(stderr,
"new session was%s cached, but we expected the opposite\n",
GetTestState(ssl.get())->got_new_session ? "" : " not");
return false;
}
}
if (out_session) {
out_session->reset(SSL_get1_session(ssl.get()));
}

View File

@ -458,8 +458,7 @@ int tls13_post_handshake(SSL *ssl) {
if (ssl->s3->tmp.message_type == SSL3_MT_NEW_SESSION_TICKET &&
!ssl->server) {
// TODO(svaldez): Handle NewSessionTicket.
return 1;
return tls13_process_new_session_ticket(ssl);
}
// TODO(svaldez): Handle post-handshake authentication.

View File

@ -566,3 +566,38 @@ enum ssl_hs_wait_t tls13_client_handshake(SSL *ssl) {
return ssl_hs_ok;
}
int tls13_process_new_session_ticket(SSL *ssl) {
SSL_SESSION *session = SSL_SESSION_dup(ssl->s3->established_session,
0 /* don't include ticket */);
if (session == NULL) {
return 0;
}
CBS cbs, extensions, ticket;
CBS_init(&cbs, ssl->init_msg, ssl->init_num);
if (!CBS_get_u32(&cbs, &session->ticket_lifetime_hint) ||
!CBS_get_u32(&cbs, &session->ticket_flags) ||
!CBS_get_u32(&cbs, &session->ticket_age_add) ||
!CBS_get_u16_length_prefixed(&cbs, &extensions) ||
!CBS_get_u16_length_prefixed(&cbs, &ticket) ||
!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen) ||
CBS_len(&cbs) != 0) {
SSL_SESSION_free(session);
ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return 0;
}
session->ticket_age_add_valid = 1;
session->not_resumable = 0;
if (ssl->ctx->new_session_cb != NULL &&
ssl->ctx->new_session_cb(ssl, session)) {
/* |new_session_cb|'s return value signals that it took ownership. */
return 1;
}
SSL_SESSION_free(session);
return 1;
}

View File

@ -43,6 +43,8 @@ enum server_hs_state_t {
state_process_client_certificate,
state_process_client_certificate_verify,
state_process_client_finished,
state_send_new_session_ticket,
state_flush_new_session_ticket,
state_done,
};
@ -506,10 +508,55 @@ static enum ssl_hs_wait_t do_process_client_finished(SSL *ssl,
}
ssl->method->received_flight(ssl);
hs->state = state_done;
hs->state = state_send_new_session_ticket;
return ssl_hs_ok;
}
static enum ssl_hs_wait_t do_send_new_session_ticket(SSL *ssl,
SSL_HANDSHAKE *hs) {
SSL_SESSION *session = ssl->s3->new_session;
session->ticket_lifetime_hint = session->timeout;
session->ticket_flags = SSL_TICKET_ALLOW_DHE_RESUMPTION;
if (!RAND_bytes((uint8_t *)&session->ticket_age_add,
sizeof(session->ticket_age_add))) {
return 0;
}
session->ticket_age_add_valid = 1;
CBB cbb, body, ticket;
if (!ssl->method->init_message(ssl, &cbb, &body,
SSL3_MT_NEW_SESSION_TICKET) ||
!CBB_add_u32(&body, session->ticket_lifetime_hint) ||
!CBB_add_u32(&body, session->ticket_flags) ||
!CBB_add_u32(&body, session->ticket_age_add) ||
!CBB_add_u16(&body, 0 /* no ticket extensions */) ||
!CBB_add_u16_length_prefixed(&body, &ticket) ||
!ssl_encrypt_ticket(ssl, &ticket, session) ||
!ssl->method->finish_message(ssl, &cbb)) {
CBB_cleanup(&cbb);
return ssl_hs_error;
}
hs->session_tickets_sent++;
hs->state = state_flush_new_session_ticket;
return ssl_hs_write_message;
}
/* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case the
* client makes several connections before getting a renewal. */
static const int kNumTickets = 2;
static enum ssl_hs_wait_t do_flush_new_session_ticket(SSL *ssl,
SSL_HANDSHAKE *hs) {
if (hs->session_tickets_sent >= kNumTickets) {
hs->state = state_done;
} else {
hs->state = state_send_new_session_ticket;
}
return ssl_hs_flush;
}
enum ssl_hs_wait_t tls13_server_handshake(SSL *ssl) {
SSL_HANDSHAKE *hs = ssl->s3->hs;
@ -562,6 +609,12 @@ enum ssl_hs_wait_t tls13_server_handshake(SSL *ssl) {
case state_process_client_finished:
ret = do_process_client_finished(ssl, hs);
break;
case state_send_new_session_ticket:
ret = do_send_new_session_ticket(ssl, hs);
break;
case state_flush_new_session_ticket:
ret = do_flush_new_session_ticket(ssl, hs);
break;
case state_done:
ret = ssl_hs_ok;
break;