// 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 "third_party/boringssl/src/include/openssl/hmac.h"

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

#include <memory>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/to_vector.h"
#include "base/numerics/safe_math.h"
#include "components/webcrypto/algorithm_implementation.h"
#include "components/webcrypto/algorithms/secret_key_util.h"
#include "components/webcrypto/algorithms/util.h"
#include "components/webcrypto/blink_key_handle.h"
#include "components/webcrypto/jwk.h"
#include "components/webcrypto/status.h"
#include "crypto/openssl_util.h"
#include "crypto/secure_util.h"
#include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
#include "third_party/blink/public/platform/web_crypto_key_algorithm.h"

namespace webcrypto {

namespace {

Status GetDigestBlockSizeBits(const blink::WebCryptoAlgorithm& algorithm,
                              unsigned int* block_size_bits) {
  const EVP_MD* md = GetDigest(algorithm);
  if (!md)
    return Status::ErrorUnsupported();
  *block_size_bits = static_cast<unsigned int>(8 * EVP_MD_block_size(md));
  return Status::Success();
}

// Gets the requested key length in bits for an HMAC import operation.
Status GetHmacImportKeyLengthBits(
    const blink::WebCryptoHmacImportParams* params,
    unsigned int key_data_byte_length,
    unsigned int* keylen_bits) {
  if (key_data_byte_length == 0)
    return Status::ErrorHmacImportEmptyKey();

  // Make sure that the key data's length can be represented in bits without
  // overflow.
  base::CheckedNumeric<unsigned int> checked_keylen_bits(key_data_byte_length);
  checked_keylen_bits *= 8;

  if (!checked_keylen_bits.IsValid())
    return Status::ErrorDataTooLarge();

  unsigned int data_keylen_bits = checked_keylen_bits.ValueOrDie();

  // Determine how many bits of the input to use.
  *keylen_bits = data_keylen_bits;
  if (params->HasLengthBits()) {
    // The requested bit length must be:
    //   * No longer than the input data length
    //   * At most 7 bits shorter.
    if (NumBitsToBytes(params->OptionalLengthBits()) != key_data_byte_length)
      return Status::ErrorHmacImportBadLength();
    *keylen_bits = params->OptionalLengthBits();
  }

  return Status::Success();
}

const char* GetJwkHmacAlgorithmName(blink::WebCryptoAlgorithmId hash) {
  switch (hash) {
    case blink::kWebCryptoAlgorithmIdSha1:
      return "HS1";
    case blink::kWebCryptoAlgorithmIdSha256:
      return "HS256";
    case blink::kWebCryptoAlgorithmIdSha384:
      return "HS384";
    case blink::kWebCryptoAlgorithmIdSha512:
      return "HS512";
    default:
      return nullptr;
  }
}

// Synthesizes an import algorithm given a key algorithm, so that
// deserialization can reuse the ImportKey*() methods.
blink::WebCryptoAlgorithm SynthesizeImportAlgorithmForClone(
    const blink::WebCryptoKeyAlgorithm& algorithm) {
  const auto& params = *algorithm.HmacParams();
  return blink::WebCryptoAlgorithm::AdoptParamsAndCreate(
      algorithm.Id(), new blink::WebCryptoHmacImportParams(
                          params.GetHash(), true, params.LengthBits()));
}

const blink::WebCryptoKeyUsageMask kAllKeyUsages =
    blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify;

Status SignHmac(const std::vector<uint8_t>& raw_key,
                const blink::WebCryptoAlgorithm& hash,
                base::span<const uint8_t> data,
                std::vector<uint8_t>* buffer) {
  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);

  const EVP_MD* digest_algorithm = GetDigest(hash);
  if (!digest_algorithm)
    return Status::ErrorUnsupported();
  size_t hmac_expected_length = EVP_MD_size(digest_algorithm);

  buffer->resize(hmac_expected_length);

  unsigned int hmac_actual_length;
  if (!HMAC(digest_algorithm, raw_key.data(), raw_key.size(), data.data(),
            data.size(), buffer->data(), &hmac_actual_length)) {
    return Status::OperationError();
  }

  // HMAC() promises to use at most EVP_MD_CTX_size(). If this was not the
  // case then memory corruption may have just occurred.
  CHECK_EQ(hmac_expected_length, hmac_actual_length);

  return Status::Success();
}

class HmacImplementation : public AlgorithmImplementation {
 public:
  HmacImplementation() = default;

