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;