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

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/bits.h"
#include "base/check.h"
#include "base/compiler_specific.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/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_cbc();
    case 32:
      return EVP_aes_256_cbc();
    default:
      return nullptr;
  }
}

Status AesCbcEncryptDecrypt(EncryptOrDecrypt cipher_operation,
                            const blink::WebCryptoAlgorithm& algorithm,
                            const blink::WebCryptoKey& key,
                            base::span<const uint8_t> data,
                            std::vector<uint8_t>* buffer) {
  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);

  const blink::WebCryptoAesCbcParams* params = algorithm.AesCbcParams();
  const std::vector<uint8_t>& raw_key = GetSymmetricKeyData(key);

  if (params->Iv().size() != 16)
    return Status::ErrorIncorrectSizeAesCbcIv();

  // Note: PKCS padding is enabled by default
  const EVP_CIPHER* const cipher = GetAESCipherByKeyLength(raw_key.size());
  DCHECK(cipher);

  bssl::ScopedEVP_CIPHER_CTX context;
  if (!EVP_CipherInit_ex(context.get(), cipher, nullptr, &raw_key[0],
                         params->Iv().data(), cipher_operation)) {
    return Status::OperationError();
  }

  if (cipher_operation == ENCRYPT) {
    // CBC encryption is padded by at least one byte, up to a block boundary.
    // (This cannot overflow because `data.size()` is at most `PTRDIFF_MAX`. If
    // it did wraparound, `EVP_CipherUpdate_ex` and `EVP_CipherFinal_ex2` will
    // check the provided bounds and cleanly fail.)
    buffer->resize(
        base::bits::AlignUp(data.size() + 1, size_t{AES_BLOCK_SIZE}));
  } else {
    // CBC decryption will output at most the input size.
    buffer->resize(data.size());
  }

  size_t output_len = 0;
  if (!EVP_CipherUpdate_ex(context.get(), buffer->data(), &output_len,
                           buffer->size(), data.data(), data.size())) {
    return Status::OperationError();
  }
  auto remainder = base::span(*buffer).subspan(output_len);
  size_t final_output_chunk_len = 0;
  if (!EVP_CipherFinal_ex2(context.get(), remainder.data(),
                           &final_output_chunk_len, remainder.size())) {
    return Status::OperationError();
  }

  buffer->resize(output_len + final_output_chunk_len);
  return Status::Success();
}

class AesCbcImplementation : public AesAlgorithm {
 public:
  AesCbcImplementation() : AesAlgorithm("CBC") {}

  Status Encrypt(const blink::WebCryptoAlgorithm& algorithm,
                 const blink::WebCryptoKey& key,
                 base::span<const uint8_t> data,
                 std::vector<uint8_t>* buffer) const override {
    return AesCbcEncryptDecrypt(ENCRYPT, 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 AesCbcEncryptDecrypt(DECRYPT, algorithm, key, data, buffer);
  }

  bool Supports(blink::WebCryptoOperation op,
                const blink::WebCryptoAlgorithm& algorithm,
                std::optional<unsigned int> length_bits) const override {
    if (op == blink::kWebCryptoOperationEncrypt ||
        op == blink::kWebCryptoOperationDecrypt) {
      const blink::WebCryptoAesCbcParams* params = algorithm.AesCbcParams();
      return params->Iv().size() == 16;
    } else {
      return AesAlgorithm::Supports(op, algorithm, length_bits);
    }
  }
};

}  // namespace

std::unique_ptr<AlgorithmImplementation> CreateAesCbcImplementation() {
  return std::make_unique<AesCbcImplementation>();
}

}  // namespace webcrypto
