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>kris/onging/CECPQ3_patch15
@@ -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 | |||
@@ -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 | |||
@@ -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<uint8_t> 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; | |||
@@ -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; | |||
} | |||
@@ -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. | |||
@@ -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<SSL_SESSION> *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<const uint8_t *>( | |||
config->quic_transport_params.data()), | |||
config->quic_transport_params.size())) { | |||
return false; | |||
} | |||
} | |||
int sock = Connect(config->port); | |||
if (sock == -1) { | |||
@@ -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 | |||
@@ -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 | |||
@@ -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 | |||
} | |||
@@ -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 | |||
@@ -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 { | |||
@@ -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. | |||
@@ -170,6 +170,9 @@ const Flag<std::string> 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<int> kIntFlags[] = { | |||
@@ -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; | |||