| // Copyright 2021 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. |
| |
| #include <cryptohome/storage/cryptohome_vault.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <brillo/secure_blob.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/storage/encrypted_container/backing_device.h" |
| #include "cryptohome/storage/encrypted_container/backing_device_factory.h" |
| #include "cryptohome/storage/encrypted_container/encrypted_container.h" |
| #include "cryptohome/storage/encrypted_container/fake_backing_device.h" |
| #include "cryptohome/storage/encrypted_container/fake_encrypted_container_factory.h" |
| #include "cryptohome/storage/encrypted_container/filesystem_key.h" |
| #include "cryptohome/storage/mock_homedirs.h" |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::Return; |
| using ::testing::SetArgPointee; |
| |
| namespace cryptohome { |
| |
| namespace { |
| struct CryptohomeVaultTestParams { |
| CryptohomeVaultTestParams(EncryptedContainerType type, |
| EncryptedContainerType migrating_type, |
| EncryptedContainerType cache_type) |
| : container_type(type), |
| migrating_container_type(migrating_type), |
| cache_container_type(cache_type) {} |
| |
| EncryptedContainerType container_type; |
| EncryptedContainerType migrating_container_type; |
| EncryptedContainerType cache_container_type; |
| }; |
| } // namespace |
| |
| class CryptohomeVaultTest |
| : public ::testing::TestWithParam<CryptohomeVaultTestParams> { |
| public: |
| CryptohomeVaultTest() |
| : obfuscated_username_("foo"), |
| key_reference_({.fek_sig = brillo::SecureBlob("random keyref")}), |
| key_({.fek = brillo::SecureBlob("random key")}), |
| backing_dir_(ShadowRoot().Append(obfuscated_username_)), |
| encrypted_container_factory_(&platform_) {} |
| ~CryptohomeVaultTest() override = default; |
| |
| EncryptedContainerType ContainerType() { return GetParam().container_type; } |
| |
| EncryptedContainerType MigratingContainerType() { |
| return GetParam().migrating_container_type; |
| } |
| |
| EncryptedContainerType CacheContainerType() { |
| return GetParam().cache_container_type; |
| } |
| |
| EncryptedContainerConfig ConfigFromType(EncryptedContainerType type, |
| const std::string& name) { |
| EncryptedContainerConfig config; |
| config.type = type; |
| switch (type) { |
| case EncryptedContainerType::kEcryptfs: |
| config.backing_dir = backing_dir_.Append(kEcryptfsVaultDir); |
| break; |
| case EncryptedContainerType::kFscrypt: |
| config.backing_dir = backing_dir_.Append(kMountDir); |
| break; |
| case EncryptedContainerType::kDmcrypt: |
| config = { |
| .type = EncryptedContainerType::kDmcrypt, |
| .dmcrypt_config = { |
| .backing_device_config = |
| {.type = BackingDeviceType::kLogicalVolumeBackingDevice, |
| .name = name, |
| .size = 100 * 1024 * 1024, |
| .logical_volume = {.thinpool_name = "thinpool", |
| .physical_volume = |
| base::FilePath("/dev/sda1")}}, |
| .dmcrypt_device_name = "dmcrypt-" + name, |
| .dmcrypt_cipher = "aes-xts-plain64", |
| .mkfs_opts = {"-O", "^huge_file,^flex_bg,", "-E", |
| "discard,lazy_itable_init"}, |
| .tune2fs_opts = {"-O", "verity,quota", "-Q", |
| "usrquota,grpquota"}}}; |
| break; |
| default: |
| config.type = EncryptedContainerType::kUnknown; |
| break; |
| } |
| |
| return config; |
| } |
| |
| void ExpectEcryptfsSetup() { |
| EXPECT_CALL(platform_, AddEcryptfsAuthToken(_, _, _)) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| void ExpectFscryptSetup() { |
| EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, SetDirCryptoKey(backing_dir_.Append(kMountDir), _)) |
| .WillOnce(Return(true)); |
| } |
| |
| void ExpectDmcryptSetup(const std::string& name) { |
| base::FilePath backing_device_path = base::FilePath("/dev").Append(name); |
| base::FilePath dmcrypt_device("/dev/mapper/dmcrypt-" + name); |
| EXPECT_CALL(platform_, GetBlkSize(backing_device_path, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(1024 * 1024 * 1024), Return(true))); |
| EXPECT_CALL(platform_, UdevAdmSettle(dmcrypt_device, _)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, FormatExt4(_, _, _)).WillRepeatedly(Return(true)); |
| EXPECT_CALL(platform_, Tune2Fs(dmcrypt_device, _)).WillOnce(Return(true)); |
| } |
| |
| void ExpectEcryptfsTeardown() { |
| EXPECT_CALL(platform_, ClearUserKeyring()).WillRepeatedly(Return(true)); |
| } |
| |
| void ExpectFscryptTeardown() { |
| EXPECT_CALL(platform_, |
| InvalidateDirCryptoKey(_, backing_dir_.Append(kMountDir))) |
| .WillRepeatedly(Return(true)); |
| } |
| |
| void ExpectContainerSetup(EncryptedContainerType type) { |
| switch (type) { |
| case EncryptedContainerType::kEcryptfs: |
| ExpectEcryptfsSetup(); |
| break; |
| case EncryptedContainerType::kFscrypt: |
| ExpectFscryptSetup(); |
| break; |
| case EncryptedContainerType::kDmcrypt: |
| ExpectDmcryptSetup("data"); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ExpectCacheContainerSetup(EncryptedContainerType type) { |
| switch (type) { |
| case EncryptedContainerType::kDmcrypt: |
| ExpectDmcryptSetup("cache"); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void ExpectContainerTeardown(EncryptedContainerType type) { |
| switch (type) { |
| case EncryptedContainerType::kEcryptfs: |
| ExpectEcryptfsTeardown(); |
| break; |
| case EncryptedContainerType::kFscrypt: |
| ExpectFscryptTeardown(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void CreateExistingContainer(EncryptedContainerType type) { |
| switch (type) { |
| case EncryptedContainerType::kEcryptfs: |
| platform_.CreateDirectory(backing_dir_.Append(kEcryptfsVaultDir)); |
| break; |
| case EncryptedContainerType::kFscrypt: |
| platform_.CreateDirectory(backing_dir_.Append(kMountDir)); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void CheckContainersExist() { |
| // For newly created fscrypt containers, add the expectation that fetching |
| // the key state returns encrypted. |
| if (vault_->container_->GetType() == EncryptedContainerType::kFscrypt || |
| (vault_->migrating_container_ && |
| vault_->migrating_container_->GetType() == |
| EncryptedContainerType::kFscrypt)) { |
| EXPECT_CALL(platform_, |
| GetDirCryptoKeyState(backing_dir_.Append(kMountDir))) |
| .WillOnce(Return(dircrypto::KeyState::ENCRYPTED)); |
| } |
| EXPECT_TRUE(vault_->container_->Exists()); |
| if (vault_->migrating_container_) { |
| EXPECT_TRUE(vault_->migrating_container_->Exists()); |
| } |
| if (vault_->cache_container_) { |
| EXPECT_TRUE(vault_->cache_container_->Exists()); |
| } |
| } |
| |
| void ExpectVaultSetup() { |
| EXPECT_CALL(platform_, ClearUserKeyring()).WillOnce(Return(true)); |
| EXPECT_CALL(platform_, SetupProcessKeyring()).WillOnce(Return(true)); |
| } |
| |
| void ExpectVaultTeardownOnDestruction() { |
| ExpectContainerTeardown(ContainerType()); |
| ExpectContainerTeardown(MigratingContainerType()); |
| ExpectContainerTeardown(CacheContainerType()); |
| } |
| |
| void GenerateVault(bool create_container, |
| bool create_migrating_container, |
| bool create_cache_container) { |
| std::unique_ptr<EncryptedContainer> container = |
| encrypted_container_factory_.Generate( |
| ConfigFromType(ContainerType(), "data"), key_reference_, |
| create_container); |
| if (create_container) |
| CreateExistingContainer(ContainerType()); |
| |
| std::unique_ptr<EncryptedContainer> migrating_container = |
| encrypted_container_factory_.Generate( |
| ConfigFromType(MigratingContainerType(), "data"), key_reference_, |
| create_migrating_container); |
| if (create_migrating_container) |
| CreateExistingContainer(MigratingContainerType()); |
| |
| std::unique_ptr<EncryptedContainer> cache_container = |
| encrypted_container_factory_.Generate( |
| ConfigFromType(CacheContainerType(), "cache"), key_reference_, |
| create_cache_container); |
| if (create_cache_container) |
| CreateExistingContainer(CacheContainerType()); |
| |
| vault_ = std::make_unique<CryptohomeVault>( |
| obfuscated_username_, std::move(container), |
| std::move(migrating_container), std::move(cache_container), &platform_); |
| } |
| |
| protected: |
| const std::string obfuscated_username_; |
| const FileSystemKeyReference key_reference_; |
| const FileSystemKey key_; |
| const base::FilePath backing_dir_; |
| |
| MockHomeDirs homedirs_; |
| MockPlatform platform_; |
| FakeEncryptedContainerFactory encrypted_container_factory_; |
| std::unique_ptr<CryptohomeVault> vault_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(WithEcryptfs, |
| CryptohomeVaultTest, |
| ::testing::Values(CryptohomeVaultTestParams( |
| EncryptedContainerType::kEcryptfs, |
| EncryptedContainerType::kUnknown, |
| EncryptedContainerType::kUnknown))); |
| INSTANTIATE_TEST_SUITE_P(WithFscrypt, |
| CryptohomeVaultTest, |
| ::testing::Values(CryptohomeVaultTestParams( |
| EncryptedContainerType::kFscrypt, |
| EncryptedContainerType::kUnknown, |
| EncryptedContainerType::kUnknown))); |
| INSTANTIATE_TEST_SUITE_P(WithFscryptMigration, |
| CryptohomeVaultTest, |
| ::testing::Values(CryptohomeVaultTestParams( |
| EncryptedContainerType::kEcryptfs, |
| EncryptedContainerType::kFscrypt, |
| EncryptedContainerType::kUnknown))); |
| |
| INSTANTIATE_TEST_SUITE_P(WithDmcrypt, |
| CryptohomeVaultTest, |
| ::testing::Values(CryptohomeVaultTestParams( |
| EncryptedContainerType::kDmcrypt, |
| EncryptedContainerType::kUnknown, |
| EncryptedContainerType::kDmcrypt))); |
| |
| // Tests failure path on failure to setup process keyring for eCryptfs and |
| // fscrypt. |
| TEST_P(CryptohomeVaultTest, FailedProcessKeyringSetup) { |
| GenerateVault(/*create_container=*/false, |
| /*create_migrating_container=*/false, |
| /*create_cache_container=*/false); |
| EXPECT_CALL(platform_, SetupProcessKeyring()).WillOnce(Return(false)); |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/true), |
| MOUNT_ERROR_SETUP_PROCESS_KEYRING_FAILED); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the failure path on Setup if setting up the container fails. |
| TEST_P(CryptohomeVaultTest, ContainerSetupFailed) { |
| GenerateVault(/*create_container=*/false, |
| /*create_migrating_container=*/false, |
| /*create_cache_container=*/false); |
| ExpectVaultSetup(); |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/true), MOUNT_ERROR_KEYRING_FAILED); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the failure path on Setup if setting up the container fails. |
| TEST_P(CryptohomeVaultTest, MigratingContainerSetupFailed) { |
| GenerateVault(/*create_container=*/false, |
| /*create_migrating_container=*/false, |
| /*create_cache_container=*/false); |
| ExpectVaultSetup(); |
| ExpectContainerSetup(ContainerType()); |
| ExpectCacheContainerSetup(CacheContainerType()); |
| |
| // In absence of a migrating container, the vault setup should succeed. |
| MountError error = |
| MigratingContainerType() != EncryptedContainerType::kUnknown |
| ? MOUNT_ERROR_KEYRING_FAILED |
| : MOUNT_ERROR_NONE; |
| |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/true), error); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the setup path of a pristine cryptohome. |
| TEST_P(CryptohomeVaultTest, CreateVault) { |
| GenerateVault(/*create_container=*/false, |
| /*create_migrating_container=*/false, |
| /*create_cache_container=*/false); |
| ExpectVaultSetup(); |
| ExpectContainerSetup(ContainerType()); |
| ExpectContainerSetup(MigratingContainerType()); |
| ExpectCacheContainerSetup(CacheContainerType()); |
| |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/true), MOUNT_ERROR_NONE); |
| |
| CheckContainersExist(); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the setup path for an existing container with no migrating container |
| // setup. |
| TEST_P(CryptohomeVaultTest, ExistingVaultNoMigratingVault) { |
| GenerateVault(/*create_container=*/true, |
| /*create_migrating_container=*/false, |
| /*create_cache_container=*/false); |
| ExpectVaultSetup(); |
| ExpectContainerSetup(ContainerType()); |
| ExpectContainerSetup(MigratingContainerType()); |
| ExpectCacheContainerSetup(CacheContainerType()); |
| |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/false), MOUNT_ERROR_NONE); |
| |
| CheckContainersExist(); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the setup path for an existing vault with an existing migrating |
| // container (incomplete migration). |
| TEST_P(CryptohomeVaultTest, ExistingMigratingVault) { |
| GenerateVault(/*create_container=*/true, /*create_migrating_container=*/true, |
| /*create_cache_container=*/false); |
| ExpectVaultSetup(); |
| ExpectContainerSetup(ContainerType()); |
| ExpectContainerSetup(MigratingContainerType()); |
| ExpectCacheContainerSetup(CacheContainerType()); |
| |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/false), MOUNT_ERROR_NONE); |
| |
| CheckContainersExist(); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| // Tests the setup path for an existing vault with an existing cache container |
| // (incomplete migration). |
| TEST_P(CryptohomeVaultTest, ExistingCacheContainer) { |
| GenerateVault(/*create_container=*/true, /*create_migrating_container=*/false, |
| /*create_cache_container=*/true); |
| ExpectVaultSetup(); |
| ExpectContainerSetup(ContainerType()); |
| ExpectContainerSetup(MigratingContainerType()); |
| ExpectCacheContainerSetup(CacheContainerType()); |
| |
| EXPECT_EQ(vault_->Setup(key_, /*create=*/false), MOUNT_ERROR_NONE); |
| |
| CheckContainersExist(); |
| ExpectVaultTeardownOnDestruction(); |
| } |
| |
| } // namespace cryptohome |