Push V2ClientHello handling into ssl3_get_message.
V2ClientHello is going to be ugly wherever we do it, but this hides it
behind the transport method table. It removes a place where the
handshake state machine reaches into ssl3_get_message's internal state.
ssl3_get_message will now silently translate V2ClientHellos into true
ClientHellos and manage the handshake hash appropriately.
Now the only accesses of init_buf from the handshake state machines are
to create and destroy the buffer.
Change-Id: I81467a038f6ac472a465eec7486a443fe50a98e1
Reviewed-on: https://boringssl-review.googlesource.com/8641
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index fd16318..e67f6a4 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -368,6 +368,154 @@
return 1;
}
+static int read_v2_client_hello(SSL *ssl) {
+ /* Read the first 5 bytes, the size of the TLS record header. This is
+ * sufficient to detect a V2ClientHello and ensures that we never read beyond
+ * the first record. */
+ int ret = ssl_read_buffer_extend_to(ssl, SSL3_RT_HEADER_LENGTH);
+ if (ret <= 0) {
+ return ret;
+ }
+ const uint8_t *p = ssl_read_buffer(ssl);
+
+ /* Some dedicated error codes for protocol mixups should the application wish
+ * to interpret them differently. (These do not overlap with ClientHello or
+ * V2ClientHello.) */
+ if (strncmp("GET ", (const char *)p, 4) == 0 ||
+ strncmp("POST ", (const char *)p, 5) == 0 ||
+ strncmp("HEAD ", (const char *)p, 5) == 0 ||
+ strncmp("PUT ", (const char *)p, 4) == 0) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_HTTP_REQUEST);
+ return -1;
+ }
+ if (strncmp("CONNE", (const char *)p, 5) == 0) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_HTTPS_PROXY_REQUEST);
+ return -1;
+ }
+
+ if ((p[0] & 0x80) == 0 || p[2] != SSL2_MT_CLIENT_HELLO ||
+ p[3] != SSL3_VERSION_MAJOR) {
+ /* Not a V2ClientHello. */
+ return 1;
+ }
+
+ /* Determine the length of the V2ClientHello. */
+ size_t msg_length = ((p[0] & 0x7f) << 8) | p[1];
+ if (msg_length > (1024 * 4)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_TOO_LARGE);
+ return -1;
+ }
+ if (msg_length < SSL3_RT_HEADER_LENGTH - 2) {
+ /* Reject lengths that are too short early. We have already read
+ * |SSL3_RT_HEADER_LENGTH| bytes, so we should not attempt to process an
+ * (invalid) V2ClientHello which would be shorter than that. */
+ OPENSSL_PUT_ERROR(SSL, SSL_R_RECORD_LENGTH_MISMATCH);
+ return -1;
+ }
+
+ /* Read the remainder of the V2ClientHello. */
+ ret = ssl_read_buffer_extend_to(ssl, 2 + msg_length);
+ if (ret <= 0) {
+ return ret;
+ }
+
+ CBS v2_client_hello;
+ CBS_init(&v2_client_hello, ssl_read_buffer(ssl) + 2, msg_length);
+
+ /* The V2ClientHello without the length is incorporated into the handshake
+ * hash. */
+ if (!ssl3_update_handshake_hash(ssl, CBS_data(&v2_client_hello),
+ CBS_len(&v2_client_hello))) {
+ return -1;
+ }
+
+ ssl_do_msg_callback(ssl, 0 /* read */, SSL2_VERSION, 0,
+ CBS_data(&v2_client_hello), CBS_len(&v2_client_hello));
+
+ uint8_t msg_type;
+ uint16_t version, cipher_spec_length, session_id_length, challenge_length;
+ CBS cipher_specs, session_id, challenge;
+ if (!CBS_get_u8(&v2_client_hello, &msg_type) ||
+ !CBS_get_u16(&v2_client_hello, &version) ||
+ !CBS_get_u16(&v2_client_hello, &cipher_spec_length) ||
+ !CBS_get_u16(&v2_client_hello, &session_id_length) ||
+ !CBS_get_u16(&v2_client_hello, &challenge_length) ||
+ !CBS_get_bytes(&v2_client_hello, &cipher_specs, cipher_spec_length) ||
+ !CBS_get_bytes(&v2_client_hello, &session_id, session_id_length) ||
+ !CBS_get_bytes(&v2_client_hello, &challenge, challenge_length) ||
+ CBS_len(&v2_client_hello) != 0) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ return -1;
+ }
+
+ /* msg_type has already been checked. */
+ assert(msg_type == SSL2_MT_CLIENT_HELLO);
+
+ /* The client_random is the V2ClientHello challenge. Truncate or
+ * left-pad with zeros as needed. */
+ size_t rand_len = CBS_len(&challenge);
+ if (rand_len > SSL3_RANDOM_SIZE) {
+ rand_len = SSL3_RANDOM_SIZE;
+ }
+ uint8_t random[SSL3_RANDOM_SIZE];
+ memset(random, 0, SSL3_RANDOM_SIZE);
+ memcpy(random + (SSL3_RANDOM_SIZE - rand_len), CBS_data(&challenge),
+ rand_len);
+
+ /* Write out an equivalent SSLv3 ClientHello. */
+ CBB client_hello, hello_body, cipher_suites;
+ if (!CBB_init_fixed(&client_hello, (uint8_t *)ssl->init_buf->data,
+ ssl->init_buf->max) ||
+ !CBB_add_u8(&client_hello, SSL3_MT_CLIENT_HELLO) ||
+ !CBB_add_u24_length_prefixed(&client_hello, &hello_body) ||
+ !CBB_add_u16(&hello_body, version) ||
+ !CBB_add_bytes(&hello_body, random, SSL3_RANDOM_SIZE) ||
+ /* No session id. */
+ !CBB_add_u8(&hello_body, 0) ||
+ !CBB_add_u16_length_prefixed(&hello_body, &cipher_suites)) {
+ CBB_cleanup(&client_hello);
+ OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+ return -1;
+ }
+
+ /* Copy the cipher suites. */
+ while (CBS_len(&cipher_specs) > 0) {
+ uint32_t cipher_spec;
+ if (!CBS_get_u24(&cipher_specs, &cipher_spec)) {
+ CBB_cleanup(&client_hello);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ return -1;
+ }
+
+ /* Skip SSLv2 ciphers. */
+ if ((cipher_spec & 0xff0000) != 0) {
+ continue;
+ }
+ if (!CBB_add_u16(&cipher_suites, cipher_spec)) {
+ CBB_cleanup(&client_hello);
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return -1;
+ }
+ }
+
+ /* Add the null compression scheme and finish. */
+ if (!CBB_add_u8(&hello_body, 1) || !CBB_add_u8(&hello_body, 0) ||
+ !CBB_finish(&client_hello, NULL, &ssl->init_buf->length)) {
+ CBB_cleanup(&client_hello);
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return -1;
+ }
+
+ /* Mark the message for "re"-use. */
+ ssl->s3->tmp.reuse_message = 1;
+ ssl->s3->tmp.message_complete = 1;
+
+ /* Consume and discard the V2ClientHello. */
+ ssl_read_buffer_consume(ssl, 2 + msg_length);
+ ssl_read_buffer_discard(ssl);
+ return 1;
+}
+
/* Obtain handshake message of message type |msg_type| (any if |msg_type| ==
* -1). */
long ssl3_get_message(SSL *ssl, int msg_type,
@@ -375,6 +523,16 @@
*ok = 0;
again:
+ if (ssl->server && !ssl->s3->v2_hello_done) {
+ /* Bypass the record layer for the first message to handle V2ClientHello. */
+ assert(hash_message == ssl_hash_message);
+ int ret = read_v2_client_hello(ssl);
+ if (ret <= 0) {
+ return ret;
+ }
+ ssl->s3->v2_hello_done = 1;
+ }
+
if (ssl->s3->tmp.reuse_message) {
/* A ssl_dont_hash_message call cannot be combined with reuse_message; the
* ssl_dont_hash_message would have to have been applied to the previous