| // 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(const uint8_t *from, const uint8_t plaintext_len, | |
| const uint8_t index, uint8_t *to) { | |
| uint8_t half = plaintext_len / 2; | |
| to[0] = plaintext_len; | |
| to[1] = index; | |
| memcpy(to + 2, from, half); | |
| if (plaintext_len % 2) { | |
| to[2 + half] = from[half] & 0xf0; | |
| half++; | |
| } | |
| memset(to + 2 + half, 0, kLoadBalancerBlockSize - 2 - half); | |
| } | |
| void TakePlaintextFromRight(const uint8_t *from, const uint8_t plaintext_len, | |
| const uint8_t index, uint8_t *to) { | |
| uint8_t half = plaintext_len / 2; | |
| to[0] = plaintext_len; | |
| to[1] = index; | |
| memcpy(to + 2, from + half, half + (plaintext_len % 2)); | |
| if (plaintext_len % 2) { | |
| to[2] &= 0x0f; | |
| half++; | |
| } | |
| memset(to + 2 + half, 0, kLoadBalancerBlockSize - 2 - half); | |
| } | |
| // 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(const uint8_t *from, const uint8_t plaintext_len, | |
| uint8_t *to) { | |
| 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(const uint8_t *from, const uint8_t plaintext_len, | |
| uint8_t *to) { | |
| uint8_t half = plaintext_len / 2; | |
| int i = 0; | |
| if (plaintext_len % 2) { | |
| to[half] ^= (from[0] & 0x0f); | |
| i++; | |
| } | |
| while ((half + i) < plaintext_len) { | |
| to[half + i] ^= from[i]; | |
| 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(target.data(), plaintext_len(), index, buf); | |
| } else { | |
| TakePlaintextFromRight(target.data(), plaintext_len(), index, buf); | |
| } | |
| if (!BlockEncrypt(buf, buf)) { | |
| return false; | |
| } | |
| // XOR bits over the correct half. | |
| if (index % 2) { | |
| CiphertextXorWithRight(buf, plaintext_len(), target.data()); | |
| } else { | |
| CiphertextXorWithLeft(buf, plaintext_len(), target.data()); | |
| } | |
| 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 |