| // Copyright (c) 2012 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 Crypto. |
| |
| #include "cryptohome/crypto.h" |
| |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/rand.h> |
| #include <openssl/sha.h> |
| |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/secure_blob.h> |
| #include <gtest/gtest.h> |
| #include <vector> |
| |
| #include "cryptohome/attestation.pb.h" |
| #include "cryptohome/crypto_error.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/mock_le_credential_manager.h" |
| #include "cryptohome/mock_platform.h" |
| #include "cryptohome/mock_tpm.h" |
| #include "cryptohome/mock_tpm_init.h" |
| #include "cryptohome/vault_keyset.h" |
| |
| using base::FilePath; |
| using brillo::Blob; |
| using brillo::SecureBlob; |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::DoAll; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| |
| namespace cryptohome { |
| |
| const char kImageDir[] = "test_image_dir"; |
| |
| // FIPS 180-2 test vectors for SHA-1 and SHA-256 |
| class ShaTestVectors { |
| public: |
| explicit ShaTestVectors(int type); |
| |
| ~ShaTestVectors() {} |
| const brillo::Blob* input(int index) const { return &input_[index]; } |
| const brillo::SecureBlob* output(int index) const { return &output_[index]; } |
| size_t count() const { return 3; } // sizeof(input_); } |
| |
| static const char* kOneBlockMessage; |
| static const char* kMultiBlockMessage; |
| static const uint8_t kSha1Results[][SHA_DIGEST_LENGTH]; |
| static const uint8_t kSha256Results[][SHA256_DIGEST_LENGTH]; |
| |
| private: |
| brillo::Blob input_[3]; |
| brillo::SecureBlob output_[3]; |
| }; |
| |
| const char* ShaTestVectors::kMultiBlockMessage = |
| "abcdbcdecdefdefgefghfghighijhijkijkl" |
| "jklmklmnlmnomnopnopq"; |
| const char* ShaTestVectors::kOneBlockMessage = "abc"; |
| const uint8_t ShaTestVectors::kSha1Results[][SHA_DIGEST_LENGTH] = { |
| {0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, |
| 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}, |
| {0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2, 0x6e, 0xba, 0xae, |
| 0x4a, 0xa1, 0xf9, 0x51, 0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1}, |
| {0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda, 0xa4, 0xf6, 0x1e, |
| 0xeb, 0x2b, 0xdb, 0xad, 0x27, 0x31, 0x65, 0x34, 0x01, 0x6f}}; |
| const uint8_t ShaTestVectors::kSha256Results[][SHA256_DIGEST_LENGTH] = { |
| {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, |
| 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, |
| 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}, |
| {0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, |
| 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, |
| 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}, |
| {0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, |
| 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, |
| 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}}; |
| |
| ShaTestVectors::ShaTestVectors(int type) { |
| // Since we don't do 512+, we can prep here for all types and |
| // don't need to get fancy. |
| input_[0].resize(strlen(kOneBlockMessage)); |
| memcpy(&(input_[0][0]), kOneBlockMessage, input_[0].size()); |
| input_[1].resize(strlen(kMultiBlockMessage)); |
| memcpy(&input_[1][0], kMultiBlockMessage, input_[1].size()); |
| input_[2].assign(1000000, 'a'); |
| |
| switch (type) { |
| case 1: |
| for (size_t i = 0; i < count(); ++i) { |
| output_[i].resize(SHA_DIGEST_LENGTH); |
| memcpy(output_[i].data(), kSha1Results[i], output_[i].size()); |
| } |
| break; |
| case 256: |
| for (size_t i = 0; i < count(); ++i) { |
| output_[i].resize(SHA256_DIGEST_LENGTH); |
| memcpy(output_[i].data(), kSha256Results[i], output_[i].size()); |
| } |
| break; |
| default: |
| CHECK(false) << "Only SHA-256 and SHA-1 are supported"; |
| } |
| } |
| |
| class CryptoTest : public ::testing::Test { |
| public: |
| CryptoTest() {} |
| CryptoTest(const CryptoTest&) = delete; |
| CryptoTest& operator=(const CryptoTest&) = delete; |
| |
| virtual ~CryptoTest() {} |
| |
| static bool FindBlobInBlob(const SecureBlob& haystack, |
| const SecureBlob& needle) { |
| if (needle.size() > haystack.size()) { |
| return false; |
| } |
| for (unsigned int start = 0; start <= (haystack.size() - needle.size()); |
| start++) { |
| if (brillo::SecureMemcmp(&haystack[start], needle.data(), |
| needle.size()) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static void GetSerializedBlob(const SerializedVaultKeyset& serialized, |
| SecureBlob* blob) { |
| SecureBlob final_blob(serialized.ByteSizeLong()); |
| serialized.SerializeWithCachedSizesToArray( |
| static_cast<google::protobuf::uint8*>(final_blob.data())); |
| blob->swap(final_blob); |
| } |
| |
| static bool FromSerializedBlob(const SecureBlob& blob, |
| SerializedVaultKeyset* serialized) { |
| return serialized->ParseFromArray(blob.data(), blob.size()); |
| } |
| |
| protected: |
| MockPlatform platform_; |
| }; |
| |
| TEST_F(CryptoTest, EncryptionTest) { |
| // Check that EncryptVaultKeyset returns something other than the bytes passed |
| Crypto crypto(&platform_); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| |
| SecureBlob original; |
| ASSERT_TRUE(vault_keyset.ToKeysBlob(&original)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_GT(encrypted.size(), 0); |
| ASSERT_FALSE(CryptoTest::FindBlobInBlob(encrypted, original)); |
| } |
| |
| TEST_F(CryptoTest, DecryptionTest) { |
| // Check that DecryptVaultKeyset returns the original keyset |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(encrypted, salt)); |
| |
| ASSERT_TRUE(CryptoTest::FromSerializedBlob(encrypted, &serialized)); |
| |
| VaultKeyset new_keyset; |
| new_keyset.Initialize(&platform_, &crypto); |
| unsigned int crypt_flags = 0; |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| ASSERT_TRUE(crypto.DecryptVaultKeyset( |
| serialized, key, false /* locked_to_single_user */, &crypt_flags, |
| &crypto_error, &new_keyset)); |
| |
| SecureBlob original_data; |
| ASSERT_TRUE(vault_keyset.ToKeysBlob(&original_data)); |
| SecureBlob new_data; |
| ASSERT_TRUE(new_keyset.ToKeysBlob(&new_data)); |
| |
| EXPECT_EQ(new_data.size(), original_data.size()); |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(new_data, original_data)); |
| } |
| |
| TEST_F(CryptoTest, SaltCreateTest) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| |
| // Case 1: No salt exists |
| SecureBlob salt; |
| SecureBlob salt_written; |
| SecureBlob* salt_ptr = &salt_written; |
| FilePath salt_path(FilePath(kImageDir).Append("crypto_test_salt")); |
| EXPECT_CALL(platform, FileExists(salt_path)).WillOnce(Return(false)); |
| EXPECT_CALL(platform, WriteSecureBlobToFileAtomicDurable(salt_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(salt_ptr), Return(true))); |
| crypto.GetOrCreateSalt(salt_path, 32, false, &salt); |
| |
| ASSERT_EQ(32, salt.size()); |
| EXPECT_EQ(salt.to_string(), std::string(salt_ptr->begin(), salt_ptr->end())); |
| |
| // Case 2: Salt exists, but forced |
| SecureBlob new_salt; |
| salt_written.resize(0); |
| salt_ptr = &salt_written; |
| EXPECT_CALL(platform, FileExists(salt_path)).WillOnce(Return(true)); |
| int64_t salt_size = 32; |
| EXPECT_CALL(platform, GetFileSize(salt_path, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(salt_size), Return(true))); |
| EXPECT_CALL(platform, WriteSecureBlobToFileAtomicDurable(salt_path, _, _)) |
| .WillOnce(DoAll(SaveArg<1>(salt_ptr), Return(true))); |
| crypto.GetOrCreateSalt(salt_path, 32, true, &new_salt); |
| ASSERT_EQ(32, new_salt.size()); |
| EXPECT_EQ(new_salt.to_string(), |
| std::string(salt_ptr->begin(), salt_ptr->end())); |
| |
| EXPECT_EQ(salt.size(), new_salt.size()); |
| EXPECT_FALSE(CryptoTest::FindBlobInBlob(salt, new_salt)); |
| |
| // TODO(wad): cases not covered: file is 0 bytes, file fails to read, |
| // existing salt is read. |
| } |
| |
| TEST_F(CryptoTest, BlobToHexTest) { |
| // Check that BlobToHexToBuffer works |
| SecureBlob blob_in(256); |
| SecureBlob blob_out(512); |
| |
| for (int i = 0; i < 256; i++) { |
| blob_in[i] = i; |
| blob_out[i * 2] = 0; |
| blob_out[i * 2 + 1] = 0; |
| } |
| |
| CryptoLib::SecureBlobToHexToBuffer(blob_in, blob_out.data(), blob_out.size()); |
| for (int i = 0; i < 256; i++) { |
| std::string digits = base::StringPrintf("%02x", i); |
| ASSERT_EQ(digits[0], blob_out[i * 2]); |
| ASSERT_EQ(digits[1], blob_out[i * 2 + 1]); |
| } |
| } |
| |
| TEST_F(CryptoTest, TpmStepTest) { |
| // Check that the code path changes to support the TPM work |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| |
| crypto.set_tpm(&tpm); |
| |
| SecureBlob vkk_key; |
| EXPECT_CALL(tpm, GetVersion()).WillRepeatedly(Return(Tpm::TPM_2_0)); |
| EXPECT_CALL(tpm, SealToPcrWithAuthorization(_, _, _, _, _)) |
| .Times(2) // Once for each valid PCR state. |
| .WillRepeatedly(DoAll(SaveArg<1>(&vkk_key), Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm_init, HasCryptohomeKey()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init, SetupTpm(true)) |
| .Times(AtLeast(2)); // One by crypto.Init(), one by crypto.EnsureTpm() |
| SecureBlob blob("public key hash"); |
| EXPECT_CALL(tpm, GetPublicKeyHash(_, _)) |
| .Times(2) // Once on Encrypt and once on Decrypt of Vault. |
| .WillRepeatedly( |
| DoAll(SetArgPointee<1>(blob), Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm, IsOwned()).WillRepeatedly(Return(true)); |
| |
| crypto.Init(&tpm_init); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(encrypted, salt)); |
| |
| ASSERT_TRUE(CryptoTest::FromSerializedBlob(encrypted, &serialized)); |
| |
| VaultKeyset new_keyset; |
| new_keyset.Initialize(&platform, &crypto); |
| unsigned int crypt_flags = 0; |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| |
| EXPECT_CALL(tpm, UnsealWithAuthorization(_, _, _, _, _)) |
| .WillOnce(DoAll(SetArgPointee<4>(vkk_key), Return(Tpm::kTpmRetryNone))); |
| |
| ASSERT_TRUE(crypto.DecryptVaultKeyset( |
| serialized, key, false /* locked_to_single_user */, &crypt_flags, |
| &crypto_error, &new_keyset)); |
| |
| SecureBlob original_data; |
| ASSERT_TRUE(vault_keyset.ToKeysBlob(&original_data)); |
| SecureBlob new_data; |
| ASSERT_TRUE(new_keyset.ToKeysBlob(&new_data)); |
| |
| EXPECT_EQ(new_data.size(), original_data.size()); |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(new_data, original_data)); |
| |
| // Check that the keyset was indeed wrapped by the TPM, and the |
| // keys were derived using scrypt. |
| EXPECT_EQ(0, (crypt_flags & SerializedVaultKeyset::SCRYPT_WRAPPED)); |
| EXPECT_EQ(SerializedVaultKeyset::TPM_WRAPPED, |
| (crypt_flags & SerializedVaultKeyset::TPM_WRAPPED)); |
| EXPECT_EQ(SerializedVaultKeyset::SCRYPT_DERIVED, |
| (crypt_flags & SerializedVaultKeyset::SCRYPT_DERIVED)); |
| EXPECT_EQ(SerializedVaultKeyset::PCR_BOUND, |
| (crypt_flags & SerializedVaultKeyset::PCR_BOUND)); |
| } |
| |
| TEST_F(CryptoTest, Tpm1_2_StepTest) { |
| // Check that the code path changes to support the TPM work |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| |
| crypto.set_tpm(&tpm); |
| |
| SecureBlob vkk_key; |
| EXPECT_CALL(tpm, GetVersion()).WillRepeatedly(Return(Tpm::TPM_1_2)); |
| EXPECT_CALL(tpm, EncryptBlob(_, _, _, _)) |
| .Times(1) |
| .WillRepeatedly(DoAll(SaveArg<1>(&vkk_key), Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm_init, HasCryptohomeKey()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init, SetupTpm(true)) |
| .Times(AtLeast(2)); // One by crypto.Init(), one by crypto.EnsureTpm() |
| SecureBlob blob("public key hash"); |
| EXPECT_CALL(tpm, GetPublicKeyHash(_, _)) |
| .Times(2) // Once on Encrypt and once on Decrypt of Vault. |
| .WillRepeatedly( |
| DoAll(SetArgPointee<1>(blob), Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm, IsOwned()).WillRepeatedly(Return(true)); |
| |
| crypto.Init(&tpm_init); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(encrypted, salt)); |
| |
| ASSERT_TRUE(CryptoTest::FromSerializedBlob(encrypted, &serialized)); |
| |
| VaultKeyset new_keyset; |
| new_keyset.Initialize(&platform, &crypto); |
| unsigned int crypt_flags = 0; |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| |
| EXPECT_CALL(tpm, DecryptBlob(_, _, _, _, _)) |
| .WillOnce(DoAll(SetArgPointee<4>(vkk_key), Return(Tpm::kTpmRetryNone))); |
| |
| ASSERT_TRUE(crypto.DecryptVaultKeyset( |
| serialized, key, false /* locked_to_single_user */, &crypt_flags, |
| &crypto_error, &new_keyset)); |
| |
| SecureBlob original_data; |
| ASSERT_TRUE(vault_keyset.ToKeysBlob(&original_data)); |
| SecureBlob new_data; |
| ASSERT_TRUE(new_keyset.ToKeysBlob(&new_data)); |
| |
| EXPECT_EQ(new_data.size(), original_data.size()); |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(new_data, original_data)); |
| |
| // Check that the keyset was indeed wrapped by the TPM, and the |
| // keys were derived using scrypt. |
| EXPECT_EQ(0, (crypt_flags & SerializedVaultKeyset::SCRYPT_WRAPPED)); |
| EXPECT_EQ(SerializedVaultKeyset::TPM_WRAPPED, |
| (crypt_flags & SerializedVaultKeyset::TPM_WRAPPED)); |
| EXPECT_EQ(SerializedVaultKeyset::SCRYPT_DERIVED, |
| (crypt_flags & SerializedVaultKeyset::SCRYPT_DERIVED)); |
| EXPECT_EQ(0, (crypt_flags & SerializedVaultKeyset::PCR_BOUND)); |
| } |
| |
| TEST_F(CryptoTest, TpmDecryptFailureTest) { |
| // Check how TPM error on Decrypt is reported. |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| |
| crypto.set_tpm(&tpm); |
| |
| EXPECT_CALL(tpm, SealToPcrWithAuthorization(_, _, _, _, _)).Times(2); |
| EXPECT_CALL(tpm_init, HasCryptohomeKey()) |
| .WillOnce(Return(false)) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_init, SetupTpm(true)) |
| .Times(AtLeast(2)); // One by crypto.Init(), one by crypto.EnsureTpm() |
| SecureBlob blob("public key hash"); |
| EXPECT_CALL(tpm, GetPublicKeyHash(_, _)) |
| .Times(2) // Once on Encrypt and once on Decrypt of Vault. |
| .WillRepeatedly( |
| DoAll(SetArgPointee<1>(blob), Return(Tpm::kTpmRetryNone))); |
| EXPECT_CALL(tpm, IsOwned()).WillRepeatedly(Return(true)); |
| crypto.Init(&tpm_init); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform_, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(encrypted, salt)); |
| |
| ASSERT_TRUE(CryptoTest::FromSerializedBlob(encrypted, &serialized)); |
| |
| VaultKeyset new_keyset; |
| new_keyset.Initialize(&platform, &crypto); |
| unsigned int crypt_flags = 0; |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| |
| // UnsealWithAuthorization operation will fail. |
| EXPECT_CALL(tpm, UnsealWithAuthorization(_, _, _, _, _)) |
| .WillOnce(Return(Tpm::kTpmRetryFatal)); |
| |
| ASSERT_FALSE(crypto.DecryptVaultKeyset( |
| serialized, key, false /* locked_to_single_user */, &crypt_flags, |
| &crypto_error, &new_keyset)); |
| ASSERT_NE(CryptoError::CE_NONE, crypto_error); |
| } |
| |
| TEST_F(CryptoTest, ScryptStepTest) { |
| // Check that the code path changes to support scrypt work |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| |
| VaultKeyset vault_keyset; |
| vault_keyset.Initialize(&platform, &crypto); |
| vault_keyset.CreateRandom(); |
| |
| SecureBlob key(20); |
| CryptoLib::GetSecureRandom(key.data(), key.size()); |
| SecureBlob salt(PKCS5_SALT_LEN); |
| CryptoLib::GetSecureRandom(salt.data(), salt.size()); |
| |
| SerializedVaultKeyset serialized; |
| ASSERT_TRUE( |
| crypto.EncryptVaultKeyset(vault_keyset, key, salt, "", &serialized)); |
| SecureBlob encrypted; |
| GetSerializedBlob(serialized, &encrypted); |
| |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(encrypted, salt)); |
| |
| ASSERT_TRUE(CryptoTest::FromSerializedBlob(encrypted, &serialized)); |
| |
| VaultKeyset new_keyset; |
| new_keyset.Initialize(&platform, &crypto); |
| unsigned int crypt_flags = 0; |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| ASSERT_TRUE(crypto.DecryptVaultKeyset( |
| serialized, key, false /* locked_to_single_user */, &crypt_flags, |
| &crypto_error, &new_keyset)); |
| |
| SecureBlob original_data; |
| ASSERT_TRUE(vault_keyset.ToKeysBlob(&original_data)); |
| SecureBlob new_data; |
| ASSERT_TRUE(new_keyset.ToKeysBlob(&new_data)); |
| |
| EXPECT_EQ(new_data.size(), original_data.size()); |
| ASSERT_TRUE(CryptoTest::FindBlobInBlob(new_data, original_data)); |
| } |
| |
| TEST_F(CryptoTest, GetSha1FipsTest) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| ShaTestVectors vectors(1); |
| for (size_t i = 0; i < vectors.count(); ++i) { |
| Blob digest = CryptoLib::Sha1(*vectors.input(i)); |
| std::string computed(reinterpret_cast<const char*>(digest.data()), |
| digest.size()); |
| std::string expected = vectors.output(i)->to_string(); |
| EXPECT_EQ(expected, computed); |
| } |
| } |
| |
| TEST_F(CryptoTest, GetSha256FipsTest) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| ShaTestVectors vectors(256); |
| for (size_t i = 0; i < vectors.count(); ++i) { |
| Blob digest = CryptoLib::Sha256(*vectors.input(i)); |
| std::string computed(reinterpret_cast<const char*>(digest.data()), |
| digest.size()); |
| std::string expected = vectors.output(i)->to_string(); |
| EXPECT_EQ(expected, computed); |
| } |
| } |
| |
| TEST_F(CryptoTest, ComputeEncryptedDataHMAC) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| EncryptedData pb; |
| std::string data = "iamsoawesome"; |
| std::string iv = "123456"; |
| pb.set_encrypted_data(data.data(), data.size()); |
| pb.set_iv(iv.data(), iv.size()); |
| |
| // Create hash key. |
| SecureBlob hmac_key(32); |
| CryptoLib::GetSecureRandom(hmac_key.data(), hmac_key.size()); |
| |
| // Perturb iv and data slightly. Verify hashes are all different. |
| std::string hmac1 = CryptoLib::ComputeEncryptedDataHMAC(pb, hmac_key); |
| data = "iamsoawesomf"; |
| pb.set_encrypted_data(data.data(), data.size()); |
| std::string hmac2 = CryptoLib::ComputeEncryptedDataHMAC(pb, hmac_key); |
| iv = "123457"; |
| pb.set_iv(iv.data(), iv.size()); |
| std::string hmac3 = CryptoLib::ComputeEncryptedDataHMAC(pb, hmac_key); |
| |
| EXPECT_NE(hmac1, hmac2); |
| EXPECT_NE(hmac2, hmac3); |
| EXPECT_NE(hmac1, hmac3); |
| } |
| |
| TEST_F(CryptoTest, EncryptAndDecryptWithTpm) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| crypto.set_tpm(&tpm); |
| crypto.Init(&tpm_init); |
| |
| std::string data = "iamsomestufftoencrypt"; |
| SecureBlob data_blob(data); |
| |
| std::string encrypted_data; |
| SecureBlob output_blob; |
| |
| SecureBlob aes_key(32, 'A'); |
| brillo::SecureBlob sealed_key(32, 'S'); |
| SecureBlob iv(16, 'I'); |
| |
| // Setup the data from the above blobs. |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(32, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(aes_key), Return(true))); |
| EXPECT_CALL(tpm, SealToPCR0(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(sealed_key), Return(true))); |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(16, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(iv), Return(true))); |
| |
| // Matching calls of encrypt/decrypt should give me back the same data. |
| EXPECT_TRUE(crypto.EncryptWithTpm(data_blob, &encrypted_data)); |
| |
| // Unseal for the tpm. |
| EXPECT_CALL(tpm, Unseal(sealed_key, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(aes_key), Return(true))); |
| |
| EXPECT_TRUE(crypto.DecryptWithTpm(encrypted_data, &output_blob)); |
| EXPECT_EQ(data_blob, output_blob); |
| |
| // Perturb the data a little and verify we can no longer decrypt it. |
| encrypted_data = encrypted_data + "Z"; |
| EXPECT_FALSE(crypto.DecryptWithTpm(encrypted_data, &output_blob)); |
| } |
| |
| TEST_F(CryptoTest, EncryptAndDecryptWithTpmWithRandomlyFailingTpm) { |
| MockPlatform platform; |
| Crypto crypto(&platform); |
| |
| NiceMock<MockTpm> tpm; |
| NiceMock<MockTpmInit> tpm_init; |
| crypto.set_tpm(&tpm); |
| crypto.Init(&tpm_init); |
| |
| std::string data = "iamsomestufftoencrypt"; |
| SecureBlob data_blob(data); |
| |
| std::string encrypted_data; |
| SecureBlob output_blob; |
| |
| SecureBlob aes_key(32, 'A'); |
| brillo::SecureBlob sealed_key(32, 'S'); |
| SecureBlob iv(16, 'I'); |
| |
| // Setup the data from the above blobs and fail to seal the key with the tpm. |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(32, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(aes_key), Return(true))); |
| EXPECT_CALL(tpm, SealToPCR0(_, _)).WillOnce(Return(false)); |
| EXPECT_FALSE(crypto.EncryptWithTpm(data_blob, &encrypted_data)); |
| |
| // Failed to get random data. |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(32, _)).WillOnce(Return(false)); |
| EXPECT_FALSE(crypto.EncryptWithTpm(data_blob, &encrypted_data)); |
| |
| // Now setup successful encrypt data but fail to unseal. |
| // Setup the data from the above blobs. |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(32, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(aes_key), Return(true))); |
| EXPECT_CALL(tpm, SealToPCR0(_, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(sealed_key), Return(true))); |
| EXPECT_CALL(tpm, GetRandomDataSecureBlob(16, _)) |
| .WillOnce(DoAll(SetArgPointee<1>(iv), Return(true))); |
| |
| // Matching calls of encrypt/decrypt should give me back the same data. |
| EXPECT_TRUE(crypto.EncryptWithTpm(data_blob, &encrypted_data)); |
| |
| // Tpm failing to unseal a valid key. |
| EXPECT_CALL(tpm, Unseal(sealed_key, _)).WillOnce(Return(false)); |
| EXPECT_FALSE(crypto.DecryptWithTpm(encrypted_data, &output_blob)); |
| } |
| |
| namespace { |
| constexpr char kHexHeSecret[] = |
| "F3D9D5B126C36676689E18BB8517D95DF4F30947E71D4A840824425760B1D3FA"; |
| constexpr char kHexResetSecret[] = |
| "B133D2450392335BA8D33AA95AD52488254070C66F5D79AEA1A46AC4A30760D4"; |
| constexpr char kHexWrappedKeyset[] = |
| "B737B5D73E39BD390A4F361CE2FC166CF1E89EC6AEAA35D4B34456502C48B4F5EFA310077" |
| "324B393E13AF633DF3072FF2EC78BD2B80D919035DB97C30F1AD418737DA3F26A4D35DF6B" |
| "6A9743BD0DF3D37D8A68DE0932A9905452D05ECF92701B9805937F76EE01D10924268F057" |
| "EDD66087774BB86C2CB92B01BD3A3C41C10C52838BD3A3296474598418E5191DEE9E8D831" |
| "3C859C9EDB0D5F2BC1D7FC3C108A0D4ABB2D90E413086BCFFD0902AB68E2BF787817EB10C" |
| "25E2E43011CAB3FB8AA"; |
| constexpr char kHexSalt[] = "D470B9B108902241"; |
| constexpr char kHexVaultKey[] = |
| "665A58534E684F2B61516B6D42624B514E6749732B4348427450305453754158377232347" |
| "37A79466C6B383D"; |
| constexpr char kHexFekIv[] = "EA80F14BF29C6D580D536E7F0CC47F3E"; |
| constexpr char kHexChapsIv[] = "ED85D928940E5B02ED218F29225AA34F"; |
| constexpr char kHexWrappedChapsKey[] = |
| "7D7D01EECC8DAE7906CAD56310954BBEB3CC81765210D29902AB92DDE074217771AD284F2" |
| "12C13897C6CBB30CEC4CD75"; |
| |
| std::string HexDecode(const std::string& hex) { |
| std::vector<uint8_t> output; |
| CHECK(base::HexStringToBytes(hex, &output)); |
| return std::string(output.begin(), output.end()); |
| } |
| } // namespace |
| |
| class LeCredentialsManagerTest : public ::testing::Test { |
| public: |
| LeCredentialsManagerTest() : crypto_(&platform_) { |
| crypto_.set_tpm(&tpm_); |
| |
| EXPECT_CALL(tpm_init_, SetupTpm(true)) |
| .WillOnce( |
| Return(true)); // because HasCryptohomeKey returned false once. |
| |
| EXPECT_CALL(tpm_, IsEnabled()).WillRepeatedly(Return(true)); |
| EXPECT_CALL(tpm_, IsOwned()).WillRepeatedly(Return(true)); |
| |
| // Raw pointer as crypto expects unique_ptr, which we will wrap this |
| // allocation into. |
| le_cred_manager_ = new MockLECredentialManager(); |
| EXPECT_CALL(*le_cred_manager_, CheckCredential(_, _, _, _)) |
| .WillRepeatedly(DoAll( |
| SetArgPointee<2>(brillo::SecureBlob(HexDecode(kHexHeSecret))), |
| SetArgPointee<3>(brillo::SecureBlob(HexDecode(kHexResetSecret))), |
| Return(LE_CRED_SUCCESS))); |
| crypto_.set_le_manager_for_testing( |
| std::unique_ptr<cryptohome::LECredentialManager>(le_cred_manager_)); |
| |
| crypto_.Init(&tpm_init_); |
| |
| pin_vault_keyset_.Initialize(&platform_, &crypto_); |
| } |
| |
| ~LeCredentialsManagerTest() override = default; |
| |
| // Not copyable or movable |
| LeCredentialsManagerTest(const LeCredentialsManagerTest&) = delete; |
| LeCredentialsManagerTest& operator=(const LeCredentialsManagerTest&) = delete; |
| LeCredentialsManagerTest(LeCredentialsManagerTest&&) = delete; |
| LeCredentialsManagerTest& operator=(LeCredentialsManagerTest&&) = delete; |
| |
| protected: |
| MockPlatform platform_; |
| Crypto crypto_; |
| NiceMock<MockTpm> tpm_; |
| NiceMock<MockTpmInit> tpm_init_; |
| MockLECredentialManager* le_cred_manager_; |
| |
| VaultKeyset pin_vault_keyset_; |
| }; |
| |
| TEST_F(LeCredentialsManagerTest, Encrypt) { |
| EXPECT_CALL(*le_cred_manager_, InsertCredential(_, _, _, _, _, _)) |
| .WillOnce(Return(LE_CRED_SUCCESS)); |
| |
| pin_vault_keyset_.CreateRandom(); |
| pin_vault_keyset_.mutable_serialized() |
| ->mutable_key_data() |
| ->mutable_policy() |
| ->set_low_entropy_credential(true); |
| |
| EXPECT_TRUE(crypto_.EncryptVaultKeyset( |
| pin_vault_keyset_, brillo::SecureBlob(HexDecode(kHexVaultKey)), |
| brillo::SecureBlob(HexDecode(kHexSalt)), "unused", |
| pin_vault_keyset_.mutable_serialized())); |
| |
| EXPECT_EQ(pin_vault_keyset_.serialized().flags(), |
| SerializedVaultKeyset::LE_CREDENTIAL); |
| } |
| |
| TEST_F(LeCredentialsManagerTest, EncryptFail) { |
| EXPECT_CALL(*le_cred_manager_, InsertCredential(_, _, _, _, _, _)) |
| .WillOnce(Return(LE_CRED_ERROR_NO_FREE_LABEL)); |
| |
| pin_vault_keyset_.CreateRandom(); |
| pin_vault_keyset_.mutable_serialized() |
| ->mutable_key_data() |
| ->mutable_policy() |
| ->set_low_entropy_credential(true); |
| |
| EXPECT_FALSE(crypto_.EncryptVaultKeyset( |
| pin_vault_keyset_, brillo::SecureBlob(HexDecode(kHexVaultKey)), |
| brillo::SecureBlob(HexDecode(kHexSalt)), "unused", |
| pin_vault_keyset_.mutable_serialized())); |
| } |
| |
| TEST_F(LeCredentialsManagerTest, Decrypt) { |
| pin_vault_keyset_.mutable_serialized()->set_flags( |
| SerializedVaultKeyset::LE_CREDENTIAL); |
| pin_vault_keyset_.mutable_serialized()->set_le_fek_iv(HexDecode(kHexFekIv)); |
| pin_vault_keyset_.mutable_serialized()->set_le_chaps_iv( |
| HexDecode(kHexChapsIv)); |
| pin_vault_keyset_.mutable_serialized()->set_wrapped_keyset( |
| HexDecode(kHexWrappedKeyset)); |
| pin_vault_keyset_.mutable_serialized()->set_wrapped_chaps_key( |
| HexDecode(kHexWrappedChapsKey)); |
| pin_vault_keyset_.mutable_serialized()->set_salt(HexDecode(kHexSalt)); |
| |
| CryptoError crypto_error = CryptoError::CE_NONE; |
| EXPECT_TRUE(crypto_.DecryptVaultKeyset( |
| pin_vault_keyset_.serialized(), |
| brillo::SecureBlob(HexDecode(kHexVaultKey)), false, nullptr, |
| &crypto_error, &pin_vault_keyset_)); |
| EXPECT_EQ(CryptoError::CE_NONE, crypto_error); |
| } |
| |
| } // namespace cryptohome |