Add support for QUIC transport params.

This adds support for sending the quic_transport_parameters
(draft-ietf-quic-tls) in ClientHello and EncryptedExtensions, as well as
reading the value sent by the peer.

Bug: boringssl:224
Change-Id: Ied633f557cb13ac87454d634f2bd81ab156f5399
Reviewed-on: https://boringssl-review.googlesource.com/24464
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index a44241a..a6c2880 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2953,6 +2953,38 @@
 OPENSSL_EXPORT int SSL_set_dummy_pq_padding_size(SSL *ssl, size_t num_bytes);
 
 
+// QUIC Transport Parameters.
+//
+// draft-ietf-quic-tls defines a new TLS extension quic_transport_parameters
+// used by QUIC for each endpoint to unilaterally declare its supported
+// transport parameters. draft-ietf-quic-transport (section 7.4) defines the
+// contents of that extension (a TransportParameters struct) and describes how
+// to handle it and its semantic meaning.
+//
+// BoringSSL handles this extension as an opaque byte string. The caller is
+// responsible for serializing and parsing it.
+
+// SSL_set_quic_transport_params configures |ssl| to send |params| (of length
+// |params_len|) in the quic_transport_parameters extension in either the
+// ClientHello or EncryptedExtensions handshake message. This extension will
+// only be sent if the TLS version is at least 1.3, and for a server, only if
+// the client sent the extension. The buffer pointed to by |params| only need be
+// valid for the duration of the call to this function. This function returns 1
+// on success and 0 on failure.
+OPENSSL_EXPORT int SSL_set_quic_transport_params(SSL *ssl,
+                                                 const uint8_t *params,
+                                                 size_t params_len);
+
+// SSL_get_peer_quic_transport_params provides the caller with the value of the
+// quic_transport_parameters extension sent by the peer. A pointer to the buffer
+// containing the TransportParameters will be put in |*out_params|, and its
+// length in |*params_len|. This buffer will be valid for the lifetime of the
+// |SSL|. If no params were received from the peer, |*out_params_len| will be 0.
+OPENSSL_EXPORT void SSL_get_peer_quic_transport_params(const SSL *ssl,
+                                                       const uint8_t **out_params,
+                                                       size_t *out_params_len);
+
+
 // Early data.
 //
 // WARNING: 0-RTT support in BoringSSL is currently experimental and not fully
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 682bb9b..105ab8e 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -205,6 +205,9 @@
 // ExtensionType value from draft-ietf-tokbind-negotiation-10
 #define TLSEXT_TYPE_token_binding 24
 
+// ExtensionType value from draft-ietf-quic-tls
+#define TLSEXT_TYPE_quic_transport_parameters 26
+
 // ExtensionType value from RFC4507
 #define TLSEXT_TYPE_session_ticket 35
 
diff --git a/ssl/internal.h b/ssl/internal.h
index 04d534a..af4eaae 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2378,6 +2378,9 @@
   //     verified Channel ID from the client: a P256 point, (x,y), where
   //     each are big-endian values.
   uint8_t tlsext_channel_id[64] = {0};
+
+  // Contains the QUIC transport params received by the peer.
+  Array<uint8_t> peer_quic_transport_params;
 };
 
 // lengths of messages
@@ -2629,6 +2632,10 @@
   // |token_binding_negotiated| is set.
   uint8_t negotiated_token_binding_param;
 
+  // Contains the QUIC transport params that this endpoint will send.
+  uint8_t *quic_transport_params;
+  size_t quic_transport_params_len;
+
   // renegotiate_mode controls how peer renegotiation attempts are handled.
   enum ssl_renegotiate_mode_t renegotiate_mode;
 
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 75e438d..d05e613 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -772,6 +772,7 @@
   OPENSSL_free(ssl->supported_group_list);
   OPENSSL_free(ssl->alpn_client_proto_list);
   OPENSSL_free(ssl->token_binding_params);
+  OPENSSL_free(ssl->quic_transport_params);
   EVP_PKEY_free(ssl->tlsext_channel_id_private);
   OPENSSL_free(ssl->psk_identity_hint);
   sk_CRYPTO_BUFFER_pop_free(ssl->client_CA, CRYPTO_BUFFER_free);
@@ -1164,6 +1165,23 @@
   return ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
 }
 
+int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params,
+                                  size_t params_len) {
+  ssl->quic_transport_params = (uint8_t *)BUF_memdup(params, params_len);
+  if (!ssl->quic_transport_params) {
+    return 0;
+  }
+  ssl->quic_transport_params_len = params_len;
+  return 1;
+}
+
+void SSL_get_peer_quic_transport_params(const SSL *ssl,
+                                        const uint8_t **out_params,
+                                        size_t *out_params_len) {
+  *out_params = ssl->s3->peer_quic_transport_params.data();
+  *out_params_len = ssl->s3->peer_quic_transport_params.size();
+}
+
 void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled) {
   ctx->cert->enable_early_data = !!enabled;
 }
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 4303d4e..d972949 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -2588,6 +2588,77 @@
   return true;
 }
 
