| // Copyright (c) 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/third_party/quiche/src/quic/core/crypto/transport_parameters.h" |
| |
| #include "third_party/boringssl/src/include/openssl/bytestring.h" |
| #include "net/third_party/quiche/src/quic/core/crypto/crypto_framer.h" |
| |
| namespace quic { |
| |
| namespace { |
| |
| // Values of the TransportParameterId enum as defined in |
| // draft-ietf-quic-transport-08 section 7.4. When parameters are encoded, one of |
| // these enum values is used to indicate which parameter is encoded. |
| enum TransportParameterId : uint16_t { |
| kInitialMaxStreamDataId = 0, |
| kInitialMaxDataId = 1, |
| kInitialMaxBidiStreamsId = 2, |
| kIdleTimeoutId = 3, |
| kMaxOutgoingPacketSizeId = 5, |
| kStatelessResetTokenId = 6, |
| kAckDelayExponentId = 7, |
| kInitialMaxUniStreamsId = 8, |
| |
| kMaxKnownParameterId = 9, |
| }; |
| |
| // Value for the TransportParameterId to use for non-standard Google QUIC params |
| // in Transport Parameters. |
| const uint16_t kGoogleQuicParamId = 18257; |
| |
| // The following constants define minimum and maximum allowed values for some of |
| // the parameters. These come from draft-ietf-quic-transport-08 section 7.4.1. |
| const uint16_t kMaxAllowedIdleTimeout = 600; |
| const uint16_t kMinAllowedMaxPacketSize = 1200; |
| const uint16_t kMaxAllowedMaxPacketSize = 65527; |
| const uint8_t kMaxAllowedAckDelayExponent = 20; |
| |
| static_assert(kMaxKnownParameterId <= 32, "too many parameters to bit pack"); |
| |
| // The initial_max_stream_data, initial_max_data, and idle_timeout parameters |
| // are always required to be present. When parsing the extension, a bitmask is |
| // used to keep track of which parameter have been seen so far, and that bitmask |
| // will be compared to this mask to check that all of the required parameters |
| // were present. |
| static constexpr uint16_t kRequiredParamsMask = (1 << kInitialMaxStreamDataId) | |
| (1 << kInitialMaxDataId) | |
| (1 << kIdleTimeoutId); |
| |
| } // namespace |
| |
| TransportParameters::TransportParameters() = default; |
| |
| TransportParameters::~TransportParameters() = default; |
| |
| bool TransportParameters::is_valid() const { |
| if (perspective == Perspective::IS_CLIENT && !stateless_reset_token.empty()) { |
| return false; |
| } |
| if (perspective == Perspective::IS_SERVER && |
| stateless_reset_token.size() != 16) { |
| return false; |
| } |
| if (idle_timeout > kMaxAllowedIdleTimeout || |
| (max_packet_size.present && |
| (max_packet_size.value > kMaxAllowedMaxPacketSize || |
| max_packet_size.value < kMinAllowedMaxPacketSize)) || |
| (ack_delay_exponent.present && |
| ack_delay_exponent.value > kMaxAllowedAckDelayExponent)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool SerializeTransportParameters(const TransportParameters& in, |
| std::vector<uint8_t>* out) { |
| if (!in.is_valid()) { |
| return false; |
| } |
| bssl::ScopedCBB cbb; |
| // 28 is the minimum size that the serialized TransportParameters can be, |
| // which is when it is for a client and only the required parameters are |
| // present. The CBB will grow to fit larger serializations. |
| if (!CBB_init(cbb.get(), 28) || !CBB_add_u32(cbb.get(), in.version)) { |
| return false; |
| } |
| CBB versions; |
| if (in.perspective == Perspective::IS_SERVER) { |
| if (!CBB_add_u8_length_prefixed(cbb.get(), &versions)) { |
| return false; |
| } |
| for (QuicVersionLabel version : in.supported_versions) { |
| if (!CBB_add_u32(&versions, version)) { |
| return false; |
| } |
| } |
| } |
| |
| CBB params, initial_max_stream_data_param, initial_max_data_param, |
| idle_timeout_param; |
| // required parameters |
| if (!CBB_add_u16_length_prefixed(cbb.get(), ¶ms) || |
| // initial_max_stream_data |
| !CBB_add_u16(¶ms, kInitialMaxStreamDataId) || |
| !CBB_add_u16_length_prefixed(¶ms, &initial_max_stream_data_param) || |
| !CBB_add_u32(&initial_max_stream_data_param, |
| in.initial_max_stream_data) || |
| // initial_max_data |
| !CBB_add_u16(¶ms, kInitialMaxDataId) || |
| !CBB_add_u16_length_prefixed(¶ms, &initial_max_data_param) || |
| !CBB_add_u32(&initial_max_data_param, in.initial_max_data) || |
| // idle_timeout |
| !CBB_add_u16(¶ms, kIdleTimeoutId) || |
| !CBB_add_u16_length_prefixed(¶ms, &idle_timeout_param) || |
| !CBB_add_u16(&idle_timeout_param, in.idle_timeout)) { |
| return false; |
| } |
| |
| CBB stateless_reset_token_param; |
| if (!in.stateless_reset_token.empty()) { |
| if (!CBB_add_u16(¶ms, kStatelessResetTokenId) || |
| !CBB_add_u16_length_prefixed(¶ms, &stateless_reset_token_param) || |
| !CBB_add_bytes(&stateless_reset_token_param, |
| in.stateless_reset_token.data(), |
| in.stateless_reset_token.size())) { |
| return false; |
| } |
| } |
| |
| CBB initial_max_bidi_streams_param; |
| if (in.initial_max_bidi_streams.present) { |
| if (!CBB_add_u16(¶ms, kInitialMaxBidiStreamsId) || |
| !CBB_add_u16_length_prefixed(¶ms, |
| &initial_max_bidi_streams_param) || |
| !CBB_add_u16(&initial_max_bidi_streams_param, |
| in.initial_max_bidi_streams.value)) { |
| return false; |
| } |
| } |
| CBB initial_max_uni_streams_param; |
| if (in.initial_max_uni_streams.present) { |
| if (!CBB_add_u16(¶ms, kInitialMaxUniStreamsId) || |
| !CBB_add_u16_length_prefixed(¶ms, &initial_max_uni_streams_param) || |
| !CBB_add_u16(&initial_max_uni_streams_param, |
| in.initial_max_uni_streams.value)) { |
| return false; |
| } |
| } |
| CBB max_packet_size_param; |
| if (in.max_packet_size.present) { |
| if (!CBB_add_u16(¶ms, kMaxOutgoingPacketSizeId) || |
| !CBB_add_u16_length_prefixed(¶ms, &max_packet_size_param) || |
| !CBB_add_u16(&max_packet_size_param, in.max_packet_size.value)) { |
| return false; |
| } |
| } |
| CBB ack_delay_exponent_param; |
| if (in.ack_delay_exponent.present) { |
| if (!CBB_add_u16(¶ms, kAckDelayExponentId) || |
| !CBB_add_u16_length_prefixed(¶ms, &ack_delay_exponent_param) || |
| !CBB_add_u8(&ack_delay_exponent_param, in.ack_delay_exponent.value)) { |
| return false; |
| } |
| } |
| CBB google_quic_params; |
| if (in.google_quic_params) { |
| const QuicData& serialized_google_quic_params = |
| in.google_quic_params->GetSerialized(); |
| if (!CBB_add_u16(¶ms, kGoogleQuicParamId) || |
| !CBB_add_u16_length_prefixed(¶ms, &google_quic_params) || |
| !CBB_add_bytes(&google_quic_params, |
| reinterpret_cast<const uint8_t*>( |
| serialized_google_quic_params.data()), |
| serialized_google_quic_params.length())) { |
| return false; |
| } |
| } |
| if (!CBB_flush(cbb.get())) { |
| return false; |
| } |
| out->resize(CBB_len(cbb.get())); |
| memcpy(out->data(), CBB_data(cbb.get()), CBB_len(cbb.get())); |
| return true; |
| } |
| |
| bool ParseTransportParameters(const uint8_t* in, |
| size_t in_len, |
| Perspective perspective, |
| TransportParameters* out) { |
| CBS cbs; |
| CBS_init(&cbs, in, in_len); |
| if (!CBS_get_u32(&cbs, &out->version)) { |
| return false; |
| } |
| if (perspective == Perspective::IS_SERVER) { |
| CBS versions; |
| if (!CBS_get_u8_length_prefixed(&cbs, &versions) || |
| CBS_len(&versions) % 4 != 0) { |
| return false; |
| } |
| while (CBS_len(&versions) > 0) { |
| QuicVersionLabel version; |
| if (!CBS_get_u32(&versions, &version)) { |
| return false; |
| } |
| out->supported_versions.push_back(version); |
| } |
| } |
| out->perspective = perspective; |
| |
| uint32_t present_params = 0; |
| bool has_google_quic_params = false; |
| CBS params; |
| if (!CBS_get_u16_length_prefixed(&cbs, ¶ms)) { |
| return false; |
| } |
| while (CBS_len(¶ms) > 0) { |
| uint16_t param_id; |
| CBS value; |
| if (!CBS_get_u16(¶ms, ¶m_id) || |
| !CBS_get_u16_length_prefixed(¶ms, &value)) { |
| return false; |
| } |
| if (param_id < kMaxKnownParameterId) { |
| uint16_t mask = 1 << param_id; |
| if (present_params & mask) { |
| return false; |
| } |
| present_params |= mask; |
| } |
| switch (param_id) { |
| case kInitialMaxStreamDataId: |
| if (!CBS_get_u32(&value, &out->initial_max_stream_data) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| break; |
| case kInitialMaxDataId: |
| if (!CBS_get_u32(&value, &out->initial_max_data) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| break; |
| case kInitialMaxBidiStreamsId: |
| if (!CBS_get_u16(&value, &out->initial_max_bidi_streams.value) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| out->initial_max_bidi_streams.present = true; |
| break; |
| case kIdleTimeoutId: |
| if (!CBS_get_u16(&value, &out->idle_timeout) || CBS_len(&value) != 0) { |
| return false; |
| } |
| break; |
| case kMaxOutgoingPacketSizeId: |
| if (!CBS_get_u16(&value, &out->max_packet_size.value) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| out->max_packet_size.present = true; |
| break; |
| case kStatelessResetTokenId: |
| if (CBS_len(&value) == 0) { |
| return false; |
| } |
| out->stateless_reset_token.assign(CBS_data(&value), |
| CBS_data(&value) + CBS_len(&value)); |
| break; |
| case kAckDelayExponentId: |
| if (!CBS_get_u8(&value, &out->ack_delay_exponent.value) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| out->ack_delay_exponent.present = true; |
| break; |
| case kInitialMaxUniStreamsId: |
| if (!CBS_get_u16(&value, &out->initial_max_uni_streams.value) || |
| CBS_len(&value) != 0) { |
| return false; |
| } |
| out->initial_max_uni_streams.present = true; |
| break; |
| case kGoogleQuicParamId: |
| if (has_google_quic_params) { |
| return false; |
| } |
| has_google_quic_params = true; |
| QuicStringPiece serialized_params( |
| reinterpret_cast<const char*>(CBS_data(&value)), CBS_len(&value)); |
| out->google_quic_params = CryptoFramer::ParseMessage(serialized_params); |
| } |
| } |
| if ((present_params & kRequiredParamsMask) != kRequiredParamsMask) { |
| return false; |
| } |
| return out->is_valid(); |
| } |
| |
| } // namespace quic |