| // Copyright 2023 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 "presence/presence_device_provider.h" |
| |
| #include <memory> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "protobuf-matchers/protocol-buffer-matchers.h" |
| #include "gtest/gtest.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/time.h" |
| #include "internal/crypto/ed25519.h" |
| #include "internal/interop/authentication_status.h" |
| #include "internal/interop/authentication_transport.h" |
| #include "internal/platform/implementation/credential_callbacks.h" |
| #include "internal/platform/implementation/system_clock.h" |
| #include "internal/proto/credential.pb.h" |
| #include "internal/proto/local_credential.pb.h" |
| #include "internal/proto/metadata.pb.h" |
| #include "presence/implementation/connection_authenticator.h" |
| #include "presence/implementation/mock_connection_authenticator.h" |
| #include "presence/implementation/mock_service_controller.h" |
| #include "presence/presence_device.h" |
| #include "presence/proto/presence_frame.pb.h" |
| |
| namespace nearby { |
| namespace presence { |
| namespace { |
| using ::nearby::internal::DeviceIdentityMetaData; |
| |
| constexpr absl::string_view kMacAddr = "\x4C\x8B\x1D\xCE\xBA\xD1"; |
| constexpr absl::string_view kManagerAppId = "test_app_id"; |
| constexpr char kUkey2Secret[] = {0x34, 0x56, 0x78, 0x90}; |
| constexpr char kKeySeed[] = {1, 2, 3, 4, 5, 6, 7, 8}; |
| constexpr int kPresenceVersion = 1; |
| constexpr absl::string_view kSharedCredentialHash = "shared_cred_hash"; |
| constexpr absl::string_view kPrivateKeySignature = "private_key_signature"; |
| |
| DeviceIdentityMetaData CreateTestDeviceIdentityMetaData() { |
| DeviceIdentityMetaData device_identity_metadata; |
| device_identity_metadata.set_device_type( |
| internal::DeviceType::DEVICE_TYPE_PHONE); |
| device_identity_metadata.set_device_name("NP test device"); |
| device_identity_metadata.set_bluetooth_mac_address(kMacAddr); |
| device_identity_metadata.set_device_id("\x12\xab\xcd"); |
| return device_identity_metadata; |
| } |
| |
| nearby::internal::LocalCredential CreateValidLocalCredential( |
| const crypto::Ed25519KeyPair& key_pair) { |
| nearby::internal::LocalCredential credential; |
| absl::Time now = SystemClock::ElapsedRealtime(); |
| credential.set_start_time_millis(absl::ToUnixMillis(now)); |
| credential.set_end_time_millis(absl::ToUnixMillis(now + absl::Minutes(10))); |
| credential.mutable_connection_signing_key()->set_key( |
| absl::StrCat(key_pair.private_key, key_pair.public_key)); |
| credential.set_key_seed(kKeySeed); |
| return credential; |
| } |
| |
| nearby::internal::LocalCredential CreateExpiredLocalCredential() { |
| nearby::internal::LocalCredential credential; |
| absl::Time now = SystemClock::ElapsedRealtime(); |
| credential.set_start_time_millis(absl::ToUnixMillis(now - absl::Minutes(30))); |
| credential.set_end_time_millis(absl::ToUnixMillis(now - absl::Minutes(10))); |
| return credential; |
| } |
| |
| internal::SharedCredential BuildSharedCredential( |
| const crypto::Ed25519KeyPair& key_pair) { |
| internal::SharedCredential shared_credential; |
| shared_credential.set_connection_signature_verification_key( |
| key_pair.public_key); |
| shared_credential.set_key_seed(kKeySeed); |
| return shared_credential; |
| } |
| |
| ConnectionAuthenticator::TwoWayInitiatorData BuildDefaultInitiatorData() { |
| ConnectionAuthenticator::TwoWayInitiatorData data; |
| data.shared_credential_hash = kSharedCredentialHash; |
| data.private_key_signature = kPrivateKeySignature; |
| return data; |
| } |
| |
| class MockAuthenticationTransport : public AuthenticationTransport { |
| public: |
| MOCK_METHOD(void, WriteMessage, (absl::string_view), (const override)); |
| MOCK_METHOD(std::string, ReadMessage, (), (const override)); |
| }; |
| |
| class PresenceDeviceProviderTest : public ::testing::Test { |
| public: |
| PresenceDeviceProviderTest() { |
| ON_CALL(mock_service_controller_, GetDeviceIdentityMetaData) |
| .WillByDefault(testing::Return(CreateTestDeviceIdentityMetaData())); |
| provider_ = std::make_unique<PresenceDeviceProvider>( |
| &mock_service_controller_, &mock_connection_authenticator_); |
| } |
| |
| void SetUp() override { |
| auto key_pair_or_status = crypto::Ed25519Signer::CreateNewKeyPair(); |
| ASSERT_OK_AND_ASSIGN(key_pair_, key_pair_or_status); |
| } |
| |
| protected: |
| MockServiceController mock_service_controller_; |
| std::unique_ptr<PresenceDeviceProvider> provider_; |
| crypto::Ed25519KeyPair key_pair_; |
| MockConnectionAuthenticator mock_connection_authenticator_; |
| }; |
| |
| TEST_F(PresenceDeviceProviderTest, ProviderIsNotTriviallyConstructible) { |
| EXPECT_FALSE(std::is_trivially_constructible<PresenceDeviceProvider>::value); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, DeviceProviderWorks) { |
| auto device = provider_->GetLocalDevice(); |
| ASSERT_EQ(device->GetType(), NearbyDevice::Type::kPresenceDevice); |
| auto presence_device = static_cast<const PresenceDevice*>(device); |
| EXPECT_EQ(presence_device->GetDeviceIdentityMetadata().SerializeAsString(), |
| CreateTestDeviceIdentityMetaData().SerializeAsString()); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, DeviceProviderCanUpdateDevice) { |
| auto device = provider_->GetLocalDevice(); |
| ASSERT_EQ(device->GetType(), NearbyDevice::Type::kPresenceDevice); |
| auto presence_device = static_cast<const PresenceDevice*>(device); |
| EXPECT_EQ(presence_device->GetDeviceIdentityMetadata().SerializeAsString(), |
| CreateTestDeviceIdentityMetaData().SerializeAsString()); |
| auto new_metadata = CreateTestDeviceIdentityMetaData(); |
| new_metadata.set_device_name("NP interop device"); |
| provider_->UpdateDeviceIdentityMetaData(new_metadata); |
| EXPECT_EQ(presence_device->GetDeviceIdentityMetadata().SerializeAsString(), |
| new_metadata.SerializeAsString()); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, SetGetManagerAppId) { |
| provider_->SetManagerAppId(kManagerAppId); |
| EXPECT_EQ(provider_->GetManagerAppId(), kManagerAppId); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, |
| AuthenticateAsInitiatorFails_FailToFetchCredentials) { |
| EXPECT_CALL(mock_service_controller_, GetLocalCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetLocalCredentialsResultCallback callback) { |
| std::move(callback.credentials_fetched_cb)( |
| absl::Status(absl::StatusCode::kCancelled, /*msg=*/std::string())); |
| }); |
| |
| PresenceDevice remote_device(CreateTestDeviceIdentityMetaData()); |
| MockAuthenticationTransport authentication_transport; |
| auto status = provider_->AuthenticateAsInitiator( |
| /*remote_device=*/remote_device, /*shared_secret=*/kUkey2Secret, |
| /*authentication_transport=*/authentication_transport); |
| EXPECT_EQ(AuthenticationStatus::kFailure, status); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, |
| AuthenticateAsInitiatorFails_NoValidCredentials) { |
| EXPECT_CALL(mock_service_controller_, GetLocalCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetLocalCredentialsResultCallback callback) { |
| std::vector<nearby::internal::LocalCredential> credentials; |
| credentials.push_back(CreateExpiredLocalCredential()); |
| std::move(callback.credentials_fetched_cb)(credentials); |
| }); |
| |
| PresenceDevice remote_device(CreateTestDeviceIdentityMetaData()); |
| MockAuthenticationTransport authentication_transport; |
| auto status = provider_->AuthenticateAsInitiator( |
| /*remote_device=*/remote_device, /*shared_secret=*/kUkey2Secret, |
| /*authentication_transport=*/authentication_transport); |
| EXPECT_EQ(AuthenticationStatus::kFailure, status); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, |
| AuthenticateAsInitiator_NoRemoteSharedCredential) { |
| EXPECT_CALL(mock_service_controller_, GetLocalCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetLocalCredentialsResultCallback callback) { |
| std::vector<nearby::internal::LocalCredential> credentials; |
| credentials.push_back(CreateValidLocalCredential(key_pair_)); |
| std::move(callback.credentials_fetched_cb)(credentials); |
| }); |
| |
| PresenceDevice remote_device(CreateTestDeviceIdentityMetaData()); |
| MockAuthenticationTransport authentication_transport; |
| auto status = provider_->AuthenticateAsInitiator( |
| /*remote_device=*/remote_device, /*shared_secret=*/kUkey2Secret, |
| /*authentication_transport=*/authentication_transport); |
| |
| EXPECT_EQ(AuthenticationStatus::kFailure, status); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, AuthenticateAsInitiator_FailureToVerify) { |
| EXPECT_CALL(mock_service_controller_, GetLocalCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetLocalCredentialsResultCallback callback) { |
| std::vector<nearby::internal::LocalCredential> credentials; |
| credentials.push_back(CreateValidLocalCredential(key_pair_)); |
| std::move(callback.credentials_fetched_cb)(credentials); |
| }); |
| EXPECT_CALL(mock_service_controller_, GetLocalPublicCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetPublicCredentialsResultCallback callback) { |
| std::vector<nearby::internal::SharedCredential> credentials; |
| credentials.push_back(BuildSharedCredential(key_pair_)); |
| std::move(callback.credentials_fetched_cb)(credentials); |
| }); |
| |
| PresenceDevice remote_device(CreateTestDeviceIdentityMetaData()); |
| remote_device.SetDecryptSharedCredential(BuildSharedCredential(key_pair_)); |
| |
| MockAuthenticationTransport authentication_transport; |
| EXPECT_CALL(authentication_transport, WriteMessage) |
| .WillOnce([&](absl::string_view message) { |
| PresenceAuthenticationFrame authentication_frame; |
| EXPECT_TRUE(authentication_frame.ParseFromString(message)); |
| EXPECT_EQ(kPresenceVersion, authentication_frame.version()); |
| }); |
| EXPECT_CALL(authentication_transport, ReadMessage).WillOnce([&]() { |
| PresenceAuthenticationFrame authentication_frame; |
| return authentication_frame.SerializeAsString(); |
| }); |
| |
| EXPECT_CALL(mock_connection_authenticator_, BuildSignedMessageAsInitiator) |
| .WillOnce(testing::Return(BuildDefaultInitiatorData())); |
| EXPECT_CALL(mock_connection_authenticator_, VerifyMessageAsInitiator) |
| .WillOnce(testing::Return( |
| absl::Status(absl::StatusCode::kCancelled, /*msg=*/std::string()))); |
| |
| auto status = provider_->AuthenticateAsInitiator( |
| /*remote_device=*/remote_device, /*shared_secret=*/kUkey2Secret, |
| /*authentication_transport=*/authentication_transport); |
| EXPECT_EQ(AuthenticationStatus::kFailure, status); |
| } |
| |
| TEST_F(PresenceDeviceProviderTest, AuthenticateAsInitiator_Success) { |
| EXPECT_CALL(mock_service_controller_, GetLocalCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetLocalCredentialsResultCallback callback) { |
| std::vector<nearby::internal::LocalCredential> credentials; |
| credentials.push_back(CreateValidLocalCredential(key_pair_)); |
| std::move(callback.credentials_fetched_cb)(credentials); |
| }); |
| EXPECT_CALL(mock_service_controller_, GetLocalPublicCredentials) |
| .WillOnce([&](const CredentialSelector& credential_selector, |
| GetPublicCredentialsResultCallback callback) { |
| std::vector<nearby::internal::SharedCredential> credentials; |
| credentials.push_back(BuildSharedCredential(key_pair_)); |
| std::move(callback.credentials_fetched_cb)(std::move(credentials)); |
| }); |
| |
| PresenceDevice remote_device(CreateTestDeviceIdentityMetaData()); |
| remote_device.SetDecryptSharedCredential(BuildSharedCredential(key_pair_)); |
| |
| ON_CALL(mock_connection_authenticator_, BuildSignedMessageAsInitiator) |
| .WillByDefault(testing::Return(BuildDefaultInitiatorData())); |
| |
| MockAuthenticationTransport authentication_transport; |
| EXPECT_CALL(authentication_transport, WriteMessage) |
| .WillOnce([&](absl::string_view message) { |
| PresenceAuthenticationFrame authentication_frame; |
| EXPECT_TRUE(authentication_frame.ParseFromString(message)); |
| EXPECT_EQ(kPresenceVersion, authentication_frame.version()); |
| }); |
| |
| auto status = provider_->AuthenticateAsInitiator( |
| /*remote_device=*/remote_device, /*shared_secret=*/kUkey2Secret, |
| /*authentication_transport=*/authentication_transport); |
| |
| EXPECT_EQ(AuthenticationStatus::kSuccess, status); |
| } |
| |
| } // namespace |
| } // namespace presence |
| } // namespace nearby |