+// QUIC Transport Parameters
+
+static bool ext_quic_transport_params_add_clienthello(SSL_HANDSHAKE *hs,
+                                                      CBB *out) {
+  SSL *const ssl = hs->ssl;
+  if (!ssl->quic_transport_params || hs->max_version <= TLS1_2_VERSION) {
+    return true;
+  }
+
+  CBB contents;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_quic_transport_parameters) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_bytes(&contents, ssl->quic_transport_params,
+                     ssl->quic_transport_params_len) ||
+      !CBB_flush(out)) {
+    return false;
+  }
+  return true;
+}
+
+static bool ext_quic_transport_params_parse_serverhello(SSL_HANDSHAKE *hs,
+                                                        uint8_t *out_alert,
+                                                        CBS *contents) {
+  SSL *const ssl = hs->ssl;
+  if (contents == nullptr) {
+    return true;
+  }
+  // QUIC requires TLS 1.3.
+  if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+    *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+    return false;
+  }
+
+  return ssl->s3->peer_quic_transport_params.CopyFrom(*contents);
+}
+
+static bool ext_quic_transport_params_parse_clienthello(SSL_HANDSHAKE *hs,
+                                                        uint8_t *out_alert,
+                                                        CBS *contents) {
+  SSL *const ssl = hs->ssl;
+  if (!contents || !ssl->quic_transport_params) {
+    return true;
+  }
+  // Ignore the extension before TLS 1.3.
+  if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+    return true;
+  }
+
+  return ssl->s3->peer_quic_transport_params.CopyFrom(*contents);
+}
+
+static bool ext_quic_transport_params_add_serverhello(SSL_HANDSHAKE *hs,
+                                                      CBB *out) {
+  SSL *const ssl = hs->ssl;
+  if (!ssl->quic_transport_params) {
+    return true;
+  }
+
+  CBB contents;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_quic_transport_parameters) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_bytes(&contents, ssl->quic_transport_params,
+                     ssl->quic_transport_params_len) ||
+      !CBB_flush(out)) {
+    return false;
+  }
+
+  return true;
+}
+
+
 // kExtensions contains all the supported extensions.
 static const struct tls_extension kExtensions[] = {
   {
@@ -2745,6 +2816,14 @@
     ignore_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_quic_transport_parameters,
+    NULL,
+    ext_quic_transport_params_add_clienthello,
+    ext_quic_transport_params_parse_serverhello,
+    ext_quic_transport_params_parse_clienthello,
+    ext_quic_transport_params_add_serverhello,
+  },
   // The final extension must be non-empty. WebSphere Application Server 7.0 is
   // intolerant to the last extension being zero-length. See
   // https://crbug.com/363583.
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 50182b1..6885b0f 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1711,6 +1711,19 @@
     }
   }
 
+  if (!config->expected_quic_transport_params.empty()) {
+    const uint8_t *peer_params;
+    size_t peer_params_len;
+    SSL_get_peer_quic_transport_params(ssl, &peer_params, &peer_params_len);
+    if (peer_params_len != config->expected_quic_transport_params.size() ||
+        OPENSSL_memcmp(peer_params,
+                       config->expected_quic_transport_params.data(),
+                       peer_params_len) != 0) {
+      fprintf(stderr, "QUIC transport params mismatch\n");
+      return false;
+    }
+  }
+
   if (!config->expected_channel_id.empty()) {
     uint8_t channel_id[64];
     if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) {
@@ -2076,6 +2089,15 @@
       !SSL_set_dummy_pq_padding_size(ssl.get(), config->dummy_pq_padding_len)) {
     return false;
   }
+  if (!config->quic_transport_params.empty()) {
+    if (!SSL_set_quic_transport_params(
+            ssl.get(),
+            reinterpret_cast<const uint8_t *>(
+                config->quic_transport_params.data()),
+            config->quic_transport_params.size())) {
+      return false;
+    }
+  }
 
   int sock = Connect(config->port);
   if (sock == -1) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index d89f7fb..dcf8afe 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -123,6 +123,7 @@
 	extensionPadding                    uint16 = 21
 	extensionExtendedMasterSecret       uint16 = 23
 	extensionTokenBinding               uint16 = 24
+	extensionQUICTransportParams        uint16 = 26
 	extensionSessionTicket              uint16 = 35
 	extensionOldKeyShare                uint16 = 40    // draft-ietf-tls-tls13-16
 	extensionPreSharedKey               uint16 = 41    // draft-ietf-tls-tls13-16
