Check duplicate extensions before processing.

ClientHello and ServerHello are not allowed to include duplicate extensions.
Add a new helper function to check this and call as appropriate. Remove ad-hoc
per-extension duplicate checks which are no unnecessary.

Add runner.go tests to verify such message correctly rejected.

Change-Id: I7babd5b642dfec941459512869e2dd6de26a831c
Reviewed-on: https://boringssl-review.googlesource.com/1100
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/ssl.h b/ssl/ssl.h
index 07de254..87ffe9b 100644
--- a/ssl/ssl.h
+++ b/ssl/ssl.h
@@ -2580,6 +2580,7 @@
 #define SSL_F_tls1_change_cipher_state_cipher 278
 #define SSL_F_tls1_change_cipher_state_aead 279
 #define SSL_F_tls1_aead_ctx_init 280
+#define SSL_F_tls1_check_duplicate_extensions 281
 #define SSL_R_UNABLE_TO_FIND_ECDH_PARAMETERS 100
 #define SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC 101
 #define SSL_R_INVALID_NULL_CMD_NAME 102
diff --git a/ssl/ssl_error.c b/ssl/ssl_error.c
index ac61bcf..eaac152 100644
--- a/ssl/ssl_error.c
+++ b/ssl/ssl_error.c
@@ -191,6 +191,7 @@
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_change_cipher_state, 0), "tls1_change_cipher_state"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_change_cipher_state_aead, 0), "tls1_change_cipher_state_aead"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_change_cipher_state_cipher, 0), "tls1_change_cipher_state_cipher"},
+  {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_check_duplicate_extensions, 0), "tls1_check_duplicate_extensions"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_export_keying_material, 0), "tls1_export_keying_material"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_get_server_supplemental_data, 0), "tls1_get_server_supplemental_data"},
   {ERR_PACK(ERR_LIB_SSL, SSL_F_tls1_heartbeat, 0), "tls1_heartbeat"},
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 3015ee9..86914dc 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -107,6 +107,7 @@
  * Hudson (tjh@cryptsoft.com). */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <assert.h>
 
 #include <openssl/bytestring.h>
@@ -210,12 +211,94 @@
 	s->version = s->method->version;
 	}
 
+static int compare_uint16_t(const void *p1, const void *p2)
+	{
+	uint16_t u1 = *((const uint16_t*)p1);
+	uint16_t u2 = *((const uint16_t*)p2);
+	if (u1 < u2)
+		{
+		return -1;
+		}
+	else if (u1 > u2)
+		{
+		return 1;
+		}
+	else
+		{
+		return 0;
+		}
+	}
+
+/* Per http://tools.ietf.org/html/rfc5246#section-7.4.1.4, there may not be more
+ * than one extension of the same type in a ClientHello or ServerHello. This
+ * function does an initial scan over the extensions block to filter those
+ * out. */
+static int tls1_check_duplicate_extensions(const CBS *cbs)
+	{
+	CBS extensions = *cbs;
+	size_t num_extensions = 0, i = 0;
+	uint16_t *extension_types = NULL;
+	int ret = 0;
+
+	/* First pass: count the extensions. */
+	while (CBS_len(&extensions) > 0)
+		{
+		uint16_t type;
+		CBS extension;
+
+		if (!CBS_get_u16(&extensions, &type) ||
+			!CBS_get_u16_length_prefixed(&extensions, &extension))
+			{
+			goto done;
+			}
+
+		num_extensions++;
+		}
+
+	extension_types = (uint16_t*)OPENSSL_malloc(sizeof(uint16_t) * num_extensions);
+	if (extension_types == NULL)
+		{
+		OPENSSL_PUT_ERROR(SSL, tls1_check_duplicate_extensions, ERR_R_MALLOC_FAILURE);
+		goto done;
+		}
+
+	/* Second pass: gather the extension types. */
+	extensions = *cbs;
+	for (i = 0; i < num_extensions; i++)
+		{
+		CBS extension;
+
+		if (!CBS_get_u16(&extensions, &extension_types[i]) ||
+			!CBS_get_u16_length_prefixed(&extensions, &extension))
+			{
+			/* This should not happen. */
+			goto done;
+			}
+		}
+	assert(CBS_len(&extensions) == 0);
+
+	/* Sort the extensions and make sure there are no duplicates. */
+	qsort(extension_types, num_extensions, sizeof(uint16_t), compare_uint16_t);
+	for (i = 1; i < num_extensions; i++)
+		{
+		if (extension_types[i-1] == extension_types[i])
+			{
+			goto done;
+			}
+		}
+
+	ret = 1;
+done:
+	if (extension_types)
+		OPENSSL_free(extension_types);
+	return ret;
+	}
+
 char ssl_early_callback_init(struct ssl_early_callback_ctx *ctx)
 	{
 	size_t len = ctx->client_hello_len;
 	const unsigned char *p = ctx->client_hello;
-	uint16_t *extension_types;
-	unsigned num_extensions;
+	CBS extensions;
 
 	/* Skip client version. */
 	if (len < 2)
@@ -302,48 +385,15 @@
 		return 0;
 
 	/* Verify that the extensions have valid lengths and that there are
-	 * no duplicates. Each extension takes, at least, four bytes, so
-	 * we can allocate a buffer of extensions_len/4 elements and be sure
-	 * that we have enough space for all the extension types. */
-	extension_types =
-		OPENSSL_malloc(sizeof(uint16_t) * ctx->extensions_len/4);
-	if (extension_types == NULL)
+	 * no duplicates.
+	 *
+	 * TODO(fork): Port the rest of this processing to CBS.
+	 */
+	CBS_init(&extensions, ctx->extensions, ctx->extensions_len);
+	if (!tls1_check_duplicate_extensions(&extensions))
 		return 0;
-	num_extensions = 0;
 
-	while (len != 0)
-		{
-		uint16_t extension_type, extension_len;
-		unsigned i;
-
-		if (len < 4)
-			goto err;
-		n2s(p, extension_type);
-		n2s(p, extension_len);
-		len -= 4;
-
-		if (len < extension_len)
-			goto err;
-		p += extension_len; len -= extension_len;
-
-		for (i = 0; i < num_extensions; i++)
-			{
-			if (extension_types[i] == extension_type)
-				{
-				/* Duplicate extension type. */
-				goto err;
-				}
-			}
-		extension_types[num_extensions] = extension_type;
-		num_extensions++;
-		}
-
-	OPENSSL_free(extension_types);
 	return 1;
-
-err:
-	OPENSSL_free(extension_types);
-	return 0;
 	}
 
 char
