| // Copyright 2023 The ChromiumOS Authors |
| // 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/encrypted.h" |
| |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include <brillo/secure_blob.h> |
| #include <cryptohome/proto_bindings/UserDataAuth.pb.h> |
| #include <libhwsec-foundation/crypto/aes.h> |
| #include <libhwsec-foundation/status/status_chain_macros.h> |
| |
| #include "cryptohome/error/locations.h" |
| |
| namespace cryptohome { |
| namespace { |
| |
| using ::cryptohome::error::CryptohomeError; |
| using ::cryptohome::error::ErrorActionSet; |
| using ::cryptohome::error::PossibleAction; |
| using ::cryptohome::error::PrimaryAction; |
| using ::hwsec_foundation::AesGcmDecrypt; |
| using ::hwsec_foundation::kAesGcm256KeySize; |
| using ::hwsec_foundation::kAesGcmIVSize; |
| using ::hwsec_foundation::kAesGcmTagSize; |
| using ::hwsec_foundation::status::MakeStatus; |
| using ::hwsec_foundation::status::OkStatus; |
| |
| // Converts the wrapped key block information from serializable structs into the |
| // constainer struct wrapped key block map. |
| decltype(EncryptedUss::Container::wrapped_key_blocks) |
| GetKeyBlocksFromSerializableStructs( |
| const std::vector<UserSecretStashWrappedKeyBlock>& serializable_blocks) { |
| decltype(EncryptedUss::Container::wrapped_key_blocks) 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) { |
| LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an " |
| "unset algorithm"; |
| continue; |
| } |
| if (*serializable_block.encryption_algorithm != |
| UserSecretStashEncryptionAlgorithm::AES_GCM_256) { |
| LOG(WARNING) << "Ignoring UserSecretStash wrapped key block with an " |
| "unknown algorithm: " |
| << static_cast<int>( |
| *serializable_block.encryption_algorithm); |
| 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; |
| } |
| |
| EncryptedUss::WrappedKeyBlock key_block = { |
| .encryption_algorithm = *serializable_block.encryption_algorithm, |
| .encrypted_key = serializable_block.encrypted_key, |
| .iv = serializable_block.iv, |
| .gcm_tag = serializable_block.gcm_tag, |
| }; |
| key_blocks.emplace(serializable_block.wrapping_id, std::move(key_block)); |
| } |
| |
| return key_blocks; |
| } |
| |
| } // namespace |
| |
| CryptohomeStatusOr<EncryptedUss::Container> EncryptedUss::Container::FromBlob( |
| const brillo::Blob& flatbuffer) { |
| Container container; |
| |
| // This check is redundant to the flatbuffer parsing below, but we check it |
| // here in order to distinguish "empty file" from "corrupted file" in metrics |
| // and logs. |
| if (flatbuffer.empty()) { |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSEmptySerializedInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDeleteVault, PossibleAction::kAuth, |
| PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| std::optional<UserSecretStashContainer> deserialized = |
| UserSecretStashContainer::Deserialize(flatbuffer); |
| if (!deserialized) { |
| LOG(ERROR) << "Failed to deserialize UserSecretStashContainer"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSDeserializeFailedInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| if (!deserialized->encryption_algorithm) { |
| LOG(ERROR) << "UserSecretStashContainer has no algorithm set"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSNoAlgInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| if (*deserialized->encryption_algorithm != |
| UserSecretStashEncryptionAlgorithm::AES_GCM_256) { |
| LOG(ERROR) << "UserSecretStashContainer uses unknown algorithm: " |
| << static_cast<int>(*deserialized->encryption_algorithm); |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSUnknownAlgInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| if (deserialized->ciphertext.empty()) { |
| LOG(ERROR) << "UserSecretStash has empty ciphertext"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSNoCiphertextInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| container.ciphertext = deserialized->ciphertext; |
| |
| if (deserialized->iv.empty()) { |
| LOG(ERROR) << "UserSecretStash has empty IV"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSNoIVInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| if (deserialized->iv.size() != kAesGcmIVSize) { |
| LOG(ERROR) << "UserSecretStash has IV of wrong length: " |
| << deserialized->iv.size() << ", expected: " << kAesGcmIVSize; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSIVWrongSizeInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| container.iv = deserialized->iv; |
| |
| if (deserialized->gcm_tag.empty()) { |
| LOG(ERROR) << "UserSecretStash has empty AES-GCM tag"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSNoGCMTagInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| if (deserialized->gcm_tag.size() != kAesGcmTagSize) { |
| LOG(ERROR) << "UserSecretStash has AES-GCM tag of wrong length: " |
| << deserialized->gcm_tag.size() |
| << ", expected: " << kAesGcmTagSize; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSTagWrongSizeInGetContainerFromFB), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| container.gcm_tag = deserialized->gcm_tag; |
| |
| container.wrapped_key_blocks = |
| GetKeyBlocksFromSerializableStructs(deserialized->wrapped_key_blocks); |
| |
| container.created_on_os_version = deserialized->created_on_os_version; |
| |
| container.user_metadata = deserialized->user_metadata; |
| |
| return std::move(container); |
| } |
| |
| CryptohomeStatusOr<EncryptedUss> EncryptedUss::FromBlob( |
| const brillo::Blob& flatbuffer) { |
| ASSIGN_OR_RETURN(Container container, Container::FromBlob(flatbuffer)); |
| return EncryptedUss(std::move(container)); |
| } |
| |
| CryptohomeStatusOr<EncryptedUss> EncryptedUss::FromStorage( |
| const UserUssStorage& storage) { |
| ASSIGN_OR_RETURN(brillo::Blob flatbuffer, storage.LoadPersisted()); |
| return FromBlob(flatbuffer); |
| } |
| |
| EncryptedUss::EncryptedUss(Container container) |
| : container_(std::move(container)) {} |
| |
| CryptohomeStatusOr<brillo::SecureBlob> EncryptedUss::DecryptPayload( |
| const brillo::SecureBlob& main_key) const { |
| // Verify the main key format. |
| if (main_key.size() != kAesGcm256KeySize) { |
| LOG(ERROR) << "The UserSecretStash main key is of wrong length: " |
| << main_key.size() << ", expected: " << kAesGcm256KeySize; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSInvalidKeySizeInFromEncContainer), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| // Use the main key to decrypt the USS payload. |
| brillo::SecureBlob serialized_payload; |
| if (!AesGcmDecrypt(container_.ciphertext, /*ad=*/std::nullopt, |
| container_.gcm_tag, main_key, container_.iv, |
| &serialized_payload)) { |
| LOG(ERROR) << "Failed to decrypt UserSecretStash payload"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSAesGcmFailedInFromEncPayload), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| return serialized_payload; |
| } |
| |
| std::set<std::string_view> EncryptedUss::WrappedMainKeyIds() const { |
| std::set<std::string_view> ids; |
| for (const auto& [wrapping_id, unused] : container_.wrapped_key_blocks) { |
| ids.insert(wrapping_id); |
| } |
| return ids; |
| } |
| |
| CryptohomeStatusOr<brillo::SecureBlob> EncryptedUss::UnwrapMainKey( |
| const std::string& wrapping_id, |
| const brillo::SecureBlob& wrapping_key) const { |
| // Verify the wrapping key and ID format. |
| if (wrapping_id.empty()) { |
| LOG(ERROR) << "Empty wrapping ID is passed for UserSecretStash main key " |
| "unwrapping."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSEmptyWrappingIDInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| if (wrapping_key.size() != kAesGcm256KeySize) { |
| LOG(ERROR) << "Wrong wrapping key size is passed for UserSecretStash " |
| "main key unwrapping. Received: " |
| << wrapping_key.size() << ", expected " << kAesGcm256KeySize |
| << "."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSWrongWKSizeInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| // Find the wrapped key block. |
| const auto wrapped_key_block_iter = |
| container_.wrapped_key_blocks.find(wrapping_id); |
| if (wrapped_key_block_iter == container_.wrapped_key_blocks.end()) { |
| LOG(ERROR) |
| << "UserSecretStash wrapped key block with the given ID not found."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSWrappedBlockNotFoundInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| const EncryptedUss::WrappedKeyBlock& wrapped_key_block = |
| wrapped_key_block_iter->second; |
| |
| // Verify the wrapped key block format. |
| 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 MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSUnknownAlgInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| if (wrapped_key_block.encrypted_key.empty()) { |
| LOG(ERROR) << "UserSecretStash wrapped main key has empty encrypted key."; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSEmptyEncKeyInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| 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 MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSWrongIVSizeInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| 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 MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSWrongTagSizeInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| |
| // Attempt the unwrapping. |
| brillo::SecureBlob main_key; |
| if (!AesGcmDecrypt(wrapped_key_block.encrypted_key, |
| /*ad=*/std::nullopt, wrapped_key_block.gcm_tag, |
| wrapping_key, wrapped_key_block.iv, &main_key)) { |
| LOG(ERROR) << "Failed to unwrap UserSecretStash main key"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSDecryptFailedInUnwrapMKFromBlocks), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| return main_key; |
| } |
| |
| CryptohomeStatusOr<brillo::Blob> EncryptedUss::ToBlob() const { |
| // Pack the container struct into the flatbuffer object. |
| UserSecretStashContainer container = { |
| .encryption_algorithm = UserSecretStashEncryptionAlgorithm::AES_GCM_256, |
| .ciphertext = container_.ciphertext, |
| .iv = container_.iv, |
| .gcm_tag = container_.gcm_tag, |
| .created_on_os_version = container_.created_on_os_version, |
| .user_metadata = container_.user_metadata, |
| }; |
| for (const auto& [wrapping_id, wrapped_key_block] : |
| container_.wrapped_key_blocks) { |
| 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, |
| }); |
| } |
| |
| // Attempt to serialize the flatbuffer into a blob. |
| std::optional<brillo::Blob> serialized_container = container.Serialize(); |
| if (!serialized_container) { |
| LOG(ERROR) << "Failed to serialize UserSecretStashContainer"; |
| return MakeStatus<CryptohomeError>( |
| CRYPTOHOME_ERR_LOC(kLocUSSContainerSerializeFailedInGetEncContainer), |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState, |
| PossibleAction::kAuth, PossibleAction::kDeleteVault}), |
| user_data_auth::CRYPTOHOME_ERROR_BACKING_STORE_FAILURE); |
| } |
| return *serialized_container; |
| } |
| |
| CryptohomeStatus EncryptedUss::ToStorage(UserUssStorage& storage) const { |
| ASSIGN_OR_RETURN(brillo::Blob flatbuffer, ToBlob()); |
| return storage.Persist(flatbuffer); |
| } |
| |
| } // namespace cryptohome |