Add SSL_AEAD_CTX_seal_scatter.
This plumbs EVP_AEAD_CTX_seal_scatter all the way through to
tls_record.c, so we can add a new zero-copy record sealing method on top
of the existing code.
Change-Id: I01fdd88abef5442dc16605ea31b29b4b1231c073
Reviewed-on: https://boringssl-review.googlesource.com/17684
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/tls_record.cc b/ssl/tls_record.cc
index 3bc0b29..28ffb4e 100644
--- a/ssl/tls_record.cc
+++ b/ssl/tls_record.cc
@@ -358,30 +358,24 @@
return ssl_open_record_discard;
}
-static int do_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) {
- assert(!buffers_alias(in, in_len, out, max_out));
+static int do_seal_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
+ uint8_t *out_suffix, size_t *out_suffix_len,
+ const size_t max_out_suffix_len, uint8_t type,
+ const uint8_t *in, const size_t in_len) {
+ assert(in == out || !buffers_alias(in, in_len, out, in_len));
+ assert(!buffers_alias(in, in_len, out_prefix, ssl_record_prefix_len(ssl)));
+ assert(!buffers_alias(in, in_len, out_suffix, max_out_suffix_len));
/* TLS 1.3 hides the actual record type inside the encrypted data. */
+ uint8_t *extra_in = NULL;
+ size_t extra_in_len = 0;
if (ssl->s3->aead_write_ctx != NULL &&
ssl->s3->aead_write_ctx->version >= TLS1_3_VERSION) {
- if (in_len > in_len + SSL3_RT_HEADER_LENGTH + 1 ||
- max_out < in_len + SSL3_RT_HEADER_LENGTH + 1) {
- OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
- return 0;
- }
-
- OPENSSL_memcpy(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 (max_out < SSL3_RT_HEADER_LENGTH) {
- OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
- return 0;
+ extra_in = &type;
+ extra_in_len = 1;
+ out_prefix[0] = SSL3_RT_APPLICATION_DATA;
+ } else {
+ out_prefix[0] = type;
}
/* The TLS record-layer version number is meaningless and, starting in
@@ -395,65 +389,144 @@
if (ssl->s3->have_version && ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
wire_version = ssl->version;
}
-
- /* Write the non-length portions of the header. */
- out[0] = type;
- out[1] = wire_version >> 8;
- out[2] = wire_version & 0xff;
+ out_prefix[1] = wire_version >> 8;
+ out_prefix[2] = wire_version & 0xff;
/* Write the ciphertext, leaving two bytes for the length. */
- size_t ciphertext_len;
- if (!SSL_AEAD_CTX_seal(ssl->s3->aead_write_ctx, out + SSL3_RT_HEADER_LENGTH,
- &ciphertext_len, max_out - SSL3_RT_HEADER_LENGTH, type,
- wire_version, ssl->s3->write_sequence, in, in_len) ||
+ if (!SSL_AEAD_CTX_seal_scatter(
+ ssl->s3->aead_write_ctx, out_prefix + SSL3_RT_HEADER_LENGTH, out,
+ out_suffix, out_suffix_len, max_out_suffix_len, type, wire_version,
+ ssl->s3->write_sequence, in, in_len, extra_in, extra_in_len) ||
!ssl_record_sequence_update(ssl->s3->write_sequence, 8)) {
return 0;
}
/* Fill in the length. */
+ const size_t ciphertext_len =
+ SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx) + in_len +
+ *out_suffix_len;
if (ciphertext_len >= 1 << 15) {
OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
return 0;
}
- out[3] = ciphertext_len >> 8;
- out[4] = ciphertext_len & 0xff;
+ out_prefix[3] = ciphertext_len >> 8;
+ out_prefix[4] = ciphertext_len & 0xff;
- *out_len = SSL3_RT_HEADER_LENGTH + ciphertext_len;
-
- ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HEADER, out,
+ ssl_do_msg_callback(ssl, 1 /* write */, SSL3_RT_HEADER, out_prefix,
SSL3_RT_HEADER_LENGTH);
return 1;
}
-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) {
- if (buffers_alias(in, in_len, out, max_out)) {
- OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
- return 0;
- }
-
- size_t frag_len = 0;
+static size_t tls_seal_scatter_prefix_len(const SSL *ssl, uint8_t type,
+ size_t in_len) {
+ size_t ret = SSL3_RT_HEADER_LENGTH;
if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
ssl_needs_record_splitting(ssl)) {
- if (!do_seal_record(ssl, out, &frag_len, max_out, type, in, 1)) {
+ /* In the case of record splitting, the 1-byte record (of the 1/n-1 split)
+ * will be placed in the prefix, as will four of the five bytes of the
+ * record header for the main record. The final byte will replace the first
+ * byte of the plaintext that was used in the small record. */
+ ret += ssl_cipher_get_record_split_len(ssl->s3->aead_write_ctx->cipher);
+ ret += SSL3_RT_HEADER_LENGTH - 1;
+ } else {
+ ret += SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx);
+ }
+ return ret;
+}
+
+/* tls_seal_scatter_record seals a new record of type |type| and body |in| and
+ * splits it between |out_prefix|, |out|, and |out_suffix|. Exactly
+ * |tls_seal_scatter_prefix_len| bytes are written to |out_prefix|, |in_len|
+ * bytes to |out|, and up to 1 + |SSL_AEAD_CTX_max_overhead| bytes to
+ * |out_suffix|. |*out_suffix_len| is set to the actual number of bytes written
+ * to |out_suffix|. It returns one on success and zero on error. If enabled,
+ * |tls_seal_scatter_record| implements TLS 1.0 CBC 1/n-1 record splitting and
+ * may write two records concatenated. */
+static int tls_seal_scatter_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,
+ uint8_t *out_suffix, size_t *out_suffix_len,
+ size_t max_out_suffix_len, uint8_t type,
+ const uint8_t *in, size_t in_len) {
+ if (type == SSL3_RT_APPLICATION_DATA && in_len > 1 &&
+ ssl_needs_record_splitting(ssl)) {
+ assert(SSL_AEAD_CTX_explicit_nonce_len(ssl->s3->aead_write_ctx) == 0);
+ const size_t prefix_len = SSL3_RT_HEADER_LENGTH;
+
+ /* Write the 1-byte fragment into |out_prefix|. */
+ uint8_t *split_body = out_prefix + prefix_len;
+ uint8_t *split_suffix = split_body + 1;
+
+ /* TODO(martinkr): Make AEAD code not complain if max_suffix_len is lower
+ * than |EVP_AEAD_max_overhead| but still sufficiently large. */
+ size_t split_max_suffix_len =
+ SSL_AEAD_CTX_max_suffix_len(ssl->s3->aead_write_ctx, 0);
+ size_t split_suffix_len = 0;
+ if (!do_seal_record(ssl, out_prefix, split_body, split_suffix,
+ &split_suffix_len, split_max_suffix_len, type, in, 1)) {
return 0;
}
- in++;
- in_len--;
- out += frag_len;
- max_out -= frag_len;
+
+ size_t split_record_len = prefix_len + 1 + split_suffix_len;
#if !defined(BORINGSSL_UNSAFE_FUZZER_MODE)
assert(SSL3_RT_HEADER_LENGTH + ssl_cipher_get_record_split_len(
ssl->s3->aead_write_ctx->cipher) ==
- frag_len);
+ split_record_len);
#endif
+
+ /* Write the n-1-byte fragment. The header gets split between |out_prefix|
+ * (header[:-1]) and |out| (header[-1:]). */
+ uint8_t tmp_prefix[SSL3_RT_HEADER_LENGTH];
+ if (!do_seal_record(ssl, tmp_prefix, out + 1, out_suffix, out_suffix_len,
+ max_out_suffix_len, type, in + 1, in_len - 1)) {
+ return 0;
+ }
+ assert(tls_seal_scatter_prefix_len(ssl, type, in_len) ==
+ split_record_len + SSL3_RT_HEADER_LENGTH - 1);
+ OPENSSL_memcpy(out_prefix + split_record_len, tmp_prefix,
+ SSL3_RT_HEADER_LENGTH - 1);
+ OPENSSL_memcpy(out, tmp_prefix + SSL3_RT_HEADER_LENGTH - 1, 1);
+ return 1;
}
- if (!do_seal_record(ssl, out, out_len, max_out, type, in, in_len)) {
+ return do_seal_record(ssl, out_prefix, out, out_suffix, out_suffix_len,
+ max_out_suffix_len, type, in, in_len);
+}
+
+int tls_seal_record(SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out_len,
+ uint8_t type, const uint8_t *in, size_t in_len) {
+ if (buffers_alias(in, in_len, out, max_out_len)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_OUTPUT_ALIASES_INPUT);
return 0;
}
- *out_len += frag_len;
+
+ const size_t prefix_len = tls_seal_scatter_prefix_len(ssl, type, in_len);
+
+ if (in_len + prefix_len < in_len) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+ return 0;
+ }
+ if (max_out_len < in_len + prefix_len) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ uint8_t *prefix = out;
+ uint8_t *body = out + prefix_len;
+ uint8_t *suffix = body + in_len;
+ size_t max_suffix_len = max_out_len - prefix_len - in_len;
+ size_t suffix_len = 0;
+
+ if (!tls_seal_scatter_record(ssl, prefix, body, suffix, &suffix_len,
+ max_suffix_len, type, in, in_len)) {
+ return 0;
+ }
+
+ if (prefix_len + in_len + suffix_len < prefix_len + in_len) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+ return 0;
+ }
+
+ *out_len = prefix_len + in_len + suffix_len;
return 1;
}