1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <array>
#include <memory>
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
#include "components/webcrypto/algorithms/aes.h"
#include "components/webcrypto/algorithms/util.h"
#include "components/webcrypto/blink_key_handle.h"
#include "components/webcrypto/status.h"
#include "crypto/openssl_util.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/cipher.h"
namespace webcrypto {
namespace {
const EVP_CIPHER* GetAESCipherByKeyLength(size_t key_length_bytes) {
// 192-bit AES is intentionally unsupported (http://crbug.com/533699).
switch (key_length_bytes) {
case 16:
return EVP_aes_128_ctr();
case 32:
return EVP_aes_256_ctr();
default:
return nullptr;
}
}
// Encrypts/decrypts given a 128-bit counter.
//
// |output| must have the same length as |input|.
Status AesCtrEncrypt128BitCounter(const EVP_CIPHER* cipher,
base::span<const uint8_t> raw_key,
base::span<const uint8_t> input,
base::span<const uint8_t, 16> counter,
base::span<uint8_t> output) {
DCHECK(cipher);
DCHECK_EQ(input.size(), output.size());
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::ScopedEVP_CIPHER_CTX context;
if (!EVP_CipherInit_ex(context.get(), cipher, nullptr, raw_key.data(),
counter.data(), ENCRYPT)) {
return Status::OperationError();
}
int output_len = 0;
if (!EVP_CipherUpdate(context.get(), output.data(), &output_len, input.data(),
base::checked_cast<int>(input.size()))) {
return Status::OperationError();
}
int final_output_chunk_len = 0;
if (!EVP_CipherFinal_ex(context.get(), output.data() + output_len,
&final_output_chunk_len)) {
return Status::OperationError();
}
output_len += final_output_chunk_len;
if (static_cast<size_t>(output_len) != input.size())
return Status::ErrorUnexpected();
return Status::Success();
}
// Returns ceil(a/b), where a and b are integers.
template <typename T>
T CeilDiv(T a, T b) {
return a == 0 ? 0 : 1 + (a - 1) / b;
}
// Extracts the counter as a `absl::uint128`. The counter is the rightmost
// `counter_length_bits` of the block, interpreted as a big-endian number.
absl::uint128 GetCounter(
base::span<const uint8_t, AES_BLOCK_SIZE> counter_block,
unsigned int counter_length_bits) {
unsigned int counter_length_remainder_bits = counter_length_bits % 8;
unsigned int byte_length = CeilDiv(counter_length_bits, 8u);
DCHECK_GT(byte_length, 0u);
base::span suffix = counter_block.last(byte_length);
absl::uint128 ret = suffix[0];
// The first byte may be partial.
if (counter_length_remainder_bits != 0) {
ret &= ~(0xFF << counter_length_remainder_bits);
}
for (uint8_t b : suffix.subspan<1>()) {
ret = (ret << 8) | b;
}
return ret;
}
// Returns a counter block with the counter bits all set all zero.
std::array<uint8_t, AES_BLOCK_SIZE> BlockWithZeroedCounter(
base::span<const uint8_t, AES_BLOCK_SIZE> counter_block,
unsigned int counter_length_bits) {
unsigned int counter_length_bytes = counter_length_bits / 8;
unsigned int counter_length_bits_remainder = counter_length_bits % 8;
std::array<uint8_t, AES_BLOCK_SIZE> new_counter_block;
memcpy(new_counter_block.data(), counter_block.data(), AES_BLOCK_SIZE);
size_t index = new_counter_block.size() - counter_length_bytes;
memset(&new_counter_block.front() + index, 0, counter_length_bytes);
if (counter_length_bits_remainder) {
new_counter_block[index - 1] &= 0xFF << counter_length_bits_remainder;
}
return new_counter_block;
}
// This function does encryption/decryption for AES-CTR (encryption and
// decryption are the same).
//
// BoringSSL's interface for AES-CTR differs from that of WebCrypto. In
// WebCrypto the caller specifies a 16-byte counter block and designates how
// many of the right-most X bits to use as a big-endian counter. Whereas in
// BoringSSL the entire counter block is interpreted as a 128-bit counter.
//
// In AES-CTR, the counter block MUST be unique across all messages that are
// encrypted/decrypted. WebCrypto expects that the counter can start at any
// value, and is therefore permitted to wrap around to zero on overflow.
//
// Some care is taken to fail if the counter wraps back to an earlier value.
// However this protection is only enforced during a *single* call to
// encrypt/decrypt.
Status AesCtrEncryptDecrypt(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& key,
base::span<const uint8_t> data,
std::vector<uint8_t>* buffer) {
const blink::WebCryptoAesCtrParams* params = algorithm.AesCtrParams();
const std::vector<uint8_t>& raw_key = GetSymmetricKeyData(key);
if (params->Counter().size() != AES_BLOCK_SIZE)
return Status::ErrorIncorrectSizeAesCtrCounter();
auto counter_block =
*base::span(params->Counter()).to_fixed_extent<AES_BLOCK_SIZE>();
unsigned int counter_length_bits = params->LengthBits();
if (counter_length_bits < 1 || counter_length_bits > 128)
return Status::ErrorInvalidAesCtrCounterLength();
// The output of AES-CTR is the same size as the input. However BoringSSL
// expects buffer sizes as an "int".
base::CheckedNumeric<int> output_max_len = data.size();
if (!output_max_len.IsValid())
return Status::ErrorDataTooLarge();
const EVP_CIPHER* const cipher = GetAESCipherByKeyLength(raw_key.size());
if (!cipher)
return Status::ErrorUnexpected();
buffer->resize(base::ValueOrDieForType<size_t>(output_max_len));
absl::uint128 current_counter =
GetCounter(counter_block, counter_length_bits);
if (counter_length_bits == 128) {
return AesCtrEncrypt128BitCounter(cipher, raw_key, data, counter_block,
*buffer);
}
// The total number of possible counter values is pow(2, counter_length_bits)
absl::uint128 num_counter_values = absl::uint128(1) << counter_length_bits;
// The number of AES blocks needed for encryption/decryption. The counter is
// incremented this many times.
size_t num_output_blocks = CeilDiv(buffer->size(), size_t{AES_BLOCK_SIZE});
// If the counter is going to be incremented more times than there are counter
// values, fail. (Repeating values of the counter block is bad).
if (num_output_blocks > num_counter_values)
return Status::ErrorAesCtrInputTooLongCounterRepeated();
// This is the number of blocks that can be successfully encrypted without
// overflowing the counter. Encrypting the subsequent block will need to
// reset the counter to zero.
absl::uint128 num_blocks_until_reset = num_counter_values - current_counter;
// If the counter can be incremented for the entire input without
// wrapping-around, do it as a single call into BoringSSL.
if (num_blocks_until_reset >= num_output_blocks) {
return AesCtrEncrypt128BitCounter(cipher, raw_key, data, counter_block,
*buffer);
}
// Otherwise the encryption needs to be done in 2 parts. The first part using
// the current counter_block, and the next part resetting the counter portion
// of the block to zero.
// This is guaranteed to fit in an `size_t` because it is bounded by the input
// size.
size_t input_size_part1 =
static_cast<size_t>(num_blocks_until_reset * AES_BLOCK_SIZE);
DCHECK_LT(input_size_part1, data.size());
const auto [output_part1, output_part2] =
base::span(*buffer).split_at(input_size_part1);
// Encrypt the first part (before wrap-around).
Status status =
AesCtrEncrypt128BitCounter(cipher, raw_key, data.first(input_size_part1),
counter_block, output_part1);
if (status.IsError())
return status;
// Encrypt the second part (after wrap-around).
std::array<uint8_t, AES_BLOCK_SIZE> counter_block_part2 =
BlockWithZeroedCounter(counter_block, counter_length_bits);
return AesCtrEncrypt128BitCounter(cipher, raw_key,
data.subspan(input_size_part1),
counter_block_part2, output_part2);
}
class AesCtrImplementation : public AesAlgorithm {
public:
AesCtrImplementation() : AesAlgorithm("CTR") {}
Status Encrypt(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& key,
base::span<const uint8_t> data,
std::vector<uint8_t>* buffer) const override {
return AesCtrEncryptDecrypt(algorithm, key, data, buffer);
}
Status Decrypt(const blink::WebCryptoAlgorithm& algorithm,
const blink::WebCryptoKey& key,
base::span<const uint8_t> data,
std::vector<uint8_t>* buffer) const override {
return AesCtrEncryptDecrypt(algorithm, key, data, buffer);
}
};
} // namespace
std::unique_ptr<AlgorithmImplementation> CreateAesCtrImplementation() {
return std::make_unique<AesCtrImplementation>();
}
} // namespace webcrypto
|