| // Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // Unit tests for Service and ServiceMonolithic |
| |
| #include "cryptohome/service_monolithic.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/at_exit.h> |
| #include <base/bind.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/strings/sys_string_conversions.h> |
| #include <base/synchronization/lock.h> |
| #include <base/threading/platform_thread.h> |
| #include <base/time/time.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/secure_blob.h> |
| #include <chaps/token_manager_client_mock.h> |
| #include <chromeos/constants/cryptohome.h> |
| #include <glib-object.h> |
| #include <gtest/gtest.h> |
| #include <policy/libpolicy.h> |
| #include <policy/mock_device_policy.h> |
| |
| #include "cryptohome/bootlockbox/mock_boot_attributes.h" |
| #include "cryptohome/bootlockbox/mock_boot_lockbox.h" |
| #include "cryptohome/credentials.h" |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/glib_transition.h" |
| #include "cryptohome/interface.h" |
| #include "cryptohome/make_tests.h" |
| #include "cryptohome/mock_arc_disk_quota.h" |
| #include "cryptohome/mock_attestation.h" |
| #include "cryptohome/mock_crypto.h" |
| #include "cryptohome/mock_firmware_management_parameters.h" |
| #include "cryptohome/mock_homedirs.h" |
| #include "cryptohome/mock_install_attributes.h" |
| #include "cryptohome/mock_mount.h" |
| #include "cryptohome/mock_mount_factory.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| #include "cryptohome/mock_tpm_init.h" |
| #include "cryptohome/mock_vault_keyset.h" |
| #include "cryptohome/user_oldest_activity_timestamp_cache.h" |
| |
| using base::FilePath; |
| using base::PlatformThread; |
| using brillo::SecureBlob; |
| using ::testing::_; |
| using ::testing::AnyOf; |
| using ::testing::AtLeast; |
| using ::testing::DoAll; |
| using ::testing::EndsWith; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SaveArgPointee; |
| using ::testing::SetArgPointee; |
| using ::testing::StrEq; |
| using ::testing::WithArgs; |
| |
| namespace cryptohome { |
| |
| namespace { |
| |
| constexpr char kImageDir[] = "test_image_dir"; |
| constexpr char kSaltFile[] = "test_image_dir/salt"; |
| |
| class FakeEventSourceSink : public CryptohomeEventSourceSink { |
| public: |
| FakeEventSourceSink() = default; |
| ~FakeEventSourceSink() override = default; |
| |
| void NotifyEvent(CryptohomeEventBase* event) override { |
| const std::string event_name = event->GetEventName(); |
| if (event_name == kDBusBlobReplyEventType) { |
| EXPECT_FALSE(reply_); |
| EXPECT_FALSE(error_reply_); |
| auto* dbus_reply = static_cast<const DBusBlobReply*>(event); |
| BaseReply parsed_reply; |
| EXPECT_TRUE(parsed_reply.ParseFromString(dbus_reply->reply())); |
| reply_ = std::make_unique<BaseReply>(parsed_reply); |
| } else if (event_name == kDBusErrorReplyEventType) { |
| EXPECT_FALSE(reply_); |
| EXPECT_FALSE(error_reply_); |
| auto* dbus_error_reply = static_cast<const DBusErrorReply*>(event); |
| error_reply_ = |
| std::make_unique<std::string>(dbus_error_reply->error().message); |
| } else if (event_name == kClosureEventType) { |
| ClosureEvent* closure_event = static_cast<ClosureEvent*>(event); |
| closure_event->Run(); |
| } |
| } |
| |
| const BaseReply* reply() const { return reply_.get(); } |
| |
| const std::string* error_reply() const { return error_reply_.get(); } |
| |
| void ClearReplies() { |
| reply_.reset(); |
| error_reply_.reset(); |
| } |
| |
| private: |
| std::unique_ptr<BaseReply> reply_; |
| std::unique_ptr<std::string> error_reply_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeEventSourceSink); |
| }; |
| |
| bool AssignSalt(size_t size, SecureBlob* salt) { |
| SecureBlob fake_salt(size, 'S'); |
| salt->swap(fake_salt); |
| return true; |
| } |
| |
| bool ProtosAreEqual(const google::protobuf::MessageLite& lhs, |
| const google::protobuf::MessageLite& rhs) { |
| std::string serialized_lhs, serialized_rhs; |
| return lhs.SerializeToString(&serialized_lhs) && |
| rhs.SerializeToString(&serialized_rhs) && |
| serialized_lhs == serialized_rhs; |
| } |
| |
| } // namespace |
| |
| // Tests that need to do more setup work before calling Service::Initialize can |
| // use this instead of ServiceTest. |
| class ServiceTestNotInitialized : public ::testing::Test { |
| public: |
| ServiceTestNotInitialized() = default; |
| ~ServiceTestNotInitialized() override = default; |
| |
| void SetUp() override { |
| service_.set_crypto(&crypto_); |
| service_.set_attestation(&attest_); |
| service_.set_homedirs(&homedirs_); |
| service_.set_install_attrs(&attrs_); |
| service_.set_initialize_tpm(false); |
| service_.set_use_tpm(false); |
| service_.set_platform(&platform_); |
| service_.set_chaps_client(&chaps_client_); |
| service_.set_boot_lockbox(&lockbox_); |
| service_.set_boot_attributes(&boot_attributes_); |
| service_.set_firmware_management_parameters(&fwmp_); |
| service_.set_event_source_sink(&event_sink_); |
| service_.set_arc_disk_quota(&arc_disk_quota_); |
| test_helper_.SetUpSystemSalt(); |
| homedirs_.set_crypto(&crypto_); |
| homedirs_.set_platform(&platform_); |
| ON_CALL(homedirs_, Init(_, _, _)).WillByDefault(Return(true)); |
| ON_CALL(homedirs_, AmountOfFreeDiskSpace()).WillByDefault( |
| Return(kNotifyDiskSpaceThreshold)); |
| ON_CALL(boot_attributes_, Load()).WillByDefault(Return(true)); |
| // Empty token list by default. The effect is that there are no attempts |
| // to unload tokens unless a test explicitly sets up the token list. |
| ON_CALL(chaps_client_, GetTokenList(_, _)).WillByDefault(Return(true)); |
| // Skip CleanUpStaleMounts by default. |
| ON_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillByDefault(Return(false)); |
| // Setup fake salt by default. |
| ON_CALL(crypto_, GetOrCreateSalt(_, _, _, _)) |
| .WillByDefault(WithArgs<1, 3>(Invoke(AssignSalt))); |
| // Skip StatefulRecovery by default. |
| ON_CALL(platform_, |
| ReadFileToString( |
| Property(&FilePath::value, EndsWith("decrypt_stateful")), |
| _)) |
| .WillByDefault(Return(false)); |
| |
| ON_CALL(arc_disk_quota_, Initialize()).WillByDefault(Return()); |
| } |
| |
| void SetupMount(const std::string& username) { |
| mount_ = new NiceMock<MockMount>(); |
| service_.set_mount_for_user(username, mount_.get()); |
| } |
| |
| void TearDown() override { |
| test_helper_.TearDownSystemSalt(); |
| } |
| |
| protected: |
| void DispatchEvents() { service_.DispatchEventsForTesting(); } |
| |
| const BaseReply* reply() const { return event_sink_.reply(); } |
| |
| bool ReplyIsEmpty() const { |
| EXPECT_TRUE(reply()); |
| return reply() && ProtosAreEqual(BaseReply(), *reply()); |
| } |
| |
| const std::string* error_reply() const { return event_sink_.error_reply(); } |
| |
| void ClearReplies() { event_sink_.ClearReplies(); } |
| |
| MakeTests test_helper_; |
| NiceMock<MockCrypto> crypto_; |
| NiceMock<MockAttestation> attest_; |
| NiceMock<MockHomeDirs> homedirs_; |
| NiceMock<MockInstallAttributes> attrs_; |
| NiceMock<MockBootLockbox> lockbox_; |
| NiceMock<MockBootAttributes> boot_attributes_; |
| NiceMock<MockFirmwareManagementParameters> fwmp_; |
| NiceMock<MockPlatform> platform_; |
| NiceMock<MockArcDiskQuota> arc_disk_quota_; |
| NiceMock<chaps::TokenManagerClientMock> chaps_client_; |
| FakeEventSourceSink event_sink_; |
| scoped_refptr<MockMount> mount_; |
| // Declare service_ last so it gets destroyed before all the mocks. This is |
| // important because otherwise the background thread may call into mocks that |
| // have already been destroyed. |
| ServiceMonolithic service_{std::string()}; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceTestNotInitialized); |
| }; |
| |
| class ServiceTest : public ServiceTestNotInitialized { |
| public: |
| ServiceTest() = default; |
| ~ServiceTest() override = default; |
| |
| void SetUp() override { |
| ServiceTestNotInitialized::SetUp(); |
| ASSERT_TRUE(service_.Initialize()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceTest); |
| }; |
| |
| TEST_F(ServiceTest, ValidAbeDataTest) { |
| brillo::SecureBlob abe_data; |
| ASSERT_TRUE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "2eac34fa74994262b907c15a3a1462e349e5108ca0d0e807f4b1a3ee741a5594", |
| &abe_data)); |
| ASSERT_EQ(32, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTest, ValidAbeDataTestTrailingNewlines) { |
| brillo::SecureBlob abe_data; |
| ASSERT_TRUE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "2eac34fa74994262b907c15a3a1462e349e5108ca0d0e807f4b1a3ee741a5594\n\n", |
| &abe_data)); |
| ASSERT_EQ(32, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTest, ValidAbeDataTestEmpty) { |
| brillo::SecureBlob abe_data; |
| ASSERT_TRUE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "", &abe_data)); |
| ASSERT_EQ(0, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTest, ValidAbeDataTestNewlines) { |
| brillo::SecureBlob abe_data; |
| ASSERT_TRUE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "\n", &abe_data)); |
| ASSERT_EQ(0, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTest, InvalidAbeDataTestShort) { |
| brillo::SecureBlob abe_data; |
| ASSERT_FALSE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "2eac34fa74994262b907c15a3a1462e349e5108c", |
| &abe_data)); |
| ASSERT_EQ(0, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTest, InvalidAbeDataTestNotHex) { |
| brillo::SecureBlob abe_data; |
| ASSERT_FALSE(ServiceMonolithic::GetAttestationBasedEnterpriseEnrollmentData( |
| "garbage", &abe_data)); |
| ASSERT_EQ(0, abe_data.size()); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckAsyncTestCredentials) { |
| // Setup a real homedirs instance (making this a pseudo-integration test). |
| test_helper_.InjectSystemSalt(&platform_, FilePath(kSaltFile)); |
| test_helper_.InitTestData(FilePath(kImageDir), kDefaultUsers, 1, |
| false /* force_ecryptfs */); |
| TestUser* user = &test_helper_.users[0]; |
| user->InjectKeyset(&platform_); |
| // Inject the dirs because InitTestData uses its own platform |
| // mock for some reason... |
| user->InjectUserPaths(&platform_, 1000, 1000, 1001, 0, false); |
| EXPECT_CALL(platform_, DirectoryExists(user->base_path)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, FileExists(base::FilePath(kLockedToSingleUserFile))) |
| .WillRepeatedly(Return(false)); |
| |
| SecureBlob passkey; |
| cryptohome::Crypto::PasswordToPasskey(user->password, |
| test_helper_.system_salt, &passkey); |
| std::string passkey_string = passkey.to_string(); |
| Crypto real_crypto(&platform_); |
| real_crypto.set_use_tpm(false); |
| real_crypto.Init(nullptr); |
| HomeDirs real_homedirs; |
| real_homedirs.set_crypto(&real_crypto); |
| real_homedirs.set_shadow_root(FilePath(kImageDir)); |
| real_homedirs.set_platform(&platform_); |
| policy::PolicyProvider policy_provider( |
| std::unique_ptr<NiceMock<policy::MockDevicePolicy>>( |
| new NiceMock<policy::MockDevicePolicy>)); |
| real_homedirs.set_policy_provider(&policy_provider); |
| // Avoid calling FreeDiskSpace routine. |
| EXPECT_CALL(platform_, AmountOfFreeDiskSpace(_)) |
| .WillRepeatedly(Return(kMinFreeSpaceInBytes * 2)); |
| service_.set_homedirs(&real_homedirs); |
| service_.set_crypto(&real_crypto); |
| service_.Initialize(); |
| |
| AccountIdentifier id; |
| id.set_account_id(user->username); |
| AuthorizationRequest auth; |
| auth.mutable_key()->set_secret(passkey_string.c_str()); |
| CheckKeyRequest req; |
| |
| // Run will never be called because we aren't running the event loop. |
| service_.DoCheckKeyEx(&id, &auth, &req, NULL); |
| |
| // Expect an empty reply as success. |
| DispatchEvents(); |
| EXPECT_TRUE(ReplyIsEmpty()); |
| |
| // Reset pointers from local variables that will be destroyed before |
| // |service_|. |
| service_.set_homedirs(&homedirs_); |
| service_.set_crypto(&crypto_); |
| } |
| |
| TEST_F(ServiceTest, GetPublicMountPassKey) { |
| const char kPublicUser1[] = "public_user_1"; |
| const char kPublicUser2[] = "public_user_2"; |
| std::string public_user1_passkey; |
| service_.GetPublicMountPassKey(kPublicUser1, &public_user1_passkey); |
| std::string public_user2_passkey; |
| service_.GetPublicMountPassKey(kPublicUser2, &public_user2_passkey); |
| // The passkey should be different for different user. |
| EXPECT_NE(public_user1_passkey, public_user2_passkey); |
| std::string public_user1_passkey2; |
| service_.GetPublicMountPassKey(kPublicUser1, &public_user1_passkey2); |
| // The passkey should be the same for the same user. |
| EXPECT_EQ(public_user1_passkey, public_user1_passkey2); |
| } |
| |
| TEST_F(ServiceTest, GetSanitizedUsername) { |
| char username[] = "chromeos-user"; |
| gchar *sanitized = NULL; |
| GError *error = NULL; |
| EXPECT_TRUE(service_.GetSanitizedUsername(username, &sanitized, &error)); |
| EXPECT_TRUE(error == NULL); |
| ASSERT_TRUE(sanitized); |
| |
| const std::string expected( |
| brillo::cryptohome::home::SanitizeUserName(username)); |
| EXPECT_FALSE(expected.empty()); |
| |
| EXPECT_EQ(expected, sanitized); |
| g_free(sanitized); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckAutoCleanupCallback) { |
| // Checks that DoAutoCleanup() is called periodically. |
| // Service will schedule periodic clean-ups. |
| SetupMount("some-user-to-clean-up"); |
| |
| // Check that UpdateCurrentUserActivityTimestamp happens daily. |
| EXPECT_CALL(*mount_, UpdateCurrentUserActivityTimestamp(0)).Times(AtLeast(1)); |
| |
| // These are shared between Mount and Platform threads, guarded by the lock. |
| int free_disk_space_count = 0; |
| base::Time current_time; |
| base::Lock lock; |
| |
| // These will be invoked from the mount thread. |
| EXPECT_CALL(homedirs_, FreeDiskSpace()).WillRepeatedly(Invoke([&]() { |
| base::AutoLock scoped_lock(lock); |
| ++free_disk_space_count; |
| })); |
| EXPECT_CALL(platform_, GetCurrentTime()).WillRepeatedly(Invoke([&]() { |
| base::AutoLock scoped_lock(lock); |
| return current_time; |
| })); |
| |
| const int period_ms = 1; |
| |
| // This will cause the low disk space callback to be called every ms |
| service_.set_low_disk_notification_period_ms(period_ms); |
| service_.Initialize(); |
| |
| // Make sure that we have at least 48 FreeDiskSpace calls executed. |
| // (48 hourly callbacks == two days, |
| // at least 1 UpdateCurrentUserActivityTimestamp) |
| for (int count = 0; count < 48;) { |
| { |
| base::AutoLock scoped_lock(lock); |
| // Advance platform time. Let each period_ms = 30 minutes. |
| current_time += base::TimeDelta::FromMinutes(30); |
| count = free_disk_space_count; |
| } |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(period_ms)); |
| } |
| |
| // Currently low disk space callback runs every 1 ms. If that test callback |
| // runs before we finish test teardown but after platform_ object is cleared, |
| // then we'll get error. Therefore, we need to set test callback interval |
| // back to 1 minute, so we will not have any race condition. |
| service_.set_low_disk_notification_period_ms(60 * 1000); |
| // Wait for the change to take effect. |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(period_ms * 5)); |
| |
| // Cleanup invokable lambdas so they don't capture this test variables anymore |
| Mock::VerifyAndClear(&homedirs_); |
| Mock::VerifyAndClear(&platform_); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckAutoCleanupCallbackFirst) { |
| // Checks that DoAutoCleanup() is called first right after init. |
| // Service will schedule first cleanup right after its init. |
| EXPECT_CALL(homedirs_, FreeDiskSpace()).Times(1); |
| service_.set_low_disk_notification_period_ms(1000); // 1s - long enough |
| service_.Initialize(); |
| // short delay to see the first invocation |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); |
| } |
| |
| static gboolean SignalCounter(GSignalInvocationHint *ihint, |
| guint n_param_values, |
| const GValue *param_values, |
| gpointer data) { |
| int *count = reinterpret_cast<int *>(data); |
| (*count)++; |
| return true; |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckLowDiskCallback) { |
| // Checks that LowDiskCallback is called periodically. |
| EXPECT_CALL(homedirs_, AmountOfFreeDiskSpace()) |
| .Times(AtLeast(5)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold - 1)) |
| .WillRepeatedly(Return(kNotifyDiskSpaceThreshold + 1)); |
| // Checks that DoAutoCleanup is called second time ahead of schedule |
| // if disk space goes below threshold and recovers back to normal. |
| EXPECT_CALL(homedirs_, FreeDiskSpace()).Times(2); |
| |
| service_.set_low_disk_notification_period_ms(2); |
| |
| guint low_disk_space_signal = |
| g_signal_lookup("low_disk_space", gobject::cryptohome_get_type()); |
| if (!low_disk_space_signal) { |
| low_disk_space_signal = g_signal_new( |
| "low_disk_space", gobject::cryptohome_get_type(), G_SIGNAL_RUN_LAST, 0, |
| nullptr, nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_UINT64); |
| } |
| int count_signals = 0; |
| gulong hook_id = g_signal_add_emission_hook( |
| low_disk_space_signal, 0, SignalCounter, &count_signals, nullptr); |
| |
| service_.Initialize(); |
| |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| EXPECT_EQ(1, count_signals); |
| g_signal_remove_emission_hook(low_disk_space_signal, hook_id); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckLowDiskCallbackFreeDiskSpaceOnce) { |
| EXPECT_CALL(homedirs_, AmountOfFreeDiskSpace()) |
| .Times(AtLeast(5)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillOnce(Return(kNotifyDiskSpaceThreshold + 1)) |
| .WillRepeatedly(Return(kNotifyDiskSpaceThreshold - 1)); |
| // Checks that DoAutoCleanup is called second time ahead of schedule |
| // if disk space goes below threshold and stays below forever. |
| EXPECT_CALL(homedirs_, FreeDiskSpace()).Times(2); |
| |
| service_.set_low_disk_notification_period_ms(2); |
| |
| service_.Initialize(); |
| |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, UploadAlertsCallback) { |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| tpm_init.set_tpm(&tpm); |
| |
| service_.set_use_tpm(true); |
| service_.set_tpm(&tpm); |
| service_.set_tpm_init(&tpm_init); |
| service_.set_initialize_tpm(true); |
| |
| // Checks that LowDiskCallback is called periodically. |
| EXPECT_CALL(tpm, GetAlertsData(_)).Times(::testing::AtLeast(1)); |
| |
| service_.Initialize(); |
| |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| // TODO(anatol): check that alerts are written to /var/lib/metrics/uma-events |
| } |
| |
| TEST_F(ServiceTest, NoDeadlocksInInitializeTpmComplete) { |
| char user[] = "chromeos-user"; |
| SetupMount(user); |
| |
| // Put a task on mount_thread_ that starts before InitializeTpmComplete |
| // and finishes after it exits. Verify it doesn't wait for |
| // InitializeTpmComplete forever. |
| base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent event_stop( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| bool finished = false; |
| service_.mount_thread_.task_runner()->PostTask(FROM_HERE, |
| base::Bind([](bool* finished, |
| base::WaitableEvent* event, |
| base::WaitableEvent* event_stop) { |
| event->Signal(); // Signal "Ready to start" |
| // Wait up to 2s for InitializeTpmComplete to finish |
| *finished = event_stop->TimedWait(base::TimeDelta::FromSeconds(2)); |
| event->Signal(); // Signal "Result ready" |
| }, &finished, &event, &event_stop)); |
| |
| event.Wait(); // Wait for "Ready to start" |
| service_.OwnershipCallback(true, true); |
| event_stop.Signal(); |
| event.Wait(); // Wait for "Result ready" |
| ASSERT_TRUE(finished); |
| } |
| |
| struct Mounts { |
| const FilePath src; |
| const FilePath dst; |
| }; |
| |
| const Mounts kShadowMounts[] = { |
| { FilePath("/home/.shadow/a"), FilePath("/home/user/0")}, |
| { FilePath("/home/.shadow/a"), FilePath("/home/root/0")}, |
| { FilePath("/home/.shadow/b"), FilePath("/home/user/1")}, |
| { FilePath("/home/.shadow/a"), FilePath("/home/chronos/user")}, |
| { FilePath("/home/.shadow/b"), FilePath("/home/root/1")}, |
| { FilePath("/home/user/b/Downloads"), |
| FilePath("/home/user/b/MyFiles/Downloads")}, |
| { FilePath("/home/chronos/u-b/Downloads"), |
| FilePath("/home/chronos/u-b/MyFiles/Downloads")}, |
| { FilePath("/home/chronos/user/Downloads"), |
| FilePath("/home/chronos/user/MyFiles/Downloads")}, |
| }; |
| const int kShadowMountsCount = 8; |
| |
| const Mounts kLoopDevMounts[] = { |
| { FilePath("/dev/loop7"), FilePath("/run/cryptohome/ephemeral_mount/1")}, |
| { FilePath("/dev/loop7"), FilePath("/home/user/0")}, |
| { FilePath("/dev/loop7"), FilePath("/home/root/0")}, |
| { FilePath("/dev/loop7"), FilePath("/home/chronos/u-1")}, |
| { FilePath("/dev/loop7"), FilePath("/home/chronos/user")}, |
| { FilePath("/dev/loop1"), FilePath("/opt/google/containers")}, |
| { FilePath("/dev/loop2"), FilePath("/home/root/1")}, |
| { FilePath("/dev/loop2"), FilePath("/home/user/1")}, |
| }; |
| const int kEphemeralMountsCount = 5; |
| |
| const Platform::LoopDevice kLoopDevices[] = { |
| { FilePath("/mnt/stateful_partition/encrypted.block"), |
| FilePath("/dev/loop0")}, |
| { FilePath("/run/cryptohome/ephemeral_data/1"), FilePath("/dev/loop7")}, |
| }; |
| |
| const FilePath kSparseFiles[] = { |
| FilePath("/run/cryptohome/ephemeral_data/2"), |
| FilePath("/run/cryptohome/ephemeral_data/1"), |
| }; |
| |
| bool StaleShadowMounts( |
| const FilePath& from_prefix, |
| std::multimap<const FilePath, const FilePath>* mounts) { |
| if (from_prefix.value() == "/home/.shadow") { |
| if (!mounts) |
| return true; |
| const struct Mounts* m = &kShadowMounts[0]; |
| for (int i = 0; i < kShadowMountsCount; ++i, ++m) { |
| mounts->insert( |
| std::pair<const FilePath, const FilePath>(m->src, m->dst)); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool LoopDeviceMounts( |
| std::multimap<const FilePath, const FilePath>* mounts) { |
| if (!mounts) |
| return false; |
| const Mounts* m = kLoopDevMounts; |
| for (int i = 0; i < arraysize(kLoopDevMounts); ++i, ++m) |
| mounts->insert(std::make_pair(m->src, m->dst)); |
| return true; |
| } |
| |
| bool EnumerateSparseFiles(const base::FilePath& path, bool is_recursive, |
| std::vector<base::FilePath>* ent_list) { |
| if (path != FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir)) |
| return false; |
| ent_list->insert(ent_list->end(), std::begin(kSparseFiles), |
| std::end(kSparseFiles)); |
| return true; |
| } |
| |
| TEST_F(ServiceTest, CleanUpStale_NoOpenFiles_Ephemeral) { |
| // Check that when we have ephemeral mounts, no active mounts, |
| // and no open filehandles, all stale mounts are unmounted, loop device is |
| // detached and sparse file is deleted. |
| |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(homedirs_.shadow_root(), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>( |
| std::begin(kLoopDevices), std::end(kLoopDevices)))); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Invoke(LoopDeviceMounts)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Invoke(EnumerateSparseFiles)); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(kEphemeralMountsCount); |
| |
| for (int i = 0; i < kEphemeralMountsCount; ++i) { |
| EXPECT_CALL(platform_, Unmount(kLoopDevMounts[i].dst, true, _)) |
| .WillRepeatedly(Return(true)); |
| } |
| EXPECT_CALL(platform_, DetachLoop(FilePath("/dev/loop7"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kSparseFiles[0], _)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kSparseFiles[1], _)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kLoopDevMounts[0].dst, _)) |
| .WillOnce(Return(true)); |
| EXPECT_FALSE(service_.CleanUpStaleMounts(false)); |
| } |
| |
| TEST_F(ServiceTest, CleanUpStale_OpenLegacy_Ephemeral) { |
| // Check that when we have ephemeral mounts, no active mounts, |
| // and some open filehandles to the legacy homedir, everything is kept. |
| |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(homedirs_.shadow_root(), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>( |
| std::begin(kLoopDevices), std::end(kLoopDevices)))); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Invoke(LoopDeviceMounts)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Invoke(EnumerateSparseFiles)); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(kEphemeralMountsCount - 1); |
| std::vector<ProcessInformation> processes(1); |
| processes[0].set_process_id(1); |
| EXPECT_CALL(platform_, |
| GetProcessesWithOpenFiles(FilePath("/home/chronos/user"), _)) |
| .Times(1) |
| .WillRepeatedly(SetArgPointee<1>(processes)); |
| |
| EXPECT_CALL(platform_, Unmount(_, _, _)) |
| .Times(0); |
| EXPECT_TRUE(service_.CleanUpStaleMounts(false)); |
| } |
| |
| TEST_F(ServiceTest, CleanUpStale_OpenLegacy_Ephemeral_Forced) { |
| // Check that when we have ephemeral mounts, no active mounts, |
| // and some open filehandles to the legacy homedir, but cleanup is forced, |
| // all mounts are unmounted, loop device is detached and file is deleted. |
| |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(homedirs_.shadow_root(), _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>( |
| std::begin(kLoopDevices), std::end(kLoopDevices)))); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Invoke(LoopDeviceMounts)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Invoke(EnumerateSparseFiles)); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(0); |
| |
| for (int i = 0; i < kEphemeralMountsCount; ++i) { |
| EXPECT_CALL(platform_, Unmount(kLoopDevMounts[i].dst, true, _)) |
| .WillRepeatedly(Return(true)); |
| } |
| EXPECT_CALL(platform_, DetachLoop(FilePath("/dev/loop7"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kSparseFiles[0], _)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kSparseFiles[1], _)).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, DeleteFile(kLoopDevMounts[0].dst, _)) |
| .WillOnce(Return(true)); |
| EXPECT_FALSE(service_.CleanUpStaleMounts(true)); |
| } |
| |
| TEST_F(ServiceTest, CleanUpStale_EmptyMap_NoOpenFiles_ShadowOnly) { |
| // Check that when we have a bunch of stale shadow mounts, no active mounts, |
| // and no open filehandles, all stale mounts are unmounted. |
| |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillOnce(Invoke(StaleShadowMounts)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>())); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(kShadowMountsCount); |
| EXPECT_CALL(platform_, Unmount(_, true, _)) |
| .Times(kShadowMountsCount) |
| .WillRepeatedly(Return(true)); |
| EXPECT_FALSE(service_.CleanUpStaleMounts(false)); |
| } |
| |
| TEST_F(ServiceTest, CleanUpStale_EmptyMap_OpenLegacy_ShadowOnly) { |
| // Check that when we have a bunch of stale shadow mounts, no active mounts, |
| // and some open filehandles to the legacy homedir, all mounts without |
| // filehandles are unmounted. |
| |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillOnce(Invoke(StaleShadowMounts)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>())); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Return(false)); |
| std::vector<ProcessInformation> processes(1); |
| processes[0].set_process_id(1); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(kShadowMountsCount - 1); |
| EXPECT_CALL(platform_, |
| GetProcessesWithOpenFiles(FilePath("/home/chronos/user"), _)) |
| .Times(1) |
| .WillRepeatedly(SetArgPointee<1>(processes)); |
| EXPECT_CALL( |
| platform_, |
| Unmount(Property(&FilePath::value, |
| AnyOf(EndsWith("/1"), EndsWith("/MyFiles/Downloads"))), |
| true, _)) |
| .Times(5) |
| .WillRepeatedly(Return(true)); |
| EXPECT_TRUE(service_.CleanUpStaleMounts(false)); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, |
| CleanUpStale_FilledMap_NoOpenFiles_ShadowOnly) { |
| // Checks that when we have a bunch of stale shadow mounts, some active |
| // mounts, and no open filehandles, all inactive mounts are unmounted. |
| |
| // ownership handed off to the Service MountMap |
| MockMountFactory mount_factory; |
| MockMount* mount = new MockMount(); |
| EXPECT_CALL(mount_factory, New()) |
| .WillOnce(Return(mount)); |
| service_.set_mount_factory(&mount_factory); |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>())); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Return(false)); |
| ASSERT_TRUE(service_.Initialize()); |
| |
| EXPECT_CALL(*mount, Init(&platform_, service_.crypto(), _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*mount, MountCryptohome(_, _, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*mount, UpdateCurrentUserActivityTimestamp(_)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>())); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Return(false)); |
| |
| gint error_code = 0; |
| gboolean result = FALSE; |
| ASSERT_TRUE(service_.Mount("foo@bar.net", "key", true, false, |
| &error_code, &result, nullptr)); |
| ASSERT_EQ(TRUE, result); |
| EXPECT_CALL(platform_, GetMountsBySourcePrefix(_, _)) |
| .WillOnce(Invoke(StaleShadowMounts)); |
| EXPECT_CALL(platform_, GetAttachedLoopDevices()) |
| .WillRepeatedly(Return(std::vector<Platform::LoopDevice>())); |
| EXPECT_CALL(platform_, GetLoopDeviceMounts(_)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, |
| EnumerateDirectoryEntries( |
| FilePath(kEphemeralCryptohomeDir).Append(kSparseFileDir), _, _)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(platform_, GetProcessesWithOpenFiles(_, _)) |
| .Times(kShadowMountsCount); |
| |
| EXPECT_CALL(*mount, OwnsMountPoint(_)) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(*mount, OwnsMountPoint(FilePath("/home/user/1"))) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*mount, OwnsMountPoint(FilePath("/home/root/1"))) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, |
| Unmount(Property(&FilePath::value, EndsWith("/0")), true, _)) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Unmount(FilePath("/home/chronos/user"), true, _)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(platform_, Unmount(Property(&FilePath::value, |
| EndsWith("/MyFiles/Downloads")), |
| true, _)) |
| .Times(3) |
| .WillRepeatedly(Return(true)); |
| |
| std::vector<std::string> fake_token_list; |
| fake_token_list.push_back("/home/chronos/user/token"); |
| fake_token_list.push_back("/home/user/1/token"); |
| fake_token_list.push_back("/home/root/1/token"); |
| EXPECT_CALL(chaps_client_, GetTokenList(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(fake_token_list), |
| Return(true))); |
| |
| EXPECT_CALL(chaps_client_, |
| UnloadToken(_, FilePath("/home/chronos/user/token"))) |
| .Times(1); |
| |
| // Expect that CleanUpStaleMounts() tells us it skipped no mounts. |
| EXPECT_FALSE(service_.CleanUpStaleMounts(false)); |
| } |
| |
| class ServiceExTest : public ServiceTest { |
| public: |
| ServiceExTest() = default; |
| ~ServiceExTest() override = default; |
| |
| VaultKeyset* GetNiceMockVaultKeyset(const std::string& obfuscated_username, |
| const std::string& key_label) const { |
| std::unique_ptr<VaultKeyset> mvk(new NiceMock<MockVaultKeyset>); |
| mvk->mutable_serialized()->mutable_key_data()->set_label(key_label); |
| return mvk.release(); |
| } |
| |
| protected: |
| void PrepareArguments() { |
| id_.reset(new AccountIdentifier); |
| auth_.reset(new AuthorizationRequest); |
| add_req_.reset(new AddKeyRequest); |
| check_req_.reset(new CheckKeyRequest); |
| migrate_req_.reset(new MigrateKeyRequest); |
| mount_req_.reset(new MountRequest); |
| remove_req_.reset(new RemoveKeyRequest); |
| list_keys_req_.reset(new ListKeysRequest); |
| } |
| |
| template<class ProtoBuf> |
| brillo::Blob BlobFromProtobuf(const ProtoBuf& pb) { |
| std::string serialized; |
| CHECK(pb.SerializeToString(&serialized)); |
| return brillo::BlobFromString(serialized); |
| } |
| |
| template <class ProtoBuf> |
| brillo::SecureBlob SecureBlobFromProtobuf(const ProtoBuf& pb) { |
| std::string serialized; |
| CHECK(pb.SerializeToString(&serialized)); |
| return brillo::SecureBlob(serialized); |
| } |
| |
| std::unique_ptr<AccountIdentifier> id_; |
| std::unique_ptr<AuthorizationRequest> auth_; |
| std::unique_ptr<AddKeyRequest> add_req_; |
| std::unique_ptr<CheckKeyRequest> check_req_; |
| std::unique_ptr<MigrateKeyRequest> migrate_req_; |
| std::unique_ptr<MountRequest> mount_req_; |
| std::unique_ptr<RemoveKeyRequest> remove_req_; |
| std::unique_ptr<ListKeysRequest> list_keys_req_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceExTest); |
| }; |
| |
| TEST_F(ServiceExTest, MountInvalidArgsNoEmail) { |
| PrepareArguments(); |
| // Run will never be called because we aren't running the event loop. |
| // For the same reason, DoMountEx is called directly. |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, MountInvalidArgsNoSecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, MountInvalidArgsEmptySecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret(""); |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, MountInvalidArgsCreateWithNoKey) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("blerg"); |
| mount_req_->mutable_create(); |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("CreateRequest supplied with no keys", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, MountInvalidArgsCreateWithEmptyKey) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("blerg"); |
| mount_req_->mutable_create()->add_keys(); |
| // TODO(wad) Add remaining missing field tests and NULL tests |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("CreateRequest Keys are not fully specified", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, MountPublicWithExistingMounts) { |
| constexpr char kUser[] = "chromeos-user"; |
| PrepareArguments(); |
| SetupMount("foo@gmail.com"); |
| |
| id_->set_account_id(kUser); |
| mount_req_->set_public_mount(true); |
| EXPECT_CALL(homedirs_, Exists(_)).WillOnce(Return(true)); |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, MountPublicUsesPublicMountPasskey) { |
| constexpr char kUser[] = "chromeos-user"; |
| PrepareArguments(); |
| |
| id_->set_account_id(kUser); |
| mount_req_->set_public_mount(true); |
| EXPECT_CALL(homedirs_, Exists(_)).WillOnce(testing::InvokeWithoutArgs([&]() { |
| SetupMount(kUser); |
| EXPECT_CALL(*mount_, MountCryptohome(_, _, _)) |
| .WillOnce(testing::Invoke([](const Credentials& credentials, |
| const Mount::MountArgs& mount_args, |
| MountError* error) { |
| brillo::SecureBlob passkey; |
| credentials.GetPasskey(&passkey); |
| // Tests that the passkey is filled when public_mount is set. |
| EXPECT_FALSE(passkey.empty()); |
| return true; |
| })); |
| return true; |
| })); |
| service_.DoMountEx(std::move(id_), std::move(auth_), std::move(mount_req_), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| } |
| |
| TEST_F(ServiceExTest, AddKeyInvalidArgsNoEmail) { |
| PrepareArguments(); |
| // Run will never be called because we aren't running the event loop. |
| // For the same reason, DoMountEx is called directly. |
| service_.DoAddKeyEx(id_.get(), auth_.get(), add_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, AddKeyInvalidArgsNoSecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| service_.DoAddKeyEx(id_.get(), auth_.get(), add_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, AddKeyInvalidArgsNoNewKeySet) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("blerg"); |
| add_req_->clear_key(); |
| service_.DoAddKeyEx(id_.get(), auth_.get(), add_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No new key supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, AddKeyInvalidArgsNoKeyFilled) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("blerg"); |
| add_req_->mutable_key(); |
| service_.DoAddKeyEx(id_.get(), auth_.get(), add_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No new key supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, AddKeyInvalidArgsNoNewKeyLabel) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("blerg"); |
| add_req_->mutable_key(); |
| // No label |
| add_req_->mutable_key()->set_secret("some secret"); |
| service_.DoAddKeyEx(id_.get(), auth_.get(), add_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No new key label supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeySuccessTest) { |
| constexpr char kUser[] = "chromeos-user"; |
| constexpr char kKey[] = "274146c6e8886a843ddfea373e2dc71b"; |
| |
| PrepareArguments(); |
| SetupMount(kUser); |
| |
| id_->set_account_id(kUser); |
| auth_->mutable_key()->set_secret(kKey); |
| CheckKeyRequest req; |
| |
| EXPECT_CALL(*mount_, AreSameUser(_)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(homedirs_, Exists(_)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(homedirs_, AreCredentialsValid(_)) |
| .WillOnce(Return(true)); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), nullptr); |
| |
| // Expect an empty reply as success. |
| DispatchEvents(); |
| EXPECT_TRUE(ReplyIsEmpty()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeyMountTest) { |
| constexpr char kUser[] = "chromeos-user"; |
| constexpr char kKey[] = "274146c6e8886a843ddfea373e2dc71b"; |
| |
| PrepareArguments(); |
| SetupMount(kUser); |
| |
| id_->set_account_id(kUser); |
| auth_->mutable_key()->set_secret(kKey); |
| |
| EXPECT_CALL(*mount_, AreSameUser(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*mount_, AreValid(_)).WillOnce(Return(true)); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), nullptr); |
| |
| // Expect an empty reply as success. |
| DispatchEvents(); |
| EXPECT_TRUE(ReplyIsEmpty()); |
| Mock::VerifyAndClearExpectations(mount_.get()); |
| |
| // Rinse and repeat but fail. |
| ClearReplies(); |
| EXPECT_CALL(*mount_, AreSameUser(_)).WillOnce(Return(true)); |
| EXPECT_CALL(*mount_, AreValid(_)).WillOnce(Return(false)); |
| EXPECT_CALL(homedirs_, Exists(_)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(homedirs_, AreCredentialsValid(_)).WillOnce(Return(false)); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), nullptr); |
| |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED, reply()->error()); |
| } |
| |
| MATCHER_P(CredentialsEqual, credentials, "") { |
| const Credentials& expected_creds = credentials; |
| brillo::SecureBlob l; |
| brillo::SecureBlob r; |
| expected_creds.GetPasskey(&l); |
| arg.GetPasskey(&r); |
| return expected_creds.username() == arg.username() && l == r; |
| } |
| |
| TEST_F(ServiceExTest, MigrateKeyTest) { |
| constexpr char kUser[] = "chromeos-user"; |
| constexpr char kOldKey[] = "274146c6e8886a843ddfea373e2dc71b"; |
| constexpr char kNewKey[] = "274146c6e8886a843ddfea373e2dc71c"; |
| |
| PrepareArguments(); |
| SetupMount(kUser); |
| |
| id_->set_account_id(kUser); |
| auth_->mutable_key()->set_secret(kOldKey); |
| migrate_req_->set_secret(kNewKey); |
| |
| Credentials credentials(kUser, SecureBlob(kNewKey)); |
| |
| EXPECT_CALL(homedirs_, |
| Migrate(CredentialsEqual(testing::ByRef(credentials)), |
| SecureBlob(kOldKey), |
| scoped_refptr<cryptohome::Mount>(mount_.get()))) |
| .WillRepeatedly(Return(true)); |
| service_.DoMigrateKeyEx(id_.get(), auth_.get(), migrate_req_.get(), nullptr); |
| |
| // Expect an empty reply as success. |
| DispatchEvents(); |
| EXPECT_TRUE(ReplyIsEmpty()); |
| Mock::VerifyAndClearExpectations(mount_.get()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeyHomedirsTest) { |
| constexpr char kUser[] = "chromeos-user"; |
| constexpr char kKey[] = "274146c6e8886a843ddfea373e2dc71b"; |
| |
| PrepareArguments(); |
| SetupMount(kUser); |
| |
| id_->set_account_id(kUser); |
| auth_->mutable_key()->set_secret(kKey); |
| |
| EXPECT_CALL(*mount_, AreSameUser(_)).WillRepeatedly(Return(false)); |
| EXPECT_CALL(homedirs_, Exists(_)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(homedirs_, AreCredentialsValid(_)).WillOnce(Return(true)); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), nullptr); |
| |
| // Expect an empty reply as success. |
| DispatchEvents(); |
| EXPECT_TRUE(ReplyIsEmpty()); |
| Mock::VerifyAndClearExpectations(&homedirs_); |
| |
| // Ensure failure |
| ClearReplies(); |
| EXPECT_CALL(homedirs_, Exists(_)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(homedirs_, AreCredentialsValid(_)).WillOnce(Return(false)); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), nullptr); |
| |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_AUTHORIZATION_KEY_FAILED, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeyInvalidArgsNoEmail) { |
| PrepareArguments(); |
| // Run will never be called because we aren't running the event loop. |
| // For the same reason, DoMountEx is called directly. |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeyInvalidArgsNoSecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, CheckKeyInvalidArgsEmptySecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret(""); |
| service_.DoCheckKeyEx(id_.get(), auth_.get(), check_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveKeyInvalidArgsNoEmail) { |
| PrepareArguments(); |
| // Run will never be called because we aren't running the event loop. |
| // For the same reason, DoMountEx is called directly. |
| service_.DoRemoveKeyEx(id_.get(), auth_.get(), remove_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveKeyInvalidArgsNoSecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| service_.DoRemoveKeyEx(id_.get(), auth_.get(), remove_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveKeyInvalidArgsEmptySecret) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret(""); |
| service_.DoRemoveKeyEx(id_.get(), auth_.get(), remove_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No key secret supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveKeyInvalidArgsEmptyRemoveLabel) { |
| PrepareArguments(); |
| id_->set_account_id("foo@gmail.com"); |
| auth_->mutable_key()->set_secret("some secret"); |
| remove_req_->mutable_key()->mutable_data(); |
| service_.DoRemoveKeyEx(id_.get(), auth_.get(), remove_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No label provided for target key", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxSignSuccess) { |
| SecureBlob test_signature("test"); |
| EXPECT_CALL(lockbox_, Sign(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>(test_signature), |
| Return(true))); |
| |
| SignBootLockboxRequest request; |
| request.set_data("test_data"); |
| service_.DoSignBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_TRUE(reply()->HasExtension(SignBootLockboxReply::reply)); |
| EXPECT_EQ("test", |
| reply()->GetExtension(SignBootLockboxReply::reply).signature()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxSignBadArgs) { |
| // Try with bad proto data. |
| service_.DoSignBootLockbox(brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| // Try with |data| not set. |
| ClearReplies(); |
| SignBootLockboxRequest request; |
| service_.DoSignBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxSignError) { |
| EXPECT_CALL(lockbox_, Sign(_, _)) |
| .WillRepeatedly(Return(false)); |
| |
| SignBootLockboxRequest request; |
| request.set_data("test_data"); |
| service_.DoSignBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_LOCKBOX_CANNOT_SIGN, reply()->error()); |
| EXPECT_FALSE(reply()->HasExtension(SignBootLockboxReply::reply)); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxVerifySuccess) { |
| EXPECT_CALL(lockbox_, Verify(_, _)) |
| .WillRepeatedly(Return(true)); |
| |
| VerifyBootLockboxRequest request; |
| request.set_data("test_data"); |
| request.set_signature("test_signature"); |
| service_.DoVerifyBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_FALSE(reply()->HasExtension(SignBootLockboxReply::reply)); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxVerifyBadArgs) { |
| // Try with bad proto data. |
| service_.DoVerifyBootLockbox(brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| // Try with |signature| not set. |
| ClearReplies(); |
| VerifyBootLockboxRequest request; |
| request.set_data("test_data"); |
| service_.DoVerifyBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| // Try with |data| not set. |
| ClearReplies(); |
| VerifyBootLockboxRequest request2; |
| request2.set_signature("test_data"); |
| service_.DoVerifyBootLockbox(BlobFromProtobuf(request2), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxVerifyError) { |
| EXPECT_CALL(lockbox_, Verify(_, _)) |
| .WillRepeatedly(Return(false)); |
| |
| VerifyBootLockboxRequest request; |
| request.set_data("test_data"); |
| request.set_signature("test_signature"); |
| service_.DoVerifyBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_LOCKBOX_SIGNATURE_INVALID, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxFinalizeSuccess) { |
| EXPECT_CALL(lockbox_, FinalizeBoot()) |
| .WillRepeatedly(Return(true)); |
| |
| FinalizeBootLockboxRequest request; |
| service_.DoFinalizeBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_FALSE(reply()->HasExtension(SignBootLockboxReply::reply)); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxFinalizeBadArgs) { |
| // Try with bad proto data. |
| service_.DoFinalizeBootLockbox(brillo::BlobFromString("not_a_protobuf"), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, BootLockboxFinalizeError) { |
| EXPECT_CALL(lockbox_, FinalizeBoot()) |
| .WillRepeatedly(Return(false)); |
| |
| FinalizeBootLockboxRequest request; |
| service_.DoFinalizeBootLockbox(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_TPM_COMM_ERROR, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, GetBootAttributeSuccess) { |
| EXPECT_CALL(boot_attributes_, Get(_, _)) |
| .WillRepeatedly(DoAll(SetArgPointee<1>("1234"), Return(true))); |
| |
| GetBootAttributeRequest request; |
| request.set_name("test"); |
| service_.DoGetBootAttribute(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_TRUE(reply()->HasExtension(GetBootAttributeReply::reply)); |
| EXPECT_EQ("1234", |
| reply()->GetExtension(GetBootAttributeReply::reply).value()); |
| } |
| |
| TEST_F(ServiceExTest, GetBootAttributeBadArgs) { |
| // Try with bad proto data. |
| service_.DoGetBootAttribute(brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, GetBootAttributeError) { |
| EXPECT_CALL(boot_attributes_, Get(_, _)) |
| .WillRepeatedly(Return(false)); |
| |
| GetBootAttributeRequest request; |
| request.set_name("test"); |
| service_.DoGetBootAttribute(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_BOOT_ATTRIBUTE_NOT_FOUND, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, SetBootAttributeSuccess) { |
| SetBootAttributeRequest request; |
| request.set_name("test"); |
| request.set_value("1234"); |
| service_.DoSetBootAttribute(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| } |
| |
| TEST_F(ServiceExTest, SetBootAttributeBadArgs) { |
| // Try with bad proto data. |
| service_.DoSetBootAttribute(brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, FlushAndSignBootAttributesSuccess) { |
| EXPECT_CALL(boot_attributes_, FlushAndSign()) |
| .WillRepeatedly(Return(true)); |
| |
| FlushAndSignBootAttributesRequest request; |
| service_.DoFlushAndSignBootAttributes(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| } |
| |
| TEST_F(ServiceExTest, FlushAndSignBootAttributesBadArgs) { |
| // Try with bad proto data. |
| service_.DoFlushAndSignBootAttributes( |
| brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, FlushAndSignBootAttributesError) { |
| EXPECT_CALL(boot_attributes_, FlushAndSign()) |
| .WillRepeatedly(Return(false)); |
| |
| FlushAndSignBootAttributesRequest request; |
| service_.DoFlushAndSignBootAttributes(BlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_BOOT_ATTRIBUTES_CANNOT_SIGN, reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, GetLoginStatusSuccess) { |
| EXPECT_CALL(homedirs_, GetPlainOwner(_)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(lockbox_, IsFinalized()) |
| .WillOnce(Return(false)); |
| |
| GetLoginStatusRequest request; |
| service_.DoGetLoginStatus(SecureBlobFromProtobuf(request), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_TRUE(reply()->HasExtension(GetLoginStatusReply::reply)); |
| EXPECT_TRUE( |
| reply()->GetExtension(GetLoginStatusReply::reply).owner_user_exists()); |
| EXPECT_FALSE(reply() |
| ->GetExtension(GetLoginStatusReply::reply) |
| .boot_lockbox_finalized()); |
| } |
| |
| TEST_F(ServiceExTest, GetLoginStatusBadArgs) { |
| // Try with bad proto data. |
| service_.DoVerifyBootLockbox(brillo::BlobFromString("not_a_protobuf"), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_NE("", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, GetKeyDataExNoMatch) { |
| PrepareArguments(); |
| |
| EXPECT_CALL(homedirs_, Exists(_)) |
| .WillRepeatedly(Return(true)); |
| |
| id_->set_account_id("unittest@example.com"); |
| GetKeyDataRequest req; |
| req.mutable_key()->mutable_data()->set_label("non-existent label"); |
| // Ensure there are no matches. |
| EXPECT_CALL(homedirs_, GetVaultKeyset(_, _)) |
| .Times(1) |
| .WillRepeatedly(Return(static_cast<VaultKeyset*>(NULL))); |
| service_.DoGetKeyDataEx(id_.get(), auth_.get(), &req, NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| GetKeyDataReply sub_reply = reply()->GetExtension(GetKeyDataReply::reply); |
| EXPECT_EQ(0, sub_reply.key_data_size()); |
| } |
| |
| TEST_F(ServiceExTest, GetKeyDataExOneMatch) { |
| // Request the single key by label. |
| PrepareArguments(); |
| |
| static const char *kExpectedLabel = "find-me"; |
| GetKeyDataRequest req; |
| req.mutable_key()->mutable_data()->set_label(kExpectedLabel); |
| |
| EXPECT_CALL(homedirs_, Exists(_)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(homedirs_, GetVaultKeyset(_, _)) |
| .Times(1) |
| .WillRepeatedly(Invoke(this, &ServiceExTest::GetNiceMockVaultKeyset)); |
| |
| id_->set_account_id("unittest@example.com"); |
| service_.DoGetKeyDataEx(id_.get(), auth_.get(), &req, NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| |
| GetKeyDataReply sub_reply = reply()->GetExtension(GetKeyDataReply::reply); |
| ASSERT_EQ(1, sub_reply.key_data_size()); |
| EXPECT_EQ(std::string(kExpectedLabel), sub_reply.key_data(0).label()); |
| } |
| |
| TEST_F(ServiceExTest, GetKeyDataInvalidArgsNoEmail) { |
| PrepareArguments(); |
| GetKeyDataRequest req; |
| service_.DoGetKeyDataEx(id_.get(), auth_.get(), &req, NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, ListKeysInvalidArgsNoEmail) { |
| PrepareArguments(); |
| // Run will never be called because we aren't running the event loop. |
| // For the same reason, DoListKeysEx is called directly. |
| service_.DoListKeysEx(id_.get(), auth_.get(), list_keys_req_.get(), NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(error_reply()); |
| EXPECT_EQ("No email supplied", *error_reply()); |
| } |
| |
| TEST_F(ServiceExTest, GetFirmwareManagementParametersSuccess) { |
| brillo::Blob hash = brillo::BlobFromString("its_a_hash"); |
| |
| EXPECT_CALL(fwmp_, Load()) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(fwmp_, GetFlags(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(0x1234), Return(true))); |
| EXPECT_CALL(fwmp_, GetDeveloperKeyHash(_)) |
| .WillRepeatedly(DoAll(SetArgPointee<0>(hash), Return(true))); |
| |
| GetFirmwareManagementParametersRequest request; |
| service_.DoGetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_TRUE( |
| reply()->HasExtension(GetFirmwareManagementParametersReply::reply)); |
| EXPECT_EQ(reply() |
| ->GetExtension(GetFirmwareManagementParametersReply::reply) |
| .flags(), |
| 0x1234); |
| EXPECT_EQ(reply() |
| ->GetExtension(GetFirmwareManagementParametersReply::reply) |
| .developer_key_hash(), |
| brillo::BlobToString(hash)); |
| } |
| |
| TEST_F(ServiceExTest, GetFirmwareManagementParametersError) { |
| EXPECT_CALL(fwmp_, Load()) |
| .WillRepeatedly(Return(false)); |
| |
| GetFirmwareManagementParametersRequest request; |
| service_.DoGetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID, |
| reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, SetFirmwareManagementParametersSuccess) { |
| brillo::Blob hash = brillo::BlobFromString("its_a_hash"); |
| brillo::Blob out_hash; |
| |
| EXPECT_CALL(fwmp_, Create()) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(fwmp_, Store(0x1234, _)) |
| .WillOnce(DoAll(SaveArgPointee<1>(&out_hash), Return(true))); |
| |
| SetFirmwareManagementParametersRequest request; |
| request.set_flags(0x1234); |
| request.set_developer_key_hash(brillo::BlobToString(hash)); |
| service_.DoSetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| EXPECT_EQ(hash, out_hash); |
| } |
| |
| TEST_F(ServiceExTest, SetFirmwareManagementParametersNoHash) { |
| brillo::Blob hash(0); |
| |
| EXPECT_CALL(fwmp_, Create()) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(fwmp_, Store(0x1234, NULL)) |
| .WillOnce(Return(true)); |
| |
| SetFirmwareManagementParametersRequest request; |
| request.set_flags(0x1234); |
| service_.DoSetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| } |
| |
| TEST_F(ServiceExTest, SetFirmwareManagementParametersCreateError) { |
| brillo::Blob hash = brillo::BlobFromString("its_a_hash"); |
| |
| EXPECT_CALL(fwmp_, Create()) |
| .WillOnce(Return(false)); |
| |
| SetFirmwareManagementParametersRequest request; |
| request.set_flags(0x1234); |
| request.set_developer_key_hash(brillo::BlobToString(hash)); |
| service_.DoSetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE, |
| reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, SetFirmwareManagementParametersStoreError) { |
| brillo::Blob hash = brillo::BlobFromString("its_a_hash"); |
| |
| EXPECT_CALL(fwmp_, Create()) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(fwmp_, Store(_, _)) |
| .WillOnce(Return(false)); |
| |
| SetFirmwareManagementParametersRequest request; |
| request.set_flags(0x1234); |
| request.set_developer_key_hash(brillo::BlobToString(hash)); |
| service_.DoSetFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_STORE, |
| reply()->error()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveFirmwareManagementParametersSuccess) { |
| EXPECT_CALL(fwmp_, Destroy()) |
| .WillOnce(Return(true)); |
| |
| RemoveFirmwareManagementParametersRequest request; |
| service_.DoRemoveFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_FALSE(reply()->has_error()); |
| } |
| |
| TEST_F(ServiceExTest, RemoveFirmwareManagementParametersError) { |
| EXPECT_CALL(fwmp_, Destroy()) |
| .WillOnce(Return(false)); |
| |
| RemoveFirmwareManagementParametersRequest request; |
| service_.DoRemoveFirmwareManagementParameters(SecureBlobFromProtobuf(request), |
| NULL); |
| DispatchEvents(); |
| ASSERT_TRUE(reply()); |
| EXPECT_TRUE(reply()->has_error()); |
| EXPECT_EQ(CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_CANNOT_REMOVE, |
| reply()->error()); |
| } |
| |
| void ImmediatelySignalOwnership(TpmInit::OwnershipCallback callback) { |
| callback.Run(true, false); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckTpmInitRace) { |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| |
| // Emulate quick tpm initialization by calling the ownership callback from |
| // TpmInit::Init(). In reality, it is called from the thread created by |
| // TpmInit::AsyncTakeOwnership(), but since it's guarded by a command line |
| // switch, call it from Init() instead. It should be safe to call the |
| // ownership callback from the main thread. |
| EXPECT_CALL(tpm_init, Init(_)) |
| .WillOnce(Invoke(ImmediatelySignalOwnership)); |
| service_.set_tpm(&tpm); |
| service_.set_tpm_init(&tpm_init); |
| service_.set_initialize_tpm(true); |
| service_.Initialize(); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, CheckTpmGetPassword) { |
| NiceMock<MockTpmInit> tpm_init; |
| service_.set_tpm_init(&tpm_init); |
| |
| std::string pwd1_ascii_str("abcdefgh", 8); |
| SecureBlob pwd1_ascii_blob(pwd1_ascii_str); |
| std::string pwd2_non_ascii_str("ab\xB2\xFF\x00\xA0gh", 8); |
| SecureBlob pwd2_non_ascii_blob(pwd2_non_ascii_str); |
| std::wstring pwd2_non_ascii_wstr( |
| pwd2_non_ascii_blob.data(), |
| pwd2_non_ascii_blob.data() + pwd2_non_ascii_blob.size()); |
| std::string pwd2_non_ascii_str_utf8(base::SysWideToUTF8(pwd2_non_ascii_wstr)); |
| EXPECT_CALL(tpm_init, GetTpmPassword(_)) |
| .WillOnce(Return(false)) |
| .WillOnce(DoAll(SetArgPointee<0>(pwd1_ascii_blob), Return(true))) |
| .WillOnce(DoAll(SetArgPointee<0>(pwd2_non_ascii_blob), Return(true))); |
| |
| gchar* res_pwd; |
| GError* res_err; |
| // Return success and NULL if getting tpm password failed. |
| EXPECT_TRUE(service_.TpmGetPassword(&res_pwd, &res_err)); |
| EXPECT_EQ(NULL, res_pwd); |
| g_free(res_pwd); |
| // Check that the ASCII password is returned as is. |
| EXPECT_TRUE(service_.TpmGetPassword(&res_pwd, &res_err)); |
| EXPECT_EQ(0, memcmp(pwd1_ascii_str.data(), |
| res_pwd, |
| pwd1_ascii_str.size())); |
| g_free(res_pwd); |
| // Check that non-ASCII password is converted to UTF-8. |
| EXPECT_TRUE(service_.TpmGetPassword(&res_pwd, &res_err)); |
| EXPECT_EQ(0, memcmp(pwd2_non_ascii_str_utf8.data(), |
| res_pwd, |
| pwd2_non_ascii_str_utf8.size())); |
| g_free(res_pwd); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, InitializeArcDiskQuota) { |
| EXPECT_CALL(arc_disk_quota_, Initialize()).Times(1); |
| EXPECT_TRUE(service_.Initialize()); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, IsQuotaSupported) { |
| EXPECT_CALL(arc_disk_quota_, IsQuotaSupported()).WillOnce(Return(true)); |
| |
| GError* res_err; |
| gboolean quota_supported; |
| EXPECT_TRUE(service_.IsQuotaSupported("a_supported, &res_err)); |
| EXPECT_EQ(TRUE, quota_supported); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, GetCurrentSpaceForUid) { |
| EXPECT_CALL(arc_disk_quota_, GetCurrentSpaceForUid(10)).WillOnce(Return(20)); |
| GError* res_err; |
| gint64 cur_space; |
| EXPECT_TRUE(service_.GetCurrentSpaceForUid(10, &cur_space, &res_err)); |
| EXPECT_EQ(20, cur_space); |
| } |
| |
| TEST_F(ServiceTestNotInitialized, GetCurrentSpaceForGid) { |
| EXPECT_CALL(arc_disk_quota_, GetCurrentSpaceForGid(10)).WillOnce(Return(20)); |
| GError* res_err; |
| gint64 cur_space; |
| EXPECT_TRUE(service_.GetCurrentSpaceForGid(10, &cur_space, &res_err)); |
| EXPECT_EQ(20, cur_space); |
| } |
| |
| TEST_F(ServiceTest, PostTaskToEventLoop) { |
| // In this test, we take the IsQuotaSupported() function to test |
| // if PostTaskToEventLoop() actually causes the posted OnceClosure |
| // to run. |
| EXPECT_CALL(arc_disk_quota_, IsQuotaSupported()).WillOnce(Return(true)); |
| |
| service_.PostTaskToEventLoop(base::BindOnce([] (Service *service) { |
| GError* res_err; |
| gboolean quota_supported; |
| EXPECT_TRUE(service->IsQuotaSupported("a_supported, &res_err)); |
| EXPECT_EQ(TRUE, quota_supported); |
| }, base::Unretained(&service_))); |
| |
| DispatchEvents(); |
| |
| PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); |
| } |
| |
| } // namespace cryptohome |