| // Copyright 2020 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/tpm_not_bound_to_pcr_auth_block.h" |
| |
| #include <map> |
| #include <string> |
| |
| #include <base/optional.h> |
| #include <brillo/secure_blob.h> |
| |
| #include "cryptohome/crypto.h" |
| #include "cryptohome/crypto_error.h" |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/cryptolib.h" |
| #include "cryptohome/tpm.h" |
| #include "cryptohome/tpm_auth_block_utils.h" |
| #include "cryptohome/tpm_init.h" |
| #include "cryptohome/vault_keyset.pb.h" |
| |
| namespace cryptohome { |
| |
| TpmNotBoundToPcrAuthBlock::TpmNotBoundToPcrAuthBlock(Tpm* tpm, |
| TpmInit* tpm_init) |
| : tpm_(tpm), tpm_init_(tpm_init), utils_(tpm, tpm_init) { |
| CHECK(tpm != nullptr); |
| CHECK(tpm_init != nullptr); |
| } |
| |
| bool TpmNotBoundToPcrAuthBlock::Derive(const AuthInput& auth_input, |
| const AuthBlockState& state, |
| KeyBlobs* key_out_data, |
| CryptoError* error) { |
| const SerializedVaultKeyset& serialized = state.vault_keyset.value(); |
| if (!utils_.CheckTPMReadiness(serialized, error)) |
| return false; |
| |
| key_out_data->vkk_iv = brillo::SecureBlob(kAesBlockSize); |
| key_out_data->vkk_key = brillo::SecureBlob(kDefaultAesKeySize); |
| |
| brillo::SecureBlob salt(serialized.salt().begin(), serialized.salt().end()); |
| brillo::SecureBlob tpm_key(serialized.tpm_key().begin(), |
| serialized.tpm_key().end()); |
| |
| if (!DecryptTpmNotBoundToPcr( |
| serialized, auth_input.user_input.value(), tpm_key, salt, error, |
| &key_out_data->vkk_iv.value(), &key_out_data->vkk_key.value())) { |
| return false; |
| } |
| |
| key_out_data->chaps_iv = key_out_data->vkk_iv; |
| key_out_data->wrapped_reset_seed = brillo::SecureBlob(); |
| key_out_data->wrapped_reset_seed.value().assign( |
| serialized.wrapped_reset_seed().begin(), |
| serialized.wrapped_reset_seed().end()); |
| |
| if (!serialized.has_tpm_public_key_hash() && error) { |
| *error = CryptoError::CE_NO_PUBLIC_KEY_HASH; |
| } |
| |
| return true; |
| } |
| |
| base::Optional<AuthBlockState> TpmNotBoundToPcrAuthBlock::Create( |
| const AuthInput& user_input, KeyBlobs* key_blobs, CryptoError* error) { |
| const brillo::SecureBlob& vault_key = user_input.user_input.value(); |
| const brillo::SecureBlob& salt = user_input.salt.value(); |
| |
| // If the cryptohome key isn't loaded, try to load it. |
| if (!tpm_init_->HasCryptohomeKey()) |
| tpm_init_->SetupTpm(/*load_key=*/true); |
| |
| // If the key still isn't loaded, fail the operation. |
| if (!tpm_init_->HasCryptohomeKey()) |
| return base::nullopt; |
| |
| const auto local_blob = CryptoLib::CreateSecureRandomBlob(kDefaultAesKeySize); |
| brillo::SecureBlob tpm_key; |
| brillo::SecureBlob aes_skey(kDefaultAesKeySize); |
| brillo::SecureBlob kdf_skey(kDefaultAesKeySize); |
| brillo::SecureBlob vkk_iv(kAesBlockSize); |
| if (!CryptoLib::DeriveSecretsScrypt(vault_key, salt, |
| {&aes_skey, &kdf_skey, &vkk_iv})) { |
| return base::nullopt; |
| } |
| |
| // Encrypt the VKK using the TPM and the user's passkey. The output is an |
| // encrypted blob in tpm_key, which is stored in the serialized vault |
| // keyset. |
| if (tpm_->EncryptBlob(tpm_init_->GetCryptohomeKey(), local_blob, aes_skey, |
| &tpm_key) != Tpm::kTpmRetryNone) { |
| LOG(ERROR) << "Failed to wrap vkk with creds."; |
| return base::nullopt; |
| } |
| |
| // Allow this to fail. It is not absolutely necessary; it allows us to |
| // detect a TPM clear. If this fails due to a transient issue, then on next |
| // successful login, the vault keyset will be re-saved anyway. |
| SerializedVaultKeyset serialized; |
| brillo::SecureBlob pub_key_hash; |
| if (tpm_->GetPublicKeyHash(tpm_init_->GetCryptohomeKey(), &pub_key_hash) == |
| Tpm::kTpmRetryNone) { |
| serialized.set_tpm_public_key_hash(pub_key_hash.data(), |
| pub_key_hash.size()); |
| } |
| |
| serialized.set_flags(SerializedVaultKeyset::TPM_WRAPPED | |
| SerializedVaultKeyset::SCRYPT_DERIVED); |
| serialized.set_tpm_key(tpm_key.data(), tpm_key.size()); |
| |
| // Pass back the vkk_key and vkk_iv so the generic secret wrapping can use it. |
| key_blobs->vkk_key = CryptoLib::HmacSha256(kdf_skey, local_blob); |
| key_blobs->vkk_iv = vkk_iv; |
| key_blobs->chaps_iv = vkk_iv; |
| key_blobs->auth_iv = vkk_iv; |
| |
| return {{serialized}}; |
| } |
| |
| bool TpmNotBoundToPcrAuthBlock::DecryptTpmNotBoundToPcr( |
| const SerializedVaultKeyset& serialized, |
| const brillo::SecureBlob& vault_key, |
| const brillo::SecureBlob& tpm_key, |
| const brillo::SecureBlob& salt, |
| CryptoError* error, |
| brillo::SecureBlob* vkk_iv, |
| brillo::SecureBlob* vkk_key) const { |
| brillo::SecureBlob aes_skey(kDefaultAesKeySize); |
| brillo::SecureBlob kdf_skey(kDefaultAesKeySize); |
| brillo::SecureBlob local_vault_key(vault_key.begin(), vault_key.end()); |
| unsigned int rounds; |
| if (serialized.has_password_rounds()) { |
| rounds = serialized.password_rounds(); |
| } else { |
| rounds = kDefaultLegacyPasswordRounds; |
| } |
| |
| bool scrypt_derived = |
| serialized.flags() & SerializedVaultKeyset::SCRYPT_DERIVED; |
| if (scrypt_derived) { |
| if (!CryptoLib::DeriveSecretsScrypt(vault_key, salt, |
| {&aes_skey, &kdf_skey, vkk_iv})) { |
| PopulateError(error, CryptoError::CE_OTHER_FATAL); |
| return false; |
| } |
| } else { |
| CryptoLib::PasskeyToAesKey(vault_key, salt, rounds, &aes_skey, NULL); |
| } |
| |
| for (int i = 0; i < kTpmDecryptMaxRetries; i++) { |
| Tpm::TpmRetryAction retry_action = |
| tpm_->DecryptBlob(tpm_init_->GetCryptohomeKey(), tpm_key, aes_skey, |
| std::map<uint32_t, std::string>(), &local_vault_key); |
| |
| if (retry_action == Tpm::kTpmRetryNone) |
| break; |
| |
| if (!TpmAuthBlockUtils::TpmErrorIsRetriable(retry_action)) { |
| LOG(ERROR) << "Failed to unwrap VKK with creds."; |
| ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed); |
| *error = TpmAuthBlockUtils::TpmErrorToCrypto(retry_action); |
| return false; |
| } |
| |
| // If the error is retriable, reload the key first. |
| if (!tpm_init_->ReloadCryptohomeKey()) { |
| LOG(ERROR) << "Unable to reload Cryptohome key."; |
| ReportCryptohomeError(kDecryptAttemptWithTpmKeyFailed); |
| *error = TpmAuthBlockUtils::TpmErrorToCrypto(Tpm::kTpmRetryFailNoRetry); |
| return false; |
| } |
| } |
| |
| if (scrypt_derived) { |
| *vkk_key = CryptoLib::HmacSha256(kdf_skey, local_vault_key); |
| } else { |
| if (!CryptoLib::PasskeyToAesKey(local_vault_key, salt, rounds, vkk_key, |
| vkk_iv)) { |
| LOG(ERROR) << "Failure converting IVKK to VKK."; |
| PopulateError(error, CryptoError::CE_OTHER_FATAL); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace cryptohome |