0

Add crosapi service for accessing GPM passkeys

The `PasskeyAuthenticator` crosapi service gives Lacros assertion access
to GPM passkeys. Ash has access to the security domain secret needed to
decrypt key material on the `WebauthnCredential` entities that represent
passkeys in Sync.

Bug: 1223853
Change-Id: I1a165340304e0b98207429a883c002e501d29d94
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4956453
Reviewed-by: Adam Langley <agl@chromium.org>
Auto-Submit: Martin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1226183}
This commit is contained in:
Martin Kreichgauer
2023-11-17 17:13:35 +00:00
committed by Chromium LUCI CQ
parent b37e609bff
commit 59b8313011
15 changed files with 654 additions and 3 deletions

@ -2299,6 +2299,10 @@ source_set("ash") {
"ownership/owner_settings_service_ash_factory.h",
"ownership/ownership_histograms.cc",
"ownership/ownership_histograms.h",
"passkeys/passkey_authenticator_service_ash.cc",
"passkeys/passkey_authenticator_service_ash.h",
"passkeys/passkey_authenticator_service_factory_ash.cc",
"passkeys/passkey_authenticator_service_factory_ash.h",
"pcie_peripheral/ash_usb_detector.cc",
"pcie_peripheral/ash_usb_detector.h",
"phonehub/attestation_certificate_generator_impl.cc",
@ -4221,6 +4225,7 @@ source_set("ash") {
"//components/webapps/browser",
"//components/webapps/browser:constants",
"//components/webapps/common",
"//components/webauthn/core/browser:browser",
"//components/zoom",
"//content/public/common",
"//content/public/common:main_function_params",
@ -5672,6 +5677,7 @@ source_set("unit_tests") {
"os_feedback/os_feedback_screenshot_manager_unittest.cc",
"ownership/owner_key_loader_unittest.cc",
"ownership/owner_settings_service_ash_unittest.cc",
"passkeys/passkey_authenticator_service_ash_unittest.cc",
"pcie_peripheral/ash_usb_detector_unittest.cc",
"phonehub/attestation_certificate_generator_impl_unittest.cc",
"phonehub/browser_tabs_metadata_fetcher_impl_unittest.cc",
@ -6411,6 +6417,8 @@ source_set("unit_tests") {
"//components/webapps/browser",
"//components/webapps/browser:constants",
"//components/webapps/common",
"//components/webauthn/core/browser",
"//components/webauthn/core/browser:test_support",
"//content/public/browser",
"//content/public/common",
"//content/test:test_support",

@ -110,6 +110,8 @@
#include "chrome/browser/ash/crosapi/web_kiosk_service_ash.h"
#include "chrome/browser/ash/crosapi/web_page_info_ash.h"
#include "chrome/browser/ash/input_method/editor_mediator_factory.h"
#include "chrome/browser/ash/passkeys/passkey_authenticator_service_ash.h"
#include "chrome/browser/ash/passkeys/passkey_authenticator_service_factory_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/remote_apps/remote_apps_manager_factory.h"
#include "chrome/browser/ash/sync/sync_mojo_service_ash.h"
@ -146,6 +148,7 @@
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "chromeos/crosapi/mojom/message_center.mojom.h"
#include "chromeos/crosapi/mojom/multi_capture_service.mojom.h"
#include "chromeos/crosapi/mojom/passkeys.mojom.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "chromeos/crosapi/mojom/select_file.mojom.h"
#include "chromeos/crosapi/mojom/task_manager.mojom.h"
@ -784,6 +787,17 @@ void CrosapiAsh::BindParentAccess(
parent_access_ash_->BindReceiver(std::move(receiver));
}
void CrosapiAsh::BindPasskeyAuthenticator(
mojo::PendingReceiver<mojom::PasskeyAuthenticator> receiver) {
auto* passkey_authenticator =
ash::PasskeyAuthenticatorServiceFactoryAsh::GetForProfile(
GetAshProfile());
if (!passkey_authenticator) {
return;
}
passkey_authenticator->BindReceiver(std::move(receiver));
}
void CrosapiAsh::BindPaymentAppInstance(
mojo::PendingReceiver<chromeos::payments::mojom::PaymentAppInstance>
receiver) {

@ -100,6 +100,7 @@ class NetworkChangeAsh;
class NetworkSettingsServiceAsh;
class NetworkingAttributesAsh;
class NetworkingPrivateAsh;
class PasskeyAuthenticator;
class ParentAccessAsh;
class PaymentAppInstanceAsh;
class PolicyServiceAsh;
@ -310,6 +311,8 @@ class CrosapiAsh : public mojom::Crosapi {
mojo::PendingReceiver<mojom::NetworkingAttributes> receiver) override;
void BindNetworkingPrivate(
mojo::PendingReceiver<mojom::NetworkingPrivate> receiver) override;
void BindPasskeyAuthenticator(
mojo::PendingReceiver<mojom::PasskeyAuthenticator> receiver) override;
void BindParentAccess(
mojo::PendingReceiver<mojom::ParentAccess> receiver) override;
void BindPaymentAppInstance(

@ -114,6 +114,7 @@
#include "chromeos/crosapi/mojom/networking_attributes.mojom.h"
#include "chromeos/crosapi/mojom/networking_private.mojom.h"
#include "chromeos/crosapi/mojom/parent_access.mojom.h"
#include "chromeos/crosapi/mojom/passkeys.mojom.h"
#include "chromeos/crosapi/mojom/policy_service.mojom.h"
#include "chromeos/crosapi/mojom/power.mojom.h"
#include "chromeos/crosapi/mojom/prefs.mojom.h"
@ -296,7 +297,7 @@ constexpr InterfaceVersionEntry MakeInterfaceVersionEntry() {
return {T::Uuid_, T::Version_};
}
static_assert(crosapi::mojom::Crosapi::Version_ == 123,
static_assert(crosapi::mojom::Crosapi::Version_ == 124,
"If you add a new crosapi, please add it to "
"kInterfaceVersionEntries below.");
@ -382,6 +383,7 @@ constexpr InterfaceVersionEntry kInterfaceVersionEntries[] = {
MakeInterfaceVersionEntry<crosapi::mojom::NetworkingAttributes>(),
MakeInterfaceVersionEntry<crosapi::mojom::NetworkingPrivate>(),
MakeInterfaceVersionEntry<crosapi::mojom::NetworkSettingsService>(),
MakeInterfaceVersionEntry<crosapi::mojom::PasskeyAuthenticator>(),
MakeInterfaceVersionEntry<crosapi::mojom::PolicyService>(),
MakeInterfaceVersionEntry<crosapi::mojom::Power>(),
MakeInterfaceVersionEntry<crosapi::mojom::Prefs>(),

@ -0,0 +1,3 @@
include_rules = [
"+device/fido",
]

@ -0,0 +1,4 @@
monorail {
component: "Blink>WebAuthentication"
}
team_email: "identity-dev@chromium.org"

@ -0,0 +1 @@
file://device/fido/OWNERS

@ -0,0 +1,189 @@
// 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 "base/containers/span.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/authenticator_data.h"
namespace ash {
namespace {
absl::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 absl::nullopt;
}
auto signer = crypto::ECSignatureCreator::Create(ec_private_key.get());
std::vector<uint8_t> signature;
if (!signer->Sign(signed_over_data, &signature)) {
return absl::nullopt;
}
return signature;
}
} // namespace
PasskeyAuthenticatorServiceAsh::RequestState::RequestState() = default;
PasskeyAuthenticatorServiceAsh::RequestState::~RequestState() = 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::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 (request_state_) {
std::move(callback).Run(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kPendingRequest));
return;
}
request_state_.emplace();
request_state_->pending_assert_callback = std::move(callback);
request_state_->assert_request = std::move(request);
FetchTrustedVaultKeys(base::BindOnce(
&PasskeyAuthenticatorServiceAsh::DoAssert, weak_factory_.GetWeakPtr()));
}
void PasskeyAuthenticatorServiceAsh::FetchTrustedVaultKeys(
base::OnceCallback<void()> callback) {
trusted_vault_client_->FetchKeys(
primary_account_info_,
base::BindOnce(&PasskeyAuthenticatorServiceAsh::OnHaveTrustedVaultKeys,
weak_factory_.GetWeakPtr())
.Then(std::move(callback)));
}
void PasskeyAuthenticatorServiceAsh::OnHaveTrustedVaultKeys(
const std::vector<std::vector<uint8_t>>& keys) {
if (keys.empty()) {
// TODO(crbug.com/1223853): Implement security domain secret recovery UI
// flow.
NOTIMPLEMENTED();
return;
}
request_state_->security_domain_secret = keys.back();
}
void PasskeyAuthenticatorServiceAsh::DoAssert() {
CHECK(request_state_);
if (!request_state_->security_domain_secret) {
FinishAssert(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::
kSecurityDomainSecretUnavailable));
return;
}
std::string credential_id(
request_state_->assert_request->credential_id.begin(),
request_state_->assert_request->credential_id.end());
absl::optional<sync_pb::WebauthnCredentialSpecifics> credential_specifics =
passkey_model_->GetPasskeyByCredentialId(
request_state_->assert_request->rp_id, credential_id);
if (!credential_specifics) {
FinishAssert(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kCredentialNotFound));
return;
}
sync_pb::WebauthnCredentialSpecifics_Encrypted credential_secrets;
if (!webauthn::passkey_model_utils::DecryptWebauthnCredentialSpecificsData(
*request_state_->security_domain_secret, *credential_specifics,
&credential_secrets)) {
FinishAssert(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::
kSecurityDomainSecretUnavailable));
return;
}
// TODO(crbug.com/1223853): Implement user verification.
device::AuthenticatorData authenticator_data(
crypto::SHA256Hash(base::as_bytes(
base::make_span(request_state_->assert_request->rp_id))),
/*user_present=*/true,
/*user_verified=*/true, /*backup_eligible=*/true, /*sign_counter=*/0,
/*attested_credential_data=*/absl::nullopt, /*extensions=*/absl::nullopt);
std::vector<uint8_t> signed_over_data =
authenticator_data.SerializeToByteArray();
signed_over_data.insert(
signed_over_data.end(),
request_state_->assert_request->client_data_hash.begin(),
request_state_->assert_request->client_data_hash.end());
absl::optional<std::vector<uint8_t>> assertion_signature =
GenerateEcSignature(
base::as_bytes(base::make_span(credential_secrets.private_key())),
signed_over_data);
if (!assertion_signature) {
FinishAssert(crosapi::mojom::PasskeyAssertionResult::NewError(
crosapi::mojom::PasskeyAssertionError::kInternalError));
return;
}
auto response = crosapi::mojom::PasskeyAssertionResponse::New();
response->signature = std::move(*assertion_signature);
FinishAssert(
crosapi::mojom::PasskeyAssertionResult::NewResponse(std::move(response)));
}
void PasskeyAuthenticatorServiceAsh::FinishAssert(
crosapi::mojom::PasskeyAssertionResultPtr result) {
CHECK(request_state_);
AssertCallback callback = std::move(request_state_->pending_assert_callback);
request_state_ = absl::nullopt;
std::move(callback).Run(std::move(result));
}
bool PasskeyAuthenticatorServiceAsh::IsPrimaryAccount(
const crosapi::mojom::AccountKeyPtr& mojo_account_key) const {
const absl::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

@ -0,0 +1,84 @@
// 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.
#ifndef CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_ASH_H_
#define CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_ASH_H_
#include <memory>
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/crosapi/mojom/account_manager.mojom.h"
#include "chromeos/crosapi/mojom/passkeys.mojom.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
namespace trusted_vault {
class TrustedVaultClient;
}
namespace webauthn {
class PasskeyModel;
}
namespace ash {
// Implements a crosapi interface for creating and asserting passkeys associated
// with the primary profile.
class PasskeyAuthenticatorServiceAsh
: public crosapi::mojom::PasskeyAuthenticator,
public KeyedService {
public:
// `account_info` must belong the primary profile. `passkey_model` and
// `trusted_vault_client` must outlive this instance.
PasskeyAuthenticatorServiceAsh(
CoreAccountInfo account_info,
webauthn::PasskeyModel* passkey_model,
trusted_vault::TrustedVaultClient* trusted_vault_client);
PasskeyAuthenticatorServiceAsh(const PasskeyAuthenticatorServiceAsh&) =
delete;
PasskeyAuthenticatorServiceAsh& operator=(PasskeyAuthenticatorServiceAsh&) =
delete;
~PasskeyAuthenticatorServiceAsh() override;
void BindReceiver(mojo::PendingReceiver<crosapi::mojom::PasskeyAuthenticator>
pending_receiver);
void Assert(crosapi::mojom::AccountKeyPtr account_key,
crosapi::mojom::PasskeyAssertionRequestPtr request,
AssertCallback callback) override;
private:
struct RequestState {
RequestState();
~RequestState();
crosapi::mojom::PasskeyAssertionRequestPtr assert_request;
AssertCallback pending_assert_callback;
absl::optional<std::vector<uint8_t>> security_domain_secret;
};
void FetchTrustedVaultKeys(base::OnceCallback<void()> callback);
void OnHaveTrustedVaultKeys(const std::vector<std::vector<uint8_t>>& keys);
void DoAssert();
void FinishAssert(crosapi::mojom::PasskeyAssertionResultPtr result);
bool IsPrimaryAccount(const crosapi::mojom::AccountKeyPtr& account_key) const;
const CoreAccountInfo primary_account_info_;
const raw_ptr<webauthn::PasskeyModel> passkey_model_;
const raw_ptr<trusted_vault::TrustedVaultClient> trusted_vault_client_;
absl::optional<RequestState> request_state_;
mojo::ReceiverSet<crosapi::mojom::PasskeyAuthenticator> receivers_;
base::WeakPtrFactory<PasskeyAuthenticatorServiceAsh> weak_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_ASH_H_

@ -0,0 +1,156 @@
// 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 <cstdint>
#include <memory>
#include "base/rand_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/passkeys/passkey_authenticator_service_ash.h"
#include "chromeos/crosapi/mojom/passkeys.mojom.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync/protocol/webauthn_credential_specifics.pb.h"
#include "components/trusted_vault/test/fake_trusted_vault_client.h"
#include "components/webauthn/core/browser/passkey_model_utils.h"
#include "components/webauthn/core/browser/test_passkey_model.h"
#include "crypto/ec_private_key.h"
#include "crypto/random.h"
#include "device/fido/features.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
using crosapi::mojom::AccountKey;
using crosapi::mojom::AccountType;
using crosapi::mojom::PasskeyAssertionError;
using crosapi::mojom::PasskeyAssertionRequest;
using crosapi::mojom::PasskeyAssertionRequestPtr;
using crosapi::mojom::PasskeyAssertionResponse;
using crosapi::mojom::PasskeyAssertionResponsePtr;
using crosapi::mojom::PasskeyAssertionResultPtr;
constexpr std::string_view kRpId = "example.com";
constexpr std::array<uint8_t, 3> kUserId{1, 2, 3};
constexpr size_t kSecurityDomainSecretLength = 32;
sync_pb::WebauthnCredentialSpecifics NewPasskey(
base::span<const uint8_t> domain_secret) {
sync_pb::WebauthnCredentialSpecifics specifics;
specifics.set_sync_id(base::RandBytesAsString(16));
specifics.set_credential_id(base::RandBytesAsString(16));
specifics.set_rp_id(kRpId.data());
specifics.set_user_id({kUserId.begin(), kUserId.end()});
specifics.set_creation_time(
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
sync_pb::WebauthnCredentialSpecifics_Encrypted encrypted_pb;
auto ec_key = crypto::ECPrivateKey::Create();
std::vector<uint8_t> private_key_pkcs8;
CHECK(ec_key->ExportPrivateKey(&private_key_pkcs8));
encrypted_pb.set_private_key(
{private_key_pkcs8.begin(), private_key_pkcs8.end()});
CHECK(webauthn::passkey_model_utils::EncryptWebauthnCredentialSpecificsData(
domain_secret, encrypted_pb, &specifics));
return specifics;
}
class PasskeyAuthenticatorServiceAshTest : public testing::Test {
protected:
PasskeyAuthenticatorServiceAshTest() {
account_info_ = identity_test_env_.MakePrimaryAccountAvailable(
"example@gmail.com", signin::ConsentLevel::kSignin);
passkey_authenticator_ = std::make_unique<PasskeyAuthenticatorServiceAsh>(
account_info_, &passkey_model_, &trusted_vault_client_);
passkey_authenticator_->BindReceiver(
passkey_authenticator_remote_.BindNewPipeAndPassReceiver());
crypto::RandBytes(base::make_span(security_domain_secret_));
}
void InjectTrustedVaultKey() {
const std::vector<std::vector<uint8_t>> keys = {
{security_domain_secret_.begin(), security_domain_secret_.end()}};
trusted_vault_client_.StoreKeys(account_info_.gaia, keys,
/*last_key_version=*/1);
}
sync_pb::WebauthnCredentialSpecifics InjectCredential(
std::string_view rp_id) {
sync_pb::WebauthnCredentialSpecifics specifics =
NewPasskey(security_domain_secret_);
passkey_model_.AddNewPasskeyForTesting(specifics);
return specifics;
}
base::expected<PasskeyAssertionResponsePtr, PasskeyAssertionError>
AssertPasskey(std::string_view rp_id, std::string_view credential_id) {
base::test::TestFuture<PasskeyAssertionResultPtr> future;
auto request = PasskeyAssertionRequest::New();
request->rp_id = rp_id;
request->credential_id = {credential_id.begin(), credential_id.end()};
passkey_authenticator_remote_->Assert(
AccountKey::New(account_info_.gaia, AccountType::kGaia),
std::move(request), future.GetCallback());
// Ensure all calls to `trusted_vault_client_ are able to complete.
passkey_authenticator_remote_.FlushForTesting();
trusted_vault_client_.CompleteAllPendingRequests();
PasskeyAssertionResultPtr result = future.Take();
if (result->is_error()) {
return base::unexpected(result->get_error());
}
CHECK(result->is_response());
return std::move(result->get_response());
}
base::test::SingleThreadTaskEnvironment task_environment_;
base::test::ScopedFeatureList scoped_feature_list_{device::kChromeOsPasskeys};
std::unique_ptr<PasskeyAuthenticatorServiceAsh> passkey_authenticator_;
mojo::Remote<crosapi::mojom::PasskeyAuthenticator>
passkey_authenticator_remote_;
signin::IdentityTestEnvironment identity_test_env_;
AccountInfo account_info_;
webauthn::TestPasskeyModel passkey_model_;
trusted_vault::FakeTrustedVaultClient trusted_vault_client_;
std::array<uint8_t, kSecurityDomainSecretLength> security_domain_secret_;
};
// The service should return an error if a GetAssertion request is performed
// without a security domain secret.
// TODO(crbug.com/1223853): Implement domain secret recovery.
TEST_F(PasskeyAuthenticatorServiceAshTest, GetAssertionWithoutDomainSecret) {
ASSERT_TRUE(trusted_vault_client_.GetStoredKeys(account_info_.gaia).empty());
auto result = AssertPasskey(kRpId, "unknown id");
EXPECT_EQ(result.error(),
PasskeyAssertionError::kSecurityDomainSecretUnavailable);
}
TEST_F(PasskeyAuthenticatorServiceAshTest, GetAssertionUnknownCredential) {
InjectTrustedVaultKey();
sync_pb::WebauthnCredentialSpecifics credential = InjectCredential(kRpId);
auto result = AssertPasskey(kRpId, "unknown id");
EXPECT_EQ(result.error(), PasskeyAssertionError::kCredentialNotFound);
}
TEST_F(PasskeyAuthenticatorServiceAshTest, GetAssertion) {
InjectTrustedVaultKey();
sync_pb::WebauthnCredentialSpecifics credential = InjectCredential(kRpId);
auto result = AssertPasskey(kRpId, credential.credential_id());
ASSERT_TRUE(result.has_value());
EXPECT_FALSE(result.value()->signature.empty());
}
} // namespace
} // namespace ash

@ -0,0 +1,67 @@
// 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_factory_ash.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "chrome/browser/ash/passkeys/passkey_authenticator_service_ash.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
#include "chrome/browser/webauthn/passkey_model_factory.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/features.h"
#include "components/trusted_vault/trusted_vault_client.h"
#include "components/trusted_vault/trusted_vault_service.h"
#include "components/webauthn/core/browser/passkey_model.h"
#include "content/public/browser/storage_partition.h"
#include "device/fido/features.h"
namespace ash {
PasskeyAuthenticatorServiceFactoryAsh*
PasskeyAuthenticatorServiceFactoryAsh::GetInstance() {
static base::NoDestructor<PasskeyAuthenticatorServiceFactoryAsh> instance;
return instance.get();
}
PasskeyAuthenticatorServiceAsh*
PasskeyAuthenticatorServiceFactoryAsh::GetForProfile(Profile* profile) {
return static_cast<PasskeyAuthenticatorServiceAsh*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
PasskeyAuthenticatorServiceFactoryAsh::PasskeyAuthenticatorServiceFactoryAsh()
: ProfileKeyedServiceFactory(
"PasskeyAuthenticatorServiceAsh",
ProfileSelections::Builder()
.WithRegular(ProfileSelection::kRedirectedToOriginal)
.Build()) {
DependsOn(IdentityManagerFactory::GetInstance());
DependsOn(PasskeyModelFactory::GetInstance());
DependsOn(TrustedVaultServiceFactory::GetInstance());
}
PasskeyAuthenticatorServiceFactoryAsh::
~PasskeyAuthenticatorServiceFactoryAsh() = default;
std::unique_ptr<KeyedService>
PasskeyAuthenticatorServiceFactoryAsh::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
if (!base::FeatureList::IsEnabled(device::kChromeOsPasskeys)) {
return nullptr;
}
auto* profile = Profile::FromBrowserContext(context);
return std::make_unique<PasskeyAuthenticatorServiceAsh>(
IdentityManagerFactory::GetForProfile(profile)->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
PasskeyModelFactory::GetForProfile(profile),
TrustedVaultServiceFactory::GetForProfile(profile)->GetTrustedVaultClient(
trusted_vault::SecurityDomainId::kPasskeys));
}
} // namespace ash

@ -0,0 +1,40 @@
// 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.
#ifndef CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_FACTORY_ASH_H_
#define CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_FACTORY_ASH_H_
#include <memory>
#include "base/no_destructor.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
namespace content {
class BrowserContext;
}
namespace ash {
class PasskeyAuthenticatorServiceAsh;
class PasskeyAuthenticatorServiceFactoryAsh
: public ProfileKeyedServiceFactory {
public:
static PasskeyAuthenticatorServiceFactoryAsh* GetInstance();
static PasskeyAuthenticatorServiceAsh* GetForProfile(Profile* profile);
private:
friend class base::NoDestructor<PasskeyAuthenticatorServiceFactoryAsh>;
PasskeyAuthenticatorServiceFactoryAsh();
~PasskeyAuthenticatorServiceFactoryAsh() override;
// BrowserContextKeyedServiceFactory:
std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const override;
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_PASSKEYS_PASSKEY_AUTHENTICATOR_SERVICE_FACTORY_ASH_H_

@ -83,6 +83,7 @@ mojom("mojom") {
"notification.mojom",
"nullable_primitives.mojom",
"parent_access.mojom",
"passkeys.mojom",
"policy_namespace.mojom",
"policy_service.mojom",
"power.mojom",

@ -78,6 +78,7 @@ import "chromeos/crosapi/mojom/networking_private.mojom";
import "chromeos/crosapi/mojom/power.mojom";
import "chromeos/crosapi/mojom/network_settings_service.mojom";
import "chromeos/crosapi/mojom/parent_access.mojom";
import "chromeos/crosapi/mojom/passkeys.mojom";
import "chromeos/crosapi/mojom/prefs.mojom";
import "chromeos/crosapi/mojom/printing_metrics.mojom";
import "chromeos/crosapi/mojom/probe_service.mojom";
@ -150,8 +151,8 @@ struct BrowserInfo {
// please note the milestone when you added it, to help us reason about
// compatibility between the client applications and older ash-chrome binaries.
//
// Next version: 124
// Next method id: 125
// Next version: 125
// Next method id: 127
[Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e",
RenamedFrom="crosapi.mojom.AshChromeService"]
interface Crosapi {
@ -525,6 +526,11 @@ interface Crosapi {
[MinVersion=97]
BindParentAccess@101(pending_receiver<ParentAccess> receiver);
// Binds an interface for creating and asserting user passkeys.
[MinVersion=124]
BindPasskeyAuthenticator@126(
pending_receiver<PasskeyAuthenticator> receiver);
// Binds the PaymentAppInstance to allow Playstore payment to be made for
// web apps listing in Playstore.
[MinVersion=113] BindPaymentAppInstance@116(

@ -0,0 +1,73 @@
// 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.
module crosapi.mojom;
import "chromeos/crosapi/mojom/account_manager.mojom";
[Stable, Extensible]
// Represents a WebAuthn UserVerificationRequirement. For semantics, compare
// https://www.w3.org/TR/webauthn-3/#enum-userVerificationRequirement.
enum UserVerificationRequirement {
kDiscouraged,
[Default] kPreferred,
kRequired,
};
[Stable]
// A request to generate a WebAuthn assertion for a passkey with the given RP
// ID and credential ID.
struct PasskeyAssertionRequest {
string rp_id;
array<uint8> credential_id;
array<uint8> challenge;
array<uint8> client_data_hash;
UserVerificationRequirement user_verification;
};
[Stable, Extensible]
// Response status for a `PasskeyAssertionRequest`.
enum PasskeyAssertionError {
// Catch-all for all unexpected error conditions.
[Default] kInternalError,
// The request originates from a profile whose user is not the signed-in user
// for the device.
kNonPrimaryAccount,
// Another request is in progress.
kPendingRequest,
// No passkey with the given credential ID exists.
kCredentialNotFound,
// Retrieving the security domain secret for decrypting passkeys failed.
kSecurityDomainSecretUnavailable,
};
[Stable]
// The response for a successful PasskeyAssertionRequest. Contains a WebAuthn
// signature for the chosen credential.
struct PasskeyAssertionResponse {
array<uint8> signature;
};
[Stable]
// The result of a PasskeyAssertionRequest.
union PasskeyAssertionResult {
// Set on success.
PasskeyAssertionResponse response;
// Set on error.
PasskeyAssertionError error;
};
[Stable, Uuid="43bd6bb3-ca9e-4625-b05a-6353ab22847a"]
// Defines an API for accessing passkeys (implemented in ash-chrome).
//
// Methods on this service will trigger OS-level UI, for example to verify the
// user with an authentication prompt. Hence, methods may return an error if
// another request is already in progress.
interface PasskeyAuthenticator {
// Generates a WebAuthn assertion signature for a given passkey. The returned
// `assertion` is non-null iff `status` equals `kOk`.
Assert@0(AccountKey account,
PasskeyAssertionRequest request) => (PasskeyAssertionResult result);
};