| // Copyright 2024 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/webauthn/chromeos/passkey_authenticator.h" |
| |
| #include "base/containers/span.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/notimplemented.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "chrome/browser/webauthn/chromeos/passkey_in_session_auth.h" |
| #include "chrome/browser/webauthn/chromeos/passkey_service.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/webauthn/core/browser/passkey_model.h" |
| #include "components/webauthn/core/browser/passkey_model_utils.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "crypto/ec_private_key.h" |
| #include "crypto/ec_signature_creator.h" |
| #include "crypto/sha2.h" |
| #include "device/fido/authenticator_get_assertion_response.h" |
| #include "device/fido/authenticator_supported_options.h" |
| #include "device/fido/ctap_get_assertion_request.h" |
| #include "device/fido/ctap_make_credential_request.h" |
| #include "device/fido/fido_authenticator.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/fido_transport_protocol.h" |
| #include "device/fido/fido_types.h" |
| #include "ui/aura/window.h" |
| |
| using device::AuthenticatorData; |
| using device::AuthenticatorGetAssertionResponse; |
| using device::AuthenticatorSupportedOptions; |
| using device::AuthenticatorType; |
| using device::CoseAlgorithmIdentifier; |
| using device::CredentialType; |
| using device::CtapGetAssertionOptions; |
| using device::CtapGetAssertionRequest; |
| using device::CtapMakeCredentialRequest; |
| using device::FidoAuthenticator; |
| using device::FidoRequestHandlerBase; |
| using device::FidoTransportProtocol; |
| using device::GetAssertionStatus; |
| using device::MakeCredentialOptions; |
| using device::PublicKeyCredentialDescriptor; |
| using device::PublicKeyCredentialUserEntity; |
| |
| namespace chromeos { |
| namespace { |
| |
| AuthenticatorSupportedOptions PasskeyAuthenticatorOptions() { |
| AuthenticatorSupportedOptions options; |
| options.is_platform_device = |
| AuthenticatorSupportedOptions::PlatformDevice::kYes; |
| options.supports_resident_key = true; |
| options.user_verification_availability = AuthenticatorSupportedOptions:: |
| UserVerificationAvailability::kSupportedAndConfigured; |
| return options; |
| } |
| |
| // Returns the WebAuthn authenticator data for this authenticator. See |
| // https://w3c.github.io/webauthn/#authenticator-data. |
| AuthenticatorData MakeAuthenticatorDataForAssertion(std::string_view rp_id) { |
| using Flag = AuthenticatorData::Flag; |
| return AuthenticatorData( |
| crypto::SHA256Hash(base::as_byte_span(rp_id)), |
| {Flag::kTestOfUserPresence, Flag::kTestOfUserVerification, |
| Flag::kBackupEligible, Flag::kBackupState}, |
| /*sign_counter=*/0u, |
| /*attested_credential_data=*/std::nullopt, |
| /*extensions=*/std::nullopt); |
| } |
| |
| std::optional<std::vector<uint8_t>> GenerateEcSignature( |
| base::span<const uint8_t> pkcs8_ec_private_key, |
| base::span<const uint8_t> signed_over_data) { |
| auto ec_private_key = |
| crypto::ECPrivateKey::CreateFromPrivateKeyInfo(pkcs8_ec_private_key); |
| if (!ec_private_key) { |
| return std::nullopt; |
| } |
| auto signer = crypto::ECSignatureCreator::Create(ec_private_key.get()); |
| std::vector<uint8_t> signature; |
| if (!signer->Sign(signed_over_data, &signature)) { |
| return std::nullopt; |
| } |
| return signature; |
| } |
| |
| } // namespace |
| |
| PasskeyAuthenticator::PasskeyAuthenticator( |
| content::RenderFrameHost* rfh, |
| PasskeyService* passkey_service, |
| webauthn::PasskeyModel* passkey_model) |
| : render_frame_host_id_(rfh->GetGlobalId()), |
| passkey_service_(passkey_service), |
| passkey_model_(passkey_model) {} |
| |
| PasskeyAuthenticator::~PasskeyAuthenticator() = default; |
| |
| AuthenticatorType PasskeyAuthenticator::GetType() const { |
| return AuthenticatorType::kChromeOSPasskeys; |
| } |
| |
| std::string PasskeyAuthenticator::GetId() const { |
| return "ChromeOSPasskeysAuthenticator"; |
| } |
| |
| std::optional<base::span<const int32_t>> PasskeyAuthenticator::GetAlgorithms() { |
| constexpr std::array<int32_t, 1> kAlgorithms{ |
| static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256)}; |
| return kAlgorithms; |
| } |
| |
| const AuthenticatorSupportedOptions& PasskeyAuthenticator::Options() const { |
| static const base::NoDestructor<AuthenticatorSupportedOptions> options( |
| PasskeyAuthenticatorOptions()); |
| return *options; |
| } |
| |
| std::optional<FidoTransportProtocol> |
| PasskeyAuthenticator::AuthenticatorTransport() const { |
| return FidoTransportProtocol::kInternal; |
| } |
| |
| void PasskeyAuthenticator::GetTouch(base::OnceClosure callback) {} |
| |
| void PasskeyAuthenticator::InitializeAuthenticator(base::OnceClosure callback) { |
| std::move(callback).Run(); |
| } |
| |
| void PasskeyAuthenticator::MakeCredential(CtapMakeCredentialRequest request, |
| MakeCredentialOptions request_options, |
| MakeCredentialCallback callback) {} |
| |
| void PasskeyAuthenticator::GetAssertion(CtapGetAssertionRequest request, |
| CtapGetAssertionOptions options, |
| GetAssertionCallback callback) { |
| std::string rp_id = request.rp_id; |
| PasskeyInSessionAuthProvider::Get()->ShowPasskeyInSessionAuthDialog( |
| content::RenderFrameHost::FromID(render_frame_host_id_) |
| ->GetNativeView() |
| ->GetToplevelWindow(), |
| rp_id, |
| base::BindOnce(&PasskeyAuthenticator::FinishGetAssertion, |
| weak_factory_.GetWeakPtr(), std::move(request), |
| std::move(options), std::move(callback))); |
| return; |
| } |
| |
| void PasskeyAuthenticator::FinishGetAssertion(CtapGetAssertionRequest request, |
| CtapGetAssertionOptions options, |
| GetAssertionCallback callback, |
| bool user_verification_success) { |
| if (!user_verification_success) { |
| std::move(callback).Run( |
| GetAssertionStatus::kUserConsentButCredentialNotRecognized, {}); |
| return; |
| } |
| |
| CHECK_EQ(request.allow_list.size(), 1u); |
| const std::vector<uint8_t>& credential_id = request.allow_list.begin()->id; |
| const std::string credential_id_str = {credential_id.begin(), |
| credential_id.end()}; |
| |
| std::optional<sync_pb::WebauthnCredentialSpecifics> credential_specifics = |
| passkey_model_->GetPasskeyByCredentialId(request.rp_id, |
| credential_id_str); |
| if (!credential_specifics) { |
| FIDO_LOG(ERROR) << "Could not find a matching GPM credential."; |
| std::move(callback).Run( |
| GetAssertionStatus::kUserConsentButCredentialNotRecognized, {}); |
| return; |
| } |
| |
| const std::optional<std::vector<uint8_t>> security_domain_secret = |
| passkey_service_->GetCachedSecurityDomainSecret(); |
| if (!security_domain_secret) { |
| FIDO_LOG(ERROR) << "Security domain secret unavailable."; |
| std::move(callback).Run( |
| GetAssertionStatus::kUserConsentButCredentialNotRecognized, {}); |
| return; |
| } |
| |
| // Decrypt the sealed data from `credential_specifics` into |
| // `credential_secrets`. Note that `DecryptWebauthnCredentialSpecificsData()` |
| // internally maps both the `encrypted` and `private_key` case of the |
| // `encrypted_data` oneof to `WebauthnCredentialSpecifics_Encrypted`. In the |
| // latter case, only the `private_key` field will be set. |
| sync_pb::WebauthnCredentialSpecifics_Encrypted unsealed_credential_secrets; |
| if (!webauthn::passkey_model_utils::DecryptWebauthnCredentialSpecificsData( |
| base::make_span(*security_domain_secret), *credential_specifics, |
| &unsealed_credential_secrets)) { |
| FIDO_LOG(ERROR) << "Decrypting WebauthnCredentialSpecifics failed."; |
| std::move(callback).Run( |
| GetAssertionStatus::kUserConsentButCredentialNotRecognized, {}); |
| return; |
| } |
| |
| AuthenticatorData authenticator_data = |
| MakeAuthenticatorDataForAssertion(request.rp_id); |
| std::vector<uint8_t> signed_over_data( |
| authenticator_data.SerializeToByteArray()); |
| signed_over_data.insert(signed_over_data.end(), |
| request.client_data_hash.begin(), |
| request.client_data_hash.end()); |
| std::optional<std::vector<uint8_t>> assertion_signature = GenerateEcSignature( |
| base::as_byte_span(unsealed_credential_secrets.private_key()), |
| signed_over_data); |
| if (!assertion_signature) { |
| FIDO_LOG(ERROR) << "Generating assertion signature failed"; |
| std::move(callback).Run( |
| GetAssertionStatus::kUserConsentButCredentialNotRecognized, {}); |
| return; |
| } |
| |
| AuthenticatorGetAssertionResponse assertion_response( |
| std::move(authenticator_data), std::move(*assertion_signature), |
| /*transport_used=*/std::nullopt); |
| assertion_response.credential = |
| PublicKeyCredentialDescriptor(CredentialType::kPublicKey, credential_id); |
| assertion_response.user_entity = PublicKeyCredentialUserEntity( |
| std::vector<uint8_t>(credential_specifics->user_id().begin(), |
| credential_specifics->user_id().end())); |
| std::vector<AuthenticatorGetAssertionResponse> responses; |
| responses.emplace_back(std::move(assertion_response)); |
| std::move(callback).Run(GetAssertionStatus::kSuccess, std::move(responses)); |
| } |
| |
| void PasskeyAuthenticator::Cancel() { |
| NOTIMPLEMENTED(); |
| } |
| |
| base::WeakPtr<FidoAuthenticator> PasskeyAuthenticator::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace chromeos |