Implement FastPairDataEncryptor for Fast Pair Windows

PiperOrigin-RevId: 508492184
This commit is contained in:
Qin Wang
2023-02-09 15:30:44 -08:00
committed by Copybara-Service
parent 5cb25a1a70
commit eaf46568a4
5 changed files with 650 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
cc_library(
name = "handshake",
srcs = [
"fast_pair_data_encryptor_impl.cc",
],
hdrs = [
"fast_pair_data_encryptor.h",
"fast_pair_data_encryptor_impl.h",
],
visibility = [
"//:__subpackages__",
"//fastpair:__subpackages__",
],
deps = [
"//fastpair/common",
"//fastpair/crypto",
"//fastpair/dataparser",
"//fastpair/repository",
"//fastpair/server_access",
"//internal/base:bluetooth_address",
"//internal/platform:base",
"//internal/platform:logging",
"@boringssl//:crypto",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/functional:bind_front",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
],
)
cc_test(
name = "fast_pair_data_encryptor_impl_test",
size = "small",
srcs = [
"fast_pair_data_encryptor_impl_test.cc",
],
shard_count = 16,
deps = [
":handshake",
"//fastpair/common",
"//fastpair/crypto",
"//fastpair/dataparser",
"//fastpair/server_access:test_support",
"//fastpair/testing",
"//internal/platform:logging",
"//internal/platform:types",
"//internal/platform/implementation/g3", # build_cleaner: keep
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/functional:bind_front",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
],
)
@@ -0,0 +1,63 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_H_
#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_H_
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <functional>
#include <optional>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "fastpair/common/constant.h"
#include "fastpair/crypto/decrypted_passkey.h"
#include "fastpair/crypto/decrypted_response.h"
namespace nearby {
namespace fastpair {
// Holds a secret key for a device and has methods to encrypt bytes, decrypt
// response and decrypt passkey.
class FastPairDataEncryptor {
public:
// Encrypts bytes with the stored secret key.
virtual std::array<uint8_t, kAesBlockByteSize> EncryptBytes(
const std::array<uint8_t, kAesBlockByteSize>& bytes_to_encrypt) = 0;
// Return stored PublicKey
virtual std::optional<std::array<uint8_t, 64>>& GetPublicKey() = 0;
// Decrypt and parse decrypted response bytes with the stored secret key.
virtual void ParseDecryptResponse(
const std::vector<uint8_t>& encrypted_response_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedResponse>)>
callback) = 0;
// Decrypt and parse decrypted passkey bytes with the stored secret key.
virtual void ParseDecryptPasskey(
const std::vector<uint8_t>& encrypted_passkey_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedPasskey>)>
callback) = 0;
virtual ~FastPairDataEncryptor() = default;
};
} // namespace fastpair
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_H_
@@ -0,0 +1,169 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fastpair/handshake/fast_pair_data_encryptor_impl.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
#include "fastpair/common/constant.h"
#include "fastpair/common/protocol.h"
#include "fastpair/crypto/decrypted_passkey.h"
#include "fastpair/crypto/decrypted_response.h"
#include "fastpair/crypto/fast_pair_encryption.h"
#include "fastpair/crypto/fast_pair_key_pair.h"
#include "fastpair/dataparser/fast_pair_data_parser.h"
#include "fastpair/handshake/fast_pair_data_encryptor.h"
#include "fastpair/repository/device_metadata.h"
#include "fastpair/server_access/fast_pair_repository.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace fastpair {
namespace {
FastPairDataEncryptorImpl::Factory* g_test_factory_ = nullptr;
bool ValidateInputSize(const std::vector<uint8_t>& encrypted_bytes) {
if (encrypted_bytes.size() != kAesBlockByteSize) {
NEARBY_LOGS(VERBOSE) << __func__ << ": Encrypted bytes should have size = "
<< kAesBlockByteSize
<< ", actual = " << encrypted_bytes.size();
return false;
}
return true;
}
} // namespace
// FastPairDataEncryptorImpl::Factory
void FastPairDataEncryptorImpl::Factory::SetFactoryForTesting(
Factory* g_test_factory) {
g_test_factory_ = g_test_factory;
}
FastPairDataEncryptorImpl::Factory::~Factory() = default;
void FastPairDataEncryptorImpl::Factory::CreateAsync(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback) {
if (g_test_factory_) {
g_test_factory_->CreateInstance(device,
std::move(on_get_instance_callback));
return;
}
if (device.protocol == Protocol::kFastPairInitialPairing) {
CreateAsyncWithKeyExchange(device, std::move(on_get_instance_callback));
}
}
void FastPairDataEncryptorImpl::Factory::CreateAsyncWithKeyExchange(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback) {
// We first have to get the metadata in order to get the public key to use
// to generate the new secret key pair.
FastPairRepository::Get()->GetDeviceMetadata(
device.model_id,
[&device, &on_get_instance_callback](DeviceMetadata& metadata) {
FastPairDataEncryptorImpl::Factory::DeviceMetadataRetrieved(
device, std::move(on_get_instance_callback), metadata);
});
}
void FastPairDataEncryptorImpl::Factory::DeviceMetadataRetrieved(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback,
DeviceMetadata& device_metadata) {
std::optional<KeyPair> key_pair =
FastPairEncryption::GenerateKeysWithEcdhKeyAgreement(
device_metadata.GetDetails().anti_spoofing_key_pair().public_key());
if (!key_pair.has_value()) {
NEARBY_LOGS(INFO) << "Fail to generate key pair";
on_get_instance_callback(nullptr);
return;
}
std::unique_ptr<FastPairDataEncryptor> data_encryptor =
std::make_unique<FastPairDataEncryptorImpl>(key_pair.value());
on_get_instance_callback(std::move(data_encryptor));
}
// FastPairDataEncryptorImpl
FastPairDataEncryptorImpl::FastPairDataEncryptorImpl(const KeyPair& key_pair)
: shared_secret_key_(key_pair.shared_secret_key),
public_key_(key_pair.public_key) {}
FastPairDataEncryptorImpl::FastPairDataEncryptorImpl(
const std::array<uint8_t, kSharedSecretKeyByteSize>& shared_secret_key)
: shared_secret_key_(shared_secret_key) {}
FastPairDataEncryptorImpl::~FastPairDataEncryptorImpl() = default;
std::array<uint8_t, kAesBlockByteSize> FastPairDataEncryptorImpl::EncryptBytes(
const std::array<uint8_t, kAesBlockByteSize>& bytes_to_encrypt) {
return FastPairEncryption::EncryptBytes(shared_secret_key_, bytes_to_encrypt);
}
std::optional<std::array<uint8_t, kPublicKeyByteSize>>&
FastPairDataEncryptorImpl::GetPublicKey() {
return public_key_;
}
void FastPairDataEncryptorImpl::ParseDecryptResponse(
const std::vector<uint8_t>& encrypted_response_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedResponse>)> callback) {
if (!ValidateInputSize(encrypted_response_bytes)) {
callback(std::nullopt);
return;
}
FastPairDataParser::ParseDecryptedResponse(
std::vector<uint8_t>(shared_secret_key_.begin(),
shared_secret_key_.end()),
encrypted_response_bytes,
{
.on_decrypted_cb = std::move(callback),
});
}
void FastPairDataEncryptorImpl::ParseDecryptPasskey(
const std::vector<uint8_t>& encrypted_passkey_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedPasskey>)> callback) {
if (!ValidateInputSize(encrypted_passkey_bytes)) {
callback(std::nullopt);
return;
}
FastPairDataParser::ParseDecryptedPasskey(
std::vector<uint8_t>(shared_secret_key_.begin(),
shared_secret_key_.end()),
encrypted_passkey_bytes,
{
.on_decrypted_cb = std::move(callback),
});
}
} // namespace fastpair
} // namespace nearby
@@ -0,0 +1,104 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_IMPL_H_
#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_IMPL_H_
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "fastpair/common/constant.h"
#include "fastpair/common/fast_pair_device.h"
#include "fastpair/crypto/fast_pair_key_pair.h"
#include "fastpair/handshake/fast_pair_data_encryptor.h"
#include "fastpair/repository/device_metadata.h"
namespace nearby {
namespace fastpair {
// Holds a secret key for a device and has methods to encrypt bytes, decrypt
// response and decrypt passkey.
class FastPairDataEncryptorImpl : public FastPairDataEncryptor {
public:
class Factory {
public:
static void CreateAsync(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback);
static void SetFactoryForTesting(Factory* test_factory);
protected:
virtual ~Factory();
virtual void CreateInstance(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback) = 0;
private:
static void CreateAsyncWithKeyExchange(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback);
static void DeviceMetadataRetrieved(
const FastPairDevice& device,
absl::AnyInvocable<void(std::unique_ptr<FastPairDataEncryptor>)>
on_get_instance_callback,
DeviceMetadata& device_metadata);
};
std::array<uint8_t, kAesBlockByteSize> EncryptBytes(
const std::array<uint8_t, kAesBlockByteSize>& bytes_to_encrypt) override;
std::optional<std::array<uint8_t, kPublicKeyByteSize>>& GetPublicKey()
override;
void ParseDecryptResponse(
const std::vector<uint8_t>& encrypted_response_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedResponse>)> callback)
override;
void ParseDecryptPasskey(
const std::vector<uint8_t>& encrypted_passkey_bytes,
absl::AnyInvocable<void(const std::optional<DecryptedPasskey>)> callback)
override;
explicit FastPairDataEncryptorImpl(const KeyPair& key_pair);
explicit FastPairDataEncryptorImpl(
const std::array<uint8_t, kSharedSecretKeyByteSize>& secret_key);
~FastPairDataEncryptorImpl() override;
private:
const std::array<uint8_t, kSharedSecretKeyByteSize> shared_secret_key_;
// The public key is only required during initial pairing and optional during
// communication with paired devices.
std::optional<std::array<uint8_t, kPublicKeyByteSize>> public_key_ =
std::nullopt;
};
} // namespace fastpair
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_IMPL_H_
@@ -0,0 +1,243 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fastpair/handshake/fast_pair_data_encryptor_impl.h"
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/functional/bind_front.h"
#include "absl/strings/escaping.h"
#include "absl/time/time.h"
#include "fastpair/common/constant.h"
#include "fastpair/common/protocol.h"
#include "fastpair/crypto/fast_pair_message_type.h"
#include "fastpair/dataparser/fast_pair_data_parser.h"
#include "fastpair/server_access/fake_fast_pair_repository.h"
namespace nearby {
namespace fastpair {
namespace {
constexpr std::array<uint8_t, kAesBlockByteSize> kResponseBytes = {
0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D,
0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6};
constexpr std::array<uint8_t, kAesBlockByteSize> kPasskeyBytes = {
0x02, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D,
0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6};
constexpr char kPublicAntiSpoof[] =
"Wuyr48lD3txnUhGiMF1IfzlTwRxxe+wMB1HLzP+"
"0wVcljfT3XPoiy1fntlneziyLD5knDVAJSE+RM/zlPRP/Jg==";
constexpr char kInvalidPublicAntiSpoof[] = "InvalidPublicAntiSpoof";
constexpr char kValidModelId[] = "718c17";
constexpr char kTestAddress[] = "test_address";
class FastPairDataEncryptorImplTest : public testing::Test {
public:
void TearDown() override { data_encryptor_.reset(); }
void FailedSetUpNoMetadata() {
repository_ = std::make_unique<FakeFastPairRepository>();
FastPairDevice device(kValidModelId, kTestAddress,
Protocol::kFastPairInitialPairing);
FastPairDataEncryptorImpl::Factory::CreateAsync(
device,
absl::bind_front(
&FastPairDataEncryptorImplTest::OnDataEncryptorCreateAsync, this));
}
void FailedSetUpNoKeyPair() {
repository_ = std::make_unique<FakeFastPairRepository>();
proto::Device metadata;
std::string decoded_key;
absl::Base64Unescape(kInvalidPublicAntiSpoof, &decoded_key);
metadata.mutable_anti_spoofing_key_pair()->set_public_key(decoded_key);
repository_->SetFakeMetadata(kValidModelId, metadata);
FastPairDevice device(kValidModelId, kTestAddress,
Protocol::kFastPairInitialPairing);
FastPairDataEncryptorImpl::Factory::CreateAsync(
device,
absl::bind_front(
&FastPairDataEncryptorImplTest::OnDataEncryptorCreateAsync, this));
}
void SuccessfulSetUp() {
repository_ = std::make_unique<FakeFastPairRepository>();
proto::Device metadata;
std::string decoded_key;
absl::Base64Unescape(kPublicAntiSpoof, &decoded_key);
metadata.mutable_anti_spoofing_key_pair()->set_public_key(decoded_key);
repository_->SetFakeMetadata(kValidModelId, metadata);
FastPairDevice device(kValidModelId, kTestAddress,
Protocol::kFastPairInitialPairing);
FastPairDataEncryptorImpl::Factory::CreateAsync(
device,
absl::bind_front(
&FastPairDataEncryptorImplTest::OnDataEncryptorCreateAsync, this));
}
void OnDataEncryptorCreateAsync(
std::unique_ptr<FastPairDataEncryptor> fast_pair_data_encryptor) {
data_encryptor_ = std::move(fast_pair_data_encryptor);
}
std::array<uint8_t, kAesBlockByteSize> EncryptBytes() {
return data_encryptor_->EncryptBytes(kResponseBytes);
}
void ParseDecryptedResponse() {
const std::array<uint8_t, kAesBlockByteSize> bytes =
data_encryptor_->EncryptBytes(kResponseBytes);
data_encryptor_->ParseDecryptResponse(
std::vector<uint8_t>(bytes.begin(), bytes.end()),
absl::bind_front(
&FastPairDataEncryptorImplTest::ParseDecryptedResponseCallback,
this));
}
void ParseDecryptedResponseInvalidBytes() {
const std::array<uint8_t, kAesBlockByteSize> bytes =
data_encryptor_->EncryptBytes(kResponseBytes);
data_encryptor_->ParseDecryptResponse(
std::vector<uint8_t>(bytes.begin() + 3, bytes.end()),
absl::bind_front(
&FastPairDataEncryptorImplTest::ParseDecryptedResponseCallback,
this));
}
void ParseDecryptedResponseCallback(
const std::optional<DecryptedResponse>& response) {
response_ = response;
}
void ParseDecryptedPasskey() {
const std::array<uint8_t, kAesBlockByteSize> bytes =
data_encryptor_->EncryptBytes(kPasskeyBytes);
data_encryptor_->ParseDecryptPasskey(
std::vector<uint8_t>(bytes.begin(), bytes.end()),
absl::bind_front(
&FastPairDataEncryptorImplTest::ParseDecryptPasskeyCallback, this));
}
void ParseDecryptedPasskeyInvalidBytes() {
const std::array<uint8_t, kAesBlockByteSize> bytes =
data_encryptor_->EncryptBytes(kPasskeyBytes);
data_encryptor_->ParseDecryptPasskey(
std::vector<uint8_t>(bytes.begin() + 3, bytes.end()),
absl::bind_front(
&FastPairDataEncryptorImplTest::ParseDecryptPasskeyCallback, this));
}
void ParseDecryptPasskeyCallback(
const std::optional<DecryptedPasskey>& passkey) {
passkey_ = passkey;
}
protected:
std::unique_ptr<FastPairDataEncryptor> data_encryptor_;
std::optional<DecryptedResponse> response_ = std::nullopt;
std::optional<DecryptedPasskey> passkey_ = std::nullopt;
std::unique_ptr<FastPairDataParser> data_parser_;
private:
std::unique_ptr<FastPairDevice> device_;
std::unique_ptr<FakeFastPairRepository> repository_;
};
TEST_F(FastPairDataEncryptorImplTest, FailedSetUpNoMetadata) {
EXPECT_FALSE(data_encryptor_);
FailedSetUpNoMetadata();
EXPECT_FALSE(data_encryptor_);
}
TEST_F(FastPairDataEncryptorImplTest, NoKeyPair) {
FailedSetUpNoKeyPair();
EXPECT_FALSE(data_encryptor_);
}
TEST_F(FastPairDataEncryptorImplTest, SuccessfulSetUp) {
EXPECT_FALSE(data_encryptor_);
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
}
TEST_F(FastPairDataEncryptorImplTest, GetPublicKey) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
ParseDecryptedPasskey();
EXPECT_NE(data_encryptor_->GetPublicKey(), std::nullopt);
}
TEST_F(FastPairDataEncryptorImplTest, EncryptBytes) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
EXPECT_FALSE(EncryptBytes().empty());
}
TEST_F(FastPairDataEncryptorImplTest, ParseDecryptedResponse) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
ParseDecryptedResponse();
EXPECT_TRUE(response_);
const std::array<uint8_t, kDecryptedResponseAddressByteSize> kAddressBytes = {
0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32};
EXPECT_EQ(response_->address_bytes, kAddressBytes);
EXPECT_EQ(response_->message_type,
FastPairMessageType::kKeyBasedPairingResponse);
}
TEST_F(FastPairDataEncryptorImplTest, ParseDecryptedResponse_InvalidInputSize) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
ParseDecryptedResponseInvalidBytes();
EXPECT_FALSE(response_);
}
TEST_F(FastPairDataEncryptorImplTest, ParseDecryptedPasskey) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
ParseDecryptedPasskey();
EXPECT_TRUE(passkey_);
// Passkey bytes.
std::array<uint8_t, 3> passkey_bytes = {0x5E, 0x3F, 0x45};
uint32_t passkey = passkey_bytes[2];
passkey += passkey_bytes[1] << 8;
passkey += passkey_bytes[0] << 16;
EXPECT_EQ(passkey_->passkey, passkey);
EXPECT_EQ(passkey_->message_type, FastPairMessageType::kSeekersPasskey);
}
TEST_F(FastPairDataEncryptorImplTest, ParseDecryptedPasskey_InvalidInputSize) {
SuccessfulSetUp();
EXPECT_TRUE(data_encryptor_);
ParseDecryptedPasskeyInvalidBytes();
EXPECT_FALSE(passkey_);
}
} // namespace
} // namespace fastpair
} // namespace nearby