blob: 56669e0e596732f11616318278587010557d1987 [file] [log] [blame]
// Copyright (c) 2022 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 "quiche/quic/load_balancer/load_balancer_config.h"
#include <memory>
#include <string_view>
#include "openssl/aes.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
namespace quic {
namespace {
// Validates all non-key parts of the input.
bool CommonValidation(const uint8_t config_id, const uint8_t server_id_len,
const uint8_t nonce_len) {
if (config_id >= kNumLoadBalancerConfigs || server_id_len == 0 ||
nonce_len < kLoadBalancerMinNonceLen ||
nonce_len > kLoadBalancerMaxNonceLen ||
server_id_len >
(kQuicMaxConnectionIdWithLengthPrefixLength - nonce_len - 1)) {
QUIC_BUG(quic_bug_433862549_01)
<< "Invalid LoadBalancerConfig "
<< "Config ID " << static_cast<int>(config_id) << " Server ID Length "
<< static_cast<int>(server_id_len) << " Nonce Length "
<< static_cast<int>(nonce_len);
return false;
}
return true;
}
// Initialize the key in the constructor
absl::optional<AES_KEY> BuildKey(absl::string_view key, bool encrypt) {
if (key.empty()) {
return absl::optional<AES_KEY>();
}
AES_KEY raw_key;
if (encrypt) {
if (AES_set_encrypt_key(reinterpret_cast<const uint8_t *>(key.data()),
key.size() * 8, &raw_key) < 0) {
return absl::optional<AES_KEY>();
}
} else if (AES_set_decrypt_key(reinterpret_cast<const uint8_t *>(key.data()),
key.size() * 8, &raw_key) < 0) {
return absl::optional<AES_KEY>();
}
return raw_key;
}
// Functions to handle 4-pass encryption/decryption.
// TakePlaintextFrom{Left,Right}() reads the left or right half of 'from' and
// expands it into a full encryption block ('to') in accordance with the
// internet-draft.
void TakePlaintextFromLeft(uint8_t *to, uint8_t *from, uint8_t plaintext_len,
uint8_t index) {
uint8_t half = plaintext_len / 2;
memset(to, 0, kLoadBalancerBlockSize - 1);
memcpy(to, from, half);
if (plaintext_len % 2) {
to[half] = from[half] & 0xf0;
}
to[kLoadBalancerBlockSize - 1] = index;
}
void TakePlaintextFromRight(uint8_t *to, uint8_t *from, uint8_t plaintext_len,
uint8_t index) {
const uint8_t half = plaintext_len / 2;
const uint8_t write_point = kLoadBalancerBlockSize - half;
const uint8_t read_point = plaintext_len - half;
memset((to + 1), 0, kLoadBalancerBlockSize - 1);
memcpy(to + write_point, from + read_point, half);
if (plaintext_len % 2) {
to[write_point - 1] = from[read_point - 1] & 0x0f;
}
to[0] = index;
}
// CiphertextXorWith{Left,Right}() takes the relevant end of the ciphertext in
// 'from' and XORs it with half of the ConnectionId stored at 'to', in
// accordance with the internet-draft.
void CiphertextXorWithLeft(uint8_t *to, uint8_t *from, uint8_t plaintext_len) {
uint8_t half = plaintext_len / 2;
for (int i = 0; i < half; i++) {
*(to + i) ^= *(from + i);
}
if (plaintext_len % 2) {
*(to + half) ^= (*(from + half) & 0xf0);
}
}
void CiphertextXorWithRight(uint8_t *to, uint8_t *from, uint8_t plaintext_len) {
const uint8_t half = plaintext_len / 2;
const uint8_t write_point = plaintext_len - half;
const uint8_t read_point = kLoadBalancerBlockSize - half;
if (plaintext_len % 2) {
*(to + write_point - 1) ^= (*(from + read_point - 1) & 0x0f);
}
for (int i = 0; i < half; i++) {
*(to + write_point + i) ^= *(from + read_point + i);
}
}
} // namespace
absl::optional<LoadBalancerConfig> LoadBalancerConfig::Create(
const uint8_t config_id, const uint8_t server_id_len,
const uint8_t nonce_len, const absl::string_view key) {
// Check for valid parameters.
if (key.size() != kLoadBalancerKeyLen) {
QUIC_BUG(quic_bug_433862549_02)
<< "Invalid LoadBalancerConfig Key Length: " << key.size();
return absl::optional<LoadBalancerConfig>();
}
if (!CommonValidation(config_id, server_id_len, nonce_len)) {
return absl::optional<LoadBalancerConfig>();
}
auto new_config =
LoadBalancerConfig(config_id, server_id_len, nonce_len, key);
if (!new_config.IsEncrypted()) {
// Something went wrong in assigning the key!
QUIC_BUG(quic_bug_433862549_03) << "Something went wrong in initializing "
"the load balancing key.";
return absl::optional<LoadBalancerConfig>();
}
return new_config;
}
// Creates an unencrypted config.
absl::optional<LoadBalancerConfig> LoadBalancerConfig::CreateUnencrypted(
const uint8_t config_id, const uint8_t server_id_len,
const uint8_t nonce_len) {
return CommonValidation(config_id, server_id_len, nonce_len)
? LoadBalancerConfig(config_id, server_id_len, nonce_len, "")
: absl::optional<LoadBalancerConfig>();
}
bool LoadBalancerConfig::EncryptionPass(absl::Span<uint8_t> target,
const uint8_t index) const {
uint8_t buf[kLoadBalancerBlockSize];
if (!key_.has_value() || target.size() < plaintext_len()) {
return false;
}
if (index % 2) { // Odd indices go from left to right
TakePlaintextFromLeft(buf, target.data(), plaintext_len(), index);
} else {
TakePlaintextFromRight(buf, target.data(), plaintext_len(), index);
}
if (!BlockEncrypt(buf, buf)) {
return false;
}
// XOR bits over the correct half.
if (index % 2) {
CiphertextXorWithRight(target.data(), buf, plaintext_len());
} else {
CiphertextXorWithLeft(target.data(), buf, plaintext_len());
}
return true;
}
bool LoadBalancerConfig::BlockEncrypt(
const uint8_t plaintext[kLoadBalancerBlockSize],
uint8_t ciphertext[kLoadBalancerBlockSize]) const {
if (!key_.has_value()) {
return false;
}
AES_encrypt(plaintext, ciphertext, &key_.value());
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_.value());
return true;
}
LoadBalancerConfig::LoadBalancerConfig(const uint8_t config_id,
const uint8_t server_id_len,
const uint8_t nonce_len,
const absl::string_view key)
: config_id_(config_id),
server_id_len_(server_id_len),
nonce_len_(nonce_len),
key_(BuildKey(key, /* encrypt = */ true)),
block_decrypt_key_((server_id_len + nonce_len == kLoadBalancerBlockSize)
? BuildKey(key, /* encrypt = */ false)
: absl::optional<AES_KEY>()) {}
} // namespace quic