Adding TLS 1.3 Record Layer.
In TLS 1.3, the actual record type is hidden within the encrypted data
and the record layer defaults to using a TLS 1.0 {3, 1} record version
for compatibility. Additionally the record layer no longer checks the
minor version of the record layer to maintain compatibility with the
TLS 1.3 spec.
Change-Id: If2c08e48baab170c1658e0715c33929d36c9be3a
Reviewed-on: https://boringssl-review.googlesource.com/8091
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/tls_record.c b/ssl/tls_record.c
index b71da17..45d9940 100644
--- a/ssl/tls_record.c
+++ b/ssl/tls_record.c
@@ -170,17 +170,21 @@
}
size_t ssl_max_seal_overhead(const SSL *ssl) {
+ size_t ret = SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
if (SSL_IS_DTLS(ssl)) {
- return DTLS1_RT_HEADER_LENGTH +
- SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
+ ret += DTLS1_RT_HEADER_LENGTH;
} else {
- size_t ret = SSL3_RT_HEADER_LENGTH +
- SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
- if (ssl_needs_record_splitting(ssl)) {
- ret *= 2;
- }
- return ret;
+ ret += SSL3_RT_HEADER_LENGTH;
}
+ /* TLS 1.3 needs an extra byte for the encrypted record type. */
+ if (ssl->s3->have_version &&
+ ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+ ret += 1;
+ }
+ if (!SSL_IS_DTLS(ssl) && ssl_needs_record_splitting(ssl)) {
+ ret *= 2;
+ }
+ return ret;
}
enum ssl_open_record_t tls_open_record(
@@ -200,9 +204,9 @@
return ssl_open_record_partial;
}
- /* Check the version. */
- if ((ssl->s3->have_version && version != ssl->version) ||
- (version >> 8) != SSL3_VERSION_MAJOR) {
+ /* Check that the major version in the record matches. As of TLS 1.3, the
+ * minor version is no longer checked. */
+ if ((version >> 8) != SSL3_VERSION_MAJOR) {
OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
*out_alert = SSL_AD_PROTOCOL_VERSION;
return ssl_open_record_error;
@@ -241,6 +245,23 @@
return ssl_open_record_error;
}
+ /* TLS 1.3 hides the record type inside the encrypted data. */
+ if (ssl->s3->have_version &&
+ ssl3_protocol_version(ssl) >= TLS1_3_VERSION &&
+ ssl->s3->aead_read_ctx != NULL) {
+ while (plaintext_len != 0 && out[plaintext_len - 1] == 0) {
+ plaintext_len--;
+ }
+
+ if (plaintext_len == 0) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
+ *out_alert = SSL_AD_DECRYPT_ERROR;
+ return ssl_open_record_error;
+ }
+ type = out[plaintext_len - 1];
+ plaintext_len--;
+ }
+
/* Check the plaintext length. */
if (plaintext_len > SSL3_RT_MAX_PLAIN_LENGTH) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
@@ -284,11 +305,14 @@
out[0] = type;
- /* Some servers hang if initial ClientHello is larger than 256 bytes and
- * record version number > TLS 1.0. */
- uint16_t wire_version = ssl->version;
- if (!ssl->s3->have_version && ssl->version > SSL3_VERSION) {
- wire_version = TLS1_VERSION;
+ /* The TLS record-layer version number is meaningless and, starting in
+ * TLS 1.3, is frozen at TLS 1.0. But for historical reasons, SSL 3.0
+ * ClientHellos should use SSL 3.0 and pre-TLS-1.3 expects the version
+ * to change after version negotiation. */
+ uint16_t wire_version = TLS1_VERSION;
+ if (ssl->version == SSL3_VERSION ||
+ (ssl->s3->have_version && ssl3_protocol_version(ssl) < TLS1_3_VERSION)) {
+ wire_version = ssl->version;
}
out[1] = wire_version >> 8;
out[2] = wire_version & 0xff;
@@ -322,6 +346,25 @@
int tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
uint8_t type, const uint8_t *in, size_t in_len) {
size_t frag_len = 0;
+
+ /* TLS 1.3 hides the actual record type inside the encrypted data. */
+ if (ssl->s3->have_version &&
+ ssl3_protocol_version(ssl) >= TLS1_3_VERSION &&
+ ssl->s3->aead_read_ctx != NULL) {
+ size_t padding = SSL3_RT_HEADER_LENGTH + 1;
+
+ if (in_len > in_len + padding || max_out < in_len + padding) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ memmove(out + SSL3_RT_HEADER_LENGTH, in, in_len);
+ out[SSL3_RT_HEADER_LENGTH + in_len] = type;
+ in = out + SSL3_RT_HEADER_LENGTH;
+ type = SSL3_RT_APPLICATION_DATA;
+ in_len++;
+ }
+
if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
ssl_needs_record_splitting(ssl)) {
/* |do_seal_record| will notice if it clobbers |in[0]|, but not if it