blob: 173674dc91e67766f4957ada280618fd67353a3e [file] [log] [blame]
// 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/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;
}
}
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:
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:
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(
key_data.data(), UNSAFE_TODO(key_data.data() + key_data.size()));
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 CreateWebCryptoSecretKey(key_data, 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();
}
};
} // namespace
std::unique_ptr<AlgorithmImplementation> CreateHmacImplementation() {
return std::make_unique<HmacImplementation>();
}
} // namespace webcrypto