  Status GenerateKey(const blink::WebCryptoAlgorithm& algorithm,
                     bool extractable,
                     blink::WebCryptoKeyUsageMask usages,
                     GenerateKeyResult* result) const override {
    Status status = CheckKeyCreationUsages(kAllKeyUsages, usages);
    if (status.IsError())
      return status;

    const blink::WebCryptoHmacKeyGenParams* params =
        algorithm.HmacKeyGenParams();

    unsigned int keylen_bits = 0;
    if (params->HasLengthBits()) {
      keylen_bits = params->OptionalLengthBits();
      // Zero-length HMAC keys are disallowed by the spec.
      if (keylen_bits == 0)
        return Status::ErrorGenerateHmacKeyLengthZero();
    } else {
      status = GetDigestBlockSizeBits(params->GetHash(), &keylen_bits);
      if (status.IsError())
        return status;
    }

    return GenerateWebCryptoSecretKey(blink::WebCryptoKeyAlgorithm::CreateHmac(
                                          params->GetHash().Id(), keylen_bits),
                                      extractable, usages, keylen_bits, result);
  }

  Status ImportKey(blink::WebCryptoKeyFormat format,
                   base::span<const uint8_t> key_data,
                   const blink::WebCryptoAlgorithm& algorithm,
                   bool extractable,
                   blink::WebCryptoKeyUsageMask usages,
                   blink::WebCryptoKey* key) const override {
    switch (format) {
      case blink::kWebCryptoKeyFormatRaw:
      case blink::kWebCryptoKeyFormatRawSecret:
        return ImportKeyRaw(key_data, algorithm, extractable, usages, key);
      case blink::kWebCryptoKeyFormatJwk:
        return ImportKeyJwk(key_data, algorithm, extractable, usages, key);
      default:
        return Status::ErrorUnsupportedImportKeyFormat();
    }
  }

  Status ExportKey(blink::WebCryptoKeyFormat format,
                   const blink::WebCryptoKey& key,
                   std::vector<uint8_t>* buffer) const override {
    switch (format) {
      case blink::kWebCryptoKeyFormatRaw:
      case blink::kWebCryptoKeyFormatRawSecret:
        return ExportKeyRaw(key, buffer);
      case blink::kWebCryptoKeyFormatJwk:
        return ExportKeyJwk(key, buffer);
      default:
        return Status::ErrorUnsupportedExportKeyFormat();
    }
  }

  Status ImportKeyRaw(base::span<const uint8_t> key_data,
                      const blink::WebCryptoAlgorithm& algorithm,
                      bool extractable,
                      blink::WebCryptoKeyUsageMask usages,
                      blink::WebCryptoKey* key) const {
    Status status = CheckKeyCreationUsages(kAllKeyUsages, usages);
    if (status.IsError())
      return status;

    const blink::WebCryptoHmacImportParams* params =
        algorithm.HmacImportParams();

    unsigned int keylen_bits = 0;
    status = GetHmacImportKeyLengthBits(params, key_data.size(), &keylen_bits);
    if (status.IsError())
      return status;

    const blink::WebCryptoKeyAlgorithm key_algorithm =
        blink::WebCryptoKeyAlgorithm::CreateHmac(params->GetHash().Id(),
                                                 keylen_bits);

    // If no bit truncation was requested, then done!
    if ((keylen_bits % 8) == 0) {
      return CreateWebCryptoSecretKey(key_data, key_algorithm, extractable,
                                      usages, key);
    }

    // Otherwise zero out the unused bits in the key data before importing.
    std::vector<uint8_t> modified_key_data = base::ToVector(key_data);
    TruncateToBitLength(keylen_bits, &modified_key_data);
    return CreateWebCryptoSecretKey(modified_key_data, key_algorithm,
                                    extractable, usages, key);
  }

  Status ImportKeyJwk(base::span<const uint8_t> key_data,
                      const blink::WebCryptoAlgorithm& algorithm,
                      bool extractable,
                      blink::WebCryptoKeyUsageMask usages,
                      blink::WebCryptoKey* key) const {
    Status status = CheckKeyCreationUsages(kAllKeyUsages, usages);
    if (status.IsError())
      return status;

    const char* algorithm_name =
        GetJwkHmacAlgorithmName(algorithm.HmacImportParams()->GetHash().Id());
    if (!algorithm_name)
      return Status::ErrorUnexpected();

    std::vector<uint8_t> raw_data;
    JwkReader jwk;
    status = ReadSecretKeyNoExpectedAlgJwk(key_data, extractable, usages,
                                           &raw_data, &jwk);
    if (status.IsError())
      return status;
    status = jwk.VerifyAlg(algorithm_name);
    if (status.IsError())
      return status;

    return ImportKeyRaw(raw_data, algorithm, extractable, usages, key);
  }

