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/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;