Move simple decryption cases out of LoadBalancerConfig.
Item for unencrypted/3Pass/1Pass
Tip of tree: 23.0/103/33.9
This CL: 21.1/104/33.3
This partially reverts cl/597301121 but keeps its large gains for 3-pass decryption.
PiperOrigin-RevId: 599637882
diff --git a/quiche/quic/load_balancer/load_balancer_config.cc b/quiche/quic/load_balancer/load_balancer_config.cc
index 726d07e..2373e6c 100644
--- a/quiche/quic/load_balancer/load_balancer_config.cc
+++ b/quiche/quic/load_balancer/load_balancer_config.cc
@@ -8,12 +8,10 @@
#include <cstring>
#include <optional>
-#include "absl/numeric/int128.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "openssl/aes.h"
#include "quiche/quic/core/quic_connection_id.h"
-#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/load_balancer/load_balancer_server_id.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
@@ -91,25 +89,10 @@
: std::optional<LoadBalancerConfig>();
}
-LoadBalancerServerId LoadBalancerConfig::Decrypt(
+LoadBalancerServerId LoadBalancerConfig::FourPassDecrypt(
absl::Span<const uint8_t> ciphertext) const {
- if (ciphertext.length() < total_len()) {
- return LoadBalancerServerId();
- }
if (!key_.has_value()) {
- return LoadBalancerServerId(
- absl::Span<const uint8_t>(ciphertext.data() + 1, server_id_len_));
- }
- if (plaintext_len() == kLoadBalancerBlockSize) {
- if (!block_decrypt_key_.has_value()) {
- QUIC_BUG(quic_bug_596735037_01) << "Block decrypt key is not set.";
- return LoadBalancerServerId();
- }
- uint8_t plaintext[kLoadBalancerBlockSize];
- AES_decrypt(ciphertext.subspan(1, kLoadBalancerBlockSize).data(), plaintext,
- &*block_decrypt_key_);
- return LoadBalancerServerId(
- absl::Span<const uint8_t>(plaintext, server_id_len_));
+ return LoadBalancerServerId();
}
// Do 3 or 4 passes. Only 3 are necessary if the server_id is short enough
// to fit in the first half of the connection ID (the decoder doesn't need
@@ -139,40 +122,16 @@
absl::Span<uint8_t>(&right[2], server_id_len_ - half_len));
}
-QuicConnectionId LoadBalancerConfig::Encrypt(
- absl::Span<uint8_t> connection_id) const {
- if (connection_id.length() < total_len()) {
+QuicConnectionId LoadBalancerConfig::FourPassEncrypt(
+ absl::Span<uint8_t> plaintext) const {
+ if (!key_.has_value()) {
return QuicConnectionId();
}
- if (!key_.has_value()) { // Plaintext connection ID
- // Fill the nonce field with a hash of the Connection ID to avoid the nonce
- // visibly increasing by one. This would allow observers to correlate
- // connection IDs as being sequential and likely from the same connection,
- // not just the same server.
- absl::uint128 nonce_hash = QuicUtils::FNV1a_128_Hash(absl::string_view(
- reinterpret_cast<char*>(connection_id.data()), connection_id.length()));
- const uint64_t lo = absl::Uint128Low64(nonce_hash);
- if (nonce_len_ <= sizeof(uint64_t)) {
- memcpy(connection_id.data() + 1 + server_id_len_, &lo, nonce_len_);
- return QuicConnectionId(connection_id);
- }
- memcpy(connection_id.data() + 1 + server_id_len_, &lo, sizeof(uint64_t));
- const uint64_t hi = absl::Uint128High64(nonce_hash);
- memcpy(connection_id.data() + 1 + server_id_len_ + sizeof(uint64_t), &hi,
- nonce_len_ - sizeof(uint64_t));
- return QuicConnectionId(connection_id);
- }
- if (plaintext_len() == kLoadBalancerBlockSize) {
- AES_encrypt(connection_id.subspan(1, plaintext_len()).data(),
- connection_id.data() + 1, &*key_);
- return QuicConnectionId(connection_id);
- }
- // 4 Pass Encryption
uint8_t left[kLoadBalancerBlockSize];
uint8_t right[kLoadBalancerBlockSize];
uint8_t half_len; // half the length of the plaintext, rounded up
bool is_length_odd =
- InitializeFourPass(connection_id.data(), left, right, &half_len);
+ InitializeFourPass(plaintext.data() + 1, left, right, &half_len);
for (uint8_t index = 1; index <= kNumLoadBalancerCryptoPasses; ++index) {
EncryptionPass(index, half_len, is_length_odd, left, right);
}
@@ -181,13 +140,34 @@
// Combine the halves of the odd byte.
left[half_len + 1] |= right[2];
}
- memcpy(connection_id.data() + 1, &left[2], half_len);
+ memcpy(plaintext.data() + 1, &left[2], half_len);
if (is_length_odd) {
- memcpy(connection_id.data() + 1 + half_len, &right[3], half_len - 1);
+ memcpy(plaintext.data() + 1 + half_len, &right[3], half_len - 1);
} else {
- memcpy(connection_id.data() + 1 + half_len, &right[2], half_len);
+ memcpy(plaintext.data() + 1 + half_len, &right[2], half_len);
}
- return QuicConnectionId(connection_id);
+ return QuicConnectionId(reinterpret_cast<char*>(plaintext.data()),
+ total_len());
+}
+
+bool LoadBalancerConfig::BlockEncrypt(
+ const uint8_t plaintext[kLoadBalancerBlockSize],
+ uint8_t ciphertext[kLoadBalancerBlockSize]) const {
+ if (!key_.has_value()) {
+ return false;
+ }
+ AES_encrypt(plaintext, ciphertext, &*key_);
+ return true;
+}
+
+bool LoadBalancerConfig::BlockDecrypt(
+ const uint8_t ciphertext[kLoadBalancerBlockSize],
+ uint8_t plaintext[kLoadBalancerBlockSize]) const {
+ if (!block_decrypt_key_.has_value()) {
+ return false;
+ }
+ AES_decrypt(ciphertext, plaintext, &*block_decrypt_key_);
+ return true;
}
LoadBalancerConfig::LoadBalancerConfig(const uint8_t config_id,
@@ -220,11 +200,11 @@
left[0] = plaintext_len();
right[0] = plaintext_len();
// Leave left_[1], right_[1] as zero. It will be set for each pass.
- memcpy(&left[2], input + 1, *half_len);
+ memcpy(&left[2], input, *half_len);
// If is_length_odd, then both left and right will have part of the middle
// byte. Then that middle byte will be split in half via the bitmask in the
// next step.
- memcpy(&right[2], input + (plaintext_len() / 2) + 1, *half_len);
+ memcpy(&right[2], input + (plaintext_len() / 2), *half_len);
if (is_length_odd) {
left[*half_len + 1] &= 0xf0;
right[2] &= 0x0f;
diff --git a/quiche/quic/load_balancer/load_balancer_config.h b/quiche/quic/load_balancer/load_balancer_config.h
index a3a5ca9..9ad396c 100644
--- a/quiche/quic/load_balancer/load_balancer_config.h
+++ b/quiche/quic/load_balancer/load_balancer_config.h
@@ -8,6 +8,7 @@
#include <cstdint>
#include <optional>
+#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "openssl/aes.h"
@@ -17,6 +18,10 @@
namespace quic {
+namespace test {
+class LoadBalancerConfigPeer;
+} // namespace test
+
// The number of bits in the first byte used for the config ID
inline constexpr uint8_t kConfigIdBits = 3;
// The number of bits in the first byte used for the connection ID length, if
@@ -58,12 +63,24 @@
uint8_t config_id, uint8_t server_id_len, uint8_t nonce_len);
// Returns an invalid Server ID if ciphertext is too small, or needed keys are
- // missing. |ciphertext| contains the full connection ID.
- LoadBalancerServerId Decrypt(absl::Span<const uint8_t> ciphertext) const;
- // Encrypts |connection_id|, which must be of the form first byte,
- // server ID, nonce. Returns empty if plaintext is not long enough. The
- // argument is NOT const, and will be overwritten.
- QuicConnectionId Encrypt(absl::Span<uint8_t> connection_id) const;
+ // missing. |ciphertext| contains the full connection ID minus the first byte.
+ LoadBalancerServerId FourPassDecrypt(
+ absl::Span<const uint8_t> ciphertext) const;
+ // Returns an empty connection ID if the plaintext is too small, or needed
+ // keys are missing. |plaintext| contains the full unencrypted connection ID,
+ // including the first byte.
+ QuicConnectionId FourPassEncrypt(absl::Span<uint8_t> plaintext) const;
+
+ // Use the key to do a block encryption, which is used both in all cases of
+ // encrypted configs. Returns false if there's no key. Type char is
+ // convenient because that's what QuicConnectionId uses.
+ ABSL_MUST_USE_RESULT bool BlockEncrypt(
+ const uint8_t plaintext[kLoadBalancerBlockSize],
+ uint8_t ciphertext[kLoadBalancerBlockSize]) const;
+ // Returns false if the config does not require block decryption.
+ ABSL_MUST_USE_RESULT bool BlockDecrypt(
+ const uint8_t ciphertext[kLoadBalancerBlockSize],
+ uint8_t plaintext[kLoadBalancerBlockSize]) const;
uint8_t config_id() const { return config_id_; }
uint8_t server_id_len() const { return server_id_len_; }
@@ -75,6 +92,8 @@
bool IsEncrypted() const { return key_.has_value(); }
private:
+ friend class test::LoadBalancerConfigPeer;
+
// Constructor is private because it doesn't validate input.
LoadBalancerConfig(uint8_t config_id, uint8_t server_id_len,
uint8_t nonce_len, absl::string_view key);
diff --git a/quiche/quic/load_balancer/load_balancer_config_test.cc b/quiche/quic/load_balancer/load_balancer_config_test.cc
index 2c52b98..fd40412 100644
--- a/quiche/quic/load_balancer/load_balancer_config_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_config_test.cc
@@ -4,6 +4,7 @@
#include "quiche/quic/load_balancer/load_balancer_config.h"
+#include <array>
#include <cstdint>
#include <cstring>
@@ -18,6 +19,21 @@
namespace test {
+class LoadBalancerConfigPeer {
+ public:
+ static bool InitializeFourPass(LoadBalancerConfig& config,
+ const uint8_t* input, uint8_t* left,
+ uint8_t* right, uint8_t* half_len) {
+ return config.InitializeFourPass(input, left, right, half_len);
+ }
+
+ static void EncryptionPass(LoadBalancerConfig& config, uint8_t index,
+ uint8_t half_len, bool is_length_odd,
+ uint8_t* left, uint8_t* right) {
+ config.EncryptionPass(index, half_len, is_length_odd, left, right);
+ }
+};
+
namespace {
constexpr char raw_key[] = {
@@ -87,69 +103,148 @@
EXPECT_TRUE(config2->IsEncrypted());
}
+// Compare EncryptionPass() results to the example in
+// draft-ietf-quic-load-balancers-15, Section 4.3.2.
+TEST_F(LoadBalancerConfigTest, TestEncryptionPassExample) {
+ auto config =
+ LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+ EXPECT_TRUE(config.has_value());
+ EXPECT_TRUE(config->IsEncrypted());
+ uint8_t input[] = {0x07, 0x31, 0x44, 0x1a, 0x9c, 0x69, 0xc2, 0x75};
+ std::array<uint8_t, kLoadBalancerBlockSize> left, right;
+ uint8_t half_len;
+
+ bool is_length_odd = LoadBalancerConfigPeer::InitializeFourPass(
+ *config, input + 1, left.data(), right.data(), &half_len);
+ EXPECT_TRUE(is_length_odd);
+ std::array<std::array<uint8_t, kLoadBalancerBlockSize>,
+ kNumLoadBalancerCryptoPasses + 1>
+ expected_left = {{
+ {0x07, 0x00, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x01, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x01, 0x02, 0x8e, 0x1b, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x03, 0x02, 0x8e, 0x1b, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x03, 0x8e, 0x9a, 0x91, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ }};
+ std::array<std::array<uint8_t, kLoadBalancerBlockSize>,
+ kNumLoadBalancerCryptoPasses + 1>
+ expected_right = {{
+ {0x07, 0x00, 0x0c, 0x69, 0xc2, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x00, 0x0f, 0x1a, 0x5b, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x02, 0x0f, 0x1a, 0x5b, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x02, 0x04, 0x94, 0x97, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x07, 0x04, 0x04, 0x94, 0x97, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00},
+ }};
+
+ EXPECT_EQ(left, expected_left[0]);
+ EXPECT_EQ(right, expected_right[0]);
+ for (int i = 1; i <= kNumLoadBalancerCryptoPasses; ++i) {
+ LoadBalancerConfigPeer::EncryptionPass(*config, i, half_len, is_length_odd,
+ left.data(), right.data());
+ EXPECT_EQ(left, expected_left[i]);
+ EXPECT_EQ(right, expected_right[i]);
+ }
+}
+
+// Check that the encryption pass code can decode its own ciphertext. Various
+// pointer errors could cause the code to overwrite bits that contain
+// important information.
+TEST_F(LoadBalancerConfigTest, EncryptionPassesAreReversible) {
+ auto config =
+ LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+ std::array<uint8_t, kLoadBalancerBlockSize> start_left = {
+ 0x07, 0x00, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ std::array<uint8_t, kLoadBalancerBlockSize> start_right = {
+ 0x07, 0x00, 0x0c, 0x69, 0xc2, 0x75, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ std::array<uint8_t, kLoadBalancerBlockSize> left = start_left,
+ right = start_right;
+ // Work left->right and right->left passes.
+ LoadBalancerConfigPeer::EncryptionPass(*config, 1, 4, true, left.data(),
+ right.data());
+ LoadBalancerConfigPeer::EncryptionPass(*config, 2, 4, true, left.data(),
+ right.data());
+ LoadBalancerConfigPeer::EncryptionPass(*config, 2, 4, true, left.data(),
+ right.data());
+ LoadBalancerConfigPeer::EncryptionPass(*config, 1, 4, true, left.data(),
+ right.data());
+ // Since index is manually written into the second byte only on input, it is
+ // not reversible.
+ left[1] = 0;
+ right[1] = 0;
+ EXPECT_EQ(left, start_left);
+ EXPECT_EQ(right, start_right);
+}
+
// Tests for Encrypt() and Decrypt() are in LoadBalancerEncoderTest and
// LoadBalancerDecoderTest, respectively.
-TEST_F(LoadBalancerConfigTest, ConfigIsCopyable) {
- const uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
+TEST_F(LoadBalancerConfigTest, InvalidBlockEncryption) {
+ uint8_t pt[kLoadBalancerBlockSize], ct[kLoadBalancerBlockSize];
+ auto pt_config = LoadBalancerConfig::CreateUnencrypted(0, 8, 8);
+ ASSERT_TRUE(pt_config.has_value());
+ EXPECT_FALSE(pt_config->BlockEncrypt(pt, ct));
+ EXPECT_FALSE(pt_config->BlockDecrypt(ct, pt));
+ EXPECT_TRUE(pt_config->FourPassEncrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
+ .IsEmpty());
+ EXPECT_FALSE(pt_config->FourPassDecrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
+ .IsValid());
+ auto small_cid_config =
+ LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+ ASSERT_TRUE(small_cid_config.has_value());
+ EXPECT_TRUE(small_cid_config->BlockEncrypt(pt, ct));
+ EXPECT_FALSE(small_cid_config->BlockDecrypt(ct, pt));
+ auto block_config =
+ LoadBalancerConfig::Create(0, 8, 8, absl::string_view(raw_key, 16));
+ ASSERT_TRUE(block_config.has_value());
+ EXPECT_TRUE(block_config->BlockEncrypt(pt, ct));
+ EXPECT_TRUE(block_config->BlockDecrypt(ct, pt));
+}
+
+// Block decrypt test from the Test Vector in
+// draft-ietf-quic-load-balancers-15, Appendix B.
+TEST_F(LoadBalancerConfigTest, BlockEncryptionExample) {
+ const uint8_t ptext[] = {0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
0xee, 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5};
- uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
- 0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
+ const uint8_t ctext[] = {0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
+ 0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
+ uint8_t result[sizeof(ptext)];
auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
- ASSERT_TRUE(config.has_value());
+ EXPECT_TRUE(config->BlockEncrypt(ptext, result));
+ EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
+ EXPECT_TRUE(config->BlockDecrypt(ctext, result));
+ EXPECT_EQ(memcmp(result, ptext, sizeof(ptext)), 0);
+}
+
+TEST_F(LoadBalancerConfigTest, ConfigIsCopyable) {
+ const uint8_t ptext[] = {0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
+ 0xee, 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5};
+ const uint8_t ctext[] = {0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
+ 0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
+ const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
+ 0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
+ uint8_t result[sizeof(ptext)];
+ auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
auto config2 = config;
- ASSERT_TRUE(config2.has_value());
- uint8_t temp_ptext[sizeof(ptext)]; // the input will be overwritten, so copy
- memcpy(temp_ptext, ptext, sizeof(ptext));
- QuicConnectionId cid1 =
- config->Encrypt(absl::Span<uint8_t>(temp_ptext, sizeof(ptext)));
- EXPECT_EQ(cid1.length(), sizeof(ctext));
- EXPECT_EQ(memcmp(cid1.data(), ctext, sizeof(ctext)), 0);
- memcpy(temp_ptext, ptext, sizeof(ptext));
- QuicConnectionId cid2 =
- config2->Encrypt(absl::Span<uint8_t>(temp_ptext, sizeof(ptext)));
- EXPECT_EQ(cid2.length(), sizeof(ctext));
- EXPECT_EQ(memcmp(cid2.data(), ctext, sizeof(ctext)), 0);
-}
-
-TEST_F(LoadBalancerConfigTest, OnePassEncryptAndDecryptIgnoreAdditionalBytes) {
- uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f, 0xee,
- 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5, 0xda, 0x41};
- uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2, 0xb9,
- 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3, 0xda, 0x41};
- const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
- 0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
- auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
- ASSERT_TRUE(config.has_value());
- LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 8));
- QuicConnectionId cid =
- config->Encrypt(absl::Span<uint8_t>(ptext, sizeof(ptext)));
- EXPECT_EQ(cid.length(), sizeof(ctext));
- EXPECT_EQ(memcmp(cid.data(), ctext, sizeof(ctext)), 0);
- LoadBalancerServerId server_id = config->Decrypt(absl::Span<const uint8_t>(
- reinterpret_cast<const uint8_t *>(cid.data()), cid.length()));
- EXPECT_EQ(server_id, original_server_id);
-}
-
-TEST_F(LoadBalancerConfigTest, FourPassEncryptAndDecryptIgnoreAdditionalBytes) {
- uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0xee,
- 0x08, 0x0d, 0xbf, 0xda, 0x41};
- uint8_t ctext[] = {0x00, 0x41, 0x26, 0xee, 0x38,
- 0xbf, 0x54, 0x54, 0xda, 0x41};
- const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
- 0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
- auto config = LoadBalancerConfig::Create(0, 3, 4, absl::string_view(key, 16));
- ASSERT_TRUE(config.has_value());
- LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 3));
- QuicConnectionId cid =
- config->Encrypt(absl::Span<uint8_t>(ptext, sizeof(ptext)));
- EXPECT_EQ(cid.length(), sizeof(ctext));
- EXPECT_EQ(memcmp(cid.data(), ctext, sizeof(ctext)), 0);
- LoadBalancerServerId server_id = config->Decrypt(absl::Span<const uint8_t>(
- reinterpret_cast<const uint8_t *>(cid.data()), cid.length()));
- EXPECT_EQ(server_id, original_server_id);
+ EXPECT_TRUE(config->BlockEncrypt(ptext, result));
+ EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
+ EXPECT_TRUE(config2->BlockEncrypt(ptext, result));
+ EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
}
} // namespace
diff --git a/quiche/quic/load_balancer/load_balancer_decoder.cc b/quiche/quic/load_balancer/load_balancer_decoder.cc
index 8a564f1..0bbb804 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder.cc
@@ -5,6 +5,7 @@
#include "quiche/quic/load_balancer/load_balancer_decoder.h"
#include <cstdint>
+#include <cstring>
#include <optional>
#include "absl/types/span.h"
@@ -45,9 +46,26 @@
if (!config.has_value()) {
return LoadBalancerServerId();
}
- return config->Decrypt(absl::MakeConstSpan(
- reinterpret_cast<const uint8_t*>(connection_id.data()),
- connection_id.length()));
+ // Benchmark tests show that minimizing the computation inside
+ // LoadBalancerConfig saves CPU cycles.
+ if (connection_id.length() < config->total_len()) {
+ return LoadBalancerServerId();
+ }
+ const uint8_t* data =
+ reinterpret_cast<const uint8_t*>(connection_id.data()) + 1;
+ uint8_t server_id_len = config->server_id_len();
+ if (!config->IsEncrypted()) {
+ return LoadBalancerServerId(absl::Span<const uint8_t>(data, server_id_len));
+ }
+ if (config->plaintext_len() == kLoadBalancerBlockSize) {
+ uint8_t scratch[kLoadBalancerBlockSize];
+ if (!config->BlockDecrypt(data, scratch)) {
+ return LoadBalancerServerId();
+ }
+ return LoadBalancerServerId(absl::Span<uint8_t>(scratch, server_id_len));
+ }
+ return config->FourPassDecrypt(
+ absl::MakeConstSpan(data, connection_id.length() - 1));
}
std::optional<uint8_t> LoadBalancerDecoder::GetConfigId(
diff --git a/quiche/quic/load_balancer/load_balancer_decoder_test.cc b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
index 2f83672..cc86d25 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
@@ -240,6 +240,20 @@
EXPECT_FALSE(config->IsEncrypted());
}
+TEST_F(LoadBalancerDecoderTest, OnePassIgnoreAdditionalBytes) {
+ uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f, 0xee,
+ 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5, 0xda, 0x41};
+ uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2, 0xb9,
+ 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3, 0xda, 0x41};
+ LoadBalancerDecoder decoder;
+ decoder.AddConfig(
+ *LoadBalancerConfig::Create(0, 8, 8, absl::string_view(kRawKey, 16)));
+ LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 8));
+ QuicConnectionId cid(absl::Span<uint8_t>(ctext, sizeof(ctext)));
+ LoadBalancerServerId server_id = decoder.GetServerId(cid);
+ EXPECT_EQ(server_id, original_server_id);
+}
+
} // namespace
} // namespace test
diff --git a/quiche/quic/load_balancer/load_balancer_encoder.cc b/quiche/quic/load_balancer/load_balancer_encoder.cc
index 0f72da3..01d63d5 100644
--- a/quiche/quic/load_balancer/load_balancer_encoder.cc
+++ b/quiche/quic/load_balancer/load_balancer_encoder.cc
@@ -5,14 +5,17 @@
#include "quiche/quic/load_balancer/load_balancer_encoder.h"
#include <cstdint>
+#include <cstring>
#include <optional>
#include "absl/cleanup/cleanup.h"
#include "absl/numeric/int128.h"
+#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/core/quic_connection_id.h"
#include "quiche/quic/core/quic_data_writer.h"
+#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/core/quic_versions.h"
#include "quiche/quic/load_balancer/load_balancer_config.h"
#include "quiche/quic/load_balancer/load_balancer_server_id.h"
@@ -140,7 +143,35 @@
if (!WriteUint128(next_nonce, config_->nonce_len(), writer)) {
return QuicConnectionId();
}
- return config_->Encrypt(absl::Span<uint8_t>(result, config_->total_len()));
+ if (!config_->IsEncrypted()) {
+ // Fill the nonce field with a hash of the Connection ID to avoid the nonce
+ // visibly increasing by one. This would allow observers to correlate
+ // connection IDs as being sequential and likely from the same connection,
+ // not just the same server.
+ absl::uint128 nonce_hash = QuicUtils::FNV1a_128_Hash(absl::string_view(
+ reinterpret_cast<char *>(result), config_->total_len()));
+ const uint64_t lo = absl::Uint128Low64(nonce_hash);
+ if (config_->nonce_len() <= sizeof(uint64_t)) {
+ memcpy(&result[1 + config_->server_id_len()], &lo, config_->nonce_len());
+ return QuicConnectionId(reinterpret_cast<char *>(result),
+ config_->total_len());
+ }
+ memcpy(&result[1 + config_->server_id_len()], &lo, sizeof(uint64_t));
+ const uint64_t hi = absl::Uint128High64(nonce_hash);
+ memcpy(&result[1 + config_->server_id_len() + sizeof(uint64_t)], &hi,
+ config_->nonce_len() - sizeof(uint64_t));
+ return QuicConnectionId(reinterpret_cast<char *>(result),
+ config_->total_len());
+ }
+ if (config_->plaintext_len() == kLoadBalancerBlockSize) {
+ if (!config_->BlockEncrypt(&result[1], &result[1])) {
+ return QuicConnectionId();
+ }
+ return (QuicConnectionId(reinterpret_cast<char *>(result),
+ config_->total_len()));
+ }
+ return config_->FourPassEncrypt(
+ absl::Span<uint8_t>(result, config_->total_len()));
}
std::optional<QuicConnectionId> LoadBalancerEncoder::GenerateNextConnectionId(