@@ -1825,13 +1875,32 @@
 		s->cert->pkeys[i].valid_flags = 0;
 		}
 
+	/* TODO(fork): we probably want OCSP stapling support, but this pulls in
+	 * a lot of code. */
+#if 0
+	/* Clear OCSP state. */
+	s->tlsext_status_type = -1;
+	if (s->tlsext_ocsp_ids)
+		{
+		sk_OCSP_RESPID_pop_free(s->tlsext_ocsp_ids, OCSP_RESPID_free);
+		s->tlsext_ocsp_ids = NULL;
+		}
+	if (s->tlsext_ocsp_exts)
+		{
+		sk_X509_EXTENSION_pop_free(s->tlsext_ocsp_exts, X509_EXTENSION_free);
+		s->tlsext_ocsp_exts = NULL;
+		}
+#endif
+
 	/* There may be no extensions. */
 	if (CBS_len(cbs) == 0)
 		{
 		goto ri_check;
 		}
 
-	if (!CBS_get_u16_length_prefixed(cbs, &extensions))
+	/* Decode the extensions block and check it is valid. */
+	if (!CBS_get_u16_length_prefixed(cbs, &extensions) ||
+		!tls1_check_duplicate_extensions(&extensions))
 		{
 		*out_alert = SSL_AD_DECODE_ERROR;
 		return 0;
@@ -1904,9 +1973,6 @@
 					return 0;
 					}
 
-				if (s->servername_done)
-					continue;
-
 				/* Only host_name is supported. */
 				if (name_type != TLSEXT_NAMETYPE_host_name)
 					continue;
