[go: nahoru, domu]

blob: e4bc8d189902287c79317450df6ce26413e67220 [file] [log] [blame]
// Copyright 2023 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/passkeys/passkey_authenticator_service_ash.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "chromeos/crosapi/mojom/passkeys.mojom.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_util.h"
#include "components/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/trusted_vault/trusted_vault_client.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "components/webauthn/core/browser/passkey_model_utils.h"
#include "crypto/ec_private_key.h"
#include "crypto/ec_signature_creator.h"
#include "crypto/sha2.h"
#include "device/fido/attested_credential_data.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/fido_constants.h"
#include "device/fido/p256_public_key.h"
#include "device/fido/public_key.h"
namespace ash {
namespace {
constexpr std::array<const uint8_t, 16> kGpmAaguid{
0xea, 0x9b, 0x8d, 0x66, 0x4d, 0x01, 0x1d, 0x21,
0x3c, 0xe4, 0xb6, 0xb4, 0x8c, 0xb5, 0x75, 0xd4};
// Returns the WebAuthn authenticator data for this authenticator. See
// https://w3c.github.io/webauthn/#authenticator-data.
std::vector<uint8_t> MakeAuthenticatorDataForAssertion(std::string_view rp_id) {
using Flag = device::AuthenticatorData::Flag;
return device::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)
.SerializeToByteArray();
}
std::vector<uint8_t> MakeAuthenticatorDataForCreation(
std::string_view rp_id,
base::span<const uint8_t> credential_id,
base::span<const uint8_t> public_key_spki_der) {
using Flag = device::AuthenticatorData::Flag;
std::unique_ptr<device::PublicKey> public_key =
device::P256PublicKey::ParseSpkiDer(
base::strict_cast<int32_t>(device::CoseAlgorithmIdentifier::kEs256),
public_key_spki_der);
device::AttestedCredentialData attested_credential_data(
kGpmAaguid, credential_id, std::move(public_key));
return device::AuthenticatorData(
crypto::SHA256Hash(base::as_byte_span(rp_id)),
{Flag::kTestOfUserPresence, Flag::kTestOfUserVerification,
Flag::kBackupEligible, Flag::kBackupState, Flag::kAttestation},
/*sign_counter=*/0u, std::move(attested_credential_data),
/*extensions=*/std::nullopt)
.SerializeToByteArray();
}
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
PasskeyAuthenticatorServiceAsh::CreateRequestContext::CreateRequestContext() =
default;
PasskeyAuthenticatorServiceAsh::CreateRequestContext::CreateRequestContext(
CreateRequestContext&&) = default;
PasskeyAuthenticatorServiceAsh::CreateRequestContext&
PasskeyAuthenticatorServiceAsh::CreateRequestContext::operator=(
CreateRequestContext&&) = default;
PasskeyAuthenticatorServiceAsh::CreateRequestContext::~CreateRequestContext() =
default;
PasskeyAuthenticatorServiceAsh::AssertRequestContext::AssertRequestContext() =
default;
PasskeyAuthenticatorServiceAsh::AssertRequestContext::AssertRequestContext(
AssertRequestContext&&) = default;
PasskeyAuthenticatorServiceAsh::AssertRequestContext&
PasskeyAuthenticatorServiceAsh::AssertRequestContext::operator=(
AssertRequestContext&&) = default;
PasskeyAuthenticatorServiceAsh::AssertRequestContext::~AssertRequestContext() =
default;
PasskeyAuthenticatorServiceAsh::PasskeyAuthenticatorServiceAsh(
CoreAccountInfo account_info,
webauthn::PasskeyModel* passkey_model,
trusted_vault::TrustedVaultClient* trusted_vault_client)
: primary_account_info_(std::move(account_info)),
passkey_model_(passkey_model),
trusted_vault_client_(trusted_vault_client) {}
PasskeyAuthenticatorServiceAsh::~PasskeyAuthenticatorServiceAsh() = default;
void PasskeyAuthenticatorServiceAsh::BindReceiver(
mojo::PendingReceiver<crosapi::mojom::PasskeyAuthenticator>
pending_receiver) {
receivers_.Add(this, std::move(pending_receiver));
}
void PasskeyAuthenticatorServiceAsh::Create(
crosapi::mojom::AccountKeyPtr account_key,
crosapi::mojom::PasskeyCreationRequestPtr request,
CreateCallback callback) {
if (!IsPrimaryAccount(account_key)) {
std::move(callback).Run(crosapi::mojom::PasskeyCreationResult::NewError(
crosapi::mojom::PasskeyCreationError::kNonPrimaryAccount));
return;
}
if (processing_request_) {
std::move(callback).Run(crosapi::mojom::PasskeyCreationResult::NewError(
crosapi::mojom::PasskeyCreationError::kPendingRequest));
return;
}
processing_request_ = true;
CreateRequestContext ctx;
ctx.request = std::move(request);
ctx.pending_callback = std::move(callback);
FetchTrustedVaultKeys(
base::BindOnce(&PasskeyAuthenticatorServiceAsh::DoCreate,
weak_factory_.GetWeakPtr(), std::move(ctx)));
}
void PasskeyAuthenticatorServiceAsh::Assert(
crosapi::mojom::AccountKeyPtr account_key,
crosapi::mojom::PasskeyAssertionRequestPtr request,
AssertCallback callback) {
if (!IsPrimaryAccount(account_key)) {
std::move(callback).Run(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kNonPrimaryAccount));
return;
}
if (processing_request_) {
std::move(callback).Run(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kPendingRequest));
return;
}
processing_request_ = true;
AssertRequestContext ctx;
ctx.request = std::move(request);
ctx.pending_callback = std::move(callback);
FetchTrustedVaultKeys(
base::BindOnce(&PasskeyAuthenticatorServiceAsh::DoAssert,
weak_factory_.GetWeakPtr(), std::move(ctx)));
}
void PasskeyAuthenticatorServiceAsh::FetchTrustedVaultKeys(
SecurityDomainSecretCallback callback) {
trusted_vault_client_->FetchKeys(
primary_account_info_,
base::BindOnce(&PasskeyAuthenticatorServiceAsh::OnHaveTrustedVaultKeys,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void PasskeyAuthenticatorServiceAsh::OnHaveTrustedVaultKeys(
SecurityDomainSecretCallback callback,
const std::vector<std::vector<uint8_t>>& keys) {
if (keys.empty()) {
// TODO(crbug.com/1223853): Implement security domain secret recovery UI
// flow.
NOTIMPLEMENTED();
return std::move(callback).Run(std::nullopt);
}
return std::move(callback).Run(keys.back());
}
void PasskeyAuthenticatorServiceAsh::DoCreate(
CreateRequestContext ctx,
std::optional<std::vector<uint8_t>> security_domain_secret) {
if (!security_domain_secret) {
FinishCreate(std::move(ctx),
crosapi::mojom::PasskeyCreationResult::NewError(
crosapi::mojom::PasskeyCreationError::
kSecurityDomainSecretUnavailable));
return;
}
// TODO(crbug.com/1223853): Implement user verification.
// TODO(crbug.com/1223853): Get the epoch/version of the security domain
// secret and pass into CreatePasskey().
std::vector<uint8_t> public_key_spki_der;
sync_pb::WebauthnCredentialSpecifics passkey = passkey_model_->CreatePasskey(
ctx.request->rp_id,
webauthn::PasskeyModel::UserEntity(ctx.request->user_id,
ctx.request->user_name,
ctx.request->user_display_name),
*security_domain_secret,
/*trusted_vault_key_version=*/0, &public_key_spki_der);
auto response = crosapi::mojom::PasskeyCreationResponse::New();
response->authenticator_data = MakeAuthenticatorDataForCreation(
ctx.request->rp_id, base::as_byte_span(passkey.credential_id()),
public_key_spki_der);
FinishCreate(
std::move(ctx),
crosapi::mojom::PasskeyCreationResult::NewResponse(std::move(response)));
}
void PasskeyAuthenticatorServiceAsh::DoAssert(
AssertRequestContext ctx,
std::optional<std::vector<uint8_t>> security_domain_secret) {
if (!security_domain_secret) {
FinishAssert(std::move(ctx),
crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::
kSecurityDomainSecretUnavailable));
return;
}
const std::string credential_id(ctx.request->credential_id.begin(),
ctx.request->credential_id.end());
std::optional<sync_pb::WebauthnCredentialSpecifics> credential_specifics =
passkey_model_->GetPasskeyByCredentialId(ctx.request->rp_id,
credential_id);
if (!credential_specifics) {
FinishAssert(
std::move(ctx),
crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kCredentialNotFound));
return;
}
sync_pb::WebauthnCredentialSpecifics_Encrypted credential_secrets;
if (!webauthn::passkey_model_utils::DecryptWebauthnCredentialSpecificsData(
*security_domain_secret, *credential_specifics,
&credential_secrets)) {
FinishAssert(std::move(ctx),
crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::
kSecurityDomainSecretUnavailable));
return;
}
// TODO(crbug.com/1223853): Implement user verification.
std::vector<uint8_t> authenticator_data =
MakeAuthenticatorDataForAssertion(ctx.request->rp_id);
std::vector<uint8_t> signed_over_data(authenticator_data);
signed_over_data.insert(signed_over_data.end(),
ctx.request->client_data_hash.begin(),
ctx.request->client_data_hash.end());
std::optional<std::vector<uint8_t>> assertion_signature = GenerateEcSignature(
base::as_byte_span(credential_secrets.private_key()), signed_over_data);
if (!assertion_signature) {
FinishAssert(std::move(ctx),
crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kInternalError));
return;
}
auto response = crosapi::mojom::PasskeyAssertionResponse::New();
response->signature = std::move(*assertion_signature);
response->authenticator_data = std::move(authenticator_data);
FinishAssert(
std::move(ctx),
crosapi::mojom::PasskeyAssertionResult::NewResponse(std::move(response)));
}
void PasskeyAuthenticatorServiceAsh::FinishCreate(
CreateRequestContext ctx,
crosapi::mojom::PasskeyCreationResultPtr result) {
processing_request_ = false;
std::move(ctx.pending_callback).Run(std::move(result));
}
void PasskeyAuthenticatorServiceAsh::FinishAssert(
AssertRequestContext ctx,
crosapi::mojom::PasskeyAssertionResultPtr result) {
processing_request_ = false;
std::move(ctx.pending_callback).Run(std::move(result));
}
bool PasskeyAuthenticatorServiceAsh::IsPrimaryAccount(
const crosapi::mojom::AccountKeyPtr& mojo_account_key) const {
const std::optional<account_manager::AccountKey> account_key =
account_manager::FromMojoAccountKey(mojo_account_key);
if (!account_key ||
(account_key->account_type() != account_manager::AccountType::kGaia) ||
account_key->id().empty()) {
return false;
}
return account_key->id() == primary_account_info_.gaia;
}
} // namespace ash