| // Copyright 2014 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.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/crypto_data.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" |
| #include "third_party/boringssl/src/include/openssl/hmac.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, |
| const CryptoData& 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.bytes(), |
| data.byte_length(), 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() {} |
| |
| 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, |
| const CryptoData& 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(const CryptoData& 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.byte_length(), |
| &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.bytes(), key_data.bytes() + key_data.byte_length()); |
| TruncateToBitLength(keylen_bits, &modified_key_data); |
| return CreateWebCryptoSecretKey(CryptoData(modified_key_data), |
| key_algorithm, extractable, usages, key); |
| } |
| |
| Status ImportKeyJwk(const CryptoData& 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(CryptoData(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(CryptoData(raw_data), algorithm_name, key.Extractable(), |
| key.Usages(), buffer); |
| |
| return Status::Success(); |
| } |
| |
| Status Sign(const blink::WebCryptoAlgorithm& algorithm, |
| const blink::WebCryptoKey& key, |
| const CryptoData& 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, |
| const CryptoData& signature, |
| const CryptoData& data, |
| bool* signature_match) const override { |
| std::vector<uint8_t> result; |
| Status status = Sign(algorithm, key, data, &result); |
| |
| if (status.IsError()) |
| return status; |
| |
| // Do not allow verification of truncated MACs. |
| *signature_match = result.size() == signature.byte_length() && |
| crypto::SecureMemEqual(result.data(), signature.bytes(), |
| signature.byte_length()); |
| |
| return Status::Success(); |
| } |
| |
| Status DeserializeKeyForClone(const blink::WebCryptoKeyAlgorithm& algorithm, |
| blink::WebCryptoKeyType type, |
| bool extractable, |
| blink::WebCryptoKeyUsageMask usages, |
| const CryptoData& 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, |
| bool* has_length_bits, |
| unsigned int* length_bits) const override { |
| const blink::WebCryptoHmacImportParams* params = |
| key_length_algorithm.HmacImportParams(); |
| |
| *has_length_bits = true; |
| if (params->HasLengthBits()) { |
| *length_bits = params->OptionalLengthBits(); |
| if (*length_bits == 0) |
| return Status::ErrorGetHmacKeyLengthZero(); |
| return Status::Success(); |
| } |
| |
| return GetDigestBlockSizeBits(params->GetHash(), length_bits); |
| } |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<AlgorithmImplementation> CreateHmacImplementation() { |
| return base::WrapUnique(new HmacImplementation); |
| } |
| |
| } // namespace webcrypto |