TLS 1.3 split handshake initial support.
This does not change the wire format for any existing (TLS 1.2 and
lower) cases, which means it should not affect interoperability of
anything that exists.
Most tests pass, but I'll land those in a separate commit, after
nailing down some loose ends, and in order to have more time to reason
about interoperability effects.
Some features are not supported yet, e.g early data.
Change-Id: I7bb377017324cb3d98df75c5d6ed4757c6901ed7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/38926
Reviewed-by: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Matt Braithwaite <mab@google.com>
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index c48918e..a9c08fe 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -181,8 +181,15 @@
return true;
}
+// uses_disallowed_feature returns true iff |ssl| enables a feature that
+// disqualifies it for split handshakes.
+static bool uses_disallowed_feature(const SSL *ssl) {
+ return ssl->method->is_dtls || (ssl->config->cert && ssl->config->cert->dc) ||
+ ssl->config->quic_transport_params.size() > 0;
+}
+
bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
- if (ssl->method->is_dtls) {
+ if (uses_disallowed_feature(ssl)) {
return false;
}
@@ -223,11 +230,13 @@
}
bool SSL_serialize_handback(const SSL *ssl, CBB *out) {
- if (!ssl->server || ssl->method->is_dtls) {
+ if (!ssl->server || uses_disallowed_feature(ssl)) {
return false;
}
+ const SSL3_STATE *const s3 = ssl->s3;
+ SSL_HANDSHAKE *const hs = s3->hs.get();
handback_t type;
- switch (ssl->s3->hs->state) {
+ switch (hs->state) {
case state12_read_change_cipher_spec:
type = handback_after_session_resumption;
break;
@@ -237,11 +246,15 @@
case state12_finish_server_handshake:
type = handback_after_handshake;
break;
+ // The outer state machine is always in |state12_tls13| for a TLS 1.3
+ // handshake as TLS 1.3 uses |tls13_state|.
+ case state12_tls13:
+ type = handback_tls13;
+ break;
default:
return false;
}
- const SSL3_STATE *const s3 = ssl->s3;
size_t hostname_len = 0;
if (s3->hostname) {
hostname_len = strlen(s3->hostname.get());
@@ -249,7 +262,7 @@
Span<const uint8_t> transcript;
if (type == handback_after_ecdhe ||
- type == handback_after_session_resumption) {
+ type == handback_after_session_resumption || type == handback_tls13) {
transcript = s3->hs->transcript.buffer();
}
size_t write_iv_len = 0;
@@ -272,8 +285,12 @@
// TODO(mab): make sure everything is serialized.
CBB seq, key_share;
- const SSL_SESSION *session =
- s3->session_reused ? ssl->session.get() : s3->hs->new_session.get();
+ const SSL_SESSION *session;
+ if (type == handback_tls13) {
+ session = hs->new_session.get();
+ } else {
+ session = s3->session_reused ? ssl->session.get() : hs->new_session.get();
+ }
if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1_uint64(&seq, kHandbackVersion) ||
!CBB_add_asn1_uint64(&seq, type) ||
@@ -314,6 +331,25 @@
!s3->hs->key_shares[0]->Serialize(&key_share)) {
return false;
}
+ if (type == handback_tls13) {
+ if (!CBB_add_asn1_octet_string(&seq, hs->client_traffic_secret_0().data(),
+ hs->client_traffic_secret_0().size()) ||
+ !CBB_add_asn1_octet_string(&seq, hs->server_traffic_secret_0().data(),
+ hs->server_traffic_secret_0().size()) ||
+ !CBB_add_asn1_octet_string(&seq, hs->client_handshake_secret().data(),
+ hs->client_handshake_secret().size()) ||
+ !CBB_add_asn1_octet_string(&seq, hs->server_handshake_secret().data(),
+ hs->server_handshake_secret().size()) ||
+ !CBB_add_asn1_octet_string(&seq, hs->secret().data(),
+ hs->secret().size()) ||
+ !CBB_add_asn1_octet_string(&seq, s3->exporter_secret,
+ s3->exporter_secret_len) ||
+ !CBB_add_asn1_bool(&seq, s3->used_hello_retry_request) ||
+ !CBB_add_asn1_bool(&seq, hs->accept_psk_mode) ||
+ !CBB_add_asn1_int64(&seq, s3->ticket_age_skew)) {
+ return false;
+ }
+ }
return CBB_flush(out);
}
@@ -360,14 +396,15 @@
}
s3->hs = ssl_handshake_new(ssl);
- if (session_reused) {
+ SSL_HANDSHAKE *const hs = s3->hs.get();
+ if (!session_reused || type == handback_tls13) {
+ hs->new_session =
+ SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool);
+ session = hs->new_session.get();
+ } else {
ssl->session =
SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool);
session = ssl->session.get();
- } else {
- s3->hs->new_session =
- SSL_SESSION_parse(&seq, ssl->ctx->x509_method, ssl->ctx->pool);
- session = s3->hs->new_session.get();
}
if (!session || !CBS_get_asn1(&seq, &next_proto, CBS_ASN1_OCTETSTRING) ||
@@ -386,7 +423,7 @@
!CBS_get_asn1_uint64(&seq, &cipher)) {
return false;
}
- if ((s3->hs->new_cipher =
+ if ((hs->new_cipher =
SSL_get_cipher_by_value(static_cast<uint16_t>(cipher))) == nullptr) {
return false;
}
@@ -394,11 +431,35 @@
!CBS_get_asn1(&seq, &key_share, CBS_ASN1_SEQUENCE)) {
return false;
}
+ CBS client_handshake_secret, server_handshake_secret, client_traffic_secret_0,
+ server_traffic_secret_0, secret, exporter_secret;
+ if (type == handback_tls13) {
+ int used_hello_retry_request, accept_psk_mode;
+ int64_t ticket_age_skew;
+ if (!CBS_get_asn1(&seq, &client_traffic_secret_0, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&seq, &server_traffic_secret_0, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&seq, &client_handshake_secret, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&seq, &server_handshake_secret, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&seq, &secret, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1(&seq, &exporter_secret, CBS_ASN1_OCTETSTRING) ||
+ !CBS_get_asn1_bool(&seq, &used_hello_retry_request) ||
+ !CBS_get_asn1_bool(&seq, &accept_psk_mode) ||
+ !CBS_get_asn1_int64(&seq, &ticket_age_skew)) {
+ return false;
+ }
+ if (ticket_age_skew > std::numeric_limits<int32_t>::max() ||
+ ticket_age_skew < std::numeric_limits<int32_t>::min()) {
+ return false;
+ }
+ s3->ticket_age_skew = static_cast<int32_t>(ticket_age_skew);
+ s3->used_hello_retry_request = used_hello_retry_request;
+ hs->accept_psk_mode = accept_psk_mode;
+ }
ssl->version = session->ssl_version;
s3->have_version = true;
if (!ssl_method_supports_version(ssl->method, ssl->version) ||
- session->cipher != s3->hs->new_cipher ||
+ session->cipher != hs->new_cipher ||
ssl_protocol_version(ssl) < SSL_CIPHER_get_min_version(session->cipher) ||
SSL_CIPHER_get_max_version(session->cipher) < ssl_protocol_version(ssl)) {
return false;
@@ -407,19 +468,23 @@
ssl->server = true;
switch (type) {
case handback_after_session_resumption:
- ssl->s3->hs->state = state12_read_change_cipher_spec;
+ hs->state = state12_read_change_cipher_spec;
if (!session_reused) {
return false;
}
break;
case handback_after_ecdhe:
- ssl->s3->hs->state = state12_read_client_certificate;
+ hs->state = state12_read_client_certificate;
if (session_reused) {
return false;
}
break;
case handback_after_handshake:
- ssl->s3->hs->state = state12_finish_server_handshake;
+ hs->state = state12_finish_server_handshake;
+ break;
+ case handback_tls13:
+ hs->state = state12_tls13;
+ hs->tls13_state = state13_read_client_certificate;
break;
default:
return false;
@@ -443,17 +508,50 @@
s3->token_binding_negotiated = token_binding_negotiated;
s3->negotiated_token_binding_param =
static_cast<uint8_t>(negotiated_token_binding_param);
- s3->hs->next_proto_neg_seen = next_proto_neg_seen;
- s3->hs->wait = ssl_hs_flush;
- s3->hs->extended_master_secret = extended_master_secret;
- s3->hs->ticket_expected = ticket_expected;
+ hs->next_proto_neg_seen = next_proto_neg_seen;
+ hs->wait = ssl_hs_flush;
+ hs->extended_master_secret = extended_master_secret;
+ hs->ticket_expected = ticket_expected;
s3->aead_write_ctx->SetVersionIfNullCipher(ssl->version);
- s3->hs->cert_request = cert_request;
+ hs->cert_request = cert_request;
// TODO(davidben): When handoff for TLS 1.3 is added, serialize
// |early_data_reason| and stabilize the constants.
s3->early_data_reason = ssl_early_data_protocol_version;
+ if ((type == handback_after_ecdhe ||
+ type == handback_after_session_resumption || type == handback_tls13) &&
+ (!hs->transcript.Init() ||
+ !hs->transcript.InitHash(ssl_protocol_version(ssl), hs->new_cipher) ||
+ !hs->transcript.Update(transcript))) {
+ return false;
+ }
+ if (type == handback_tls13) {
+ const size_t digest_len = hs->transcript.DigestLen();
+ if (digest_len != CBS_len(&client_traffic_secret_0) ||
+ digest_len != CBS_len(&server_traffic_secret_0) ||
+ digest_len != CBS_len(&client_handshake_secret) ||
+ digest_len != CBS_len(&server_handshake_secret) ||
+ digest_len != CBS_len(&secret)) {
+ return false;
+ }
+ hs->ResizeSecrets(digest_len);
+ memcpy(hs->client_traffic_secret_0().data(),
+ CBS_data(&client_traffic_secret_0), digest_len);
+ memcpy(hs->server_traffic_secret_0().data(),
+ CBS_data(&server_traffic_secret_0), digest_len);
+ memcpy(hs->client_handshake_secret().data(),
+ CBS_data(&client_handshake_secret), digest_len);
+ memcpy(hs->server_handshake_secret().data(),
+ CBS_data(&server_handshake_secret), digest_len);
+ memcpy(hs->secret().data(), CBS_data(&secret), digest_len);
+
+ if (digest_len != CBS_len(&exporter_secret)) {
+ return false;
+ }
+ memcpy(s3->exporter_secret, CBS_data(&exporter_secret), digest_len);
+ s3->exporter_secret_len = digest_len;
+ }
Array<uint8_t> key_block;
if ((type == handback_after_session_resumption ||
type == handback_after_handshake) &&
@@ -470,16 +568,15 @@
sizeof(s3->read_sequence)))) {
return false;
}
- if ((type == handback_after_ecdhe ||
- type == handback_after_session_resumption) &&
- (!s3->hs->transcript.Init() ||
- !s3->hs->transcript.InitHash(ssl_protocol_version(ssl),
- s3->hs->new_cipher) ||
- !s3->hs->transcript.Update(transcript))) {
+ if (type == handback_tls13 &&
+ (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_open,
+ hs->client_handshake_secret()) ||
+ !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_seal,
+ hs->server_traffic_secret_0()))) {
return false;
}
if (type == handback_after_ecdhe &&
- (s3->hs->key_shares[0] = SSLKeyShare::Create(&key_share)) == nullptr) {
+ (hs->key_shares[0] = SSLKeyShare::Create(&key_share)) == nullptr) {
return false;
}
diff --git a/ssl/internal.h b/ssl/internal.h
index 799a1d8..8b55a9a 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1501,6 +1501,7 @@
handback_after_session_resumption,
handback_after_ecdhe,
handback_after_handshake,
+ handback_tls13,
};
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index 83b3d62..1f6ff64 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -62,7 +62,11 @@
return false;
}
- hs->transcript.FreeBuffer();
+ // Handback includes the whole handshake transcript, so we cannot free the
+ // transcript buffer in the handback case.
+ if (!hs->handback) {
+ hs->transcript.FreeBuffer();
+ }
return hkdf_extract_to_secret(hs, psk);
}
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index de69de7..b4f2134 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -822,6 +822,9 @@
hs->client_handshake_secret())) {
return ssl_hs_error;
}
+ if (hs->handback) {
+ return ssl_hs_handback;
+ }
hs->tls13_state = state13_read_client_certificate;
return ssl_hs_ok;
}