| // 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/auth_blocks/tpm_ecc_auth_block.h" |
| |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/callback_helpers.h> |
| #include <base/check.h> |
| #include <base/logging.h> |
| #include <brillo/secure_blob.h> |
| #include <libhwsec/status.h> |
| #include <libhwsec/error/elliptic_curve_error.h> |
| #include <libhwsec/error/tpm_retry_handler.h> |
| #include <libhwsec-foundation/crypto/aes.h> |
| #include <libhwsec-foundation/crypto/scrypt.h> |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> |
| #include <libhwsec-foundation/crypto/sha.h> |
| |
| #include "cryptohome/auth_blocks/tpm_auth_block_utils.h" |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/crypto_error.h" |
| #include "cryptohome/cryptohome_keys_manager.h" |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/error/location_utils.h" |
| #include "cryptohome/tpm.h" |
| #include "cryptohome/vault_keyset.pb.h" |
| |
| using cryptohome::error::CryptohomeCryptoError; |
| using cryptohome::error::ErrorAction; |
| using cryptohome::error::ErrorActionSet; |
| using hwsec::EllipticCurveError; |
| using hwsec::EllipticCurveErrorCode; |
| using hwsec::TPMError; |
| using hwsec::TPMErrorBase; |
| using hwsec::TPMRetryAction; |
| using hwsec_foundation::CreateSecureRandomBlob; |
| using hwsec_foundation::DeriveSecretsScrypt; |
| using hwsec_foundation::kAesBlockSize; |
| using hwsec_foundation::kDefaultAesKeySize; |
| using hwsec_foundation::kDefaultPassBlobSize; |
| using hwsec_foundation::kTpmDecryptMaxRetries; |
| using hwsec_foundation::Sha256; |
| using hwsec_foundation::error::WrapError; |
| using hwsec_foundation::status::MakeStatus; |
| using hwsec_foundation::status::OkStatus; |
| using hwsec_foundation::status::StatusChain; |
| |
| namespace { |
| |
| // The failure rate of one GetEccAuthValue operation is about 2.33e-10. |
| // The failure rate of a series of 5 GetEccAuthValue operation is |
| // about 1.165e-9. Retry 5 times would let the failure rate become 2.146e-45, |
| // and that should be a reasonable failure rate. |
| constexpr int kTryCreateMaxRetryCount = 5; |
| |
| // The time of doing GetEccAuthValue operation on normal TPM2.0 is about |
| // 50~100ms, 2 rounds should be enough for rate-limiting against PIN brute-force |
| // attacks. |
| constexpr int kDefaultEccAuthValueRounds = 2; |
| |
| struct VendorAuthValueRounds { |
| uint32_t tpm_vendor_id; |
| uint32_t auth_value_rounds; |
| }; |
| |
| // Cr50 Vendor ID ("CROS"). |
| constexpr uint32_t kVendorIdCr50 = 0x43524f53; |
| // Infineon Vendor ID ("IFX"). |
| constexpr uint32_t kVendorIdIfx = 0x49465800; |
| |
| constexpr VendorAuthValueRounds kVendorAuthValueRoundsList[] = { |
| VendorAuthValueRounds{ |
| .tpm_vendor_id = kVendorIdCr50, |
| .auth_value_rounds = 5, |
| }, |
| VendorAuthValueRounds{ |
| .tpm_vendor_id = kVendorIdIfx, |
| .auth_value_rounds = 2, |
| }, |
| }; |
| |
| int CalcEccAuthValueRounds(cryptohome::Tpm* tpm) { |
| cryptohome::Tpm::TpmVersionInfo version_info = {}; |
| if (!tpm->GetVersionInfo(&version_info)) { |
| LOG(ERROR) << "Failed to get the TPM version info."; |
| return kDefaultEccAuthValueRounds; |
| } |
| |
| for (VendorAuthValueRounds item : kVendorAuthValueRoundsList) { |
| if (version_info.manufacturer == item.tpm_vendor_id) { |
| return item.auth_value_rounds; |
| } |
| } |
| return kDefaultEccAuthValueRounds; |
| } |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| TpmEccAuthBlock::TpmEccAuthBlock(Tpm* tpm, |
| CryptohomeKeysManager* cryptohome_keys_manager) |
| : SyncAuthBlock(kTpmBackedEcc), |
| tpm_(tpm), |
| cryptohome_key_loader_( |
| cryptohome_keys_manager->GetKeyLoader(CryptohomeKeyType::kECC)), |
| utils_(tpm, cryptohome_key_loader_) { |
| CHECK(tpm != nullptr); |
| CHECK(cryptohome_key_loader_ != nullptr); |
| |
| // Create the scrypt thread. |
| // TODO(yich): Create another thread in userdataauth and pass the thread here. |
| base::Thread::Options options; |
| options.message_pump_type = base::MessagePumpType::IO; |
| scrypt_thread_ = std::make_unique<base::Thread>("scrypt_thread"); |
| scrypt_thread_->StartWithOptions(std::move(options)); |
| scrypt_task_runner_ = scrypt_thread_->task_runner(); |
| } |
| |
| CryptoStatus TpmEccAuthBlock::TryCreate(const AuthInput& auth_input, |
| AuthBlockState* auth_block_state, |
| KeyBlobs* key_blobs, |
| int retry_limit) { |
| if (retry_limit == 0) { |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockRetryLimitExceededInCreate), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| const brillo::SecureBlob& user_input = auth_input.user_input.value(); |
| const std::string& obfuscated_username = |
| auth_input.obfuscated_username.value(); |
| |
| TpmEccAuthBlockState auth_state; |
| |
| // If the cryptohome key isn't loaded, try to load it. |
| if (!cryptohome_key_loader_->HasCryptohomeKey()) { |
| cryptohome_key_loader_->Init(); |
| } |
| |
| // If the key still isn't loaded, fail the operation. |
| if (!cryptohome_key_loader_->HasCryptohomeKey()) { |
| LOG(ERROR) << __func__ << ": Failed to load cryptohome key."; |
| // Tell user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockCryptohomeKeyLoadFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT); |
| } |
| |
| // Encrypt the HVKKM using the TPM and the user's passkey. The output is two |
| // encrypted blobs, bound to user state in |sealed_hvkkm| and |
| // |extended_sealed_hvkkm|, which are stored in the serialized vault keyset. |
| TpmKeyHandle cryptohome_key = cryptohome_key_loader_->GetCryptohomeKey(); |
| |
| auth_state.salt = CreateSecureRandomBlob(CRYPTOHOME_DEFAULT_KEY_SALT_SIZE); |
| |
| if (auth_state.salt.value().size() != CRYPTOHOME_DEFAULT_KEY_SALT_SIZE) { |
| LOG(ERROR) << __func__ << ": Wrong salt size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockSaltWrongSizeInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| // SVKKM: Software Vault Keyset Key Material. |
| brillo::SecureBlob svkkm(kDefaultAesKeySize); |
| brillo::SecureBlob pass_blob(kDefaultPassBlobSize); |
| if (!DeriveSecretsScrypt(user_input, auth_state.salt.value(), |
| {&pass_blob, &svkkm})) { |
| LOG(ERROR) << __func__ << ": Failed to derive pass_blob and SVKKM."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockSVKKMDerivedFailedInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| auth_state.auth_value_rounds = CalcEccAuthValueRounds(tpm_); |
| |
| brillo::SecureBlob auth_value = std::move(pass_blob); |
| |
| for (int i = 0; i < auth_state.auth_value_rounds.value(); i++) { |
| brillo::SecureBlob tmp_value; |
| hwsec::Status err; |
| for (int k = 0; k < kTpmDecryptMaxRetries; k++) { |
| err = HANDLE_TPM_COMM_ERROR( |
| tpm_->GetEccAuthValue(cryptohome_key, auth_value, &tmp_value)); |
| if (err == nullptr) { |
| break; |
| } |
| |
| LOG(ERROR) << "Failed to get ECC auth value: " << err; |
| |
| auto ecc_err = err.Find<EllipticCurveError>(); |
| if (ecc_err && |
| ecc_err->ErrorCode() == EllipticCurveErrorCode::kScalarOutOfRange) { |
| // The scalar for EC_POINT multiplication is out of range. |
| // We should retry the process again. |
| return TryCreate(auth_input, auth_block_state, key_blobs, |
| retry_limit - 1); |
| } |
| |
| if (err->ToTPMRetryAction() != TPMRetryAction::kLater) { |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockGetAuthFailedInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kReboot})) |
| .Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError( |
| std::move(err))); |
| } |
| |
| // Reload the cryptohome key may resolve this issue. |
| // This would be useful when the TPM daemon accidentally restarted and |
| // flushed all handles. |
| if (!cryptohome_key_loader_->ReloadCryptohomeKey()) { |
| LOG(ERROR) << "Unable to reload Cryptohome key while creating " |
| "TpmEccAuthBlock."; |
| // Tell user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockCryptohomeKeyReloadFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT); |
| } |
| } |
| |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to get ECC auth value: " << err; |
| // Tell user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockPersistentGetAuthFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_TPM_REBOOT) |
| .Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError( |
| std::move(err))); |
| } |
| |
| auth_value = std::move(tmp_value); |
| } |
| |
| // HVKKM: Hardware Vault Keyset Key Material. |
| const auto hvkkm = CreateSecureRandomBlob(kDefaultAesKeySize); |
| |
| // Check the size of materials size before deriving the VKK. |
| if (svkkm.size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong SVKKM size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockSVKKMWrongSizeInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| if (hvkkm.size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong HVKKM size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockHVKKMWrongSizeInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| // Use the Software & Hardware Vault Keyset Key Material to derive the VKK. |
| brillo::SecureBlob vkk = Sha256(brillo::SecureBlob::Combine(svkkm, hvkkm)); |
| |
| // Make sure the size of VKK is correct. |
| if (vkk.size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong VKK size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockVKKWrongSizeInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| std::map<uint32_t, brillo::Blob> default_pcr_map = |
| tpm_->GetPcrMap(obfuscated_username, false /* use_extended_pcr */); |
| std::map<uint32_t, brillo::Blob> extended_pcr_map = |
| tpm_->GetPcrMap(obfuscated_username, true /* use_extended_pcr */); |
| |
| brillo::SecureBlob sealed_hvkkm; |
| hwsec::Status err = HANDLE_TPM_COMM_ERROR(tpm_->SealToPcrWithAuthorization( |
| hvkkm, auth_value, default_pcr_map, &sealed_hvkkm)); |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to wrap HVKKM with creds: " << err; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockHVKKMSealFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState})) |
| .Wrap( |
| TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(std::move(err))); |
| } |
| |
| brillo::SecureBlob extended_sealed_hvkkm; |
| err = HANDLE_TPM_COMM_ERROR(tpm_->SealToPcrWithAuthorization( |
| hvkkm, auth_value, extended_pcr_map, &extended_sealed_hvkkm)); |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to wrap hvkkm with creds for extended user state: " |
| << err; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockHVKKMExtendedSealFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState})) |
| .Wrap( |
| TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(std::move(err))); |
| } |
| |
| auth_state.sealed_hvkkm = std::move(sealed_hvkkm); |
| auth_state.extended_sealed_hvkkm = std::move(extended_sealed_hvkkm); |
| |
| brillo::SecureBlob pub_key_hash; |
| err = HANDLE_TPM_COMM_ERROR( |
| tpm_->GetPublicKeyHash(cryptohome_key, &pub_key_hash)); |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to get the TPM public key hash: " << err; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockGetPubkeyHashFailedInCreate), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState})) |
| .Wrap( |
| TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(std::move(err))); |
| } else { |
| auth_state.tpm_public_key_hash = std::move(pub_key_hash); |
| } |
| |
| auth_state.vkk_iv = CreateSecureRandomBlob(kAesBlockSize); |
| |
| // Pass back the vkk and vkk_iv so the generic secret wrapping can use it. |
| key_blobs->vkk_key = std::move(vkk); |
| key_blobs->vkk_iv = auth_state.vkk_iv.value(); |
| key_blobs->chaps_iv = auth_state.vkk_iv.value(); |
| *auth_block_state = AuthBlockState{.state = std::move(auth_state)}; |
| return OkStatus<CryptohomeCryptoError>(); |
| } |
| |
| CryptoStatus TpmEccAuthBlock::Create(const AuthInput& auth_input, |
| AuthBlockState* auth_block_state, |
| KeyBlobs* key_blobs) { |
| if (!auth_input.user_input.has_value()) { |
| LOG(ERROR) << "Missing user_input"; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockNoUserInputInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| if (!auth_input.obfuscated_username.has_value()) { |
| LOG(ERROR) << "Missing obfuscated_username"; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockNoUsernameInCreate), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| return TryCreate(auth_input, auth_block_state, key_blobs, |
| kTryCreateMaxRetryCount); |
| } |
| |
| CryptoStatus TpmEccAuthBlock::Derive(const AuthInput& auth_input, |
| const AuthBlockState& state, |
| KeyBlobs* key_out_data) { |
| if (!auth_input.user_input.has_value()) { |
| LOG(ERROR) << "Missing user_input"; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockNoUserInputInDerive), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| const TpmEccAuthBlockState* auth_state; |
| if (!(auth_state = std::get_if<TpmEccAuthBlockState>(&state.state))) { |
| DLOG(FATAL) << "Invalid AuthBlockState"; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockInvalidBlockStateInDerive), |
| ErrorActionSet( |
| {ErrorAction::kDevCheckUnexpectedState, ErrorAction::kAuth}), |
| CryptoError::CE_OTHER_CRYPTO); |
| } |
| |
| // If the cryptohome key isn't loaded, try to load it. |
| if (!cryptohome_key_loader_->HasCryptohomeKey()) { |
| cryptohome_key_loader_->Init(); |
| } |
| |
| // If the key still isn't loaded, fail the operation. |
| if (!cryptohome_key_loader_->HasCryptohomeKey()) { |
| LOG(ERROR) << __func__ << ": Failed to load cryptohome key."; |
| // Tell user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockLoadKeyFailedInDerive), |
| ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT); |
| } |
| |
| brillo::SecureBlob tpm_public_key_hash = |
| auth_state->tpm_public_key_hash.value_or(brillo::SecureBlob()); |
| |
| CryptoStatus error = utils_.CheckTPMReadiness( |
| auth_state->sealed_hvkkm.has_value(), |
| auth_state->tpm_public_key_hash.has_value(), tpm_public_key_hash); |
| if (!error.ok()) { |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockTpmNotReadyInDerive)) |
| .Wrap(std::move(error)); |
| } |
| |
| bool locked_to_single_user = auth_input.locked_to_single_user.value_or(false); |
| const brillo::SecureBlob& user_input = auth_input.user_input.value(); |
| |
| brillo::SecureBlob vkk; |
| error = DeriveVkk(locked_to_single_user, user_input, *auth_state, &vkk); |
| |
| if (!error.ok()) { |
| LOG(ERROR) << "Failed to derive VKK."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockCantDeriveVKKInDerive)) |
| .Wrap(std::move(error)); |
| } |
| |
| key_out_data->vkk_key = std::move(vkk); |
| key_out_data->vkk_iv = auth_state->vkk_iv.value(); |
| key_out_data->chaps_iv = key_out_data->vkk_iv; |
| |
| return OkStatus<CryptohomeCryptoError>(); |
| } |
| |
| CryptoStatus TpmEccAuthBlock::DeriveVkk(bool locked_to_single_user, |
| const brillo::SecureBlob& user_input, |
| const TpmEccAuthBlockState& auth_state, |
| brillo::SecureBlob* vkk) { |
| const brillo::SecureBlob& salt = auth_state.salt.value(); |
| |
| // HVKKM: Hardware Vault Keyset Key Material. |
| const brillo::SecureBlob& sealed_hvkkm = |
| locked_to_single_user ? auth_state.extended_sealed_hvkkm.value() |
| : auth_state.sealed_hvkkm.value(); |
| |
| // SVKKM: Software Vault Keyset Key Material. |
| brillo::SecureBlob svkkm(kDefaultAesKeySize); |
| brillo::SecureBlob pass_blob(kDefaultPassBlobSize); |
| |
| bool derive_result = false; |
| ScopedKeyHandle preload_handle; |
| |
| { |
| // Prepare the parameters for scrypt. |
| std::vector<brillo::SecureBlob*> gen_secrets{&pass_blob, &svkkm}; |
| base::WaitableEvent scrypt_done( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| // Derive secrets on scrypt task runner. |
| scrypt_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](const brillo::SecureBlob& user_input, |
| const brillo::SecureBlob& salt, |
| std::vector<brillo::SecureBlob*> gen_secrets, bool* result, |
| base::WaitableEvent* done) { |
| *result = |
| DeriveSecretsScrypt(user_input, salt, std::move(gen_secrets)); |
| done->Signal(); |
| }, |
| user_input, salt, gen_secrets, &derive_result, &scrypt_done)); |
| |
| // The scrypt should be finished before exiting this socope. |
| base::ScopedClosureRunner joiner(base::BindOnce( |
| [](base::WaitableEvent* scrypt_done) { scrypt_done->Wait(); }, |
| base::Unretained(&scrypt_done))); |
| |
| // Preload the sealed data while deriving secrets in scrypt. |
| hwsec::Status err = HANDLE_TPM_COMM_ERROR( |
| tpm_->PreloadSealedData(sealed_hvkkm, &preload_handle)); |
| |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to preload the sealed data: " << err; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockPreloadFailedInDeriveVKK), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState})) |
| .Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError( |
| std::move(err))); |
| } |
| } |
| |
| if (!derive_result) { |
| LOG(ERROR) << "scrypt derivation failed"; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockScryptDeriveFailedInDeriveVKK), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_TPM_CRYPTO); |
| } |
| |
| if (svkkm.size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong SVKKM size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockWrongSVKKMSizeInDeriveVKK), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_TPM_CRYPTO); |
| } |
| |
| brillo::SecureBlob hvkkm; |
| CryptoStatus error = DeriveHvkkm( |
| locked_to_single_user, std::move(pass_blob), sealed_hvkkm, |
| &preload_handle, auth_state.auth_value_rounds.value(), &hvkkm); |
| if (!error.ok()) { |
| LOG(ERROR) << "Failed to derive HVKKM."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockDeriveHVKKMFailedInDeriveVKK)) |
| .Wrap(std::move(error)); |
| } |
| |
| if (hvkkm.size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong HVKKM size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockWrongHVKKMSizeInDeriveVKK), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_TPM_CRYPTO); |
| } |
| |
| // Use the Software & Hardware Vault Keyset Key Material to derive the VKK. |
| *vkk = Sha256(brillo::SecureBlob::Combine(svkkm, hvkkm)); |
| if (vkk->size() != kDefaultAesKeySize) { |
| LOG(ERROR) << __func__ << ": Wrong VKK size."; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockWrongVKKSizeInDeriveVKK), |
| ErrorActionSet({ErrorAction::kDevCheckUnexpectedState}), |
| CryptoError::CE_TPM_CRYPTO); |
| } |
| |
| return OkStatus<CryptohomeCryptoError>(); |
| } |
| |
| CryptoStatus TpmEccAuthBlock::DeriveHvkkm( |
| bool locked_to_single_user, |
| brillo::SecureBlob pass_blob, |
| const brillo::SecureBlob& sealed_hvkkm, |
| ScopedKeyHandle* preload_handle, |
| uint32_t auth_value_rounds, |
| brillo::SecureBlob* hvkkm) { |
| std::optional<TpmKeyHandle> sealed_hvkkm_handle; |
| // The preload handle may be an invalid handle, we should only use it when |
| // it's a valid handle. |
| if (preload_handle->has_value()) { |
| sealed_hvkkm_handle = preload_handle->value(); |
| } |
| |
| brillo::SecureBlob auth_value = std::move(pass_blob); |
| |
| TpmKeyHandle cryptohome_key = cryptohome_key_loader_->GetCryptohomeKey(); |
| |
| ReportTimerStart(kGenerateEccAuthValueTimer); |
| |
| for (int i = 0; i < auth_value_rounds; i++) { |
| brillo::SecureBlob tmp_value; |
| hwsec::Status err; |
| for (int k = 0; k < kTpmDecryptMaxRetries; k++) { |
| err = HANDLE_TPM_COMM_ERROR( |
| tpm_->GetEccAuthValue(cryptohome_key, auth_value, &tmp_value)); |
| if (err == nullptr) { |
| break; |
| } |
| |
| LOG(ERROR) << "Failed to get ECC auth value: " << err; |
| |
| if (err->ToTPMRetryAction() != TPMRetryAction::kLater) { |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockGetAuthFailedInDeriveHVKKM), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kAuth})) |
| .Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError( |
| std::move(err))); |
| } |
| |
| // Reload the cryptohome key may resolve this issue. |
| // This would be useful when the TPM daemon accidentally restarted and |
| // flushed all handles. |
| if (!cryptohome_key_loader_->ReloadCryptohomeKey()) { |
| LOG(ERROR) << "Unable to reload Cryptohome key while decrypting " |
| "TpmEccAuthBlock."; |
| // Tell the user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockReloadKeyFailedInDeriveHVKKM), |
| ErrorActionSet({ErrorAction::kReboot}), CryptoError::CE_TPM_REBOOT); |
| } |
| } |
| |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to get ECC auth value: " << err; |
| // Tell the user to reboot the device may resolve this issue. |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC( |
| kLocTpmEccAuthBlockPersistentGetAuthFailedInDeriveHVKKM), |
| ErrorActionSet({ErrorAction::kReboot, |
| ErrorAction::kDevCheckUnexpectedState, |
| ErrorAction::kAuth}), |
| CryptoError::CE_TPM_REBOOT) |
| .Wrap(TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError( |
| std::move(err))); |
| } |
| |
| auth_value = std::move(tmp_value); |
| } |
| |
| ReportTimerStop(kGenerateEccAuthValueTimer); |
| |
| std::map<uint32_t, brillo::Blob> pcr_map( |
| {{kTpmSingleUserPCR, brillo::Blob()}}); |
| hwsec::Status err = HANDLE_TPM_COMM_ERROR(tpm_->UnsealWithAuthorization( |
| sealed_hvkkm_handle, sealed_hvkkm, auth_value, pcr_map, hvkkm)); |
| if (err != nullptr) { |
| LOG(ERROR) << "Failed to unwrap VKK with creds: " << err; |
| return MakeStatus<CryptohomeCryptoError>( |
| CRYPTOHOME_ERR_LOC(kLocTpmEccAuthBlockUnsealFailedInDeriveHVKKM), |
| ErrorActionSet({ErrorAction::kIncorrectAuth})) |
| .Wrap( |
| TpmAuthBlockUtils::TPMErrorToCryptohomeCryptoError(std::move(err))); |
| } |
| |
| return OkStatus<CryptohomeCryptoError>(); |
| } |
| |
| } // namespace cryptohome |