// 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