  Status ExportKeyRaw(const blink::WebCryptoKey& key,
                      std::vector<uint8_t>* buffer) const {
    *buffer = GetSymmetricKeyData(key);
    return Status::Success();
  }

  Status ExportKeyJwk(const blink::WebCryptoKey& key,
                      std::vector<uint8_t>* buffer) const {
    const std::vector<uint8_t>& raw_data = GetSymmetricKeyData(key);

    const char* algorithm_name =
        GetJwkHmacAlgorithmName(key.Algorithm().HmacParams()->GetHash().Id());
    if (!algorithm_name)
      return Status::ErrorUnexpected();

    WriteSecretKeyJwk(raw_data, algorithm_name, key.Extractable(), key.Usages(),
                      buffer);

    return Status::Success();
  }

  Status Sign(const blink::WebCryptoAlgorithm& algorithm,
              const blink::WebCryptoKey& key,
              base::span<const uint8_t> data,
              std::vector<uint8_t>* buffer) const override {
    const blink::WebCryptoAlgorithm& hash =
        key.Algorithm().HmacParams()->GetHash();

    return SignHmac(GetSymmetricKeyData(key), hash, data, buffer);
  }

  Status Verify(const blink::WebCryptoAlgorithm& algorithm,
                const blink::WebCryptoKey& key,
                base::span<const uint8_t> signature,
                base::span<const uint8_t> data,
                bool* signature_match) const override {
    std::vector<uint8_t> result;
    Status status = Sign(algorithm, key, data, &result);

    if (status.IsError())
      return status;

    *signature_match = crypto::SecureMemEqual(result, signature);

    return Status::Success();
  }

  Status DeserializeKeyForClone(const blink::WebCryptoKeyAlgorithm& algorithm,
                                blink::WebCryptoKeyType type,
                                bool extractable,
                                blink::WebCryptoKeyUsageMask usages,
                                base::span<const uint8_t> key_data,
                                blink::WebCryptoKey* key) const override {
    if (algorithm.ParamsType() != blink::kWebCryptoKeyAlgorithmParamsTypeHmac ||
        type != blink::kWebCryptoKeyTypeSecret)
      return Status::ErrorUnexpected();

    return ImportKeyRaw(key_data, SynthesizeImportAlgorithmForClone(algorithm),
                        extractable, usages, key);
  }

  Status GetKeyLength(const blink::WebCryptoAlgorithm& key_length_algorithm,
                      std::optional<unsigned int>* length_bits) const override {
    const blink::WebCryptoHmacImportParams* params =
        key_length_algorithm.HmacImportParams();

    if (params->HasLengthBits()) {
      *length_bits = params->OptionalLengthBits();
      if (length_bits->value() == 0) {
        return Status::ErrorGetHmacKeyLengthZero();
      }
      return Status::Success();
    }

    unsigned int block_size_bits;
    Status status = GetDigestBlockSizeBits(params->GetHash(), &block_size_bits);
    if (status.IsError()) {
      return status;
    }
    *length_bits = block_size_bits;
    return Status::Success();
  }

  bool Supports(blink::WebCryptoOperation op,
                const blink::WebCryptoAlgorithm& algorithm,
                std::optional<unsigned int> length_bits) const override {
    if (op == blink::kWebCryptoOperationGenerateKey) {
      const blink::WebCryptoHmacKeyGenParams* params =
          algorithm.HmacKeyGenParams();
      // Zero-length HMAC keys are disallowed by the spec.
      if (params->HasLengthBits() && params->OptionalLengthBits() == 0) {
        return false;
      } else {
        // Hash algorithm name is already checked when params are parsed.
        return true;
      }
    } else if (op == blink::kWebCryptoOperationImportKey) {
      // Hash algorithm name is already checked when params are parsed, and the
      // checks on params->OptionalLengthBits() in GetHmacImportKeyLengthBits
      // can't be run without the actual key being imported.
      return true;
    } else if (op == blink::kWebCryptoOperationGetKeyLength) {
      const blink::WebCryptoHmacImportParams* params =
          algorithm.HmacImportParams();
      // Hash algorithm name is already checked when the params are parsed, so
      // we don't need to check this.
      if (params->HasLengthBits() && params->OptionalLengthBits() == 0) {
        return false;
      }
    }
    return true;
  }
};

}  // namespace

std::unique_ptr<AlgorithmImplementation> CreateHmacImplementation() {
  return std::make_unique<HmacImplementation>();
}

}  // namespace webcrypto
