diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index a44241aa..a6c2880a 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -2953,6 +2953,38 @@ OPENSSL_EXPORT const char *SSL_get_psk_identity(const SSL *ssl); 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 682bb9ba..105ab8eb 100644 --- a/include/openssl/tls1.h +++ b/include/openssl/tls1.h @@ -205,6 +205,9 @@ extern "C" { // 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 04d534a8..af4eaaef 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -2378,6 +2378,9 @@ struct SSL3_STATE { // 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 peer_quic_transport_params; }; // lengths of messages @@ -2629,6 +2632,10 @@ struct SSLConnection { // |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 75e438d4..d05e6135 100644 --- a/ssl/ssl_lib.cc +++ b/ssl/ssl_lib.cc @@ -772,6 +772,7 @@ void SSL_free(SSL *ssl) { 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 @@ int SSL_send_fatal_alert(SSL *ssl, uint8_t alert) { 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 4303d4e0..d9729499 100644 --- a/ssl/t1_lib.cc +++ b/ssl/t1_lib.cc @@ -2588,6 +2588,77 @@ static bool ext_token_binding_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) { 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 @@ static const struct tls_extension kExtensions[] = { 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 50182b1b..6885b0fa 100644 --- a/ssl/test/bssl_shim.cc +++ b/ssl/test/bssl_shim.cc @@ -1711,6 +1711,19 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, } } + 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 @@ static bool DoConnection(bssl::UniquePtr *out_session, !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( + 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 d89f7fb1..dcf8afeb 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go @@ -123,6 +123,7 @@ const ( 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 @@ type ConnectionState struct { 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 @@ type Config struct { // 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 c38fba9a..1bf4c5e8 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -62,6 +62,9 @@ type Conn struct { // 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 @@ func (c *Conn) ConnectionState() ConnectionState { 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 ab41e120..b2abc400 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go @@ -87,6 +87,7 @@ func (c *Conn) clientHandshake() error { 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 @@ func (hs *clientHandshakeState) processServerExtensions(serverExtensions *server } } + 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 c80b1cfc..2f1f7656 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go @@ -279,6 +279,7 @@ type clientHelloMsg struct { supportedVersions []uint16 secureRenegotiation []byte alpnProtocols []string + quicTransportParams []byte duplicateExtension bool channelIDSupported bool tokenBindingParams []byte @@ -331,6 +332,7 @@ func (m *clientHelloMsg) equal(i interface{}) bool { 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 @@ func (m *clientHelloMsg) marshal() []byte { 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 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } m.alpnProtocols = append(m.alpnProtocols, string(protocol)) } + case extensionQUICTransportParams: + m.quicTransportParams = body case extensionChannelID: if len(body) != 0 { return false @@ -1147,6 +1156,7 @@ type serverExtensions struct { supportedVersion uint16 supportedPoints []uint8 supportedCurves []CurveID + quicTransportParams []byte serverNameAck bool } @@ -1269,6 +1279,11 @@ func (m *serverExtensions) marshal(extensions *byteBuilder) { 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 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool { 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 123ab163..79443778 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go @@ -1369,6 +1369,11 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server } } + 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 02ea5398..68848ea9 100644 --- a/ssl/test/runner/runner.go +++ b/ssl/test/runner/runner.go @@ -475,6 +475,9 @@ type testCase struct { // 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 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr } } + 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 @@ func addExtensionTests() { }) } + // 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 7c660c8a..516a9c9c 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -170,6 +170,9 @@ const Flag kBase64Flags[] = { { "-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 kIntFlags[] = { diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h index 75743378..cc1618ad 100644 --- a/ssl/test/test_config.h +++ b/ssl/test/test_config.h @@ -59,6 +59,8 @@ struct TestConfig { 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;