diff --git a/fastpair/common/constant.h b/fastpair/common/constant.h index 64a9efb6..8a27e169 100644 --- a/fastpair/common/constant.h +++ b/fastpair/common/constant.h @@ -15,9 +15,12 @@ #ifndef THIRD_PARTY_NEARBY_FASTPAIR_COMMON_CONSTANT_H_ #define THIRD_PARTY_NEARBY_FASTPAIR_COMMON_CONSTANT_H_ +#include + namespace nearby { namespace fastpair { +// Bluetooth Uuid constexpr char kServiceId[] = "Fast Pair"; constexpr char kFastPairServiceUuid[] = "0000FE2C-0000-1000-8000-00805F9B34FB"; const char kKeyBasedPairingCharacteristicUuidV1[] = "1234"; @@ -30,6 +33,44 @@ const char kAccountKeyCharacteristicUuidV1[] = "1236"; const char kAccountKeyCharacteristicUuidV2[] = "FE2C1236-8366-4814-8EB0-01DE32100BEA"; +// Key pair +constexpr int kSharedSecretKeyByteSize = 16; +constexpr int kPublicKeyByteSize = 64; + +// Encryption +constexpr int kAesBlockByteSize = 16; +constexpr int kEncryptedDataByteSize = 16; +// Decryption +constexpr int kDecryptedResponseAddressByteSize = 6; +constexpr int kDecryptedResponseSaltByteSize = 9; +constexpr int kDecryptedPasskeySaltByteSize = 12; + +// Handshake response index +constexpr int kMessageTypeIndex = 0; +constexpr int kResponseAddressStartIndex = 1; +constexpr int kResponseSaltStartIndex = 7; +constexpr int kPasskeySaltStartIndex = 4; + +// Handshake response message type +constexpr uint8_t kKeybasedPairingResponseType = 0x01; +constexpr uint8_t kSeekerPasskeyType = 0x02; +constexpr uint8_t kProviderPasskeyType = 0x03; + +// Handshake request inex +constexpr uint8_t kProviderAddressStartIndex = 2; +constexpr uint8_t kSeekerAddressStartIndex = 8; +constexpr uint8_t kSeekerPasskey = 0x02; +constexpr uint8_t kAccountKeyStartByte = 0x04; + +// Handshake request message type +constexpr uint8_t kKeyBasedPairingType = 0x00; +constexpr uint8_t kInitialOrSubsequentFlags = 0x00; +constexpr uint8_t kRetroactiveFlags = 0x10; + +// GATT connection +constexpr int kMaxNumGattConnectionAttempts = 3; +constexpr int kGattOperationTimeout = 15; + } // namespace fastpair } // namespace nearby diff --git a/fastpair/handshake/BUILD b/fastpair/handshake/BUILD new file mode 100644 index 00000000..7878752a --- /dev/null +++ b/fastpair/handshake/BUILD @@ -0,0 +1,120 @@ +# 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_decryption.cc", + "fast_pair_encryption.cc", + ], + hdrs = [ + "decrypted_passkey.h", + "decrypted_response.h", + "fast_pair_decryption.h", + "fast_pair_encryption.h", + "fast_pair_key_pair.h", + "fast_pair_message_type.h", + ], + visibility = [ + "//:__subpackages__", + "//fastpair:__subpackages__", + ], + deps = [ + "//fastpair/common", + "//internal/platform:base", + "//internal/platform:logging", + "@boringssl//:crypto", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/types:optional", + ], +) + +cc_test( + name = "decrypted_passkey_test", + size = "small", + srcs = [ + "decrypted_passkey_test.cc", + ], + shard_count = 1, + deps = [ + ":handshake", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "decrypted_response_test", + size = "small", + srcs = [ + "decrypted_response_test.cc", + ], + shard_count = 1, + deps = [ + ":handshake", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "fast_pair_key_pair_test", + size = "small", + srcs = [ + "fast_pair_key_pair_test.cc", + ], + shard_count = 1, + deps = [ + ":handshake", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "fast_pair_encryption_test", + size = "small", + srcs = [ + "fast_pair_encryption_test.cc", + ], + shard_count = 1, + deps = [ + ":handshake", + "//fastpair/common", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "fast_pair_decryption_test", + size = "small", + srcs = [ + "fast_pair_decryption_test.cc", + ], + shard_count = 1, + deps = [ + ":handshake", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/fastpair/handshake/decrypted_passkey.h b/fastpair/handshake/decrypted_passkey.h new file mode 100644 index 00000000..1f35cebd --- /dev/null +++ b/fastpair/handshake/decrypted_passkey.h @@ -0,0 +1,45 @@ +// 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_DECRYPTED_PASSKEY_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_DECRYPTED_PASSKEY_H_ + +#include +#include + +#include + +#include "fastpair/common/constant.h" +#include "fastpair/handshake/fast_pair_message_type.h" + +namespace nearby { +namespace fastpair { + +// Thin structure which is used by the higher level components of the Fast Pair +// system to represent a decrypted account passkey. +struct DecryptedPasskey { + DecryptedPasskey( + FastPairMessageType message_type, uint32_t passkey, + const std::array& salt) + : message_type(message_type), passkey(passkey), salt(salt) {} + + FastPairMessageType message_type; + uint32_t passkey; + std::array salt; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_DECRYPTED_PASSKEY_H_ diff --git a/fastpair/handshake/decrypted_passkey_test.cc b/fastpair/handshake/decrypted_passkey_test.cc new file mode 100644 index 00000000..afa66526 --- /dev/null +++ b/fastpair/handshake/decrypted_passkey_test.cc @@ -0,0 +1,45 @@ +// 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/decrypted_passkey.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace fastpair { +namespace { + +TEST(DecryptedPasskeyTest, CreateDecryptedPasskey) { + // Message type + FastPairMessageType messgaeType = FastPairMessageType::kSeekersPasskey; + // Passkey bytes. + std::array passkey_bytes = {0x02, 0x03, 0x04}; + uint32_t passkey = passkey_bytes[2]; + passkey += passkey_bytes[1] << 8; + passkey += passkey_bytes[0] << 16; + + // Random salt + std::array salt = {0x08, 0x09, 0x0A, 0x08, 0x09, 0x0E, + 0x0A, 0x0C, 0x0D, 0x0E, 0x05, 0x02}; + + DecryptedPasskey decryptedPasskey(messgaeType, passkey, salt); + EXPECT_EQ(decryptedPasskey.message_type, messgaeType); + EXPECT_EQ(decryptedPasskey.passkey, passkey); + EXPECT_EQ(decryptedPasskey.salt, salt); +} +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/decrypted_response.h b/fastpair/handshake/decrypted_response.h new file mode 100644 index 00000000..0f0549c6 --- /dev/null +++ b/fastpair/handshake/decrypted_response.h @@ -0,0 +1,47 @@ +// 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_DECRYPTED_RESPONSE_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_DECRYPTED_RESPONSE_H_ + +#include +#include + +#include + +#include "fastpair/common/constant.h" +#include "fastpair/handshake/fast_pair_message_type.h" + +namespace nearby { +namespace fastpair { + +// Thin structure which is used by the higher level components of the Fast Pair +// system to represent a decrypted response. +struct DecryptedResponse { + DecryptedResponse( + FastPairMessageType message_type, + const std::array& + address_bytes, + const std::array& salt) + : message_type(message_type), address_bytes(address_bytes), salt(salt) {} + + FastPairMessageType message_type; + std::array address_bytes; + std::array salt; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_DECRYPTED_RESPONSE_H_ diff --git a/fastpair/handshake/decrypted_response_test.cc b/fastpair/handshake/decrypted_response_test.cc new file mode 100644 index 00000000..6e32ec2a --- /dev/null +++ b/fastpair/handshake/decrypted_response_test.cc @@ -0,0 +1,42 @@ +// 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/decrypted_response.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace fastpair { +namespace { + +TEST(DecryptedResponseTest, CreateDecryptedResponse) { + constexpr std::array address_bytes = {0x02, 0x03, 0x04, + 0x05, 0x06, 0x07}; + + constexpr std::array salt = {0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x00}; + + DecryptedResponse decryptedResponse( + FastPairMessageType::kKeyBasedPairingResponse, address_bytes, salt); + EXPECT_EQ(decryptedResponse.message_type, + FastPairMessageType::kKeyBasedPairingResponse); + EXPECT_EQ(decryptedResponse.address_bytes, address_bytes); + EXPECT_EQ(decryptedResponse.salt, salt); +} + +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_decryption.cc b/fastpair/handshake/fast_pair_decryption.cc new file mode 100644 index 00000000..e16d7021 --- /dev/null +++ b/fastpair/handshake/fast_pair_decryption.cc @@ -0,0 +1,115 @@ +// 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_decryption.h" + +#include +#include + +#ifdef NEARBY_CHROMIUM +#include "base/check.h" +#elif defined(NEARBY_SWIFTPM) +#include "internal/platform/logging.h" +#else +#include "absl/log/check.h" // nogncheck +#endif + +#include "absl/types/optional.h" +#include "fastpair/common/constant.h" +#include "fastpair/handshake/decrypted_passkey.h" +#include "fastpair/handshake/decrypted_response.h" +#include + +namespace nearby { +namespace fastpair { + +std::array FastPairDecryption::DecryptBytes( + const std::array& aes_key_bytes, + const std::array& encrypted_bytes) { + AES_KEY aes_key; + int aes_key_was_set = AES_set_decrypt_key(aes_key_bytes.data(), + aes_key_bytes.size() * 8, &aes_key); + CHECK(aes_key_was_set == 0) << "Invalid AES key size."; + std::array decrypted_bytes; + // Encrypted_bytes is less than 16 bytes and can be guaranteed to be a + // single block, so we encrypt/decrypt it using AES ECB mode + // (Approved by: b/73360609) + AES_decrypt(encrypted_bytes.data(), decrypted_bytes.data(), &aes_key); + return decrypted_bytes; +} + +// Decrypts the encrypted response +// (https://developers.google.com/nearby/fast-pair/specifications/characteristics#table1.4) +// and returns the parsed decrypted response +// (https://developers.google.com/nearby/fast-pair/specifications/characteristics#table1.3) +absl::optional FastPairDecryption::ParseDecryptResponse( + const std::array& aes_key_bytes, + const std::array& encrypted_response_bytes) { + std::array decrypted_response_bytes = + DecryptBytes(aes_key_bytes, encrypted_response_bytes); + + uint8_t message_type = decrypted_response_bytes[kMessageTypeIndex]; + + // If the message type index is not the expected fast pair message type, then + // this is not a valid fast pair response. + if (message_type != kKeybasedPairingResponseType) { + return absl::nullopt; + } + + std::array address_bytes; + std::copy(decrypted_response_bytes.begin() + kResponseAddressStartIndex, + decrypted_response_bytes.begin() + kResponseSaltStartIndex, + address_bytes.begin()); + + std::array salt; + std::copy(decrypted_response_bytes.begin() + kResponseSaltStartIndex, + decrypted_response_bytes.end(), salt.begin()); + return DecryptedResponse(FastPairMessageType::kKeyBasedPairingResponse, + address_bytes, salt); +} + +// Decrypts the encrypted passkey +// (https://developers.google.com/nearby/fast-pair/specifications/characteristics#table2.1) +// and returns the parsed decrypted passkey +// (https://developers.google.com/nearby/fast-pair/specifications/characteristics#table2.2) +// TODO(b/263400788) Add unit test to cover this function and fix all Mutants +// warning +absl::optional FastPairDecryption::ParseDecryptPasskey( + const std::array& aes_key_bytes, + const std::array& encrypted_passkey_bytes) { + std::array decrypted_passkey_bytes = + DecryptBytes(aes_key_bytes, encrypted_passkey_bytes); + + FastPairMessageType message_type; + if (decrypted_passkey_bytes[kMessageTypeIndex] == kSeekerPasskeyType) { + message_type = FastPairMessageType::kSeekersPasskey; + } else if (decrypted_passkey_bytes[kMessageTypeIndex] == + kProviderPasskeyType) { + message_type = FastPairMessageType::kProvidersPasskey; + } else { + return absl::nullopt; + } + + uint32_t passkey = decrypted_passkey_bytes[3]; + passkey += decrypted_passkey_bytes[2] << 8; + passkey += decrypted_passkey_bytes[1] << 16; + + std::array salt; + std::copy(decrypted_passkey_bytes.begin() + kPasskeySaltStartIndex, + decrypted_passkey_bytes.end(), salt.begin()); + return DecryptedPasskey(message_type, passkey, salt); +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_decryption.h b/fastpair/handshake/fast_pair_decryption.h new file mode 100644 index 00000000..72e2a60f --- /dev/null +++ b/fastpair/handshake/fast_pair_decryption.h @@ -0,0 +1,48 @@ +// 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_DECRYPTION_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DECRYPTION_H_ + +#include +#include + +#include "absl/types/optional.h" +#include "fastpair/handshake/decrypted_passkey.h" +#include "fastpair/handshake/decrypted_response.h" + +namespace nearby { +namespace fastpair { +/** Utilities used for encrypting and decrypting Fast Pair packets. */ +class FastPairDecryption { + public: + static constexpr int kAesBlockByteSize = 16; + + static std::array DecryptBytes( + const std::array& aes_key_bytes, + const std::array& encrypted_bytes); + + static absl::optional ParseDecryptResponse( + const std::array& aes_key_bytes, + const std::array& encrypted_response_bytes); + + static absl::optional ParseDecryptPasskey( + const std::array& aes_key_bytes, + const std::array& encrypted_passkey_bytes); +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_DECRYPTION_H_ diff --git a/fastpair/handshake/fast_pair_decryption_test.cc b/fastpair/handshake/fast_pair_decryption_test.cc new file mode 100644 index 00000000..ad4ab9a6 --- /dev/null +++ b/fastpair/handshake/fast_pair_decryption_test.cc @@ -0,0 +1,157 @@ +// 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_decryption.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "fastpair/handshake/fast_pair_encryption.h" + +namespace nearby { +namespace fastpair { +namespace { + +// All test data comes from +// https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#test_cases + +constexpr std::array aes_key_bytes = { + 0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6, + 0xCF, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D}; + +TEST(FastPairDecryptionTest, ParseDecryptResponseSuccess) { + std::vector response_bytes; + + // Message type. + response_bytes.push_back(0x01); + + // Address bytes. + std::array address_bytes = {0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + std::copy(address_bytes.begin(), address_bytes.end(), + std::back_inserter(response_bytes)); + + // Random salt + std::array salt = {0x08, 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x00}; + std::copy(salt.begin(), salt.end(), std::back_inserter(response_bytes)); + + std::array response_bytes_array; + std::copy_n(response_bytes.begin(), kAesBlockByteSize, + response_bytes_array.begin()); + + auto encrypted_bytes = + FastPairEncryption::EncryptBytes(aes_key_bytes, response_bytes_array); + auto response = + FastPairDecryption::ParseDecryptResponse(aes_key_bytes, encrypted_bytes); + + EXPECT_TRUE(response.has_value()); + EXPECT_EQ(response->message_type, + FastPairMessageType::kKeyBasedPairingResponse); + EXPECT_EQ(response->address_bytes, address_bytes); + EXPECT_EQ(response->salt, salt); +} + +TEST(FastPairDecryptionTest, ParseDecryptResponseFailure) { + constexpr std::array response_bytes = { + /*message_type=*/0x02, + /*address_bytes=*/0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + /*salt=*/0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x00}; + + auto encrypted_bytes = + FastPairEncryption::EncryptBytes(aes_key_bytes, response_bytes); + auto response = + FastPairDecryption::ParseDecryptResponse(aes_key_bytes, encrypted_bytes); + + EXPECT_FALSE(response.has_value()); +} + +TEST(FastPairDecryptionTest, ParseDecryptPasskeySuccess) { + std::vector passkey_bytes; + + // Message type. + passkey_bytes.push_back(0x02); + + // Passkey bytes. + uint32_t passkey = 5; + passkey_bytes.push_back(passkey >> 16); + passkey_bytes.push_back(passkey >> 8); + passkey_bytes.push_back(passkey); + + // Random salt + std::array salt = {0x08, 0x09, 0x0A, 0x08, 0x09, 0x0E, + 0x0A, 0x0C, 0x0D, 0x0E, 0x05, 0x02}; + std::copy(salt.begin(), salt.end(), std::back_inserter(passkey_bytes)); + + std::array passkey_bytes_array; + std::copy_n(passkey_bytes.begin(), kAesBlockByteSize, + passkey_bytes_array.begin()); + + auto encrypted_bytes = + FastPairEncryption::EncryptBytes(aes_key_bytes, passkey_bytes_array); + auto decrypted_passkey = + FastPairDecryption::ParseDecryptPasskey(aes_key_bytes, encrypted_bytes); + + EXPECT_TRUE(decrypted_passkey.has_value()); + EXPECT_EQ(decrypted_passkey->message_type, + FastPairMessageType::kSeekersPasskey); + EXPECT_EQ(decrypted_passkey->passkey, passkey); + EXPECT_EQ(decrypted_passkey->salt, salt); +} + +TEST(FastPairDecryptionTest, ParseDecryptPasskeyFailure) { + constexpr std::array passkey_bytes = { + /*message_type=*/0x04, + /*passkey=*/0x02, + 0x03, + 0x04, + /*salt=*/0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x0E}; + + auto encrypted_bytes = + FastPairEncryption::EncryptBytes(aes_key_bytes, passkey_bytes); + auto passkey = + FastPairDecryption::ParseDecryptPasskey(aes_key_bytes, encrypted_bytes); + + EXPECT_FALSE(passkey.has_value()); +} + +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_encryption.cc b/fastpair/handshake/fast_pair_encryption.cc new file mode 100644 index 00000000..bf4e987a --- /dev/null +++ b/fastpair/handshake/fast_pair_encryption.cc @@ -0,0 +1,165 @@ +// 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_encryption.h" + +#include +#include +#include +#include + +#ifdef NEARBY_CHROMIUM +#include "base/check.h" +#elif defined(NEARBY_SWIFTPM) +#include "internal/platform/logging.h" +#else +#include "absl/log/check.h" // nogncheck +#endif + +#include "absl/types/optional.h" +#include "fastpair/common/constant.h" +#include "fastpair/handshake/fast_pair_key_pair.h" +#include "fastpair/handshake/fast_pair_message_type.h" +#include "internal/platform/logging.h" +#include +#include +#include +#include +#include +#include +#include + +namespace nearby { +namespace fastpair { + +namespace { + +// Converts the public anti-spoofing key into an EC_Point. +bssl::UniquePtr GetEcPointFromPublicAntiSpoofingKey( + const bssl::UniquePtr& ec_group, + std::string_view decoded_public_anti_spoofing) { + std::array buffer; + buffer[0] = POINT_CONVERSION_UNCOMPRESSED; + std::copy(decoded_public_anti_spoofing.begin(), + decoded_public_anti_spoofing.end(), buffer.begin() + 1); + + bssl::UniquePtr 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 = kSharedSecretKeyByteSize; + return SHA256(static_cast(in), inlen, + static_cast(out)); +} +} // namespace + +// TODO(b/263400788) Add unit test to cover this function and fix all Mutants +// warning +absl::optional FastPairEncryption::GenerateKeysWithEcdhKeyAgreement( + std::string_view decoded_public_anti_spoofing) { + if (decoded_public_anti_spoofing.size() != kPublicKeyByteSize) { + NEARBY_LOGS(VERBOSE) << "Expected " << kPublicKeyByteSize + << " byte value for anti-spoofing key. Got:" + << decoded_public_anti_spoofing.size(); + return absl::nullopt; + } + + // Generate the secp256r1 key-pair. + bssl::UniquePtr ec_group( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr ec_key( + EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + + if (!EC_KEY_generate_key(ec_key.get())) { + NEARBY_LOGS(VERBOSE) << __func__ << ": Failed to generate ec key"; + return absl::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 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()) { + NEARBY_LOGS(VERBOSE) << __func__ + << ": EC_POINT_point2oct failed to convert public key " + "to uncompressed x9.62 format."; + return absl::nullopt; + } + + bssl::UniquePtr public_anti_spoofing_point = + GetEcPointFromPublicAntiSpoofingKey(ec_group, + decoded_public_anti_spoofing); + + if (!public_anti_spoofing_point) { + NEARBY_LOGS(VERBOSE) + << __func__ + << ": Failed to convert Public Anti-Spoofing key to EC_POINT"; + return absl::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 != kSharedSecretKeyByteSize) { + NEARBY_LOGS(VERBOSE) << __func__ << ": ECDH_compute_key failed."; + return absl::nullopt; + } + + // Take first 16 bytes from secret as the shared secret key. + std::array shared_secret_key; + std::copy(secret, secret + kSharedSecretKeyByteSize, + std::begin(shared_secret_key)); + + // Ignore the first byte since it is 0x04, from the above uncompressed X9 .62 + // format. + std::array public_key; + std::copy(uncompressed_private_key.begin() + 1, + uncompressed_private_key.end(), public_key.begin()); + + return KeyPair(shared_secret_key, public_key); +} + +std::array FastPairEncryption::EncryptBytes( + const std::array& aes_key_bytes, + const std::array& 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 encrypted_bytes; + // Bytes_to_encrypt is less than 16 bytes and can be guaranteed to be a + // single block, so we encrypt it using AES ECB mode (Approved by: b/73360609) + AES_encrypt(bytes_to_encrypt.data(), encrypted_bytes.data(), &aes_key); + return encrypted_bytes; +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_encryption.h b/fastpair/handshake/fast_pair_encryption.h new file mode 100644 index 00000000..c51981d7 --- /dev/null +++ b/fastpair/handshake/fast_pair_encryption.h @@ -0,0 +1,46 @@ +// 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_ENCRYPTION_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_ENCRYPTION_H_ + +#include + +#include +#include +#include + +#include "absl/types/optional.h" +#include "fastpair/common/constant.h" +#include "fastpair/handshake/fast_pair_key_pair.h" + +namespace nearby { +namespace fastpair { + +/** Utilities used for generating Ecdh key agreement and + * encrypting Fast Pair packets. */ +class FastPairEncryption { + public: + static absl::optional GenerateKeysWithEcdhKeyAgreement( + std::string_view decoded_public_anti_spoofing); + + static std::array EncryptBytes( + const std::array& aes_key_bytes, + const std::array& bytes_to_encrypt); +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_ENCRYPTION_H_ diff --git a/fastpair/handshake/fast_pair_encryption_test.cc b/fastpair/handshake/fast_pair_encryption_test.cc new file mode 100644 index 00000000..5690d9d4 --- /dev/null +++ b/fastpair/handshake/fast_pair_encryption_test.cc @@ -0,0 +1,84 @@ +// 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_encryption.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/escaping.h" + +namespace nearby { +namespace fastpair { +namespace { + +// All test data comes from +// https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#test_cases + +constexpr std::array aes_key_bytes = { + 0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, 0xF7, 0xB6, + 0xCF, 0x5E, 0x3F, 0x45, 0x61, 0xC3, 0x32, 0x1D}; + +std::string DecodeKey(const std::string& encoded_key) { + std::string key; + absl::Base64Unescape(encoded_key, &key); + return key; +} + +class FastPairEncryptionTest : public testing::Test {}; + +TEST(FastPairEncryptionTest, EncryptBytes_Success) { + constexpr std::array input = { + 0xF3, 0x0F, 0x4E, 0x78, 0x6C, 0x59, 0xA7, 0xBB, + 0xF3, 0x87, 0x3B, 0x5A, 0x49, 0xBA, 0x97, 0xEA}; + + constexpr std::array expected = { + 0xAC, 0x9A, 0x16, 0xF0, 0x95, 0x3A, 0x3F, 0x22, + 0x3D, 0xD1, 0x0C, 0xF5, 0x36, 0xE0, 0x9E, 0x9C}; + + EXPECT_EQ(FastPairEncryption::EncryptBytes(aes_key_bytes, input), expected); +} + +TEST(FastPairEncryptionTest, GenerateKeysWithEcdhKeyAgreement_EmptyKey) { + EXPECT_FALSE( + FastPairEncryption::GenerateKeysWithEcdhKeyAgreement("").has_value()); +} + +TEST(FastPairEncryptionTest, GenerateKeysWithEcdhKeyAgreement_ShortKey) { + EXPECT_FALSE(FastPairEncryption::GenerateKeysWithEcdhKeyAgreement("too_short") + .has_value()); +} + +TEST(FastPairEncryptionTest, GenerateKeysWithEcdhKeyAgreement_InvalidKey) { + EXPECT_FALSE( + FastPairEncryption::GenerateKeysWithEcdhKeyAgreement( + DecodeKey("U2PWc3FHTxah/o0YT9n1VRvtm57SNIRSXOEBXm4fdtMo+06tNoFlt8D0/" + "2BsN8auolz5ikwLRvQh+MiQ6oYveg==")) + .has_value()); +} + +TEST(FastPairEncryptionTest, GenerateKeysWithEcdhKeyAgreement_ValidKey) { + EXPECT_TRUE( + FastPairEncryption::GenerateKeysWithEcdhKeyAgreement( + DecodeKey("U2PWc3FHTxah/o0YU9n1VRvtm57SNIRSXOEBXm4fdtMo+06tNoFlt8D0/" + "2BsN8auolz5ikwLRvQh+MiQ6oYveg==")) + .has_value()); +} + +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_key_pair.h b/fastpair/handshake/fast_pair_key_pair.h new file mode 100644 index 00000000..d64e30ae --- /dev/null +++ b/fastpair/handshake/fast_pair_key_pair.h @@ -0,0 +1,46 @@ +// 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_KEY_PAIR_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_KEY_PAIR_H_ + +#include +#include + +#include +#include +#include + +#include "fastpair/common/constant.h" + +namespace nearby { +namespace fastpair { + +// Key pair structure to represent public and private keys used for encryption/ +// decryption. +struct KeyPair { + KeyPair( + const std::array& shared_secret_key, + const std::array& public_key) + : shared_secret_key(std::move(shared_secret_key)), + public_key(std::move(public_key)) {} + + const std::array shared_secret_key; + const std::array public_key; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_KEY_PAIR_H_ diff --git a/fastpair/handshake/fast_pair_key_pair_test.cc b/fastpair/handshake/fast_pair_key_pair_test.cc new file mode 100644 index 00000000..c35821bc --- /dev/null +++ b/fastpair/handshake/fast_pair_key_pair_test.cc @@ -0,0 +1,53 @@ +// 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_key_pair.h" + +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" + +namespace nearby { +namespace fastpair { +namespace { + +// Test data comes from +// https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#test_cases +TEST(KeyPairTest, CreateKeyPair) { + // Shared secret key bytes. + constexpr std::array + shared_secret_key_bytes = {0xA0, 0xBA, 0xF0, 0xBB, 0x95, 0x1F, + 0xF7, 0xB6, 0xCF, 0x5E, 0x3F, 0x45, + 0x61, 0xC3, 0x32, 0x1D}; + + // Public key bytes. + constexpr std::array public_key_bytes = { + 0x04, 0xb9, 0xda, 0x0d, 0x71, 0x60, 0xb3, 0x63, 0x28, 0x22, 0x67, + 0xe7, 0xe0, 0xa3, 0xf8, 0x00, 0x8e, 0x4c, 0x89, 0xed, 0x31, 0x34, + 0xf6, 0xdb, 0xc4, 0xfe, 0x0b, 0x5d, 0xe1, 0x11, 0x39, 0x49, 0xa6, + 0x50, 0xa8, 0xe3, 0x4a, 0xc0, 0x40, 0x88, 0xb8, 0x38, 0x3f, 0x56, + 0xfb, 0x33, 0x8d, 0xd4, 0x64, 0x91, 0xd6, 0x15, 0x77, 0x42, 0x27, + 0xc5, 0xaa, 0x44, 0xff, 0xab, 0x4d, 0xb5, 0x7e, 0x25}; + + KeyPair keyPair(shared_secret_key_bytes, public_key_bytes); + + EXPECT_EQ(keyPair.shared_secret_key, shared_secret_key_bytes); + EXPECT_EQ(keyPair.public_key, public_key_bytes); +} + +} // namespace +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/handshake/fast_pair_message_type.h b/fastpair/handshake/fast_pair_message_type.h new file mode 100644 index 00000000..0c6ff49f --- /dev/null +++ b/fastpair/handshake/fast_pair_message_type.h @@ -0,0 +1,38 @@ +// 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_MESSAGE_TYPE_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_MESSAGE_TYPE_H_ + +namespace nearby { +namespace fastpair { + +// Type values for Fast Pair messages. +enum class FastPairMessageType { + // Key-based Pairing Request. + kKeyBasedPairingRequest, + // Key-based Pairing Response. + kKeyBasedPairingResponse, + // Seeker's passkey. + kSeekersPasskey, + // Provider's passkey. + kProvidersPasskey, + // Unknown message type. + kUnknown, +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_HANDSHAKE_FAST_PAIR_MESSAGE_TYPE_H_