blob: ec8ec0155f5333f00fe3fdf11011820f5b5a574f [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/test/scoped_chromeos_version_info.h>
#include <brillo/secure_blob.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#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"
#include "cryptohome/storage/file_system_keyset_test_utils.h"
using ::hwsec_foundation::AesGcmDecrypt;
using ::hwsec_foundation::AesGcmEncrypt;
using ::hwsec_foundation::kAesGcm256KeySize;
namespace cryptohome {
namespace {
static bool FindBlobInBlob(const brillo::SecureBlob& haystack,
const brillo::SecureBlob& needle) {
return std::search(haystack.begin(), haystack.end(), needle.begin(),
needle.end()) != haystack.end();
}
class UserSecretStashTest : public ::testing::Test {
protected:
// Fake file system keyset data:
const brillo::SecureBlob kFek = brillo::SecureBlob("fek");
const brillo::SecureBlob kFnek = brillo::SecureBlob("fnek");
const brillo::SecureBlob kFekSalt = brillo::SecureBlob("fek-salt");
const brillo::SecureBlob kFnekSalt = brillo::SecureBlob("fnek-salt");
const brillo::SecureBlob kFekSig = brillo::SecureBlob("fek-sig");
const brillo::SecureBlob kFnekSig = brillo::SecureBlob("fnek-sig");
const brillo::SecureBlob kChapsKey = brillo::SecureBlob("chaps-key");
const FileSystemKeyset kFileSystemKeyset = FileSystemKeyset(
FileSystemKey{
.fek = kFek,
.fnek = kFnek,
.fek_salt = kFekSalt,
.fnek_salt = kFnekSalt,
},
FileSystemKeyReference{
.fek_sig = kFekSig,
.fnek_sig = kFnekSig,
},
kChapsKey);
// Fake USS Main Key.
const brillo::SecureBlob kMainKey =
brillo::SecureBlob(kAesGcm256KeySize, 0xA);
void SetUp() override {
stash_ = UserSecretStash::CreateRandom(kFileSystemKeyset);
ASSERT_TRUE(stash_);
}
std::unique_ptr<UserSecretStash> stash_;
};
} // namespace
TEST_F(UserSecretStashTest, CreateRandom) {
EXPECT_FALSE(stash_->GetResetSecret().empty());
// The secrets should be created randomly and never collide (in practice).
EXPECT_THAT(stash_->GetFileSystemKeyset(),
FileSystemKeysetEquals(kFileSystemKeyset));
}
// Verify that the USS secrets created by CreateRandom() don't repeat (in
// practice).
TEST_F(UserSecretStashTest, CreateRandomNotConstant) {
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::CreateRandom(kFileSystemKeyset);
ASSERT_TRUE(stash2);
EXPECT_NE(stash_->GetResetSecret(), stash2->GetResetSecret());
}
// Basic test of the `CreateRandomMainKey()` method.
TEST_F(UserSecretStashTest, CreateRandomMainKey) {
brillo::SecureBlob main_key = UserSecretStash::CreateRandomMainKey();
EXPECT_FALSE(main_key.empty());
}
// Test the secret main keys created by `CreateRandomMainKey()` don't repeat (in
// practice).
TEST_F(UserSecretStashTest, CreateRandomMainKeyNotConstant) {
brillo::SecureBlob main_key_1 = UserSecretStash::CreateRandomMainKey();
brillo::SecureBlob main_key_2 = UserSecretStash::CreateRandomMainKey();
EXPECT_NE(main_key_1, main_key_2);
}
// Verify the getters/setters of the wrapped key fields.
TEST_F(UserSecretStashTest, MainKeyWrapping) {
const char kWrappingId1[] = "id1";
const char kWrappingId2[] = "id2";
const brillo::SecureBlob kWrappingKey1(kAesGcm256KeySize, 0xB);
const brillo::SecureBlob kWrappingKey2(kAesGcm256KeySize, 0xC);
// Initially there's no wrapped key.
EXPECT_FALSE(stash_->HasWrappedMainKey(kWrappingId1));
EXPECT_FALSE(stash_->HasWrappedMainKey(kWrappingId2));
// And the main key wrapped with two wrapping keys.
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId1, kWrappingKey1));
EXPECT_TRUE(stash_->HasWrappedMainKey(kWrappingId1));
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId2, kWrappingKey2));
EXPECT_TRUE(stash_->HasWrappedMainKey(kWrappingId2));
// Duplicate wrapping IDs aren't allowed.
EXPECT_FALSE(
stash_->AddWrappedMainKey(kMainKey, kWrappingId1, kWrappingKey1));
// The main key can be unwrapped using any of the wrapping keys.
std::optional<brillo::SecureBlob> got_main_key1 =
stash_->UnwrapMainKey(kWrappingId1, kWrappingKey1);
ASSERT_TRUE(got_main_key1);
EXPECT_EQ(*got_main_key1, kMainKey);
std::optional<brillo::SecureBlob> got_main_key2 =
stash_->UnwrapMainKey(kWrappingId2, kWrappingKey2);
ASSERT_TRUE(got_main_key2);
EXPECT_EQ(*got_main_key2, kMainKey);
// Removal of one wrapped key block preserves the other.
EXPECT_TRUE(stash_->RemoveWrappedMainKey(kWrappingId1));
EXPECT_FALSE(stash_->HasWrappedMainKey(kWrappingId1));
EXPECT_TRUE(stash_->HasWrappedMainKey(kWrappingId2));
// Removing a non-existing wrapped key block fails.
EXPECT_FALSE(stash_->RemoveWrappedMainKey(kWrappingId1));
}
TEST_F(UserSecretStashTest, GetEncryptedUSS) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
// No raw secrets in the encrypted USS, which is written to disk.
EXPECT_FALSE(
FindBlobInBlob(*uss_container, stash_->GetFileSystemKeyset().Key().fek));
EXPECT_FALSE(FindBlobInBlob(*uss_container, stash_->GetResetSecret()));
}
TEST_F(UserSecretStashTest, EncryptAndDecryptUSS) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::FromEncryptedContainer(uss_container.value(), kMainKey);
ASSERT_TRUE(stash2);
EXPECT_THAT(stash_->GetFileSystemKeyset(),
FileSystemKeysetEquals(stash2->GetFileSystemKeyset()));
EXPECT_EQ(stash_->GetResetSecret(), stash2->GetResetSecret());
}
// Test that deserialization fails on an empty blob. Normally this never occurs,
// but we verify to be resilient against accidental or intentional file
// corruption.
TEST_F(UserSecretStashTest, DecryptErrorEmptyBuf) {
EXPECT_FALSE(
UserSecretStash::FromEncryptedContainer(brillo::SecureBlob(), kMainKey));
}
// Test that deserialization fails on a corrupted flatbuffer. Normally this
// never occurs, but we verify to be resilient against accidental or intentional
// file corruption.
TEST_F(UserSecretStashTest, DecryptErrorCorruptedBuf) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
brillo::SecureBlob corrupted_uss_container = *uss_container;
for (uint8_t& byte : corrupted_uss_container)
byte ^= 1;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(corrupted_uss_container,
kMainKey));
}
// Test that decryption fails on an empty decryption key.
TEST_F(UserSecretStashTest, DecryptErrorEmptyKey) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
EXPECT_FALSE(
UserSecretStash::FromEncryptedContainer(*uss_container, /*main_key=*/{}));
}
// Test that decryption fails on a decryption key of a wrong size.
TEST_F(UserSecretStashTest, DecryptErrorKeyBadSize) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
brillo::SecureBlob bad_size_main_key = kMainKey;
bad_size_main_key.resize(kAesGcm256KeySize - 1);
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(*uss_container,
bad_size_main_key));
}
// Test that decryption fails on a wrong decryption key.
TEST_F(UserSecretStashTest, DecryptErrorWrongKey) {
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
brillo::SecureBlob wrong_main_key = kMainKey;
wrong_main_key[0] ^= 1;
EXPECT_FALSE(
UserSecretStash::FromEncryptedContainer(*uss_container, wrong_main_key));
}
// Test that wrapped key blocks are [de]serialized correctly.
TEST_F(UserSecretStashTest, EncryptAndDecryptUSSWithWrappedKeys) {
const char kWrappingId1[] = "id1";
const char kWrappingId2[] = "id2";
const brillo::SecureBlob kWrappingKey1(kAesGcm256KeySize, 0xB);
const brillo::SecureBlob kWrappingKey2(kAesGcm256KeySize, 0xC);
// Add wrapped key blocks.
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId1, kWrappingKey1));
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId2, kWrappingKey2));
// Do the serialization-deserialization roundtrip with the USS.
auto uss_container = stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::FromEncryptedContainer(uss_container.value(), kMainKey);
ASSERT_TRUE(stash2);
// The wrapped key blocks are present in the loaded stash and can be
// decrypted.
EXPECT_TRUE(stash2->HasWrappedMainKey(kWrappingId1));
EXPECT_TRUE(stash2->HasWrappedMainKey(kWrappingId2));
std::optional<brillo::SecureBlob> got_main_key1 =
stash2->UnwrapMainKey(kWrappingId1, kWrappingKey1);
ASSERT_TRUE(got_main_key1);
EXPECT_EQ(*got_main_key1, kMainKey);
}
// Test that the USS can be loaded and decrypted using the wrapping key stored
// in it.
TEST_F(UserSecretStashTest, EncryptAndDecryptUSSViaWrappedKey) {
// Add a wrapped key block.
const char kWrappingId[] = "id";
const brillo::SecureBlob kWrappingKey(kAesGcm256KeySize, 0xB);
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId, kWrappingKey));
// Encrypt the USS.
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_NE(std::nullopt, uss_container);
// The USS can be decrypted using the wrapping key.
brillo::SecureBlob unwrapped_main_key;
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::FromEncryptedContainerWithWrappingKey(
uss_container.value(), kWrappingId, kWrappingKey,
&unwrapped_main_key);
ASSERT_TRUE(stash2);
EXPECT_THAT(stash_->GetFileSystemKeyset(),
FileSystemKeysetEquals(stash2->GetFileSystemKeyset()));
EXPECT_EQ(stash_->GetResetSecret(), stash2->GetResetSecret());
EXPECT_EQ(unwrapped_main_key, kMainKey);
}
// Test the USS experiment state is off by default, but can be toggled in tests.
TEST_F(UserSecretStashTest, ExperimentState) {
// The experiment is off by default.
EXPECT_FALSE(IsUserSecretStashExperimentEnabled());
// Verify the test can toggle the experiment state.
SetUserSecretStashExperimentForTesting(/*enabled=*/true);
EXPECT_TRUE(IsUserSecretStashExperimentEnabled());
// Unset the experiment override to avoid affecting other test cases.
SetUserSecretStashExperimentForTesting(/*enabled=*/std::nullopt);
}
// Test that a newly created USS has the current OS version stored.
TEST_F(UserSecretStashTest, OsVersion) {
constexpr char kLsbRelease[] =
"CHROMEOS_RELEASE_NAME=Chrome "
"OS\nCHROMEOS_RELEASE_VERSION=11012.0.2018_08_28_1422\n";
base::test::ScopedChromeOSVersionInfo scoped_version(
kLsbRelease, /*lsb_release_time=*/base::Time());
std::unique_ptr<UserSecretStash> stash1 =
UserSecretStash::CreateRandom(kFileSystemKeyset);
ASSERT_TRUE(stash1);
EXPECT_EQ(stash1->GetCreatedOnOsVersion(), "11012.0.2018_08_28_1422");
}
// Test that the OS version is stored in the USS and doesn't change even when
// the OS updates.
TEST_F(UserSecretStashTest, OsVersionStays) {
constexpr char kLsbRelease1[] =
"CHROMEOS_RELEASE_NAME=Chrome "
"OS\nCHROMEOS_RELEASE_VERSION=11012.0.2018_08_28_1422\n";
constexpr char kLsbRelease2[] =
"CHROMEOS_RELEASE_NAME=Chrome "
"OS\nCHROMEOS_RELEASE_VERSION=22222.0.2028_01_01_9999\n";
// Create and encrypt the USS on the version 1.
std::optional<brillo::SecureBlob> uss_container;
{
base::test::ScopedChromeOSVersionInfo scoped_version1(
kLsbRelease1, /*lsb_release_time=*/base::Time());
std::unique_ptr<UserSecretStash> stash1 =
UserSecretStash::CreateRandom(kFileSystemKeyset);
ASSERT_TRUE(stash1);
uss_container = stash1->GetEncryptedContainer(kMainKey);
ASSERT_TRUE(uss_container.has_value());
}
// Decrypt the USS on the version 2. Check that the field still mentions
// version 1.
{
base::test::ScopedChromeOSVersionInfo scoped_version2(
kLsbRelease2, /*lsb_release_time=*/base::Time());
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::FromEncryptedContainer(uss_container.value(),
kMainKey);
ASSERT_TRUE(stash2);
EXPECT_EQ(stash2->GetCreatedOnOsVersion(), "11012.0.2018_08_28_1422");
}
}
// Test that the USS is correctly created and loaded even when reading the OS
// version fails.
TEST_F(UserSecretStashTest, MissingOsVersion) {
// Note: Normally unit tests don't have access to a CrOS /etc/lsb-release
// anyway, but this override guarantees that the test passes regardless of
// that.
base::test::ScopedChromeOSVersionInfo scoped_version(
/*lsb_release=*/"", /*lsb_release_time=*/base::Time());
// A newly created USS should have an empty OS version.
std::unique_ptr<UserSecretStash> stash =
UserSecretStash::CreateRandom(kFileSystemKeyset);
ASSERT_TRUE(stash);
EXPECT_TRUE(stash->GetCreatedOnOsVersion().empty());
// Do a encrypt-decrypt roundtrip and verify the OS version is still empty.
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_TRUE(uss_container.has_value());
std::unique_ptr<UserSecretStash> stash2 =
UserSecretStash::FromEncryptedContainer(uss_container.value(), kMainKey);
ASSERT_TRUE(stash2);
EXPECT_TRUE(stash2->GetCreatedOnOsVersion().empty());
}
// Fixture that helps to read/manipulate the USS flatbuffer's internals using
// the flatbuffer C++ bindings.
class UserSecretStashInternalsTest : public UserSecretStashTest {
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(UserSecretStashTest::SetUp());
ASSERT_NO_FATAL_FAILURE(UpdateBindingStrusts());
}
// Populates |uss_container_struct_| and |uss_payload_struct_| based on
// |stash_|.
void UpdateBindingStrusts() {
// Encrypt the USS.
std::optional<brillo::SecureBlob> uss_container =
stash_->GetEncryptedContainer(kMainKey);
ASSERT_TRUE(uss_container);
// Unpack the wrapped USS flatbuffer to |uss_container_struct_|.
std::optional<UserSecretStashContainer> uss_container_struct =
UserSecretStashContainer::Deserialize(uss_container.value());
ASSERT_TRUE(uss_container_struct);
uss_container_struct_ = std::move(uss_container_struct.value());
// Decrypt and unpack the USS flatbuffer to |uss_payload_struct_|.
brillo::SecureBlob uss_payload;
ASSERT_TRUE(AesGcmDecrypt(
brillo::SecureBlob(uss_container_struct_.ciphertext),
/*ad=*/std::nullopt, brillo::SecureBlob(uss_container_struct_.gcm_tag),
kMainKey, brillo::SecureBlob(uss_container_struct_.iv), &uss_payload));
std::optional<UserSecretStashPayload> uss_payload_struct =
UserSecretStashPayload::Deserialize(uss_payload);
ASSERT_TRUE(uss_payload_struct);
uss_payload_struct_ = std::move(*uss_payload_struct);
}
// Converts |uss_container_struct_| => "container flatbuffer".
brillo::SecureBlob GetFlatbufferFromUssContainerStruct() const {
std::optional<brillo::SecureBlob> serialized =
uss_container_struct_.Serialize();
if (!serialized.has_value()) {
ADD_FAILURE() << "Failed to serialized UserSecretStashContainer";
return brillo::SecureBlob();
}
return serialized.value();
}
// Converts |uss_payload_struct_| => "payload flatbuffer" =>
// UserSecretStashContainer => "container flatbuffer".
brillo::SecureBlob GetFlatbufferFromUssPayloadStruct() const {
return GetFlatbufferFromUssPayloadBlob(PackUssPayloadStruct());
}
// Converts |uss_payload_struct_| => "payload flatbuffer".
brillo::SecureBlob PackUssPayloadStruct() const {
std::optional<brillo::SecureBlob> serialized =
uss_payload_struct_.Serialize();
if (!serialized.has_value()) {
ADD_FAILURE() << "Failed to serialized UserSecretStashPayload";
return brillo::SecureBlob();
}
return serialized.value();
}
// Converts "payload flatbuffer" => UserSecretStashContainer => "container
// flatbuffer".
brillo::SecureBlob GetFlatbufferFromUssPayloadBlob(
const brillo::SecureBlob& uss_payload) const {
// Encrypt the packed |uss_payload_struct_|.
brillo::SecureBlob iv, tag, ciphertext;
EXPECT_TRUE(AesGcmEncrypt(uss_payload, /*ad=*/std::nullopt, kMainKey, &iv,
&tag, &ciphertext));
// Create a copy of |uss_container_struct_|, with the encrypted blob
// replaced.
UserSecretStashContainer new_uss_container_struct = uss_container_struct_;
new_uss_container_struct.ciphertext = ciphertext;
new_uss_container_struct.iv = iv;
new_uss_container_struct.gcm_tag = tag;
// Pack |new_uss_container_struct|.
std::optional<brillo::SecureBlob> serialized =
new_uss_container_struct.Serialize();
if (!serialized.has_value()) {
ADD_FAILURE() << "Failed to seralize the USS container";
return brillo::SecureBlob();
}
return serialized.value();
}
UserSecretStashContainer uss_container_struct_;
UserSecretStashPayload uss_payload_struct_;
};
// Verify that the test fixture correctly generates the USS flatbuffers from the
// binding structs.
TEST_F(UserSecretStashInternalsTest, SmokeTest) {
EXPECT_TRUE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadBlob(PackUssPayloadStruct()), kMainKey));
EXPECT_TRUE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
EXPECT_TRUE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the USS payload is a corrupted flatbuffer.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorBadPayload) {
brillo::SecureBlob uss_payload = PackUssPayloadStruct();
for (uint8_t& byte : uss_payload)
byte ^= 1;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadBlob(uss_payload), kMainKey));
}
// Test that decryption fails when the USS payload is a truncated flatbuffer.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorPayloadBadSize) {
brillo::SecureBlob uss_payload = PackUssPayloadStruct();
uss_payload.resize(uss_payload.size() / 2);
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadBlob(uss_payload), kMainKey));
}
// Test that decryption fails when the encryption algorithm is not set. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoAlgorithm) {
uss_container_struct_.encryption_algorithm.reset();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the encryption algorithm is unknown. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorUnknownAlgorithm) {
uss_container_struct_
.encryption_algorithm = static_cast<UserSecretStashEncryptionAlgorithm>(
std::numeric_limits<
std::underlying_type_t<UserSecretStashEncryptionAlgorithm>>::max());
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the ciphertext field is missing. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoCiphertext) {
uss_container_struct_.ciphertext.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the ciphertext field is corrupted. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorCorruptedCiphertext) {
for (uint8_t& byte : uss_container_struct_.ciphertext)
byte ^= 1;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the iv field is missing. Normally this never
// occurs, but we verify to be resilient against accidental or intentional file
// corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoIv) {
uss_container_struct_.iv.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the iv field has a wrong value. Normally this
// never occurs, but we verify to be resilient against accidental or intentional
// file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorWrongIv) {
uss_container_struct_.iv[0] ^= 1;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the iv field is of a wrong size. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorIvBadSize) {
uss_container_struct_.iv.resize(uss_container_struct_.iv.size() - 1);
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the gcm_tag field is missing. Normally this
// never occurs, but we verify to be resilient against accidental or intentional
// file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoGcmTag) {
uss_container_struct_.gcm_tag.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the gcm_tag field has a wrong value.
TEST_F(UserSecretStashInternalsTest, DecryptErrorWrongGcmTag) {
uss_container_struct_.gcm_tag[0] ^= 1;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test that decryption fails when the gcm_tag field is of a wrong size.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorGcmTagBadSize) {
uss_container_struct_.gcm_tag.resize(uss_container_struct_.gcm_tag.size() -
1);
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssContainerStruct(), kMainKey));
}
// Test the decryption fails when the payload's FEK field is missing. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFek) {
uss_payload_struct_.fek.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's FNEK field is missing. Normally
// this never occurs, but we verify to be resilient against accidental or
// intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFnek) {
uss_payload_struct_.fnek.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's FEK salt field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFekSalt) {
uss_payload_struct_.fek_salt.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's FNEK salt field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFnekSalt) {
uss_payload_struct_.fnek_salt.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's FEK signature field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFekSig) {
uss_payload_struct_.fek_sig.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's FNEK signature field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoFnekSig) {
uss_payload_struct_.fnek_sig.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's Chaps key field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoChapsKey) {
uss_payload_struct_.chaps_key.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Test the decryption fails when the payload's reset_secret field is missing.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsTest, DecryptErrorNoResetSecret) {
uss_payload_struct_.reset_secret.clear();
EXPECT_FALSE(UserSecretStash::FromEncryptedContainer(
GetFlatbufferFromUssPayloadStruct(), kMainKey));
}
// Fixture that prebundles the USS object with a wrapped key block.
class UserSecretStashInternalsWrappingTest
: public UserSecretStashInternalsTest {
protected:
const char* const kWrappingId = "id";
const brillo::SecureBlob kWrappingKey =
brillo::SecureBlob(kAesGcm256KeySize, 0xB);
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(UserSecretStashInternalsTest::SetUp());
EXPECT_TRUE(stash_->AddWrappedMainKey(kMainKey, kWrappingId, kWrappingKey));
ASSERT_NO_FATAL_FAILURE(UpdateBindingStrusts());
}
};
// Verify that the test fixture correctly generates the flatbuffers from the
// Object API.
TEST_F(UserSecretStashInternalsWrappingTest, SmokeTest) {
brillo::SecureBlob main_key;
EXPECT_TRUE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
EXPECT_EQ(main_key, kMainKey);
}
// Test that decryption via wrapping key fails when the only block's wrapping_id
// is empty. Normally this never occurs, but we verify to be resilient against
// accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorNoWrappingId) {
uss_container_struct_.wrapped_key_blocks[0].wrapping_id = std::string();
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key succeeds despite having an extra block
// with an empty wrapping_id (this block should be ignored). Normally this never
// occurs, but we verify to be resilient against accidental or intentional file
// corruption.
TEST_F(UserSecretStashInternalsWrappingTest, SuccessWithExtraNoWrappingId) {
UserSecretStashWrappedKeyBlock key_block_clone =
uss_container_struct_.wrapped_key_blocks[0];
key_block_clone.wrapping_id = std::string();
uss_container_struct_.wrapped_key_blocks.push_back(key_block_clone);
brillo::SecureBlob main_key;
EXPECT_TRUE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key succeeds despite having an extra block
// with a duplicate wrapping_id (this block should be ignored). Normally this
// never occurs, but we verify to be resilient against accidental or intentional
// file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, SuccessWithDuplicateWrappingId) {
UserSecretStashWrappedKeyBlock key_block_clone =
uss_container_struct_.wrapped_key_blocks[0];
uss_container_struct_.wrapped_key_blocks.push_back(key_block_clone);
brillo::SecureBlob main_key;
EXPECT_TRUE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the algorithm is not
// specified in the stored block. Normally this never occurs, but we verify to
// be resilient against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorNoAlgorithm) {
uss_container_struct_.wrapped_key_blocks[0].encryption_algorithm =
std::nullopt;
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the algorithm is unknown.
// Normally this never occurs, but we verify to be resilient against accidental
// or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorUnknownAlgorithm) {
uss_container_struct_.wrapped_key_blocks[0]
.encryption_algorithm = static_cast<UserSecretStashEncryptionAlgorithm>(
std::numeric_limits<
std::underlying_type_t<UserSecretStashEncryptionAlgorithm>>::max());
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the encrypted_key is empty
// in the stored block.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorEmptyEncryptedKey) {
uss_container_struct_.wrapped_key_blocks[0].encrypted_key.clear();
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the encrypted_key in the
// stored block is corrupted.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorBadEncryptedKey) {
uss_container_struct_.wrapped_key_blocks[0].encrypted_key[0] ^= 1;
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the iv is empty in the
// stored block. Normally this never occurs, but we verify to be resilient
// against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorNoIv) {
uss_container_struct_.wrapped_key_blocks[0].iv.clear();
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the iv in the stored block
// is corrupted. Normally this never occurs, but we verify to be resilient
// against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorWrongIv) {
uss_container_struct_.wrapped_key_blocks[0].iv[0] ^= 1;
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the iv in the stored block
// is of wrong size. Normally this never occurs, but we verify to be resilient
// against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorIvBadSize) {
uss_container_struct_.wrapped_key_blocks[0].iv.resize(
uss_container_struct_.wrapped_key_blocks[0].iv.size() - 1);
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the gcm_tag is empty in the
// stored block. Normally this never occurs, but we verify to be resilient
// against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorNoGcmTag) {
uss_container_struct_.wrapped_key_blocks[0].gcm_tag.clear();
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the gcm_tag in the stored
// block is corrupted. Normally this never occurs, but we verify to be resilient
// against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorWrongGcmTag) {
uss_container_struct_.wrapped_key_blocks[0].gcm_tag[0] ^= 1;
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
// Test that decryption via wrapping key fails when the gcm_tag in the stored
// block is of wrong size. Normally this never occurs, but we verify to be
// resilient against accidental or intentional file corruption.
TEST_F(UserSecretStashInternalsWrappingTest, ErrorGcmTagBadSize) {
uss_container_struct_.wrapped_key_blocks[0].gcm_tag.resize(
uss_container_struct_.wrapped_key_blocks[0].gcm_tag.size() - 1);
brillo::SecureBlob main_key;
EXPECT_FALSE(UserSecretStash::FromEncryptedContainerWithWrappingKey(
GetFlatbufferFromUssContainerStruct(), kWrappingId, kWrappingKey,
&main_key));
}
} // namespace cryptohome