@@ -269,6 +270,7 @@
 	SCTList                    []byte                // signed certificate timestamp list
 	PeerSignatureAlgorithm     signatureAlgorithm    // algorithm used by the peer in the handshake
 	CurveID                    CurveID               // the curve used in ECDHE
+	QUICTransportParams        []byte                // the QUIC transport params received from the peer
 }
 
 // ClientAuthType declares the policy the server will follow for
@@ -496,6 +498,10 @@
 	// supported signature algorithms that are accepted.
 	VerifySignatureAlgorithms []signatureAlgorithm
 
+	// QUICTransportParams, if not empty, will be sent in the QUIC
+	// transport parameters extension.
+	QUICTransportParams []byte
+
 	// Bugs specifies optional misbehaviour to be used for testing other
 	// implementations.
 	Bugs ProtocolBugs
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index c38fba9..1bf4c5e 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -62,6 +62,9 @@
 	// curveID contains the curve that was used in the handshake, or zero if
 	// not applicable.
 	curveID CurveID
+	// quicTransportParams contains the QUIC transport params received
+	// by the peer.
+	quicTransportParams []byte
 
 	clientRandom, serverRandom [32]byte
 	earlyExporterSecret        []byte
@@ -1822,6 +1825,7 @@
 		state.SCTList = c.sctList
 		state.PeerSignatureAlgorithm = c.peerSignatureAlgorithm
 		state.CurveID = c.curveID
+		state.QUICTransportParams = c.quicTransportParams
 	}
 
 	return state
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index ab41e12..b2abc40 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -87,6 +87,7 @@
 		nextProtoNeg:            len(c.config.NextProtos) > 0,
 		secureRenegotiation:     []byte{},
 		alpnProtocols:           c.config.NextProtos,
+		quicTransportParams:     c.config.QUICTransportParams,
 		duplicateExtension:      c.config.Bugs.DuplicateExtension,
 		channelIDSupported:      c.config.ChannelID != nil,
 		tokenBindingParams:      c.config.TokenBindingParams,
@@ -1531,6 +1532,13 @@
 		}
 	}
 
+	if len(serverExtensions.quicTransportParams) > 0 {
+		if c.vers < VersionTLS13 {
+			c.sendAlert(alertHandshakeFailure)
+			return errors.New("tls: server sent QUIC transport params for TLS version less than 1.3")
+		}
+		c.quicTransportParams = serverExtensions.quicTransportParams
+	}
 	return nil
 }
 
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index c80b1cf..2f1f765 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -279,6 +279,7 @@
 	supportedVersions       []uint16
 	secureRenegotiation     []byte
 	alpnProtocols           []string
+	quicTransportParams     []byte
 	duplicateExtension      bool
 	channelIDSupported      bool
 	tokenBindingParams      []byte
@@ -331,6 +332,7 @@
 		bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) &&
 		(m.secureRenegotiation == nil) == (m1.secureRenegotiation == nil) &&
 		eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
+		bytes.Equal(m.quicTransportParams, m1.quicTransportParams) &&
 		m.duplicateExtension == m1.duplicateExtension &&
 		m.channelIDSupported == m1.channelIDSupported &&
 		bytes.Equal(m.tokenBindingParams, m1.tokenBindingParams) &&
@@ -519,6 +521,11 @@
 			protocolName.addBytes([]byte(s))
 		}
 	}
+	if len(m.quicTransportParams) > 0 {
+		extensions.addU16(extensionQUICTransportParams)
+		params := extensions.addU16LengthPrefixed()
+		params.addBytes(m.quicTransportParams)
+	}
 	if m.channelIDSupported {
 		extensions.addU16(extensionChannelID)
 		extensions.addU16(0) // Length is always 0
@@ -832,6 +839,8 @@
 				}
 				m.alpnProtocols = append(m.alpnProtocols, string(protocol))
 			}
+		case extensionQUICTransportParams:
+			m.quicTransportParams = body
 		case extensionChannelID:
 			if len(body) != 0 {
 				return false
@@ -1147,6 +1156,7 @@
 	supportedVersion        uint16
 	supportedPoints         []uint8
 	supportedCurves         []CurveID
+	quicTransportParams     []byte
 	serverNameAck           bool
 }
 
@@ -1269,6 +1279,11 @@
 			supportedCurves.addU16(uint16(curve))
 		}
 	}
+	if len(m.quicTransportParams) > 0 {
+		extensions.addU16(extensionQUICTransportParams)
+		params := extensions.addU16LengthPrefixed()
+		params.addBytes(m.quicTransportParams)
+	}
 	if m.hasEarlyData {
 		extensions.addU16(extensionEarlyData)
 		extensions.addBytes([]byte{0, 0})
@@ -1374,6 +1389,8 @@
 			if version < VersionTLS13 {
 				return false
 			}
+		case extensionQUICTransportParams:
+			m.quicTransportParams = body
 		case extensionEarlyData:
 			if version < VersionTLS13 || len(body) != 0 {
 				return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 123ab16..7944377 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -1369,6 +1369,11 @@
 		}
 	}
 
