Test split, coalesced-split and empty handshake records
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/library/ssl_msg.c b/library/ssl_msg.c
index f1fe0ec..dba8d74 100644
--- a/library/ssl_msg.c
+++ b/library/ssl_msg.c
@@ -3699,6 +3699,7 @@
rec->buf_len = rec->data_offset + rec->data_len;
if (rec->data_len == 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("rejecting empty record"));
return MBEDTLS_ERR_SSL_INVALID_RECORD;
}
diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function
index 278656c..577249c 100644
--- a/tests/suites/test_suite_ssl.function
+++ b/tests/suites/test_suite_ssl.function
@@ -108,9 +108,100 @@
typedef enum {
RECOMBINE_NOMINAL, /* param: ignored */
+ RECOMBINE_SPLIT_FIRST, /* param: offset of split (<=0 means from end) */
+ RECOMBINE_INSERT_EMPTY, /* param: offset (<0 means from end) */
RECOMBINE_COALESCE, /* param: min number of records */
+ RECOMBINE_COALESCE_SPLIT_ONCE, /* param: offset of split (<=0 means from end) */
+ RECOMBINE_COALESCE_SPLIT_ENDS, /* the hairiest one? param: offset, must be >0 */
} recombine_records_instruction_t;
+/* Split the first record into two pieces of lengths offset and
+ * record_length-offset. If offset is zero or negative, count from the end of
+ * the record. */
+static int recombine_split_first_record(mbedtls_test_ssl_buffer *buf,
+ int offset)
+{
+ const size_t header_length = 5;
+ TEST_LE_U(header_length, buf->content_length);
+ size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+
+ if (offset > 0) {
+ TEST_LE_S(offset, record_length);
+ } else {
+ TEST_LE_S(-offset, record_length);
+ offset = record_length + offset;
+ }
+
+ /* Check that we have room to insert a record header */
+ TEST_LE_U(buf->content_length + header_length, buf->capacity);
+
+ /* Make room for a record header */
+ size_t new_record_start = header_length + offset;
+ size_t new_content_start = new_record_start + header_length;
+ memmove(buf->buffer + new_content_start,
+ buf->buffer + new_record_start,
+ buf->content_length - new_record_start);
+ buf->content_length += header_length;
+
+ /* Construct a header for the new record based on the existing one */
+ memcpy(buf->buffer + new_record_start, buf->buffer, header_length);
+ MBEDTLS_PUT_UINT16_BE(record_length - offset,
+ buf->buffer, new_content_start - 2);
+
+ /* Adjust the length of the first record */
+ MBEDTLS_PUT_UINT16_BE(offset, buf->buffer, header_length - 2);
+
+ return 0;
+
+exit:
+ return -1;
+}
+
+/* Insert an empty record at the given offset. If offset is negative,
+ * count from the end of the first record. */
+static int recombine_insert_empty_record(mbedtls_test_ssl_buffer *buf,
+ int offset)
+{
+ const size_t header_length = 5;
+ TEST_LE_U(header_length, buf->content_length);
+ size_t record_length = MBEDTLS_GET_UINT16_BE(buf->buffer, header_length - 2);
+
+ if (offset >= 0) {
+ TEST_LE_S(offset, record_length);
+ } else {
+ TEST_LE_S(-offset, record_length);
+ offset = record_length + offset;
+ }
+
+ /* Check that we have room to insert two record headers */
+ TEST_LE_U(buf->content_length + 2 * header_length, buf->capacity);
+
+ /* Make room for an empty record and a record header */
+ size_t empty_record_start = header_length + offset;
+ size_t empty_content_start = empty_record_start + header_length;
+ size_t tail_record_start = empty_content_start;
+ size_t tail_content_start = tail_record_start + header_length;
+ memmove(buf->buffer + tail_content_start,
+ buf->buffer + tail_record_start,
+ buf->content_length - tail_record_start);
+ buf->content_length += 2 * header_length;
+
+ /* Construct headers for the new records based on the existing one */
+ memcpy(buf->buffer + empty_record_start, buf->buffer, header_length);
+ MBEDTLS_PUT_UINT16_BE(0, buf->buffer, empty_content_start - 2);
+ memcpy(buf->buffer + tail_record_start, buf->buffer, header_length);
+ MBEDTLS_PUT_UINT16_BE(record_length - offset,
+ buf->buffer, tail_content_start - 2);
+
+ /* Adjust the length of the first record */
+ MBEDTLS_PUT_UINT16_BE(offset, buf->buffer, header_length - 2);
+
+ return 0;
+
+exit:
+ return -1;
+}
+
/* Coalesce TLS handshake records.
* DTLS is not supported.
* Encrypted or authenticated handshake records are not supported.
@@ -179,6 +270,16 @@
case RECOMBINE_NOMINAL:
break;
+ case RECOMBINE_SPLIT_FIRST:
+ ret = recombine_split_first_record(buf, param);
+ TEST_LE_S(0, ret);
+ break;
+
+ case RECOMBINE_INSERT_EMPTY:
+ ret = recombine_insert_empty_record(buf, param);
+ TEST_LE_S(0, ret);
+ break;
+
case RECOMBINE_COALESCE:
ret = recombine_coalesce_handshake_records(buf, param);
if (param == INT_MAX) {
@@ -188,6 +289,27 @@
}
break;
+ case RECOMBINE_COALESCE_SPLIT_ONCE:
+ ret = recombine_coalesce_handshake_records(buf, INT_MAX);
+ /* Require at least two coalesced records, otherwise this
+ * doesn't lead to a meaningful test (use
+ * RECOMBINE_SPLIT_FIRST instead). */
+ TEST_LE_S(2, ret);
+ ret = recombine_split_first_record(buf, param);
+ TEST_LE_S(0, ret);
+ break;
+
+ case RECOMBINE_COALESCE_SPLIT_ENDS:
+ ret = recombine_coalesce_handshake_records(buf, INT_MAX);
+ /* Accept a single record, which will be split at both ends */
+ TEST_LE_S(1, ret);
+ TEST_LE_S(1, param);
+ ret = recombine_split_first_record(buf, -param);
+ TEST_LE_S(0, ret);
+ ret = recombine_split_first_record(buf, param);
+ TEST_LE_S(0, ret);
+ break;
+
default:
TEST_FAIL("Instructions not understood");
}
diff --git a/tests/suites/test_suite_ssl.records.data b/tests/suites/test_suite_ssl.records.data
index e31fbbd..ca19393 100644
--- a/tests/suites/test_suite_ssl.records.data
+++ b/tests/suites/test_suite_ssl.records.data
@@ -24,3 +24,91 @@
Recombine server flight 1: TLS 1.3, coalesce all
depends_on:MBEDTLS_SSL_PROTO_TLS1_3
recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_COALESCE:INT_MAX:"<= handshake wrapup":"<= handshake wrapup":MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH:0
+
+Recombine server flight 1: TLS 1.2, split first at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.3, split first at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH:0
+
+Recombine server flight 1: TLS 1.2, split first at end-1
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:-1:"subsequent handshake fragment\: 1,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.3, split first at end-1
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:-1:"subsequent handshake fragment\: 1,":"<= handshake wrapup":MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH:0
+
+# The library doesn't support an initial handshake fragment that doesn't
+# contain the full 4-byte handshake header.
+Recombine server flight 1: TLS 1.2, split first at 3 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:3:"handshake message too short\: 3":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 3 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:3:"handshake message too short\: 3":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, split first at 2 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:2:"handshake message too short\: 2":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 2 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:2:"handshake message too short\: 2":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, split first at 1 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:1:"handshake message too short\: 1":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, split first at 1 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:1:"handshake message too short\: 1":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert empty record after first (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_SPLIT_FIRST:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_CERTIFICATE:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record after first (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_SPLIT_FIRST:0:"rejecting empty record":"":MBEDTLS_SSL_ENCRYPTED_EXTENSIONS:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert empty record at start (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_EMPTY:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record at start (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_EMPTY:0:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.2, insert empty record at 42 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_INSERT_EMPTY:42:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+Recombine server flight 1: TLS 1.3, insert empty record at 42 (bad)
+depends_on:MBEDTLS_SSL_PROTO_TLS1_3
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_3:RECOMBINE_INSERT_EMPTY:42:"rejecting empty record":"":MBEDTLS_SSL_SERVER_HELLO:MBEDTLS_ERR_SSL_INVALID_RECORD
+
+# Since there is a single unencrypted handshake message in the first flight
+# from the server, and the test code that recombines handshake records can only
+# handle plaintext records, we can't have TLS 1.3 tests with coalesced
+# handshake messages. Hence most coalesce-and-split test cases are 1.2-only.
+
+Recombine server flight 1: TLS 1.2, coalesce and split at 4
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_ONCE:4:"initial handshake fragment\: 4, 0..4 of":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+# The last message of the first flight from the server is ServerHelloDone,
+# which is an empty handshake message, i.e. of length 4. The library doesn't
+# support fragmentation of a handshake message, so the last place where we
+# can split the flight is 4+1 = 5 bytes before it ends, with 1 byte in the
+# previous handshake message and 4 bytes of ServerHelloDone including header.
+Recombine server flight 1: TLS 1.2, coalesce and split at end-5
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_ONCE:-5:"subsequent handshake fragment\: 5,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0
+
+Recombine server flight 1: TLS 1.2, coalesce and split at both ends
+depends_on:MBEDTLS_SSL_PROTO_TLS1_2
+recombine_server_first_flight:MBEDTLS_SSL_VERSION_TLS1_2:RECOMBINE_COALESCE_SPLIT_ENDS:5:"subsequent handshake fragment\: 5,":"<= handshake wrapup":MBEDTLS_SSL_HANDSHAKE_OVER:0