| // Copyright 2022 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/auth_blocks/revocation.h" | |
| #include <cstdint> | |
| #include <map> | |
| #include <optional> | |
| #include <utility> | |
| #include <vector> | |
| #include <brillo/secure_blob.h> | |
| #include <libhwsec/backend/pinweaver_manager/pinweaver_manager.h> | |
| #include <libhwsec-foundation/crypto/aes.h> | |
| #include <libhwsec-foundation/crypto/hkdf.h> | |
| #include <libhwsec-foundation/crypto/scrypt.h> | |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> | |
| #include "cryptohome/crypto_error.h" | |
| #include "cryptohome/cryptohome_common.h" | |
| #include "cryptohome/cryptohome_metrics.h" | |
| #include "cryptohome/error/cryptohome_crypto_error.h" | |
| #include "cryptohome/error/cryptohome_tpm_error.h" | |
| #include "cryptohome/error/locations.h" | |
| #include "cryptohome/flatbuffer_schemas/auth_block_state.h" | |
| #include "cryptohome/key_objects.h" | |
| using ::cryptohome::error::CryptohomeCryptoError; | |
| using ::cryptohome::error::ErrorActionSet; | |
| using ::cryptohome::error::PossibleAction; | |
| using ::cryptohome::error::PrimaryAction; | |
| using ::hwsec_foundation::CreateSecureRandomBlob; | |
| using ::hwsec_foundation::DeriveSecretsScrypt; | |
| using ::hwsec_foundation::Hkdf; | |
| using ::hwsec_foundation::HkdfHash; | |
| using ::hwsec_foundation::kDefaultAesKeySize; | |
| using ::hwsec_foundation::status::MakeStatus; | |
| using ::hwsec_foundation::status::OkStatus; | |
| namespace cryptohome::revocation { | |
| namespace { | |
| constexpr int kDefaultSecretSize = 32; | |
| // String used as vector in HKDF operation to derive vkk_key from he_secret. | |
| const char kHESecretHkdfData[] = "hkdf_data"; | |
| // String used as info in HKDF operation to derive le_secret from | |
| // per_credential_secret. | |
| const char kLeSecretInfo[] = "le_secret_info"; | |
| // String used as info in HKDF operation to derive kdf_skey from | |
| // per_credential_secret. | |
| const char kKdfSkeyInfo[] = "kdf_skey_info"; | |
| // The format for a delay schedule entry is as follows: | |
| // (number_of_incorrect_attempts, delay before_next_attempt) | |
| // The delay is not needed for revocation, so we set | |
| // number_of_incorrect_attempts to UINT32_MAX. | |
| hwsec::PinWeaverManagerFrontend::DelaySchedule GetDelaySchedule() { | |
| return std::map<uint32_t, uint32_t>{{UINT32_MAX, 1}}; | |
| } | |
| CryptoError RevokeTPMRetryActionToCryptoError( | |
| hwsec::TPMRetryAction retry_action) { | |
| switch (retry_action) { | |
| case hwsec::TPMRetryAction::kNoRetry: | |
| case hwsec::TPMRetryAction::kSpaceNotFound: | |
| // Do not return an error here. RemoveCredential returns: | |
| // - TPMRetryAction::kSpaceNotFound for invalid label. | |
| // - TPMRetryAction::kNoRetry for hash tree error (implies that all state | |
| // in PinWeaver is lost). Both of these cases are considered as "success" | |
| // for revocation. | |
| return CryptoError::CE_NONE; | |
| default: | |
| return CryptoError::CE_OTHER_CRYPTO; | |
| } | |
| } | |
| bool DeriveSecret(const brillo::SecureBlob& key, | |
| const brillo::Blob& hkdf_info, | |
| brillo::SecureBlob* gen_secret) { | |
| // Note: the key is high entropy, so the salt can be empty. | |
| if (!Hkdf(HkdfHash::kSha256, /*key=*/key, | |
| /*info=*/hkdf_info, | |
| /*salt=*/brillo::Blob(), | |
| /*result_len=*/gen_secret->size(), gen_secret)) { | |
| LOG(ERROR) << "HKDF failed for revocation during secret derivation."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| bool IsRevocationSupported(const hwsec::CryptohomeFrontend* hwsec) { | |
| hwsec::StatusOr<bool> enabled = hwsec->IsPinWeaverEnabled(); | |
| return enabled.ok() && *enabled; | |
| } | |
| CryptoStatus Create(const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager, | |
| RevocationState* revocation_state, | |
| KeyBlobs* key_blobs) { | |
| if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) { | |
| LOG(ERROR) << "Failed to create secret for revocation: vkk_key is not set"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationNoVkkKeyInCreate), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| // The secret generated by AuthBlock. | |
| brillo::SecureBlob per_credential_secret = key_blobs->vkk_key.value(); | |
| brillo::SecureBlob salt = | |
| CreateSecureRandomBlob(kCryptohomeDefaultKeySaltSize); | |
| // Derive two secrets from per_credential_secret: | |
| // le_secret to be stored in LECredentialManager, | |
| // kdf_skey to be combined with he_secret for vkk_key generation. | |
| brillo::SecureBlob le_secret(kDefaultSecretSize); | |
| brillo::SecureBlob kdf_skey(kDefaultSecretSize); | |
| if (!DeriveSecret(per_credential_secret, | |
| brillo::BlobFromString(kLeSecretInfo), &le_secret) || | |
| !DeriveSecret(per_credential_secret, brillo::BlobFromString(kKdfSkeyInfo), | |
| &kdf_skey)) { | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationDeriveSecretsFailedInCreate), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| // Generate a random high entropy secret to be stored in LECredentialManager. | |
| brillo::SecureBlob he_secret = CreateSecureRandomBlob(kDefaultSecretSize); | |
| // Note: | |
| // - We send an empty blob as reset_secret because resetting the delay counter | |
| // will not compromise security (we send MAX_UINT32 attempts for the delay | |
| // schedule). The size should still be kDefaultSecretSize. | |
| // - We don't set policies because PCR binding is expected to be | |
| // already done by the AuthBlock. | |
| hwsec::StatusOr<uint64_t> result = hwsec_pw_manager->InsertCredential( | |
| /*policies=*/std::vector<hwsec::OperationPolicySetting>(), | |
| /*le_secret=*/le_secret, | |
| /*he_secret=*/he_secret, | |
| /*reset_secret=*/brillo::SecureBlob(kDefaultSecretSize), | |
| /*delay_schedule=*/GetDelaySchedule(), | |
| /*expiration_delay=*/std::nullopt); | |
| if (!result.ok()) { | |
| LOG(ERROR) << "InsertCredential failed for revocation with error " | |
| << result.err_status(); | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationInsertCredentialFailedInCreate)) | |
| .Wrap(MakeStatus<error::CryptohomeTPMError>( | |
| std::move(result).err_status())); | |
| } | |
| revocation_state->le_label = result.value(); | |
| // Combine he_secret with kdf_skey: | |
| brillo::SecureBlob vkk_key; | |
| if (!Hkdf(HkdfHash::kSha256, | |
| /*key=*/brillo::SecureBlob::Combine(he_secret, kdf_skey), | |
| /*info=*/brillo::Blob(), | |
| /*salt=*/brillo::BlobFromString(kHESecretHkdfData), | |
| /*result_len=*/0, &vkk_key)) { | |
| LOG(ERROR) << "vkk_key HKDF derivation failed for revocation"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationHkdfFailedInCreate), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| key_blobs->vkk_key = std::move(vkk_key); | |
| return OkStatus<CryptohomeCryptoError>(); | |
| } | |
| CryptoStatus Derive(const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager, | |
| const RevocationState& revocation_state, | |
| KeyBlobs* key_blobs) { | |
| if (!key_blobs->vkk_key.has_value() || key_blobs->vkk_key.value().empty()) { | |
| LOG(ERROR) << "Failed to derive secret for revocation: vkk_key is not set"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationNoVkkKeyInDerive), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| if (!revocation_state.le_label.has_value()) { | |
| LOG(ERROR) | |
| << "Failed to derive secret: revocation_state.le_label is not set"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationNoLeLabelInDerive), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| // The secret generated by AuthBlock. | |
| brillo::SecureBlob per_credential_secret = key_blobs->vkk_key.value(); | |
| brillo::SecureBlob le_secret(kDefaultSecretSize); | |
| brillo::SecureBlob kdf_skey(kDefaultSecretSize); | |
| if (!DeriveSecret(per_credential_secret, | |
| brillo::BlobFromString(kLeSecretInfo), &le_secret) || | |
| !DeriveSecret(per_credential_secret, brillo::BlobFromString(kKdfSkeyInfo), | |
| &kdf_skey)) { | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationDeriveSecretsFailedInDerive), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| hwsec::StatusOr<hwsec::PinWeaverManagerFrontend::CheckCredentialReply> | |
| result = hwsec_pw_manager->CheckCredential( | |
| /*label=*/revocation_state.le_label.value(), | |
| /*le_secret=*/le_secret); | |
| if (!result.ok()) { | |
| LOG(ERROR) << "CheckCredential failed for revocation with error " | |
| << result.err_status(); | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationCheckCredentialFailedInDerive)) | |
| .Wrap(MakeStatus<error::CryptohomeTPMError>( | |
| std::move(result).err_status())); | |
| } | |
| // Combine he_secret with kdf_skey: | |
| brillo::SecureBlob vkk_key; | |
| if (!Hkdf(HkdfHash::kSha256, | |
| /*key=*/ | |
| brillo::SecureBlob::Combine(result->he_secret, kdf_skey), | |
| /*info=*/brillo::Blob(), | |
| /*salt=*/brillo::BlobFromString(kHESecretHkdfData), | |
| /*result_len=*/0, &vkk_key)) { | |
| LOG(ERROR) << "vkk_key HKDF derivation failed for revocation"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationHkdfFailedInDerive), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| key_blobs->vkk_key = std::move(vkk_key); | |
| return OkStatus<CryptohomeCryptoError>(); | |
| } | |
| CryptoStatus Revoke(AuthBlockType auth_block_type, | |
| const hwsec::PinWeaverManagerFrontend* hwsec_pw_manager, | |
| const RevocationState& revocation_state) { | |
| if (!revocation_state.le_label.has_value()) { | |
| LOG(ERROR) | |
| << "Failed to revoke secret: revocation_state.le_label is not set"; | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationNoLeLabelInRevoke), | |
| ErrorActionSet({PossibleAction::kDevCheckUnexpectedState}), | |
| CryptoError::CE_OTHER_CRYPTO); | |
| } | |
| hwsec::Status result = hwsec_pw_manager->RemoveCredential( | |
| /*label=*/revocation_state.le_label.value()); | |
| if (!result.ok()) { | |
| LOG(ERROR) << "RemoveCredential failed for revocation with error: " | |
| << result.err_status(); | |
| ReportRevokeCredentialResult(auth_block_type, result->ToTPMRetryAction()); | |
| CryptoError revoke_error = | |
| RevokeTPMRetryActionToCryptoError(result->ToTPMRetryAction()); | |
| if (revoke_error == CryptoError::CE_NONE) { | |
| // This case is considered a success - do not return an error here. See | |
| // the comment in `RevokeTPMRetryActionToCryptoError`. | |
| return OkStatus<CryptohomeCryptoError>(); | |
| } | |
| // Note: the local error must be overridden with revoke_error. | |
| return MakeStatus<CryptohomeCryptoError>( | |
| CRYPTOHOME_ERR_LOC(kLocRevocationRemoveCredentialFailedInRevoke), | |
| ErrorActionSet({}), revoke_error) | |
| .Wrap(MakeStatus<error::CryptohomeTPMError>( | |
| std::move(result).err_status())); | |
| } | |
| ReportRevokeCredentialResult(auth_block_type, hwsec::TPMRetryAction::kNone); | |
| return OkStatus<CryptohomeCryptoError>(); | |
| } | |
| } // namespace cryptohome::revocation |