| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/attestation/tpm_challenge_key_subtle.h" |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/functional/bind.h" |
| #include "base/notreached.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/attestation/attestation_ca_client.h" |
| #include "chrome/browser/ash/attestation/machine_certificate_uploader.h" |
| #include "chrome/browser/ash/platform_keys/key_permissions/key_permissions_manager.h" |
| #include "chrome/browser/ash/platform_keys/key_permissions/key_permissions_manager_impl.h" |
| #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h" |
| #include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_platform_part_ash.h" |
| #include "chrome/browser/extensions/chrome_extension_function_details.h" |
| #include "chrome/browser/policy/profile_policy_connector.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/ash/components/attestation/attestation_flow_adaptive.h" |
| #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" |
| #include "chromeos/ash/components/dbus/attestation/attestation_client.h" |
| #include "chromeos/ash/components/dbus/attestation/interface.pb.h" |
| #include "chromeos/ash/components/dbus/constants/attestation_constants.h" |
| #include "chromeos/ash/components/install_attributes/install_attributes.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/ash/components/settings/cros_settings_names.h" |
| #include "chromeos/components/kiosk/kiosk_utils.h" |
| #include "chromeos/dbus/tpm_manager/tpm_manager.pb.h" |
| #include "chromeos/dbus/tpm_manager/tpm_manager_client.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| |
| namespace ash { |
| namespace attestation { |
| |
| using ::attestation::VerifiedAccessFlow; |
| using Result = TpmChallengeKeyResult; |
| using ResultCode = TpmChallengeKeyResultCode; |
| |
| //==================== TpmChallengeKeySubtleFactory ============================ |
| |
| TpmChallengeKeySubtle* TpmChallengeKeySubtleFactory::next_result_for_testing_ = |
| nullptr; |
| |
| // static |
| std::unique_ptr<TpmChallengeKeySubtle> TpmChallengeKeySubtleFactory::Create() { |
| if (UNLIKELY(next_result_for_testing_)) { |
| std::unique_ptr<TpmChallengeKeySubtle> result(next_result_for_testing_); |
| next_result_for_testing_ = nullptr; |
| return result; |
| } |
| |
| return std::make_unique<TpmChallengeKeySubtleImpl>(); |
| } |
| |
| // static |
| std::unique_ptr<TpmChallengeKeySubtle> |
| TpmChallengeKeySubtleFactory::CreateForPreparedKey( |
| VerifiedAccessFlow flow_type, |
| bool will_register_key, |
| ::attestation::KeyType key_crypto_type, |
| const std::string& key_name, |
| const std::string& public_key, |
| Profile* profile) { |
| auto result = TpmChallengeKeySubtleFactory::Create(); |
| result->RestorePreparedKeyState(flow_type, will_register_key, key_crypto_type, |
| key_name, public_key, profile); |
| return result; |
| } |
| |
| // static |
| void TpmChallengeKeySubtleFactory::SetForTesting( |
| std::unique_ptr<TpmChallengeKeySubtle> next_result) { |
| DCHECK(next_result_for_testing_ == nullptr); |
| // unique_ptr itself cannot be stored in a static variable because of its |
| // complex destructor. |
| next_result_for_testing_ = next_result.release(); |
| } |
| |
| // static |
| bool TpmChallengeKeySubtleFactory::WillReturnTestingInstance() { |
| return (next_result_for_testing_ != nullptr); |
| } |
| |
| //===================== TpmChallengeKeySubtleImpl ============================== |
| |
| namespace { |
| |
| // Returns true if the device is enterprise managed. |
| bool IsEnterpriseDevice() { |
| return InstallAttributes::Get()->IsEnterpriseManaged(); |
| } |
| |
| // For unmanaged devices we need to ask for user consent if the key does not |
| // exist because data will be sent to the PCA. In case of the flow type being |
| // DEVICE_TRUST_CONNECTOR, user consent is not required since it's only used |
| // for attesting the DTC payload and is not usable by extensions. |
| // Historical note: For managed device there used to be policies to control this |
| // (AttestationEnabledForUser,AttestationEnabledForDevice) but they were removed |
| // from the client after having been set to true unconditionally for all clients |
| // for a long time. |
| bool IsUserConsentRequired(VerifiedAccessFlow flow_type) { |
| if (flow_type == VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR) { |
| return false; |
| } |
| |
| return !IsEnterpriseDevice(); |
| } |
| |
| // If no key name was given, use default well-known key names so they can be |
| // reused across attestation operations (multiple challenge responses can be |
| // generated using the same key). |
| std::string GetDefaultKeyName(VerifiedAccessFlow flow_type, |
| ::attestation::KeyType key_crypto_type) { |
| // When the caller wants to "register" a key through attestation (resulting in |
| // a general-purpose key in the chaps PKCS#11 store), the behavior is |
| // different between EUK and EMK: |
| // |
| // For EUK, the EUK used to sign the attestation response is also the key that |
| // is "registered". If the EUK does not exist, one will be generated; but if |
| // it does already exist, the existing key will be used. Thus it must be |
| // parameterized with the crypto algorithm type to ensure that the caller gets |
| // a key of the type they expect. |
| // |
| // For EMK, the EMK used to sign the attestation response must remain stable |
| // and instead a newly-generated EMK is registered. This function returns the |
| // EMK that is used to sign the attesation response, so it may not be |
| // parametrized with the crypto algorithm type. |
| // |
| // See http://go/chromeos-va-registering-device-wide-keys-support for details |
| // on the concept of the stable EMK. |
| switch (flow_type) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| return kEnterpriseMachineKey; |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| switch (key_crypto_type) { |
| case ::attestation::KEY_TYPE_RSA: |
| return kEnterpriseUserKey; |
| case ::attestation::KEY_TYPE_ECC: |
| return std::string(kEnterpriseUserKey) + "-ecdsa"; |
| } |
| default: |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| // Returns the key name that should be used for the attestation platform APIs. |
| std::string GetKeyNameWithDefault(VerifiedAccessFlow flow_type, |
| ::attestation::KeyType key_crypto_type, |
| const std::string& key_name) { |
| if (!key_name.empty()) |
| return key_name; |
| |
| return GetDefaultKeyName(flow_type, key_crypto_type); |
| } |
| |
| } // namespace |
| |
| TpmChallengeKeySubtleImpl::TpmChallengeKeySubtleImpl() |
| : default_attestation_flow_(std::make_unique<AttestationFlowAdaptive>( |
| std::make_unique<AttestationCAClient>())), |
| attestation_flow_(default_attestation_flow_.get()) { |
| policy::DeviceCloudPolicyManagerAsh* manager = |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetDeviceCloudPolicyManager(); |
| if (manager) { |
| machine_certificate_uploader_ = manager->GetMachineCertificateUploader(); |
| } |
| } |
| |
| TpmChallengeKeySubtleImpl::TpmChallengeKeySubtleImpl( |
| AttestationFlow* attestation_flow_for_testing, |
| MachineCertificateUploader* machine_certificate_uploader_for_testing) |
| : attestation_flow_(attestation_flow_for_testing), |
| machine_certificate_uploader_(machine_certificate_uploader_for_testing) {} |
| |
| TpmChallengeKeySubtleImpl::~TpmChallengeKeySubtleImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void TpmChallengeKeySubtleImpl::RestorePreparedKeyState( |
| VerifiedAccessFlow flow_type, |
| bool will_register_key, |
| ::attestation::KeyType key_crypto_type, |
| const std::string& key_name, |
| const std::string& public_key, |
| Profile* profile) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!will_register_key || !public_key.empty()); |
| |
| // Ensure that the selected flow type is supported |
| CHECK(flow_type == VerifiedAccessFlow::ENTERPRISE_MACHINE || |
| flow_type == VerifiedAccessFlow::ENTERPRISE_USER); |
| |
| // For the ENTERPRISE_USER flow, a |profile| is strictly necessary. |
| DCHECK(flow_type != VerifiedAccessFlow::ENTERPRISE_USER || profile); |
| |
| // For DEVICE_TRUST_CONNECTOR, a key name is required and registering a key is |
| // not allowed. |
| CHECK(flow_type != VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR || |
| !key_name.empty()); |
| CHECK(flow_type != VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR || |
| !will_register_key); |
| |
| flow_type_ = flow_type; |
| will_register_key_ = will_register_key; |
| key_crypto_type_ = key_crypto_type; |
| key_name_ = GetKeyNameWithDefault(flow_type, key_crypto_type, key_name); |
| public_key_ = public_key; |
| profile_ = profile; |
| } |
| |
| void TpmChallengeKeySubtleImpl::StartPrepareKeyStep( |
| VerifiedAccessFlow flow_type, |
| bool will_register_key, |
| ::attestation::KeyType key_crypto_type, |
| const std::string& key_name, |
| Profile* profile, |
| TpmChallengeKeyCallback callback, |
| const std::optional<std::string>& signals) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(callback_.is_null()); |
| |
| // For ENTERPRISE_MACHINE: if |will_register_key| is true, |key_name| should |
| // not be empty, if |register_key| is false, |key_name| will not be used. |
| DCHECK((flow_type != VerifiedAccessFlow::ENTERPRISE_MACHINE) || |
| (will_register_key == !key_name.empty())) |
| << "Invalid arguments: " << will_register_key << " " << !key_name.empty(); |
| |
| // For ENTERPRISE_USER, a |profile| is strictly necessary. |
| DCHECK(flow_type != VerifiedAccessFlow::ENTERPRISE_USER || profile); |
| |
| // For DEVICE_TRUST_CONNECTOR, a key name is required and registering a key is |
| // not allowed. |
| CHECK(flow_type != VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR || |
| !key_name.empty()); |
| CHECK(flow_type != VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR || |
| !will_register_key); |
| |
| // Ensure that the selected flow type is supported |
| if (flow_type != VerifiedAccessFlow::ENTERPRISE_MACHINE && |
| flow_type != VerifiedAccessFlow::ENTERPRISE_USER && |
| flow_type != VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR) { |
| std::move(callback).Run( |
| Result::MakeError(ResultCode::kVerifiedAccessFlowUnsupportedError)); |
| return; |
| } |
| |
| flow_type_ = flow_type; |
| will_register_key_ = will_register_key; |
| key_crypto_type_ = key_crypto_type; |
| key_name_ = GetKeyNameWithDefault(flow_type, key_crypto_type, key_name); |
| profile_ = profile; |
| callback_ = std::move(callback); |
| signals_ = signals; |
| |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| PrepareEnterpriseMachineFlow(); |
| return; |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| PrepareEnterpriseUserFlow(); |
| return; |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| PrepareDeviceTrustConnectorFlow(); |
| return; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareEnterpriseMachineFlow() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Check if the device is enterprise enrolled. |
| if (!IsEnterpriseDevice()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kNonEnterpriseDeviceError)); |
| return; |
| } |
| |
| // Check whether the user is affiliated unless this is a device-wide instance. |
| if (GetUser() && !IsUserAffiliated()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUserNotManagedError)); |
| return; |
| } |
| |
| // Wait for the machine certificate to be uploaded. |
| if (machine_certificate_uploader_) { |
| machine_certificate_uploader_->WaitForUploadComplete(base::BindOnce( |
| &TpmChallengeKeySubtleImpl::PrepareKey, weak_factory_.GetWeakPtr())); |
| } else { |
| PrepareKey(true); |
| } |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareEnterpriseUserFlow() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Check if user keys are available in this profile. |
| if (!GetUser()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUserKeyNotAvailableError)); |
| return; |
| } |
| |
| if (IsEnterpriseDevice()) { |
| if (!IsUserAffiliated()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUserNotManagedError)); |
| return; |
| } |
| } |
| |
| PrepareKey(true); |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareDeviceTrustConnectorFlow() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // TODO(b/277707201): remove once user email from login screen is available |
| // here. |
| if (!GetUser()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUserKeyNotAvailableError)); |
| return; |
| } |
| |
| // Check whether the user is managed unless this is a device-wide instance. |
| if (GetUser() && !IsUserManaged()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUserNotManagedError)); |
| return; |
| } |
| |
| PrepareKey(true); |
| } |
| |
| bool TpmChallengeKeySubtleImpl::IsUserManaged() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!profile_) { |
| return false; |
| } |
| |
| const auto* profile_policy_connector = profile_->GetProfilePolicyConnector(); |
| |
| if (!profile_policy_connector) { |
| return false; |
| } |
| |
| return profile_policy_connector->IsManaged(); |
| } |
| |
| bool TpmChallengeKeySubtleImpl::IsUserAffiliated() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const user_manager::User* const user = GetUser(); |
| if (user) { |
| return user->IsAffiliated(); |
| } |
| return false; |
| } |
| |
| std::string TpmChallengeKeySubtleImpl::GetEmail() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| return std::string(); |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| [[fallthrough]]; |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| return GetAccountId().GetUserEmail(); |
| default: |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| AttestationCertificateProfile TpmChallengeKeySubtleImpl::GetCertificateProfile() |
| const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| return PROFILE_ENTERPRISE_MACHINE_CERTIFICATE; |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| return PROFILE_ENTERPRISE_USER_CERTIFICATE; |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| return PROFILE_DEVICE_TRUST_USER_CERTIFICATE; |
| default: |
| NOTREACHED(); |
| return {}; |
| } |
| } |
| |
| const user_manager::User* TpmChallengeKeySubtleImpl::GetUser() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!profile_) |
| return nullptr; |
| return ProfileHelper::Get()->GetUserByProfile(profile_); |
| } |
| |
| AccountId TpmChallengeKeySubtleImpl::GetAccountId() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const user_manager::User* user = GetUser(); |
| if (user) { |
| return user->GetAccountId(); |
| } |
| // Signin profile doesn't have associated user. |
| return EmptyAccountId(); |
| } |
| |
| AccountId TpmChallengeKeySubtleImpl::GetAccountIdForAttestationFlow() const { |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| [[fallthrough]]; |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| return EmptyAccountId(); |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| return GetAccountId(); |
| default: |
| LOG(DFATAL) << "Unsupported Verified Access flow type: " << flow_type_; |
| return EmptyAccountId(); |
| } |
| } |
| |
| std::string TpmChallengeKeySubtleImpl::GetUsernameForAttestationClient() const { |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| [[fallthrough]]; |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| return std::string(); |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| return cryptohome::Identification(GetAccountId()).id(); |
| default: |
| LOG(DFATAL) << "Unsupported Verified Access flow type: " << flow_type_; |
| return std::string(); |
| } |
| } |
| |
| // For ENTERPRISE_MACHINE attestation, don't include the certificate of the |
| // signing key, because the verified access server uses the "stable EMK |
| // certificate" uploaded to DMServer after enrollment. |
| bool TpmChallengeKeySubtleImpl::ShouldIncludeSigningKeyCertificate() const { |
| if (flow_type_ == VerifiedAccessFlow::ENTERPRISE_MACHINE) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmChallengeKeySubtleImpl::ShouldIncludeCustomerId() const { |
| // Request to include the customer ID in the challenge response when: |
| // * the request is a machine challenge |
| // * the request is a user challenge and this is a kiosk session |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| return true; |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| return chromeos::IsKioskSession(); |
| case VerifiedAccessFlow::DEVICE_TRUST_CONNECTOR: |
| return false; |
| default: |
| NOTREACHED() << "Unsupported Verified Access flow type: " << flow_type_; |
| return false; |
| } |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareKey(bool can_continue) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!can_continue) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kUploadCertificateFailedError)); |
| return; |
| } |
| |
| ::attestation::GetEnrollmentPreparationsRequest request; |
| AttestationClient::Get()->GetEnrollmentPreparations( |
| request, |
| base::BindOnce( |
| &TpmChallengeKeySubtleImpl::GetEnrollmentPreparationsCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::GetEnrollmentPreparationsCallback( |
| const ::attestation::GetEnrollmentPreparationsReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| std::move(callback_).Run( |
| Result::MakeError(reply.status() == ::attestation::STATUS_DBUS_ERROR |
| ? ResultCode::kDbusError |
| : ResultCode::kAttestationServiceInternalError)); |
| return; |
| } |
| |
| if (!AttestationClient::IsAttestationPrepared(reply)) { |
| chromeos::TpmManagerClient::Get()->GetTpmNonsensitiveStatus( |
| ::tpm_manager::GetTpmNonsensitiveStatusRequest(), |
| base::BindOnce( |
| &TpmChallengeKeySubtleImpl::PrepareKeyErrorHandlerCallback, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| ::attestation::GetKeyInfoRequest request; |
| request.set_username(GetUsernameForAttestationClient()); |
| request.set_key_label(key_name_); |
| AttestationClient::Get()->GetKeyInfo( |
| request, base::BindOnce(&TpmChallengeKeySubtleImpl::DoesKeyExistCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareKeyErrorHandlerCallback( |
| const ::tpm_manager::GetTpmNonsensitiveStatusReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::tpm_manager::STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to get TPM status; status: " << reply.status(); |
| std::move(callback_).Run(Result::MakeError(ResultCode::kDbusError)); |
| return; |
| } |
| |
| if (reply.is_enabled()) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kResetRequiredError)); |
| } else { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kAttestationUnsupportedError)); |
| } |
| } |
| |
| void TpmChallengeKeySubtleImpl::DoesKeyExistCallback( |
| const ::attestation::GetKeyInfoReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::attestation::STATUS_SUCCESS && |
| reply.status() != ::attestation::STATUS_INVALID_PARAMETER) { |
| std::move(callback_).Run( |
| Result::MakeError(reply.status() == ::attestation::STATUS_DBUS_ERROR |
| ? ResultCode::kDbusError |
| : ResultCode::kAttestationServiceInternalError)); |
| return; |
| } |
| |
| if (reply.status() == ::attestation::STATUS_SUCCESS) { |
| // The key exists. Do nothing more. |
| PrepareKeyFinished(reply); |
| return; |
| } |
| |
| // The key does not exist. Create a new key and have it signed by PCA. |
| if (IsUserConsentRequired(flow_type_)) { |
| // We should ask the user explicitly before sending any private |
| // information to PCA. |
| AskForUserConsent( |
| base::BindOnce(&TpmChallengeKeySubtleImpl::AskForUserConsentCallback, |
| weak_factory_.GetWeakPtr())); |
| } else { |
| // User consent is not required. Skip to the next step. |
| AskForUserConsentCallback(true); |
| } |
| } |
| |
| void TpmChallengeKeySubtleImpl::AskForUserConsent( |
| base::OnceCallback<void(bool)> callback) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // TODO(davidyu): right now we just simply reject the request before we have |
| // a way to ask for user consent. |
| std::move(callback).Run(false); |
| } |
| |
| void TpmChallengeKeySubtleImpl::AskForUserConsentCallback(bool result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result) { |
| // The user rejects the request. |
| std::move(callback_).Run(Result::MakeError(ResultCode::kUserRejectedError)); |
| return; |
| } |
| |
| // Generate a new key and have it signed by PCA. |
| attestation_flow_->GetCertificate( |
| /*certificate_profile=*/GetCertificateProfile(), |
| /*account_id=*/GetAccountIdForAttestationFlow(), |
| /*request_origin=*/std::string(), // Not used. |
| /*force_new_key=*/true, /*key_crypto_type=*/key_crypto_type_, |
| /*key_name=*/key_name_, /*profile_specific_data=*/std::nullopt, |
| /*callback=*/ |
| base::BindOnce(&TpmChallengeKeySubtleImpl::GetCertificateCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::GetCertificateCallback( |
| AttestationStatus status, |
| const std::string& pem_certificate_chain) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (status != ATTESTATION_SUCCESS) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kGetCertificateFailedError)); |
| return; |
| } |
| |
| GetPublicKey(); |
| } |
| |
| void TpmChallengeKeySubtleImpl::GetPublicKey() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ::attestation::GetKeyInfoRequest request; |
| request.set_username(GetUsernameForAttestationClient()); |
| request.set_key_label(key_name_); |
| AttestationClient::Get()->GetKeyInfo( |
| request, base::BindOnce(&TpmChallengeKeySubtleImpl::PrepareKeyFinished, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::PrepareKeyFinished( |
| const ::attestation::GetKeyInfoReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kGetPublicKeyFailedError)); |
| return; |
| } |
| |
| if (will_register_key_) { |
| public_key_ = reply.public_key(); |
| } |
| |
| std::move(callback_).Run(Result::MakePublicKey(reply.public_key())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::StartSignChallengeStep( |
| const std::string& challenge, |
| TpmChallengeKeyCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(callback_.is_null()); |
| |
| callback_ = std::move(callback); |
| |
| // See http://go/chromeos-va-registering-device-wide-keys-support for details |
| // about both key names. |
| |
| // Name of the key that will be used to sign challenge. |
| // ENTERPRISE_MACHINE challenges are signed using a stable key. |
| std::string key_name_for_challenge = |
| (flow_type_ == VerifiedAccessFlow::ENTERPRISE_MACHINE) |
| ? GetDefaultKeyName(flow_type_, key_crypto_type_) |
| : key_name_; |
| // Name of the key that will be included in SPKAC, it is used only when SPKAC |
| // should be included for the flow type ENTERPRISE_MACHINE. |
| std::string key_name_for_spkac = |
| (will_register_key_ && |
| flow_type_ == VerifiedAccessFlow::ENTERPRISE_MACHINE) |
| ? key_name_ |
| : std::string(); |
| |
| ::attestation::SignEnterpriseChallengeRequest request; |
| request.set_username(GetUsernameForAttestationClient()); |
| request.set_key_label(key_name_for_challenge); |
| request.set_key_name_for_spkac(key_name_for_spkac); |
| request.set_domain(GetEmail()); |
| request.set_device_id(InstallAttributes::Get()->GetDeviceId()); |
| request.set_include_signed_public_key(will_register_key_); |
| request.set_challenge(challenge); |
| request.set_va_type(AttestationClient::GetVerifiedAccessServerType()); |
| request.set_flow_type(flow_type_); |
| request.set_include_certificate(ShouldIncludeSigningKeyCertificate()); |
| if (signals_.has_value()) { |
| request.set_device_trust_signals_json(signals_.value()); |
| } |
| request.set_include_customer_id(ShouldIncludeCustomerId()); |
| AttestationClient::Get()->SignEnterpriseChallenge( |
| request, base::BindOnce(&TpmChallengeKeySubtleImpl::SignChallengeCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::SignChallengeCallback( |
| const ::attestation::SignEnterpriseChallengeReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kSignChallengeFailedError)); |
| return; |
| } |
| |
| std::move(callback_).Run( |
| Result::MakeChallengeResponse(reply.challenge_response())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::StartRegisterKeyStep( |
| TpmChallengeKeyCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(callback_.is_null()); |
| DCHECK(will_register_key_); |
| |
| callback_ = std::move(callback); |
| |
| ::attestation::RegisterKeyWithChapsTokenRequest request; |
| request.set_username(GetUsernameForAttestationClient()); |
| request.set_key_label(key_name_); |
| request.set_include_certificates(false); |
| |
| AttestationClient::Get()->RegisterKeyWithChapsToken( |
| request, base::BindOnce(&TpmChallengeKeySubtleImpl::RegisterKeyCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::RegisterKeyCallback( |
| const ::attestation::RegisterKeyWithChapsTokenReply& reply) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to call RegisterKeyWithChapsToken; status: " |
| << reply.status(); |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kKeyRegistrationFailedError)); |
| return; |
| } |
| |
| DCHECK(flow_type_ == VerifiedAccessFlow::ENTERPRISE_MACHINE || profile_); |
| |
| platform_keys::KeyPermissionsManager* key_permissions_manager = nullptr; |
| switch (flow_type_) { |
| case VerifiedAccessFlow::ENTERPRISE_USER: |
| key_permissions_manager = platform_keys::KeyPermissionsManagerImpl:: |
| GetUserPrivateTokenKeyPermissionsManager(profile_); |
| break; |
| case VerifiedAccessFlow::ENTERPRISE_MACHINE: |
| key_permissions_manager = platform_keys::KeyPermissionsManagerImpl:: |
| GetSystemTokenKeyPermissionsManager(); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| DCHECK(!public_key_.empty()); |
| key_permissions_manager->AllowKeyForUsage( |
| base::BindOnce(&TpmChallengeKeySubtleImpl::MarkCorporateKeyCallback, |
| weak_factory_.GetWeakPtr()), |
| platform_keys::KeyUsage::kCorporate, |
| std::vector<uint8_t>(public_key_.begin(), public_key_.end())); |
| } |
| |
| void TpmChallengeKeySubtleImpl::MarkCorporateKeyCallback( |
| chromeos::platform_keys::Status status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (status != chromeos::platform_keys::Status::kSuccess) { |
| std::move(callback_).Run( |
| Result::MakeError(ResultCode::kMarkCorporateKeyFailedError)); |
| return; |
| } |
| |
| std::move(callback_).Run(Result::MakeSuccess()); |
| } |
| |
| } // namespace attestation |
| } // namespace ash |