// Copyright 2021 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 "ash/quick_pair/fast_pair_handshake/fast_pair_encryption.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <iterator>
#include <optional>
#include "ash/quick_pair/fast_pair_handshake/fast_pair_key_pair.h"
#include "base/check.h"
#include "base/ranges/algorithm.h"
#include "base/types/fixed_array.h"
#include "chromeos/ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
#include "components/cross_device/logging/logging.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/ecdh.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/hmac.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
namespace {
using ash::quick_pair::fast_pair_encryption::kBlockSizeBytes;
// Converts the public anti-spoofing key into an EC_Point.
bssl::UniquePtr<EC_POINT> GetEcPointFromPublicAntiSpoofingKey(
const bssl::UniquePtr<EC_GROUP>& ec_group,
const std::string& decoded_public_anti_spoofing) {
std::array<uint8_t, kPublicKeyByteSize + 1> buffer;
buffer[0] = POINT_CONVERSION_UNCOMPRESSED;
base::ranges::copy(decoded_public_anti_spoofing, buffer.begin() + 1);
bssl::UniquePtr<EC_POINT> new_ec_point(EC_POINT_new(ec_group.get()));
if (!EC_POINT_oct2point(ec_group.get(), new_ec_point.get(), buffer.data(),
buffer.size(), nullptr)) {
return nullptr;
}
return new_ec_point;
}
// Key derivation function to be used in hashing the generated secret key.
void* KDF(const void* in, size_t inlen, void* out, size_t* outlen) {
// Set this to 16 since that's the amount of bytes we want to use
// for the key, even though more will be written by SHA256 below.
*outlen = kPrivateKeyByteSize;
return SHA256(static_cast<const uint8_t*>(in), inlen,
static_cast<uint8_t*>(out));
}
} // namespace
namespace ash {
namespace quick_pair {
namespace fast_pair_encryption {
std::optional<KeyPair> GenerateKeysWithEcdhKeyAgreement(
const std::string& decoded_public_anti_spoofing) {
if (decoded_public_anti_spoofing.size() != kPublicKeyByteSize) {
CD_LOG(WARNING, Feature::FP) << "Expected " << kPublicKeyByteSize
<< " byte value for anti-spoofing key. Got:"
<< decoded_public_anti_spoofing.size();
return std::nullopt;
}
// Generate the secp256r1 key-pair.
bssl::UniquePtr<EC_GROUP> ec_group(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
if (!EC_KEY_generate_key(ec_key.get())) {
CD_LOG(WARNING, Feature::FP) << __func__ << ": Failed to generate ec key";
return std::nullopt;
}
// The ultimate goal here is to get a 64-byte public key. We accomplish this
// by converting the generated public key into the uncompressed X9.62 format,
// which is 0x04 followed by padded x and y coordinates.
std::array<uint8_t, kPublicKeyByteSize + 1> uncompressed_private_key;
int point_bytes_written = EC_POINT_point2oct(
ec_group.get(), EC_KEY_get0_public_key(ec_key.get()),
POINT_CONVERSION_UNCOMPRESSED, uncompressed_private_key.data(),
uncompressed_private_key.size(), nullptr);
if (point_bytes_written != uncompressed_private_key.size()) {
CD_LOG(WARNING, Feature::FP)
<< __func__
<< ": EC_POINT_point2oct failed to convert public key to "
"uncompressed x9.62 format.";
return std::nullopt;
}
bssl::UniquePtr<EC_POINT> public_anti_spoofing_point =
GetEcPointFromPublicAntiSpoofingKey(ec_group,
decoded_public_anti_spoofing);
if (!public_anti_spoofing_point) {
CD_LOG(WARNING, Feature::FP)
<< __func__
<< ": Failed to convert Public Anti-Spoofing key to EC_POINT";
return std::nullopt;
}
uint8_t secret[SHA256_DIGEST_LENGTH];
int computed_key_size =
ECDH_compute_key(secret, SHA256_DIGEST_LENGTH,
public_anti_spoofing_point.get(), ec_key.get(), &KDF);
if (computed_key_size != kPrivateKeyByteSize) {
CD_LOG(WARNING, Feature::FP) << __func__ << ": ECDH_compute_key failed.";
return std::nullopt;
}
// Take first 16 bytes from secret as the private key.
std::array<uint8_t, kPrivateKeyByteSize> private_key;
std::copy(secret, secret + kPrivateKeyByteSize, std::begin(private_key));
// Ignore the first byte since it is 0x04, from the above uncompressed X9 .62
// format.
std::array<uint8_t, kPublicKeyByteSize> public_key;
std::copy(uncompressed_private_key.begin() + 1,
uncompressed_private_key.end(), public_key.begin());
return KeyPair(private_key, public_key);
}
const std::array<uint8_t, kHmacSizeBytes> GenerateHmacSha256(
const std::array<uint8_t, kSecretKeySizeBytes>& secret_key,
std::array<uint8_t, kNonceSizeBytes> nonce,
const std::vector<uint8_t>& data) {
int nonce_data_concat_size = kNonceSizeBytes + data.size();
base::FixedArray<uint8_t> nonce_data_concat(nonce_data_concat_size);
std::memcpy(nonce_data_concat.data(), nonce.data(), kNonceSizeBytes);
std::memcpy(nonce_data_concat.data() + kNonceSizeBytes, data.data(),
data.size());
std::array<uint8_t, kHmacKeySizeBytes> K = {};
std::memcpy(K.data(), secret_key.data(), kSecretKeySizeBytes);
std::array<uint8_t, kHmacSizeBytes> output;
unsigned int output_size;
HMAC(/*evp_md=*/EVP_sha256(), /*key=*/&K,
/*key_len=*/kHmacKeySizeBytes, /*data=*/nonce_data_concat.data(),
/*data_len=*/nonce_data_concat.size(),
/*out=*/output.data(), /*out_len*/ &output_size);
return output;
}
const std::array<uint8_t, kBlockSizeBytes> EncryptBytes(
const std::array<uint8_t, kBlockSizeBytes>& aes_key_bytes,
const std::array<uint8_t, kBlockSizeBytes>& bytes_to_encrypt) {
AES_KEY aes_key;
int aes_key_was_set = AES_set_encrypt_key(aes_key_bytes.data(),
aes_key_bytes.size() * 8, &aes_key);
DCHECK(aes_key_was_set == 0) << "Invalid AES key size.";
std::array<uint8_t, kBlockSizeBytes> encrypted_bytes;
AES_encrypt(bytes_to_encrypt.data(), encrypted_bytes.data(), &aes_key);
return encrypted_bytes;
}
const std::vector<uint8_t> EncryptAdditionalData(
const std::array<uint8_t, kSecretKeySizeBytes>& secret_key,
std::array<uint8_t, kNonceSizeBytes> nonce,
const std::vector<uint8_t>& data) {
if (data.empty()) {
return {};
}
AES_KEY aes_key;
int aes_key_was_set =
AES_set_encrypt_key(secret_key.data(), secret_key.size() * 8, &aes_key);
DCHECK(aes_key_was_set == 0) << "Invalid AES key size.";
uint bytes_read = 0;
unsigned char ivec[AES_BLOCK_SIZE] = {};
unsigned char ecount[AES_BLOCK_SIZE] = {};
base::FixedArray<uint8_t> encrypted_data(data.size());
// The Fast Pair Spec AES-CTR version increments the first byte of the
// initialization vector; the typical AES-CTR algorithm increments the
// last byte. So, instead of calling AES_ctr128_encrypt() once on all of
// `data`, it is called on each 128-bit block of `data` and the counter is
// incremented manually.
int bytes_to_encrypt = data.size();
int i = 0;
while (bytes_to_encrypt > 0) {
int block_size =
bytes_to_encrypt >= AES_BLOCK_SIZE ? AES_BLOCK_SIZE : bytes_to_encrypt;
std::memset(ivec, 0, AES_BLOCK_SIZE);
std::memcpy(ivec + 8, nonce.data(), kNonceSizeBytes);
ivec[0] = i;
uint offset = data.size() - bytes_to_encrypt;
AES_ctr128_encrypt(/*in=*/data.data() + offset,
/*out=*/encrypted_data.data() + offset,
/*len=*/block_size, &aes_key, /*ivec=*/ivec,
/*ecount_buf=*/ecount, &bytes_read);
bytes_to_encrypt -= block_size;
i++;
}
CHECK(!bytes_to_encrypt);
return std::vector<uint8_t>(encrypted_data.begin(), encrypted_data.end());
}
} // namespace fast_pair_encryption
} // namespace quick_pair
} // namespace ash