+	if len(hs.clientHello.quicTransportParams) > 0 {
+		c.quicTransportParams = hs.clientHello.quicTransportParams
+		serverExtensions.quicTransportParams = c.config.QUICTransportParams
+	}
+
 	if c.vers < VersionTLS13 || config.Bugs.NegotiateEMSAtAllVersions {
 		disableEMS := config.Bugs.NoExtendedMasterSecret
 		if c.cipherSuite != nil {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 02ea539..68848ea 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -475,6 +475,9 @@
 	// configured with the specified TLS 1.3 variant. This is a convenience
 	// option for configuring both concurrently.
 	tls13Variant int
+	// expectedQUICTransportParams contains the QUIC transport
+	// parameters that are expected to be sent by the peer.
+	expectedQUICTransportParams []byte
 }
 
 var testCases []testCase
@@ -714,6 +717,12 @@
 		}
 	}
 
+	if len(test.expectedQUICTransportParams) > 0 {
+		if !bytes.Equal(test.expectedQUICTransportParams, connState.QUICTransportParams) {
+			return errors.New("Peer did not send expected QUIC transport params")
+		}
+	}
+
 	if isResume && test.exportEarlyKeyingMaterial > 0 {
 		actual := make([]byte, test.exportEarlyKeyingMaterial)
 		if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -6588,6 +6597,106 @@
 			})
 		}
 
+		// Test QUIC transport params
+		if ver.version >= VersionTLS13 {
+			// Client sends params
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "QUICTransportParams-Client-" + ver.name,
+				config: Config{
+					MinVersion:          ver.version,
+					MaxVersion:          ver.version,
+					QUICTransportParams: []byte{1, 2},
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{3, 4}),
+					"-expected-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{1, 2}),
+				},
+				expectedQUICTransportParams: []byte{3, 4},
+			})
+			// Server sends params
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "QUICTransportParams-Server-" + ver.name,
+				config: Config{
+					MinVersion:          ver.version,
+					MaxVersion:          ver.version,
+					QUICTransportParams: []byte{1, 2},
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{3, 4}),
+					"-expected-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{1, 2}),
+				},
+				expectedQUICTransportParams: []byte{3, 4},
+			})
+		} else {
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "QUICTransportParams-Client-NotSent-" + ver.name,
+				config: Config{
+					MinVersion: ver.version,
+					MaxVersion: ver.version,
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-max-version",
+					strconv.Itoa(int(ver.version)),
+					"-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{3, 4}),
+				},
+			})
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "QUICTransportParams-Client-Rejected-" + ver.name,
+				config: Config{
+					MinVersion:          ver.version,
+					MaxVersion:          ver.version,
+					QUICTransportParams: []byte{1, 2},
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{3, 4}),
+				},
+				shouldFail:    true,
+				expectedError: ":ERROR_PARSING_EXTENSION:",
+			})
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "QUICTransportParams-Server-Rejected-" + ver.name,
+				config: Config{
+					MinVersion:          ver.version,
+					MaxVersion:          ver.version,
+					QUICTransportParams: []byte{1, 2},
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-expected-quic-transport-params",
+					base64.StdEncoding.EncodeToString([]byte{1, 2}),
+				},
+				shouldFail:    true,
+				expectedError: "QUIC transport params mismatch",
+			})
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "QUICTransportParams-OldServerIgnores-" + ver.name,
+				config: Config{
+					MaxVersion:          VersionTLS13,
+					QUICTransportParams: []byte{1, 2},
+				},
+				flags: []string{
+					"-min-version", ver.shimFlag(tls),
+					"-max-version", ver.shimFlag(tls),
+				},
+			})
+		}
+
 		// Test ticket behavior.
 
 		// Resume with a corrupt ticket.
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 7c660c8..516a9c9 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -170,6 +170,9 @@
   { "-ocsp-response", &TestConfig::ocsp_response },
   { "-signed-cert-timestamps", &TestConfig::signed_cert_timestamps },
   { "-ticket-key", &TestConfig::ticket_key },
+  { "-quic-transport-params", &TestConfig::quic_transport_params },
+  { "-expected-quic-transport-params",
+    &TestConfig::expected_quic_transport_params },
 };
 
 const Flag<int> kIntFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 7574337..cc1618a 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -59,6 +59,8 @@
   std::string expected_advertised_alpn;
   std::string select_alpn;
   bool decline_alpn = false;
+  std::string quic_transport_params;
+  std::string expected_quic_transport_params;
   bool expect_session_miss = false;
   bool expect_extended_master_secret = false;
   std::string psk;