@@ -1999,12 +2065,6 @@
 
 			if (!s->hit)
 				{
-				if (s->session->tlsext_ellipticcurvelist)
-					{
-					*out_alert = SSL_AD_DECODE_ERROR;
-					return 0;
-					}
-
 				if (!CBS_stow(&elliptic_curve_list,
 						&s->session->tlsext_ellipticcurvelist,
 						&s->session->tlsext_ellipticcurvelist_length))
@@ -2034,13 +2094,6 @@
 			{
 			CBS supported_signature_algorithms;
 
-			/* The extension should not appear twice. */
-			if (s->cert->peer_sigalgs)
-				{
-				*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
-				return 0;
-				}
-
 			if (!CBS_get_u16_length_prefixed(&extension, &supported_signature_algorithms) ||
 				CBS_len(&extension) != 0)
 				{
@@ -2084,15 +2137,6 @@
 			CBS responder_id_list;
 			CBS request_extensions;
 
-			/* Already seen the extension. */
-			if (s->tlsext_status_type != -1 ||
-				s->tlsext_ocsp_ids != NULL ||
-				s->tlsext_ocsp_exts != NULL)
-				{
-				*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
-				return 0;
-				}
-
 			if (!CBS_get_u8(&extension, &status_type))
 				{
 				*out_alert = SSL_AD_DECODE_ERROR;
@@ -2355,7 +2399,9 @@
 		goto ri_check;
 		}
 
-	if (!CBS_get_u16_length_prefixed(cbs, &extensions))
+	/* Decode the extensions block and check it is valid. */
+	if (!CBS_get_u16_length_prefixed(cbs, &extensions) ||
+		!tls1_check_duplicate_extensions(&extensions))
 		{
 		*out_alert = SSL_AD_DECODE_ERROR;
 		return 0;
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index fd78eb6..df7cacf 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -345,6 +345,10 @@
 	// FailIfNotFallbackSCSV causes a server handshake to fail if the
 	// client doesn't send the fallback SCSV value.
 	FailIfNotFallbackSCSV bool
+
+	// DuplicateExtension causes an extra empty extension of bogus type to
+	// be emitted in either the ClientHello or the ServerHello.
+	DuplicateExtension bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index f335d03..220e489 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -47,6 +47,7 @@
 		supportedPoints:     []uint8{pointFormatUncompressed},
 		nextProtoNeg:        len(c.config.NextProtos) > 0,
 		secureRenegotiation: true,
+		duplicateExtension:  c.config.Bugs.DuplicateExtension,
 	}
 
 	possibleCipherSuites := c.config.cipherSuites()
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index e31f47b..edb45d8 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -22,6 +22,7 @@
 	sessionTicket       []uint8
 	signatureAndHashes  []signatureAndHash
 	secureRenegotiation bool
+	duplicateExtension  bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -86,6 +87,9 @@
 		extensionsLength += 1
 		numExtensions++
 	}
+	if m.duplicateExtension {
+		numExtensions += 2
+	}
 	if numExtensions > 0 {
 		extensionsLength += 4 * numExtensions
 		length += 2 + extensionsLength
@@ -118,6 +122,12 @@
 		z[1] = byte(extensionsLength)
 		z = z[2:]
 	}
+	if m.duplicateExtension {
+		// Add a duplicate bogus extension at the beginning and end.
+		z[0] = 0xff
+		z[1] = 0xff
+		z = z[4:]
+	}
 	if m.nextProtoNeg {
 		z[0] = byte(extensionNextProtoNeg >> 8)
 		z[1] = byte(extensionNextProtoNeg & 0xff)
@@ -237,6 +247,12 @@
 		z[3] = 1
 		z = z[5:]
 	}
+	if m.duplicateExtension {
+		// Add a duplicate bogus extension at the beginning and end.
+		z[0] = 0xff
+		z[1] = 0xff
+		z = z[4:]
+	}
 
 	m.raw = x
 
@@ -419,6 +435,7 @@
 	ocspStapling        bool
 	ticketSupported     bool
 	secureRenegotiation bool
+	duplicateExtension  bool
 }
 
 func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -468,6 +485,9 @@
 		extensionsLength += 1
 		numExtensions++
 	}
+	if m.duplicateExtension {
+		numExtensions += 2
+	}
 	if numExtensions > 0 {
 		extensionsLength += 4 * numExtensions
 		length += 2 + extensionsLength
@@ -494,6 +514,12 @@
 		z[1] = byte(extensionsLength)
 		z = z[2:]
 	}
+	if m.duplicateExtension {
+		// Add a duplicate bogus extension at the beginning and end.
+		z[0] = 0xff
+		z[1] = 0xff
+		z = z[4:]
+	}
 	if m.nextProtoNeg {
 		z[0] = byte(extensionNextProtoNeg >> 8)
 		z[1] = byte(extensionNextProtoNeg & 0xff)
@@ -528,6 +554,12 @@
 		z[3] = 1
 		z = z[5:]
 	}
+	if m.duplicateExtension {
+		// Add a duplicate bogus extension at the beginning and end.
+		z[0] = 0xff
+		z[1] = 0xff
+		z = z[4:]
+	}
 
 	m.raw = x
 
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 854c7ff..328c15f 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -157,6 +157,7 @@
 	}
 	hs.hello.secureRenegotiation = hs.clientHello.secureRenegotiation
 	hs.hello.compressionMethod = compressionNone
+	hs.hello.duplicateExtension = c.config.Bugs.DuplicateExtension
 	if len(hs.clientHello.serverName) > 0 {
 		c.serverName = hs.clientHello.serverName
 	}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index f253f89..96b52fa 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -137,12 +137,34 @@
 	},
 	{
 		testType: serverTest,
-		name: "ServerNameExtension",
+		name:     "ServerNameExtension",
 		config: Config{
 			ServerName: "example.com",
 		},
 		flags: []string{"-expect-server-name", "example.com"},
 	},
+	{
+		testType: clientTest,
+		name:     "DuplicateExtensionClient",
+		config: Config{
+			Bugs: ProtocolBugs{
+				DuplicateExtension: true,
+			},
+		},
+		shouldFail:         true,
+		expectedLocalError: "remote error: error decoding message",
+	},
+	{
+		testType: serverTest,
+		name:     "DuplicateExtensionServer",
+		config: Config{
+			Bugs: ProtocolBugs{
+				DuplicateExtension: true,
+			},
+		},
+		shouldFail:         true,
+		expectedLocalError: "remote error: error decoding message",
+	},
 }
 
 func doExchange(tlsConn *Conn, messageLen int) error {