From eaf46568a45d5aa8130ce0f225904555ff63f779 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Thu, 9 Feb 2023 15:29:07 -0800 Subject: [PATCH] Implement FastPairDataEncryptor for Fast Pair Windows PiperOrigin-RevId: 508492184 --- fastpair/handshake/BUILD | 71 +++++ fastpair/handshake/fast_pair_data_encryptor.h | 63 +++++ .../fast_pair_data_encryptor_impl.cc | 169 ++++++++++++ .../handshake/fast_pair_data_encryptor_impl.h | 104 ++++++++ .../fast_pair_data_encryptor_impl_test.cc | 243 ++++++++++++++++++ 5 files changed, 650 insertions(+) create mode 100644 fastpair/handshake/BUILD create mode 100644 fastpair/handshake/fast_pair_data_encryptor.h create mode 100644 fastpair/handshake/fast_pair_data_encryptor_impl.cc create mode 100644 fastpair/handshake/fast_pair_data_encryptor_impl.h create mode 100644 fastpair/handshake/fast_pair_data_encryptor_impl_test.cc diff --git a/fastpair/handshake/BUILD b/fastpair/handshake/BUILD new file mode 100644 index 00000000..3f4c7fad --- /dev/null +++ b/fastpair/handshake/BUILD @@ -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", + ], +) diff --git a/fastpair/handshake/fast_pair_data_encryptor.h b/fastpair/handshake/fast_pair_data_encryptor.h new file mode 100644 index 00000000..6f968622 --- /dev/null +++ b/fastpair/handshake/fast_pair_data_encryptor.h @@ -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 +#include + +#include +#include +#include +#include + +#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 EncryptBytes( + const std::array& bytes_to_encrypt) = 0; + + // Return stored PublicKey + virtual std::optional>& GetPublicKey() = 0; + + // Decrypt and parse decrypted response bytes with the stored secret key. + virtual void ParseDecryptResponse( + const std::vector& encrypted_response_bytes, + absl::AnyInvocable)> + callback) = 0; + + // Decrypt and parse decrypted passkey bytes with the stored secret key. + virtual void ParseDecryptPasskey( + const std::vector& encrypted_passkey_bytes, + absl::AnyInvocable)> + callback) = 0; + + virtual ~FastPairDataEncryptor() = default; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_H_ diff --git a/fastpair/handshake/fast_pair_data_encryptor_impl.cc b/fastpair/handshake/fast_pair_data_encryptor_impl.cc new file mode 100644 index 00000000..75e18817 --- /dev/null +++ b/fastpair/handshake/fast_pair_data_encryptor_impl.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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& 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)> + 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)> + 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)> + on_get_instance_callback, + DeviceMetadata& device_metadata) { + std::optional 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 data_encryptor = + std::make_unique(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& shared_secret_key) + : shared_secret_key_(shared_secret_key) {} + +FastPairDataEncryptorImpl::~FastPairDataEncryptorImpl() = default; + +std::array FastPairDataEncryptorImpl::EncryptBytes( + const std::array& bytes_to_encrypt) { + return FastPairEncryption::EncryptBytes(shared_secret_key_, bytes_to_encrypt); +} + +std::optional>& +FastPairDataEncryptorImpl::GetPublicKey() { + return public_key_; +} + +void FastPairDataEncryptorImpl::ParseDecryptResponse( + const std::vector& encrypted_response_bytes, + absl::AnyInvocable)> callback) { + if (!ValidateInputSize(encrypted_response_bytes)) { + callback(std::nullopt); + return; + } + + FastPairDataParser::ParseDecryptedResponse( + std::vector(shared_secret_key_.begin(), + shared_secret_key_.end()), + encrypted_response_bytes, + { + .on_decrypted_cb = std::move(callback), + }); +} + +void FastPairDataEncryptorImpl::ParseDecryptPasskey( + const std::vector& encrypted_passkey_bytes, + absl::AnyInvocable)> callback) { + if (!ValidateInputSize(encrypted_passkey_bytes)) { + callback(std::nullopt); + return; + } + FastPairDataParser::ParseDecryptedPasskey( + std::vector(shared_secret_key_.begin(), + shared_secret_key_.end()), + encrypted_passkey_bytes, + { + .on_decrypted_cb = std::move(callback), + }); +} +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_data_encryptor_impl.h b/fastpair/handshake/fast_pair_data_encryptor_impl.h new file mode 100644 index 00000000..ebdda92f --- /dev/null +++ b/fastpair/handshake/fast_pair_data_encryptor_impl.h @@ -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 +#include + +#include +#include +#include +#include +#include + +#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)> + on_get_instance_callback); + + static void SetFactoryForTesting(Factory* test_factory); + + protected: + virtual ~Factory(); + + virtual void CreateInstance( + const FastPairDevice& device, + absl::AnyInvocable)> + on_get_instance_callback) = 0; + + private: + static void CreateAsyncWithKeyExchange( + const FastPairDevice& device, + absl::AnyInvocable)> + on_get_instance_callback); + + static void DeviceMetadataRetrieved( + const FastPairDevice& device, + absl::AnyInvocable)> + on_get_instance_callback, + DeviceMetadata& device_metadata); + }; + + std::array EncryptBytes( + const std::array& bytes_to_encrypt) override; + + std::optional>& GetPublicKey() + override; + + void ParseDecryptResponse( + const std::vector& encrypted_response_bytes, + absl::AnyInvocable)> callback) + override; + + void ParseDecryptPasskey( + const std::vector& encrypted_passkey_bytes, + absl::AnyInvocable)> callback) + override; + + explicit FastPairDataEncryptorImpl(const KeyPair& key_pair); + explicit FastPairDataEncryptorImpl( + const std::array& secret_key); + ~FastPairDataEncryptorImpl() override; + + private: + const std::array shared_secret_key_; + + // The public key is only required during initial pairing and optional during + // communication with paired devices. + std::optional> public_key_ = + std::nullopt; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DATA_ENCRYPTOR_IMPL_H_ diff --git a/fastpair/handshake/fast_pair_data_encryptor_impl_test.cc b/fastpair/handshake/fast_pair_data_encryptor_impl_test.cc new file mode 100644 index 00000000..a4e21283 --- /dev/null +++ b/fastpair/handshake/fast_pair_data_encryptor_impl_test.cc @@ -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 +#include +#include +#include +#include +#include + +#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 kResponseBytes = { + 0x01, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D, + 0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6}; + +constexpr std::array 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(); + FastPairDevice device(kValidModelId, kTestAddress, + Protocol::kFastPairInitialPairing); + FastPairDataEncryptorImpl::Factory::CreateAsync( + device, + absl::bind_front( + &FastPairDataEncryptorImplTest::OnDataEncryptorCreateAsync, this)); + } + + void FailedSetUpNoKeyPair() { + repository_ = std::make_unique(); + 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(); + 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 fast_pair_data_encryptor) { + data_encryptor_ = std::move(fast_pair_data_encryptor); + } + + std::array EncryptBytes() { + return data_encryptor_->EncryptBytes(kResponseBytes); + } + + void ParseDecryptedResponse() { + const std::array bytes = + data_encryptor_->EncryptBytes(kResponseBytes); + + data_encryptor_->ParseDecryptResponse( + std::vector(bytes.begin(), bytes.end()), + absl::bind_front( + &FastPairDataEncryptorImplTest::ParseDecryptedResponseCallback, + this)); + } + + void ParseDecryptedResponseInvalidBytes() { + const std::array bytes = + data_encryptor_->EncryptBytes(kResponseBytes); + + data_encryptor_->ParseDecryptResponse( + std::vector(bytes.begin() + 3, bytes.end()), + absl::bind_front( + &FastPairDataEncryptorImplTest::ParseDecryptedResponseCallback, + this)); + } + + void ParseDecryptedResponseCallback( + const std::optional& response) { + response_ = response; + } + + void ParseDecryptedPasskey() { + const std::array bytes = + data_encryptor_->EncryptBytes(kPasskeyBytes); + + data_encryptor_->ParseDecryptPasskey( + std::vector(bytes.begin(), bytes.end()), + absl::bind_front( + &FastPairDataEncryptorImplTest::ParseDecryptPasskeyCallback, this)); + } + + void ParseDecryptedPasskeyInvalidBytes() { + const std::array bytes = + data_encryptor_->EncryptBytes(kPasskeyBytes); + + data_encryptor_->ParseDecryptPasskey( + std::vector(bytes.begin() + 3, bytes.end()), + absl::bind_front( + &FastPairDataEncryptorImplTest::ParseDecryptPasskeyCallback, this)); + } + + void ParseDecryptPasskeyCallback( + const std::optional& passkey) { + passkey_ = passkey; + } + + protected: + std::unique_ptr data_encryptor_; + std::optional response_ = std::nullopt; + std::optional passkey_ = std::nullopt; + std::unique_ptr data_parser_; + + private: + std::unique_ptr device_; + std::unique_ptr 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 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 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