blob: 5f696350156af73c163db0d640e0efe738d3f68f [file] [log] [blame]
// 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/user_secret_stash.h"
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/system/sys_info.h>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <map>
#include <memory>
#include <optional>
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/flatbuffer_schemas/user_secret_stash_container.h"
#include "cryptohome/flatbuffer_schemas/user_secret_stash_payload.h"
#include "cryptohome/storage/encrypted_container/filesystem_key.h"
#include "cryptohome/storage/file_system_keyset.h"
using ::hwsec_foundation::AesGcmDecrypt;
using ::hwsec_foundation::AesGcmEncrypt;
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::kAesGcm256KeySize;
using ::hwsec_foundation::kAesGcmIVSize;
using ::hwsec_foundation::kAesGcmTagSize;
namespace cryptohome {
namespace {
// TODO(b/230069013): Add guidelines on how to update this version value and its
// documentation when we need it for the first time.
constexpr int kCurrentUssVersion = 1;
constexpr char kEnableUssExperimentFlagPath[] =
"/var/lib/cryptohome/uss_enabled";
constexpr char kDisableUssExperimentFlagPath[] =
"/var/lib/cryptohome/uss_disabled";
std::optional<bool>& GetUserSecretStashExperimentFlag() {
// The static variable holding the overridden state. The default state is
// nullopt, which fallbacks to the default enabled/disabled state.
static std::optional<bool> uss_experiment_enabled;
return uss_experiment_enabled;
}
std::optional<bool>& GetUserSecretStashExperimentOverride() {
// The static variable holding the overridden state. The default state is
// nullopt, which fallbacks to checking whether flag file exists.
static std::optional<bool> uss_experiment_enabled;
return uss_experiment_enabled;
}
bool EnableUserSecretStashExperimentFlagFileExists() {
return base::PathExists(base::FilePath(kEnableUssExperimentFlagPath));
}
bool DisableUserSecretStashExperimentFlagFileExists() {
return base::PathExists(base::FilePath(kDisableUssExperimentFlagPath));
}
// Loads the current OS version from the CHROMEOS_RELEASE_VERSION field in
// /etc/lsb-release. Returns an empty string on failure.
std::string GetCurrentOsVersion() {
std::string version;
if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION",
&version)) {
return std::string();
}
return version;
}
// Extracts the file system keyset from the given USS payload. Returns nullopt
// on failure.
std::optional<FileSystemKeyset> GetFileSystemKeyFromPayload(
const UserSecretStashPayload& uss_payload) {
if (uss_payload.fek.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FEK";
return std::nullopt;
}
if (uss_payload.fnek.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FNEK";
return std::nullopt;
}
if (uss_payload.fek_salt.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FEK salt";
return std::nullopt;
}
if (uss_payload.fnek_salt.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FNEK salt";
return std::nullopt;
}
if (uss_payload.fek_sig.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FEK signature";
return std::nullopt;
}
if (uss_payload.fnek_sig.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no FNEK signature";
return std::nullopt;
}
if (uss_payload.chaps_key.empty()) {
LOG(ERROR) << "UserSecretStashPayload has no Chaps key";
return std::nullopt;
}
FileSystemKey file_system_key = {
.fek = uss_payload.fek,
.fnek = uss_payload.fnek,
.fek_salt = uss_payload.fek_salt,
.fnek_salt = uss_payload.fnek_salt,
};
FileSystemKeyReference file_system_key_reference = {
.fek_sig = uss_payload.fek_sig,
.fnek_sig = uss_payload.fnek_sig,
};
return FileSystemKeyset(std::move(file_system_key),
std::move(file_system_key_reference),
uss_payload.chaps_key);
}
// Converts the wrapped key block information from serializable structs
// (autogenerated by the Python script) into the mapping from wrapping_id to
// `UserSecretStash::WrappedKeyBlock`.
// Malformed and duplicate entries are logged and skipped.
std::map<std::string, UserSecretStash::WrappedKeyBlock>
GetKeyBlocksFromSerializableStructs(
const std::vector<UserSecretStashWrappedKeyBlock>& serializable_blocks) {
std::map<std::string, UserSecretStash::WrappedKeyBlock> key_blocks;
for (const UserSecretStashWrappedKeyBlock& serializable_block :
serializable_blocks) {
if (serializable_block.wrapping_id.empty()) {
LOG(WARNING)
<< "Ignoring UserSecretStash wrapped key block with an empty ID.";
continue;
}
if (key_blocks.count(serializable_block.wrapping_id)) {
LOG(WARNING)
<< "Ignoring UserSecretStash wrapped key block with duplicate ID "
<< serializable_block.wrapping_id << ".";
continue;
}
if (!serializable_block.encryption_algorithm.has_value()) {
LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an "
"unset algorithm";
continue;
}
if (serializable_block.encryption_algorithm.value() !=
UserSecretStashEncryptionAlgorithm::AES_GCM_256) {
LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an "
"unknown algorithm: "
<< static_cast<int>(
serializable_block.encryption_algorithm.value());
continue;
}
if (serializable_block.encrypted_key.empty()) {
LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an "
"empty encrypted key.";
continue;
}
if (serializable_block.iv.empty()) {
LOG(WARNING)
<< "Ignoring UserSecretStash wrapped key block with an empty IV.";
continue;
}
if (serializable_block.gcm_tag.empty()) {
LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an "
"empty AES-GCM tag.";
continue;
}
UserSecretStash::WrappedKeyBlock key_block = {
.encryption_algorithm = serializable_block.encryption_algorithm.value(),
.encrypted_key = serializable_block.encrypted_key,
.iv = serializable_block.iv,
.gcm_tag = serializable_block.gcm_tag,
};
key_blocks.insert({serializable_block.wrapping_id, std::move(key_block)});
}
return key_blocks;
}
// Parses the USS container flatbuffer. On success, populates `ciphertext`,
// `iv`, `tag`, `wrapped_key_blocks`, `created_on_os_version`; on failure,
// returns false.
bool GetContainerFromFlatbuffer(
const brillo::Blob& flatbuffer,
brillo::Blob* ciphertext,
brillo::Blob* iv,
brillo::Blob* tag,
std::map<std::string, UserSecretStash::WrappedKeyBlock>* wrapped_key_blocks,
std::string* created_on_os_version) {
std::optional<UserSecretStashContainer> deserialized =
UserSecretStashContainer::Deserialize(flatbuffer);
if (!deserialized.has_value()) {
LOG(ERROR) << "Failed to deserialize UserSecretStashContainer";
return false;
}
if (!deserialized.value().encryption_algorithm.has_value()) {
LOG(ERROR) << "UserSecretStashContainer has no algorithm set";
return false;
}
if (deserialized.value().encryption_algorithm.value() !=
UserSecretStashEncryptionAlgorithm::AES_GCM_256) {
LOG(ERROR) << "UserSecretStashContainer uses unknown algorithm: "
<< static_cast<int>(deserialized->encryption_algorithm.value());
return false;
}
if (deserialized.value().ciphertext.empty()) {
LOG(ERROR) << "UserSecretStash has empty ciphertext";
return false;
}
*ciphertext = deserialized.value().ciphertext;
if (deserialized.value().iv.empty()) {
LOG(ERROR) << "UserSecretStash has empty IV";
return false;
}
if (deserialized.value().iv.size() != kAesGcmIVSize) {
LOG(ERROR) << "UserSecretStash has IV of wrong length: "
<< deserialized.value().iv.size()
<< ", expected: " << kAesGcmIVSize;
return false;
}
*iv = deserialized.value().iv;
if (deserialized.value().gcm_tag.empty()) {
LOG(ERROR) << "UserSecretStash has empty AES-GCM tag";
return false;
}
if (deserialized.value().gcm_tag.size() != kAesGcmTagSize) {
LOG(ERROR) << "UserSecretStash has AES-GCM tag of wrong length: "
<< deserialized.value().gcm_tag.size()
<< ", expected: " << kAesGcmTagSize;
return false;
}
*tag = deserialized.value().gcm_tag;
*wrapped_key_blocks = GetKeyBlocksFromSerializableStructs(
deserialized.value().wrapped_key_blocks);
*created_on_os_version = deserialized.value().created_on_os_version;
return true;
}
std::optional<brillo::SecureBlob> UnwrapMainKeyFromBlocks(
const std::map<std::string, UserSecretStash::WrappedKeyBlock>&
wrapped_key_blocks,
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key) {
// Verify preconditions.
if (wrapping_id.empty()) {
NOTREACHED() << "Empty wrapping ID is passed for UserSecretStash main key "
"unwrapping.";
return std::nullopt;
}
if (wrapping_key.size() != kAesGcm256KeySize) {
NOTREACHED() << "Wrong wrapping key size is passed for UserSecretStash "
"main key unwrapping. Received: "
<< wrapping_key.size() << ", expected " << kAesGcm256KeySize
<< ".";
return std::nullopt;
}
// Find the wrapped key block.
const auto wrapped_key_block_iter = wrapped_key_blocks.find(wrapping_id);
if (wrapped_key_block_iter == wrapped_key_blocks.end()) {
LOG(ERROR)
<< "UserSecretStash wrapped key block with the given ID not found.";
return std::nullopt;
}
const UserSecretStash::WrappedKeyBlock& wrapped_key_block =
wrapped_key_block_iter->second;
// Verify the wrapped key block format. No NOTREACHED() checks here, since the
// key block is a deserialization of the persisted blob.
if (wrapped_key_block.encryption_algorithm !=
UserSecretStashEncryptionAlgorithm::AES_GCM_256) {
LOG(ERROR) << "UserSecretStash wrapped main key uses unknown algorithm: "
<< static_cast<int>(wrapped_key_block.encryption_algorithm)
<< ".";
return std::nullopt;
}
if (wrapped_key_block.encrypted_key.empty()) {
LOG(ERROR) << "UserSecretStash wrapped main key has empty encrypted key.";
return std::nullopt;
}
if (wrapped_key_block.iv.size() != kAesGcmIVSize) {
LOG(ERROR) << "UserSecretStash wrapped main key has IV of wrong length: "
<< wrapped_key_block.iv.size() << ", expected: " << kAesGcmIVSize
<< ".";
return std::nullopt;
}
if (wrapped_key_block.gcm_tag.size() != kAesGcmTagSize) {
LOG(ERROR)
<< "UserSecretStash wrapped main key has AES-GCM tag of wrong length: "
<< wrapped_key_block.gcm_tag.size() << ", expected: " << kAesGcmTagSize
<< ".";
return std::nullopt;
}
// Attempt the unwrapping.
brillo::SecureBlob main_key;
if (!AesGcmDecrypt(
brillo::SecureBlob(wrapped_key_block.encrypted_key),
/*ad=*/std::nullopt, brillo::SecureBlob(wrapped_key_block.gcm_tag),
wrapping_key, brillo::SecureBlob(wrapped_key_block.iv), &main_key)) {
LOG(ERROR) << "Failed to unwrap UserSecretStash main key";
return std::nullopt;
}
return main_key;
}
} // namespace
int UserSecretStashExperimentVersion() {
return kCurrentUssVersion;
}
bool IsUserSecretStashExperimentEnabled() {
// If the state is overridden by tests, return this value.
if (GetUserSecretStashExperimentOverride().has_value())
return GetUserSecretStashExperimentOverride().value();
// Otherwise, defer to checking the flag file existence. The disable file
// precedes the enable file.
if (DisableUserSecretStashExperimentFlagFileExists()) {
return false;
}
if (EnableUserSecretStashExperimentFlagFileExists()) {
return true;
}
// Otherwise, check the flag set by UssExperimentConfigFetcher.
// TODO(b/230069013): Before actual launching, only report metrics for the
// result of this flag, and don't actually use its value.
UssExperimentFlag result = UssExperimentFlag::kNotFound;
std::optional<bool> flag = GetUserSecretStashExperimentFlag();
if (flag.has_value()) {
if (flag.value()) {
result = UssExperimentFlag::kEnabled;
} else {
result = UssExperimentFlag::kDisabled;
}
}
ReportUssExperimentFlag(result);
return false;
}
void SetUserSecretStashExperimentFlag(bool enabled) {
GetUserSecretStashExperimentFlag() = enabled;
}
void SetUserSecretStashExperimentForTesting(std::optional<bool> enabled) {
GetUserSecretStashExperimentOverride() = enabled;
}
// static
std::unique_ptr<UserSecretStash> UserSecretStash::CreateRandom(
const FileSystemKeyset& file_system_keyset) {
std::string current_os_version = GetCurrentOsVersion();
// Note: make_unique() wouldn't work due to the constructor being private.
std::unique_ptr<UserSecretStash> stash(
new UserSecretStash(file_system_keyset));
stash->created_on_os_version_ = std::move(current_os_version);
return stash;
}
// static
std::unique_ptr<UserSecretStash> UserSecretStash::FromEncryptedContainer(
const brillo::Blob& flatbuffer, const brillo::SecureBlob& main_key) {
if (main_key.size() != kAesGcm256KeySize) {
LOG(ERROR) << "The UserSecretStash main key is of wrong length: "
<< main_key.size() << ", expected: " << kAesGcm256KeySize;
return nullptr;
}
brillo::Blob ciphertext, iv, gcm_tag;
std::map<std::string, WrappedKeyBlock> wrapped_key_blocks;
std::string created_on_os_version;
if (!GetContainerFromFlatbuffer(flatbuffer, &ciphertext, &iv, &gcm_tag,
&wrapped_key_blocks,
&created_on_os_version)) {
// Note: the error is already logged.
return nullptr;
}
return FromEncryptedPayload(ciphertext, iv, gcm_tag, wrapped_key_blocks,
created_on_os_version, main_key);
}
// static
std::unique_ptr<UserSecretStash> UserSecretStash::FromEncryptedPayload(
const brillo::Blob& ciphertext,
const brillo::Blob& iv,
const brillo::Blob& gcm_tag,
const std::map<std::string, WrappedKeyBlock>& wrapped_key_blocks,
const std::string& created_on_os_version,
const brillo::SecureBlob& main_key) {
brillo::SecureBlob serialized_uss_payload;
if (!AesGcmDecrypt(brillo::SecureBlob(ciphertext), /*ad=*/std::nullopt,
brillo::SecureBlob(gcm_tag), main_key,
brillo::SecureBlob(iv), &serialized_uss_payload)) {
LOG(ERROR) << "Failed to decrypt UserSecretStash payload";
return nullptr;
}
std::optional<UserSecretStashPayload> uss_payload =
UserSecretStashPayload::Deserialize(serialized_uss_payload);
if (!uss_payload.has_value()) {
LOG(ERROR) << "Failed to deserialize UserSecretStashPayload";
return nullptr;
}
std::optional<FileSystemKeyset> file_system_keyset =
GetFileSystemKeyFromPayload(uss_payload.value());
if (!file_system_keyset.has_value()) {
LOG(ERROR)
<< "UserSecretStashPayload has invalid file system keyset information";
return nullptr;
}
std::map<std::string, brillo::SecureBlob> reset_secrets;
for (const ResetSecretMapping& item : uss_payload.value().reset_secrets) {
auto insertion_status =
reset_secrets.insert({item.auth_factor_label, item.reset_secret});
if (!insertion_status.second) {
LOG(ERROR) << "UserSecretStashPayload contains multiple reset secrets "
"for label: "
<< item.auth_factor_label;
}
}
// Note: make_unique() wouldn't work due to the constructor being private.
std::unique_ptr<UserSecretStash> stash(
new UserSecretStash(file_system_keyset.value(), reset_secrets));
stash->wrapped_key_blocks_ = wrapped_key_blocks;
stash->created_on_os_version_ = created_on_os_version;
return stash;
}
// static
std::unique_ptr<UserSecretStash>
UserSecretStash::FromEncryptedContainerWithWrappingKey(
const brillo::Blob& flatbuffer,
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key,
brillo::SecureBlob* main_key) {
brillo::Blob ciphertext, iv, gcm_tag;
std::map<std::string, WrappedKeyBlock> wrapped_key_blocks;
std::string created_on_os_version;
if (!GetContainerFromFlatbuffer(flatbuffer, &ciphertext, &iv, &gcm_tag,
&wrapped_key_blocks,
&created_on_os_version)) {
// Note: the error is already logged.
return nullptr;
}
std::optional<brillo::SecureBlob> main_key_optional =
UnwrapMainKeyFromBlocks(wrapped_key_blocks, wrapping_id, wrapping_key);
if (!main_key_optional) {
// Note: the error is already logged.
return nullptr;
}
std::unique_ptr<UserSecretStash> stash =
FromEncryptedPayload(ciphertext, iv, gcm_tag, wrapped_key_blocks,
created_on_os_version, *main_key_optional);
if (!stash) {
// Note: the error is already logged.
return nullptr;
}
*main_key = *main_key_optional;
return stash;
}
// static
brillo::SecureBlob UserSecretStash::CreateRandomMainKey() {
return CreateSecureRandomBlob(kAesGcm256KeySize);
}
const FileSystemKeyset& UserSecretStash::GetFileSystemKeyset() const {
return file_system_keyset_;
}
std::optional<brillo::SecureBlob> UserSecretStash::GetResetSecretForLabel(
const std::string& label) const {
const auto iter = reset_secrets_.find(label);
if (iter == reset_secrets_.end()) {
return std::nullopt;
}
return iter->second;
}
bool UserSecretStash::SetResetSecretForLabel(const std::string& label,
const brillo::SecureBlob& secret) {
const auto result = reset_secrets_.insert({label, secret});
return result.second;
}
const std::string& UserSecretStash::GetCreatedOnOsVersion() const {
return created_on_os_version_;
}
bool UserSecretStash::HasWrappedMainKey(const std::string& wrapping_id) const {
return wrapped_key_blocks_.count(wrapping_id);
}
std::optional<brillo::SecureBlob> UserSecretStash::UnwrapMainKey(
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key) const {
return UnwrapMainKeyFromBlocks(wrapped_key_blocks_, wrapping_id,
wrapping_key);
}
bool UserSecretStash::AddWrappedMainKey(
const brillo::SecureBlob& main_key,
const std::string& wrapping_id,
const brillo::SecureBlob& wrapping_key) {
// Verify preconditions.
if (main_key.empty()) {
NOTREACHED() << "Empty UserSecretStash main key is passed for wrapping.";
return false;
}
if (wrapping_id.empty()) {
NOTREACHED()
<< "Empty wrapping ID is passed for UserSecretStash main key wrapping.";
return false;
}
if (wrapping_key.size() != kAesGcm256KeySize) {
NOTREACHED() << "Wrong wrapping key size is passed for UserSecretStash "
"main key wrapping. Received: "
<< wrapping_key.size() << ", expected " << kAesGcm256KeySize
<< ".";
return false;
}
// Protect from duplicate wrapping IDs.
if (wrapped_key_blocks_.count(wrapping_id)) {
LOG(ERROR) << "A UserSecretStash main key with the given wrapping_id "
"already exists.";
return false;
}
// Perform the wrapping.
brillo::SecureBlob iv, gcm_tag, encrypted_key;
if (!AesGcmEncrypt(main_key, /*ad=*/std::nullopt, wrapping_key, &iv, &gcm_tag,
&encrypted_key)) {
LOG(ERROR) << "Failed to wrap UserSecretStash main key.";
return false;
}
wrapped_key_blocks_[wrapping_id] = WrappedKeyBlock{
.encryption_algorithm = UserSecretStashEncryptionAlgorithm::AES_GCM_256,
.encrypted_key = brillo::Blob(encrypted_key.begin(), encrypted_key.end()),
.iv = brillo::Blob(iv.begin(), iv.end()),
.gcm_tag = brillo::Blob(gcm_tag.begin(), gcm_tag.end()),
};
return true;
}
bool UserSecretStash::RemoveWrappedMainKey(const std::string& wrapping_id) {
auto iter = wrapped_key_blocks_.find(wrapping_id);
if (iter == wrapped_key_blocks_.end()) {
LOG(ERROR) << "No UserSecretStash wrapped key block is found with the "
"given wrapping ID.";
return false;
}
wrapped_key_blocks_.erase(iter);
return true;
}
std::optional<brillo::Blob> UserSecretStash::GetEncryptedContainer(
const brillo::SecureBlob& main_key) {
UserSecretStashPayload payload = {
.fek = file_system_keyset_.Key().fek,
.fnek = file_system_keyset_.Key().fnek,
.fek_salt = file_system_keyset_.Key().fek_salt,
.fnek_salt = file_system_keyset_.Key().fnek_salt,
.fek_sig = file_system_keyset_.KeyReference().fek_sig,
.fnek_sig = file_system_keyset_.KeyReference().fnek_sig,
.chaps_key = file_system_keyset_.chaps_key(),
};
// Note: It can happen that the USS container is created with empty
// |reset_secrets_| if no PinWeaver credentials are present yet.
for (const auto& item : reset_secrets_) {
const std::string& auth_factor_label = item.first;
const brillo::SecureBlob& reset_secret = item.second;
payload.reset_secrets.push_back(ResetSecretMapping{
.auth_factor_label = auth_factor_label,
.reset_secret = reset_secret,
});
}
std::optional<brillo::SecureBlob> serialized_payload = payload.Serialize();
if (!serialized_payload.has_value()) {
LOG(ERROR) << "Failed to serialize UserSecretStashPayload";
return std::nullopt;
}
brillo::SecureBlob tag, iv, ciphertext;
if (!AesGcmEncrypt(serialized_payload.value(), /*ad=*/std::nullopt, main_key,
&iv, &tag, &ciphertext)) {
LOG(ERROR) << "Failed to encrypt UserSecretStash";
return std::nullopt;
}
UserSecretStashContainer container = {
.encryption_algorithm = UserSecretStashEncryptionAlgorithm::AES_GCM_256,
.ciphertext = brillo::Blob(ciphertext.begin(), ciphertext.end()),
.iv = brillo::Blob(iv.begin(), iv.end()),
.gcm_tag = brillo::Blob(tag.begin(), tag.end()),
.created_on_os_version = created_on_os_version_,
};
// Note: It can happen that the USS container is created with empty
// |wrapped_key_blocks_| - they may be added later, when the user registers
// the first credential with their cryptohome.
for (const auto& item : wrapped_key_blocks_) {
const std::string& wrapping_id = item.first;
const UserSecretStash::WrappedKeyBlock& wrapped_key_block = item.second;
container.wrapped_key_blocks.push_back(UserSecretStashWrappedKeyBlock{
.wrapping_id = wrapping_id,
.encryption_algorithm = wrapped_key_block.encryption_algorithm,
.encrypted_key = wrapped_key_block.encrypted_key,
.iv = wrapped_key_block.iv,
.gcm_tag = wrapped_key_block.gcm_tag,
});
}
std::optional<brillo::Blob> serialized_contaner = container.Serialize();
if (!serialized_contaner.has_value()) {
LOG(ERROR) << "Failed to serialize UserSecretStashContainer";
return std::nullopt;
}
return serialized_contaner.value();
}
UserSecretStash::UserSecretStash(
const FileSystemKeyset& file_system_keyset,
const std::map<std::string, brillo::SecureBlob>& reset_secrets)
: file_system_keyset_(file_system_keyset), reset_secrets_(reset_secrets) {}
UserSecretStash::UserSecretStash(const FileSystemKeyset& file_system_keyset)
: file_system_keyset_(file_system_keyset) {}
} // namespace cryptohome