Add support for retry integrity tag This CL adds support for the retry integrity tag which was added in draft-25. It increases resilience to network errors and makes retry injection by attackers harder. This changes the wire-format of T050 and T099/draft-25 which are both disabled. gfe-relnote: support retry integrity tag, client-only, not flag-protected PiperOrigin-RevId: 292044658 Change-Id: Ib62a4d58cb761dce284c36b450816ad9151e4062
diff --git a/common/quiche_data_reader.cc b/common/quiche_data_reader.cc index 9842add..3854651 100644 --- a/common/quiche_data_reader.cc +++ b/common/quiche_data_reader.cc
@@ -120,6 +120,10 @@ return quiche::QuicheStringPiece(data_, len_); } +quiche::QuicheStringPiece QuicheDataReader::PreviouslyReadPayload() const { + return quiche::QuicheStringPiece(data_, pos_); +} + bool QuicheDataReader::ReadBytes(void* result, size_t size) { // Make sure that we have enough data to read. if (!CanRead(size)) {
diff --git a/common/quiche_data_reader.h b/common/quiche_data_reader.h index 45c0961..c837172 100644 --- a/common/quiche_data_reader.h +++ b/common/quiche_data_reader.h
@@ -101,6 +101,15 @@ // DOES NOT forward the internal iterator. quiche::QuicheStringPiece FullPayload() const; + // Returns the part of the payload that has been already read as a + // quiche::QuicheStringPiece. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // DOES NOT forward the internal iterator. + quiche::QuicheStringPiece PreviouslyReadPayload() const; + // Reads a given number of bytes into the given buffer. The buffer // must be of adequate size. // Forwards the internal iterator on success.
diff --git a/common/quiche_data_writer_test.cc b/common/quiche_data_writer_test.cc index 78f8abf..343e6ea 100644 --- a/common/quiche_data_writer_test.cc +++ b/common/quiche_data_writer_test.cc
@@ -386,6 +386,9 @@ char expected_first_read[4] = {1, 2, 3, 4}; char expected_remaining[12] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; QuicheDataReader reader(buffer, sizeof(buffer)); + quiche::QuicheStringPiece previously_read_payload1 = + reader.PreviouslyReadPayload(); + EXPECT_TRUE(previously_read_payload1.empty()); char first_read_buffer[4] = {}; EXPECT_TRUE(reader.ReadBytes(first_read_buffer, sizeof(first_read_buffer))); test::CompareCharArraysWithHexError( @@ -401,6 +404,12 @@ test::CompareCharArraysWithHexError("full_payload", full_payload.data(), full_payload.length(), buffer, sizeof(buffer)); + quiche::QuicheStringPiece previously_read_payload2 = + reader.PreviouslyReadPayload(); + test::CompareCharArraysWithHexError( + "previously_read_payload2", previously_read_payload2.data(), + previously_read_payload2.length(), first_read_buffer, + sizeof(first_read_buffer)); quiche::QuicheStringPiece read_remaining_payload = reader.ReadRemainingPayload(); test::CompareCharArraysWithHexError( @@ -412,6 +421,11 @@ test::CompareCharArraysWithHexError("full_payload2", full_payload2.data(), full_payload2.length(), buffer, sizeof(buffer)); + quiche::QuicheStringPiece previously_read_payload3 = + reader.PreviouslyReadPayload(); + test::CompareCharArraysWithHexError( + "previously_read_payload3", previously_read_payload3.data(), + previously_read_payload3.length(), buffer, sizeof(buffer)); } } // namespace
diff --git a/quic/core/chlo_extractor.cc b/quic/core/chlo_extractor.cc index 496f201..b4907d4 100644 --- a/quic/core/chlo_extractor.cc +++ b/quic/core/chlo_extractor.cc
@@ -38,7 +38,10 @@ const QuicVersionNegotiationPacket& /*packet*/) override {} void OnRetryPacket(QuicConnectionId /*original_connection_id*/, QuicConnectionId /*new_connection_id*/, - quiche::QuicheStringPiece /*retry_token*/) override {} + quiche::QuicheStringPiece /*retry_token*/, + quiche::QuicheStringPiece /*retry_integrity_tag*/, + quiche::QuicheStringPiece /*retry_without_tag*/) override { + } bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override; bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override; void OnDecryptedPacket(EncryptionLevel /*level*/) override {}
diff --git a/quic/core/crypto/crypto_utils.cc b/quic/core/crypto/crypto_utils.cc index 94b1c2c..7de442b 100644 --- a/quic/core/crypto/crypto_utils.cc +++ b/quic/core/crypto/crypto_utils.cc
@@ -10,6 +10,7 @@ #include "third_party/boringssl/src/include/openssl/bytestring.h" #include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/mem.h" #include "third_party/boringssl/src/include/openssl/sha.h" #include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_decrypter.h" #include "net/third_party/quiche/src/quic/core/crypto/aes_128_gcm_12_encrypter.h" @@ -23,9 +24,12 @@ #include "net/third_party/quiche/src/quic/core/crypto/quic_encrypter.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_hkdf.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_connection_id.h" +#include "net/third_party/quiche/src/quic/core/quic_constants.h" #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" #include "net/third_party/quiche/src/quic/core/quic_time.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" #include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h" @@ -174,6 +178,54 @@ const char kPreSharedKeyLabel[] = "QUIC PSK"; +// Retry Integrity Protection Keys and Nonces. +// https://tools.ietf.org/html/draft-ietf-quic-tls-25#section-5.8 +static_assert(kQuicIetfDraftVersion == 25, "Keys do not match draft version"); +const uint8_t kDraft25RetryIntegrityKey[] = {0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, + 0x33, 0xc8, 0x41, 0xe4, 0x04, 0x3d, + 0xf2, 0x7d, 0x44, 0x30}; +const uint8_t kDraft25RetryIntegrityNonce[] = { + 0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13, 0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75}; +// Keys used by Google versions of QUIC. When introducing a new version, +// generate a new key by running `openssl rand -hex 16`. +const uint8_t kT050RetryIntegrityKey[] = {0xc9, 0x2d, 0x32, 0x3d, 0x9c, 0xe3, + 0x0d, 0xa0, 0x88, 0xb9, 0xb7, 0xbb, + 0xdc, 0xcd, 0x50, 0xc8}; +// Nonces used by Google versions of QUIC. When introducing a new version, +// generate a new nonce by running `openssl rand -hex 12`. +const uint8_t kT050RetryIntegrityNonce[] = {0x26, 0xe4, 0xd6, 0x23, 0x83, 0xd5, + 0xc7, 0x60, 0xea, 0x02, 0xb4, 0x1f}; + +bool RetryIntegrityKeysForVersion(const ParsedQuicVersion& version, + quiche::QuicheStringPiece* key, + quiche::QuicheStringPiece* nonce) { + if (!version.HasRetryIntegrityTag()) { + QUIC_BUG << "Attempted to get retry integrity keys for invalid version " + << version; + return false; + } + if (version == ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_50)) { + *key = quiche::QuicheStringPiece( + reinterpret_cast<const char*>(kT050RetryIntegrityKey), + QUICHE_ARRAYSIZE(kT050RetryIntegrityKey)); + *nonce = quiche::QuicheStringPiece( + reinterpret_cast<const char*>(kT050RetryIntegrityNonce), + QUICHE_ARRAYSIZE(kT050RetryIntegrityNonce)); + return true; + } + if (version == ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99)) { + *key = quiche::QuicheStringPiece( + reinterpret_cast<const char*>(kDraft25RetryIntegrityKey), + QUICHE_ARRAYSIZE(kDraft25RetryIntegrityKey)); + *nonce = quiche::QuicheStringPiece( + reinterpret_cast<const char*>(kDraft25RetryIntegrityNonce), + QUICHE_ARRAYSIZE(kDraft25RetryIntegrityNonce)); + return true; + } + QUIC_BUG << "Attempted to get retry integrity keys for version " << version; + return false; +} + } // namespace // static @@ -232,6 +284,51 @@ } // static +bool CryptoUtils::ValidateRetryIntegrityTag( + ParsedQuicVersion version, + QuicConnectionId original_connection_id, + quiche::QuicheStringPiece retry_without_tag, + quiche::QuicheStringPiece integrity_tag) { + unsigned char computed_integrity_tag[kRetryIntegrityTagLength]; + if (integrity_tag.length() != QUICHE_ARRAYSIZE(computed_integrity_tag)) { + QUIC_BUG << "Invalid retry integrity tag length " << integrity_tag.length(); + return false; + } + char retry_pseudo_packet[kMaxIncomingPacketSize + 256]; + QuicDataWriter writer(QUICHE_ARRAYSIZE(retry_pseudo_packet), + retry_pseudo_packet); + if (!writer.WriteLengthPrefixedConnectionId(original_connection_id)) { + QUIC_BUG << "Failed to write original connection ID in retry pseudo packet"; + return false; + } + if (!writer.WriteStringPiece(retry_without_tag)) { + QUIC_BUG << "Failed to write retry without tag in retry pseudo packet"; + return false; + } + quiche::QuicheStringPiece key; + quiche::QuicheStringPiece nonce; + if (!RetryIntegrityKeysForVersion(version, &key, &nonce)) { + // RetryIntegrityKeysForVersion already logs failures. + return false; + } + Aes128GcmEncrypter crypter; + crypter.SetKey(key); + quiche::QuicheStringPiece associated_data(writer.data(), writer.length()); + quiche::QuicheStringPiece plaintext; // Plaintext is empty. + if (!crypter.Encrypt(nonce, associated_data, plaintext, + computed_integrity_tag)) { + QUIC_BUG << "Failed to compute retry integrity tag"; + return false; + } + if (CRYPTO_memcmp(computed_integrity_tag, integrity_tag.data(), + QUICHE_ARRAYSIZE(computed_integrity_tag)) != 0) { + QUIC_DLOG(ERROR) << "Failed to validate retry integrity tag"; + return false; + } + return true; +} + +// static void CryptoUtils::GenerateNonce(QuicWallTime now, QuicRandom* random_generator, quiche::QuicheStringPiece orbit,
diff --git a/quic/core/crypto/crypto_utils.h b/quic/core/crypto/crypto_utils.h index 8cebf14..6c167f8 100644 --- a/quic/core/crypto/crypto_utils.h +++ b/quic/core/crypto/crypto_utils.h
@@ -16,8 +16,10 @@ #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h" #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_crypter.h" +#include "net/third_party/quiche/src/quic/core/quic_connection_id.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" #include "net/third_party/quiche/src/quic/core/quic_time.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_export.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" @@ -95,6 +97,15 @@ QuicConnectionId connection_id, CrypterPair* crypters); + // IETF QUIC Retry packets carry a retry integrity tag to detect packet + // corruption and make it harder for an attacker to spoof. This function + // checks whether a given retry packet is valid. + static bool ValidateRetryIntegrityTag( + ParsedQuicVersion version, + QuicConnectionId original_connection_id, + quiche::QuicheStringPiece retry_without_tag, + quiche::QuicheStringPiece integrity_tag); + // Generates the connection nonce. The nonce is formed as: // <4 bytes> current time // <8 bytes> |orbit| (or random if |orbit| is empty)
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 03f5800..f54609a 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -328,7 +328,7 @@ processing_ack_frame_(false), supports_release_time_(false), release_time_into_future_(QuicTime::Delta::Zero()), - retry_has_been_parsed_(false), + drop_incoming_retry_packets_(false), max_consecutive_ptos_(0), bytes_received_before_address_validation_(0), bytes_sent_before_address_validation_(0), @@ -634,23 +634,35 @@ } // Handles retry for client connection. -void QuicConnection::OnRetryPacket(QuicConnectionId original_connection_id, - QuicConnectionId new_connection_id, - quiche::QuicheStringPiece retry_token) { +void QuicConnection::OnRetryPacket( + QuicConnectionId original_connection_id, + QuicConnectionId new_connection_id, + quiche::QuicheStringPiece retry_token, + quiche::QuicheStringPiece retry_integrity_tag, + quiche::QuicheStringPiece retry_without_tag) { DCHECK_EQ(Perspective::IS_CLIENT, perspective_); - if (original_connection_id != server_connection_id_) { - QUIC_DLOG(ERROR) << "Ignoring RETRY with original connection ID " - << original_connection_id << " not matching expected " - << server_connection_id_ << " token " + if (version().HasRetryIntegrityTag()) { + if (!CryptoUtils::ValidateRetryIntegrityTag( + version(), server_connection_id_, retry_without_tag, + retry_integrity_tag)) { + QUIC_DLOG(ERROR) << "Ignoring RETRY with invalid integrity tag"; + return; + } + } else { + if (original_connection_id != server_connection_id_) { + QUIC_DLOG(ERROR) << "Ignoring RETRY with original connection ID " + << original_connection_id << " not matching expected " + << server_connection_id_ << " token " + << quiche::QuicheTextUtils::HexEncode(retry_token); + return; + } + } + if (drop_incoming_retry_packets_) { + QUIC_DLOG(ERROR) << "Ignoring RETRY with token " << quiche::QuicheTextUtils::HexEncode(retry_token); return; } - if (retry_has_been_parsed_) { - QUIC_DLOG(ERROR) << "Ignoring non-first RETRY with token " - << quiche::QuicheTextUtils::HexEncode(retry_token); - return; - } - retry_has_been_parsed_ = true; + drop_incoming_retry_packets_ = true; stats_.retry_packet_processed = true; QUIC_DLOG(INFO) << "Received RETRY, replacing connection ID " << server_connection_id_ << " with " << new_connection_id @@ -683,6 +695,11 @@ bool QuicConnection::OnUnauthenticatedPublicHeader( const QuicPacketHeader& header) { + // As soon as we receive an initial we start ignoring subsequent retries. + if (header.version_flag && header.long_packet_type == INITIAL) { + drop_incoming_retry_packets_ = true; + } + QuicConnectionId server_connection_id = GetServerConnectionIdAsRecipient(header, perspective_);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index 5d44234..a10de5b 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -493,7 +493,9 @@ const QuicVersionNegotiationPacket& packet) override; void OnRetryPacket(QuicConnectionId original_connection_id, QuicConnectionId new_connection_id, - quiche::QuicheStringPiece retry_token) override; + quiche::QuicheStringPiece retry_token, + quiche::QuicheStringPiece retry_integrity_tag, + quiche::QuicheStringPiece retry_without_tag) override; bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override; bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override; void OnDecryptedPacket(EncryptionLevel level) override; @@ -1482,8 +1484,8 @@ // vector to improve performance since it is expected to be very small. std::vector<QuicConnectionId> incoming_connection_ids_; - // Indicates whether a RETRY packet has been parsed. - bool retry_has_been_parsed_; + // Indicates whether received RETRY packets should be dropped. + bool drop_incoming_retry_packets_; // If max_consecutive_ptos_ > 0, close connection if consecutive PTOs is // greater than max_consecutive_ptos.
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 7c20394..7809cc5 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -22,6 +22,7 @@ #include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_error_code_wrappers.h" #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" @@ -39,6 +40,7 @@ #include "net/third_party/quiche/src/quic/test_tools/simple_data_producer.h" #include "net/third_party/quiche/src/quic/test_tools/simple_quic_framer.h" #include "net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h" #include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" @@ -9795,6 +9797,75 @@ EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet()); } +TEST_P(QuicConnectionTest, ClientParsesRetry) { + if (!version().HasRetryIntegrityTag()) { + return; + } + if (version() != ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99)) { + // TODO(dschinazi) generate retry packets for all versions once we have + // server-side support for generating these programmatically. + return; + } + + // These values come from draft-ietf-quic-tls Appendix A.4. + char retry_packet[] = {0xff, 0xff, 0x00, 0x00, 0x19, 0x00, 0x08, 0xf0, 0x67, + 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x1e, 0x5e, 0xc5, 0xb0, 0x14, 0xcb, 0xb1, + 0xf0, 0xfd, 0x93, 0xdf, 0x40, 0x48, 0xc4, 0x46, 0xa6}; + char original_connection_id_bytes[] = {0x83, 0x94, 0xc8, 0xf0, + 0x3e, 0x51, 0x57, 0x08}; + char new_connection_id_bytes[] = {0xf0, 0x67, 0xa5, 0x50, + 0x2a, 0x42, 0x62, 0xb5}; + char retry_token_bytes[] = {0x74, 0x6f, 0x6b, 0x65, 0x6e}; + + QuicConnectionId original_connection_id( + original_connection_id_bytes, + QUICHE_ARRAYSIZE(original_connection_id_bytes)); + QuicConnectionId new_connection_id(new_connection_id_bytes, + QUICHE_ARRAYSIZE(new_connection_id_bytes)); + + std::string retry_token(retry_token_bytes, + QUICHE_ARRAYSIZE(retry_token_bytes)); + + { + TestConnection connection1( + original_connection_id, kPeerAddress, helper_.get(), + alarm_factory_.get(), writer_.get(), Perspective::IS_CLIENT, version()); + connection1.set_visitor(&visitor_); + + connection1.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicReceivedPacket(retry_packet, QUICHE_ARRAYSIZE(retry_packet), + clock_.Now())); + EXPECT_TRUE(connection1.GetStats().retry_packet_processed); + EXPECT_EQ(connection1.connection_id(), new_connection_id); + EXPECT_EQ(QuicPacketCreatorPeer::GetRetryToken( + QuicConnectionPeer::GetPacketCreator(&connection1)), + retry_token); + } + + // Now flip the last bit of the retry packet to prevent the integrity tag + // from validating correctly. + retry_packet[QUICHE_ARRAYSIZE(retry_packet) - 1] ^= 1; + + { + TestConnection connection2( + original_connection_id, kPeerAddress, helper_.get(), + alarm_factory_.get(), writer_.get(), Perspective::IS_CLIENT, version()); + connection2.set_visitor(&visitor_); + + connection2.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicReceivedPacket(retry_packet, QUICHE_ARRAYSIZE(retry_packet), + clock_.Now())); + EXPECT_FALSE(connection2.GetStats().retry_packet_processed); + EXPECT_EQ(connection2.connection_id(), original_connection_id); + EXPECT_TRUE(QuicPacketCreatorPeer::GetRetryToken( + QuicConnectionPeer::GetPacketCreator(&connection2)) + .empty()); + } +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h index c4a83b3..15f0abb 100644 --- a/quic/core/quic_constants.h +++ b/quic/core/quic_constants.h
@@ -105,6 +105,10 @@ // Number of bytes reserved for version number in the packet header. const size_t kQuicVersionSize = 4; +// Length of the retry integrity tag in bytes. +// https://tools.ietf.org/html/draft-ietf-quic-transport-25#section-17.2.5 +const size_t kRetryIntegrityTagLength = 16; + // Signifies that the QuicPacket will contain version of the protocol. const bool kIncludeVersion = true; // Signifies that the QuicPacket will include a diversification nonce.
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc index 080aa01..1170595 100644 --- a/quic/core/quic_framer.cc +++ b/quic/core/quic_framer.cc
@@ -1495,6 +1495,31 @@ const QuicPacketHeader& header) { DCHECK_EQ(Perspective::IS_CLIENT, perspective_); + if (version_.HasRetryIntegrityTag()) { + DCHECK(version_.HasLengthPrefixedConnectionIds()) << version_; + const size_t bytes_remaining = reader->BytesRemaining(); + if (bytes_remaining <= kRetryIntegrityTagLength) { + set_detailed_error("Retry packet too short to parse integrity tag."); + return false; + } + const size_t retry_token_length = + bytes_remaining - kRetryIntegrityTagLength; + DCHECK_GT(retry_token_length, 0u); + quiche::QuicheStringPiece retry_token; + if (!reader->ReadStringPiece(&retry_token, retry_token_length)) { + set_detailed_error("Failed to read retry token."); + return false; + } + quiche::QuicheStringPiece retry_without_tag = + reader->PreviouslyReadPayload(); + quiche::QuicheStringPiece integrity_tag = reader->ReadRemainingPayload(); + DCHECK_EQ(integrity_tag.length(), kRetryIntegrityTagLength); + visitor_->OnRetryPacket(EmptyQuicConnectionId(), + header.source_connection_id, retry_token, + integrity_tag, retry_without_tag); + return true; + } + QuicConnectionId original_destination_connection_id; if (version_.HasLengthPrefixedConnectionIds()) { // Parse Original Destination Connection ID. @@ -1526,7 +1551,9 @@ quiche::QuicheStringPiece retry_token = reader->ReadRemainingPayload(); visitor_->OnRetryPacket(original_destination_connection_id, - header.source_connection_id, retry_token); + header.source_connection_id, retry_token, + /*retry_integrity_tag=*/quiche::QuicheStringPiece(), + /*retry_without_tag=*/quiche::QuicheStringPiece()); return true; }
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h index 1a83989..5914e5a 100644 --- a/quic/core/quic_framer.h +++ b/quic/core/quic_framer.h
@@ -94,10 +94,21 @@ const QuicVersionNegotiationPacket& packet) = 0; // Called only when |perspective_| is IS_CLIENT and a retry packet has been - // parsed. + // parsed. |new_connection_id| contains the value of the Source Connection + // ID field, and |retry_token| contains the value of the Retry Token field. + // On versions where HasRetryIntegrityTag() is false, + // |original_connection_id| contains the value of the Original Destination + // Connection ID field, and both |retry_integrity_tag| and + // |retry_without_tag| are empty. + // On versions where HasRetryIntegrityTag() is true, + // |original_connection_id| is empty, |retry_integrity_tag| contains the + // value of the Retry Integrity Tag field, and |retry_without_tag| contains + // the entire RETRY packet except the Retry Integrity Tag field. virtual void OnRetryPacket(QuicConnectionId original_connection_id, QuicConnectionId new_connection_id, - quiche::QuicheStringPiece retry_token) = 0; + quiche::QuicheStringPiece retry_token, + quiche::QuicheStringPiece retry_integrity_tag, + quiche::QuicheStringPiece retry_without_tag) = 0; // Called when all fields except packet number has been parsed, but has not // been authenticated. If it returns false, framing for this packet will
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc index c51c9c2..c24520c 100644 --- a/quic/core/quic_framer_test.cc +++ b/quic/core/quic_framer_test.cc
@@ -225,12 +225,18 @@ void OnRetryPacket(QuicConnectionId original_connection_id, QuicConnectionId new_connection_id, - quiche::QuicheStringPiece retry_token) override { + quiche::QuicheStringPiece retry_token, + quiche::QuicheStringPiece retry_integrity_tag, + quiche::QuicheStringPiece retry_without_tag) override { retry_original_connection_id_ = std::make_unique<QuicConnectionId>(original_connection_id); retry_new_connection_id_ = std::make_unique<QuicConnectionId>(new_connection_id); retry_token_ = std::make_unique<std::string>(std::string(retry_token)); + retry_token_integrity_tag_ = + std::make_unique<std::string>(std::string(retry_integrity_tag)); + retry_without_tag_ = + std::make_unique<std::string>(std::string(retry_without_tag)); EXPECT_EQ(0u, framer_->current_received_frame_type()); } @@ -555,6 +561,8 @@ std::unique_ptr<QuicConnectionId> retry_original_connection_id_; std::unique_ptr<QuicConnectionId> retry_new_connection_id_; std::unique_ptr<std::string> retry_token_; + std::unique_ptr<std::string> retry_token_integrity_tag_; + std::unique_ptr<std::string> retry_without_tag_; std::vector<std::unique_ptr<QuicStreamFrame>> stream_frames_; std::vector<std::unique_ptr<QuicCryptoFrame>> crypto_frames_; std::vector<std::unique_ptr<QuicAckFrame>> ack_frames_; @@ -5734,11 +5742,32 @@ 'H', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'i', 's', ' ', 'i', 's', ' ', 'R', 'E', 'T', 'R', 'Y', '!', }; + unsigned char packet_with_tag[] = { + // public flags (long header with packet type RETRY) + 0xF0, + // version + QUIC_VERSION_BYTES, + // destination connection ID length + 0x00, + // source connection ID length + 0x08, + // source connection ID + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11, + // retry token + 'H', 'e', 'l', 'l', 'o', ' ', 't', 'h', 'i', 's', + ' ', 'i', 's', ' ', 'R', 'E', 'T', 'R', 'Y', '!', + // retry token integrity tag + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; // clang-format on unsigned char* p = packet; size_t p_length = QUICHE_ARRAYSIZE(packet); - if (framer_.transport_version() >= QUIC_VERSION_49) { + if (framer_.version().HasRetryIntegrityTag()) { + p = packet_with_tag; + p_length = QUICHE_ARRAYSIZE(packet_with_tag); + } else if (framer_.transport_version() >= QUIC_VERSION_49) { p = packet49; p_length = QUICHE_ARRAYSIZE(packet49); } @@ -5748,12 +5777,31 @@ EXPECT_THAT(framer_.error(), IsQuicNoError()); ASSERT_TRUE(visitor_.header_.get()); - ASSERT_TRUE(visitor_.retry_original_connection_id_.get()); ASSERT_TRUE(visitor_.retry_new_connection_id_.get()); ASSERT_TRUE(visitor_.retry_token_.get()); - EXPECT_EQ(FramerTestConnectionId(), - *visitor_.retry_original_connection_id_.get()); + if (framer_.version().HasRetryIntegrityTag()) { + ASSERT_TRUE(visitor_.retry_token_integrity_tag_.get()); + static const unsigned char expected_integrity_tag[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; + quiche::test::CompareCharArraysWithHexError( + "retry integrity tag", visitor_.retry_token_integrity_tag_->data(), + visitor_.retry_token_integrity_tag_->length(), + reinterpret_cast<const char*>(expected_integrity_tag), + QUICHE_ARRAYSIZE(expected_integrity_tag)); + ASSERT_TRUE(visitor_.retry_without_tag_.get()); + quiche::test::CompareCharArraysWithHexError( + "retry without tag", visitor_.retry_without_tag_->data(), + visitor_.retry_without_tag_->length(), + reinterpret_cast<const char*>(packet_with_tag), 35); + } else { + ASSERT_TRUE(visitor_.retry_original_connection_id_.get()); + EXPECT_EQ(FramerTestConnectionId(), + *visitor_.retry_original_connection_id_.get()); + } + EXPECT_EQ(FramerTestConnectionIdPlusOne(), *visitor_.retry_new_connection_id_.get()); EXPECT_EQ("Hello this is RETRY!", *visitor_.retry_token_.get());
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc index 96f0a82..e92f048 100644 --- a/quic/core/quic_versions.cc +++ b/quic/core/quic_versions.cc
@@ -76,6 +76,11 @@ return transport_version > QUIC_VERSION_46; } +bool ParsedQuicVersion::HasRetryIntegrityTag() const { + DCHECK(IsKnown()); + return handshake_protocol == PROTOCOL_TLS1_3; +} + bool ParsedQuicVersion::SendsVariableLengthPacketNumberInLongHeader() const { DCHECK(IsKnown()); return transport_version > QUIC_VERSION_46;
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h index 299f247..6e514f0 100644 --- a/quic/core/quic_versions.h +++ b/quic/core/quic_versions.h
@@ -229,6 +229,9 @@ // Returns whether this version supports IETF RETRY packets. bool SupportsRetry() const; + // Returns whether RETRY packets carry the Retry Integrity Tag field. + bool HasRetryIntegrityTag() const; + // Returns true if this version sends variable length packet number in long // header. bool SendsVariableLengthPacketNumberInLongHeader() const;
diff --git a/quic/test_tools/quic_packet_creator_peer.cc b/quic/test_tools/quic_packet_creator_peer.cc index 7c8db91..8224012 100644 --- a/quic/test_tools/quic_packet_creator_peer.cc +++ b/quic/test_tools/quic_packet_creator_peer.cc
@@ -144,5 +144,10 @@ return creator->framer_; } +// static +std::string QuicPacketCreatorPeer::GetRetryToken(QuicPacketCreator* creator) { + return creator->retry_token_; +} + } // namespace test } // namespace quic
diff --git a/quic/test_tools/quic_packet_creator_peer.h b/quic/test_tools/quic_packet_creator_peer.h index 88587a2..bdadc2b 100644 --- a/quic/test_tools/quic_packet_creator_peer.h +++ b/quic/test_tools/quic_packet_creator_peer.h
@@ -58,6 +58,7 @@ static EncryptionLevel GetEncryptionLevel(QuicPacketCreator* creator); static QuicFramer* framer(QuicPacketCreator* creator); + static std::string GetRetryToken(QuicPacketCreator* creator); }; } // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index 4ce21a8..974d9e2 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -262,10 +262,12 @@ MOCK_METHOD1(OnPublicResetPacket, void(const QuicPublicResetPacket& header)); MOCK_METHOD1(OnVersionNegotiationPacket, void(const QuicVersionNegotiationPacket& packet)); - MOCK_METHOD3(OnRetryPacket, + MOCK_METHOD5(OnRetryPacket, void(QuicConnectionId original_connection_id, QuicConnectionId new_connection_id, - quiche::QuicheStringPiece retry_token)); + quiche::QuicheStringPiece retry_token, + quiche::QuicheStringPiece retry_integrity_tag, + quiche::QuicheStringPiece retry_without_tag)); // The constructor sets this up to return true by default. MOCK_METHOD1(OnUnauthenticatedHeader, bool(const QuicPacketHeader& header)); // The constructor sets this up to return true by default. @@ -325,7 +327,10 @@ const QuicVersionNegotiationPacket& /*packet*/) override {} void OnRetryPacket(QuicConnectionId /*original_connection_id*/, QuicConnectionId /*new_connection_id*/, - quiche::QuicheStringPiece /*retry_token*/) override {} + quiche::QuicheStringPiece /*retry_token*/, + quiche::QuicheStringPiece /*retry_integrity_tag*/, + quiche::QuicheStringPiece /*retry_without_tag*/) override { + } bool OnProtocolVersionMismatch(ParsedQuicVersion version) override; bool OnUnauthenticatedHeader(const QuicPacketHeader& header) override; bool OnUnauthenticatedPublicHeader(const QuicPacketHeader& header) override; @@ -1076,9 +1081,11 @@ MOCK_METHOD1(OnVersionNegotiationPacket, void(const QuicVersionNegotiationPacket&)); - MOCK_METHOD3(OnRetryPacket, + MOCK_METHOD5(OnRetryPacket, void(QuicConnectionId, QuicConnectionId, + quiche::QuicheStringPiece, + quiche::QuicheStringPiece, quiche::QuicheStringPiece)); };
diff --git a/quic/test_tools/simple_quic_framer.cc b/quic/test_tools/simple_quic_framer.cc index 09fdd12..3d2652f 100644 --- a/quic/test_tools/simple_quic_framer.cc +++ b/quic/test_tools/simple_quic_framer.cc
@@ -41,7 +41,10 @@ void OnRetryPacket(QuicConnectionId /*original_connection_id*/, QuicConnectionId /*new_connection_id*/, - quiche::QuicheStringPiece /*retry_token*/) override {} + quiche::QuicheStringPiece /*retry_token*/, + quiche::QuicheStringPiece /*retry_integrity_tag*/, + quiche::QuicheStringPiece /*retry_without_tag*/) override { + } bool OnUnauthenticatedPublicHeader( const QuicPacketHeader& /*header*/) override {
diff --git a/quic/tools/quic_packet_printer_bin.cc b/quic/tools/quic_packet_printer_bin.cc index 56522aa..798b66e 100644 --- a/quic/tools/quic_packet_printer_bin.cc +++ b/quic/tools/quic_packet_printer_bin.cc
@@ -65,7 +65,9 @@ } void OnRetryPacket(QuicConnectionId /*original_connection_id*/, QuicConnectionId /*new_connection_id*/, - quiche::QuicheStringPiece /*retry_token*/) override { + quiche::QuicheStringPiece /*retry_token*/, + quiche::QuicheStringPiece /*retry_integrity_tag*/, + quiche::QuicheStringPiece /*retry_without_tag*/) override { std::cerr << "OnRetryPacket\n"; } bool OnUnauthenticatedPublicHeader(