| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/payments/content/browser_binding/passkey_browser_binder.h" |
| |
| #include <cstdint> |
| #include <utility> |
| |
| #include "base/containers/to_vector.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "components/payments/content/browser_binding/browser_bound_key_metadata.h" |
| #include "components/payments/content/browser_binding/fake_browser_bound_key_store.h" |
| #include "components/payments/content/mock_web_payments_web_data_service.h" |
| #include "components/payments/core/secure_payment_confirmation_metrics.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "device/fido/public_key_credential_params.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace payments { |
| namespace { |
| |
| testing::Matcher<BrowserBoundKeyMetadata::RelyingPartyAndCredentialId> |
| EqRelyingPartyAndCredentialId(std::string relying_party_id, |
| std::vector<uint8_t> credential_id) { |
| return testing::AllOf( |
| testing::Field("relying_party_id", |
| &BrowserBoundKeyMetadata::RelyingPartyAndCredentialId:: |
| relying_party_id, |
| relying_party_id), |
| testing::Field( |
| "credential_id", |
| &BrowserBoundKeyMetadata::RelyingPartyAndCredentialId::credential_id, |
| credential_id)); |
| } |
| |
| BrowserBoundKeyMetadata MakeBrowserBoundKeyMetadata( |
| std::vector<uint8_t> credential_id, |
| std::string relying_party_id, |
| std::vector<uint8_t> bbk_id) { |
| BrowserBoundKeyMetadata meta; |
| meta.passkey = BrowserBoundKeyMetadata::RelyingPartyAndCredentialId( |
| std::move(relying_party_id), std::move(credential_id)); |
| meta.browser_bound_key_id = std::move(bbk_id); |
| return meta; |
| } |
| |
| using GetMatchingCredentialIdsCallback = base::RepeatingCallback<void( |
| const std::string& relying_party_id, |
| const std::vector<std::vector<uint8_t>>& credential_ids, |
| bool require_third_party_payment_bit_set, |
| base::OnceCallback<void(std::vector<std::vector<uint8_t>>)>)>; |
| |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::DoAll; |
| using ::testing::Eq; |
| using ::testing::IsNull; |
| using ::testing::NotNull; |
| using ::testing::Pointee; |
| using ::testing::Property; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| |
| static const int32_t kCoseEs256 = -7; |
| |
| class PasskeyBrowserBinderTest : public ::testing::Test { |
| protected: |
| std::unique_ptr<PasskeyBrowserBinder> CreatePasskeyBrowserBinder( |
| bool is_new_bbk = true) { |
| fake_browser_bound_key_store_->SetDeviceSupportsHardwareKeys(true); |
| auto binder = std::make_unique<PasskeyBrowserBinder>( |
| fake_browser_bound_key_store_, mock_web_data_service_); |
| binder->SetRandomBytesAsVectorCallbackForTesting( |
| base::BindLambdaForTesting([this](size_t length) { |
| EXPECT_EQ(length, 32u); |
| return fake_bbk_id_; |
| })); |
| fake_browser_bound_key_store_->PutFakeKey(FakeBrowserBoundKey( |
| fake_bbk_id_, fake_public_key_, |
| /*signature=*/{}, kCoseEs256, |
| /*expected_client_data=*/{}, /*is_new=*/is_new_bbk)); |
| return binder; |
| } |
| |
| const std::vector<uint8_t> fake_bbk_id_ = {11, 12, 13, 14}; |
| const std::vector<uint8_t> fake_credential_id_ = {21, 22, 23, 24}; |
| const std::vector<uint8_t> fake_public_key_ = {31, 32, 33, 34}; |
| const std::string fake_relying_party_ = "relying.test"; |
| |
| content::BrowserTaskEnvironment task_environment_; |
| scoped_refptr<FakeBrowserBoundKeyStore> fake_browser_bound_key_store_ = |
| base::MakeRefCounted<FakeBrowserBoundKeyStore>(); |
| scoped_refptr<MockWebPaymentsWebDataService> mock_web_data_service_ = |
| base::MakeRefCounted<MockWebPaymentsWebDataService>(); |
| }; |
| |
| TEST_F(PasskeyBrowserBinderTest, CreatesUnboundKey) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| |
| ASSERT_TRUE(key.has_value()); |
| EXPECT_EQ(fake_public_key_, key->Get().GetPublicKeyAsCoseKey()); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, CreatesUnboundKeyFailureWithHardwareSupport) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| fake_browser_bound_key_store_->DeleteBrowserBoundKey(fake_bbk_id_); |
| |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| |
| EXPECT_FALSE(key.has_value()); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kFailureWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, |
| CreatesUnboundKeyFailureWithNoHardwareSupport) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| fake_browser_bound_key_store_->SetDeviceSupportsHardwareKeys(false); |
| fake_browser_bound_key_store_->DeleteBrowserBoundKey(fake_bbk_id_); |
| |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| |
| EXPECT_FALSE(key.has_value()); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kFailureWithoutDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, DeletesUnboundKey) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| ASSERT_TRUE(key.has_value()); |
| std::vector<uint8_t> bbk_id = key->Get().GetIdentifier(); |
| |
| // Let the key go out of scope by resetting the std::optional without calling |
| // binder->BindKey(std::move(key), ...). |
| key.reset(); |
| |
| EXPECT_FALSE(fake_browser_bound_key_store_->ContainsFakeKey(bbk_id)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, BindsBrowserBoundKey) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| WebDataServiceConsumer* web_data_service_consumer_ = nullptr; |
| EXPECT_CALL(*mock_web_data_service_, |
| SetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| fake_bbk_id_, /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<3>(&web_data_service_consumer_), |
| Return(web_data_service_handle))); |
| |
| binder->BindKey(std::move(key.value()), fake_credential_id_, |
| fake_relying_party_); |
| ASSERT_TRUE(web_data_service_consumer_); |
| web_data_service_consumer_->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<bool>>(WDResultType::BOOL_RESULT, true)); |
| |
| key.reset(); |
| EXPECT_TRUE(fake_browser_bound_key_store_->ContainsFakeKey(fake_bbk_id_)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, DeletesBrowserBoundKeyIfBindingFails) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| std::optional<PasskeyBrowserBinder::UnboundKey> key = |
| binder->CreateUnboundKey(/*allowed_algorithms=*/{ |
| device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}); |
| |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| WebDataServiceConsumer* web_data_service_consumer_ = nullptr; |
| EXPECT_CALL(*mock_web_data_service_, |
| SetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| fake_bbk_id_, /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<3>(&web_data_service_consumer_), |
| Return(web_data_service_handle))); |
| |
| binder->BindKey(std::move(key.value()), fake_credential_id_, |
| fake_relying_party_); |
| ASSERT_TRUE(web_data_service_consumer_); |
| web_data_service_consumer_->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<bool>>(WDResultType::BOOL_RESULT, false)); |
| |
| key.reset(); |
| EXPECT_FALSE(fake_browser_bound_key_store_->ContainsFakeKey(fake_bbk_id_)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, |
| GetOrCreateBoundKeyForPasskeyRetrievesExistingKey) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = |
| CreatePasskeyBrowserBinder(/*is_new_bbk=*/false); |
| WebDataServiceConsumer* web_data_service_consumer = nullptr; |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| base::MockCallback< |
| base::OnceCallback<void(bool, std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&web_data_service_consumer), |
| Return(web_data_service_handle))); |
| EXPECT_CALL(*mock_web_data_service_, SetBrowserBoundKey).Times(0); |
| EXPECT_CALL(mock_callback, |
| Run( |
| /*is_new=*/false, |
| AllOf(NotNull(), Pointee(Property( |
| &BrowserBoundKey::GetPublicKeyAsCoseKey, |
| fake_public_key_))))); |
| |
| binder->GetOrCreateBoundKeyForPasskey( |
| fake_credential_id_, fake_relying_party_, /*allowed_algorithms=*/ |
| {device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}, |
| mock_callback.Get()); |
| ASSERT_TRUE(web_data_service_consumer); |
| web_data_service_consumer->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, fake_bbk_id_)); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreRetrieve", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, GetBoundKeyForPasskeyRetrievesExistingKey) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = |
| CreatePasskeyBrowserBinder(/*is_new_bbk=*/false); |
| WebDataServiceConsumer* web_data_service_consumer = nullptr; |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| base::MockCallback<base::OnceCallback<void(std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&web_data_service_consumer), |
| Return(web_data_service_handle))); |
| EXPECT_CALL(*mock_web_data_service_, SetBrowserBoundKey).Times(0); |
| EXPECT_CALL(mock_callback, |
| Run(AllOf(NotNull(), Pointee(Property( |
| &BrowserBoundKey::GetPublicKeyAsCoseKey, |
| fake_public_key_))))); |
| |
| binder->GetBoundKeyForPasskey(fake_credential_id_, fake_relying_party_, |
| mock_callback.Get()); |
| ASSERT_TRUE(web_data_service_consumer); |
| web_data_service_consumer->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, fake_bbk_id_)); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreRetrieve", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, |
| GetOrCreateBoundKeyForPasskeyRecreatesWhenEmpty) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| WebDataServiceConsumer* get_consumer = nullptr; |
| WebDataServiceBase::Handle get_handle = 1234; |
| WebDataServiceConsumer* set_consumer = nullptr; |
| WebDataServiceBase::Handle set_handle = 5678; |
| base::MockCallback< |
| base::OnceCallback<void(bool, std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&get_consumer), Return(get_handle))); |
| EXPECT_CALL( |
| *mock_web_data_service_, |
| SetBrowserBoundKey(fake_credential_id_, fake_relying_party_, fake_bbk_id_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<3>(&set_consumer), Return(set_handle))); |
| EXPECT_CALL(mock_callback, |
| Run( |
| /*is_new=*/true, |
| AllOf(NotNull(), Pointee(Property( |
| &BrowserBoundKey::GetPublicKeyAsCoseKey, |
| fake_public_key_))))); |
| |
| binder->GetOrCreateBoundKeyForPasskey( |
| fake_credential_id_, fake_relying_party_, /*allowed_algorithms=*/ |
| {device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}, |
| mock_callback.Get()); |
| ASSERT_TRUE(get_consumer); |
| get_consumer->OnWebDataServiceRequestDone( |
| get_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, std::vector<uint8_t>())); |
| ASSERT_TRUE(set_consumer); |
| set_consumer->OnWebDataServiceRequestDone( |
| set_handle, |
| std::make_unique<WDResult<bool>>(WDResultType::BOOL_RESULT, true)); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| EXPECT_TRUE(fake_browser_bound_key_store_->ContainsFakeKey(fake_bbk_id_)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, |
| GetOrCreateBoundKeyForPasskeyDeletestBrowserBoundKeyWhenBindingFails) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| WebDataServiceConsumer* get_consumer = nullptr; |
| WebDataServiceBase::Handle get_handle = 1234; |
| WebDataServiceConsumer* set_consumer = nullptr; |
| WebDataServiceBase::Handle set_handle = 5678; |
| base::MockCallback< |
| base::OnceCallback<void(bool, std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&get_consumer), Return(get_handle))); |
| EXPECT_CALL( |
| *mock_web_data_service_, |
| SetBrowserBoundKey(fake_credential_id_, fake_relying_party_, fake_bbk_id_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<3>(&set_consumer), Return(set_handle))); |
| EXPECT_CALL(mock_callback, |
| Run( |
| /*is_new=*/true, |
| AllOf(NotNull(), Pointee(Property( |
| &BrowserBoundKey::GetPublicKeyAsCoseKey, |
| fake_public_key_))))); |
| |
| binder->GetOrCreateBoundKeyForPasskey( |
| fake_credential_id_, fake_relying_party_, /*allowed_algorithms=*/ |
| {device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}, |
| mock_callback.Get()); |
| ASSERT_TRUE(get_consumer); |
| get_consumer->OnWebDataServiceRequestDone( |
| get_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, std::vector<uint8_t>())); |
| ASSERT_TRUE(set_consumer); |
| set_consumer->OnWebDataServiceRequestDone( |
| set_handle, |
| std::make_unique<WDResult<bool>>(WDResultType::BOOL_RESULT, false)); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| EXPECT_FALSE(fake_browser_bound_key_store_->ContainsFakeKey(fake_bbk_id_)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, |
| GetOrCreateBoundKeyForPasskeyRecreatesWhenNullopt) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| WebDataServiceConsumer* web_data_service_consumer = nullptr; |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| base::MockCallback< |
| base::OnceCallback<void(bool, std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&web_data_service_consumer), |
| Return(web_data_service_handle))); |
| EXPECT_CALL( |
| *mock_web_data_service_, |
| SetBrowserBoundKey(fake_credential_id_, fake_relying_party_, fake_bbk_id_, |
| /*consumer=*/NotNull())); |
| EXPECT_CALL(mock_callback, |
| Run( |
| /*is_new=*/true, |
| AllOf(NotNull(), Pointee(Property( |
| &BrowserBoundKey::GetPublicKeyAsCoseKey, |
| fake_public_key_))))); |
| |
| binder->GetOrCreateBoundKeyForPasskey( |
| fake_credential_id_, fake_relying_party_, /*allowed_algorithms=*/ |
| {device::PublicKeyCredentialParams::CredentialInfo{.algorithm = |
| kCoseEs256}}, |
| mock_callback.Get()); |
| ASSERT_TRUE(web_data_service_consumer); |
| web_data_service_consumer->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, std::nullopt)); |
| histograms.ExpectUniqueSample( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| SecurePaymentConfirmationBrowserBoundKeyDeviceResult:: |
| kSuccessWithDeviceHardware, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(PasskeyBrowserBinderTest, GetBoundKeyForPasskeyReturnsNullWhenNullOpt) { |
| base::HistogramTester histograms; |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| WebDataServiceConsumer* web_data_service_consumer = nullptr; |
| WebDataServiceBase::Handle web_data_service_handle = 1234; |
| base::MockCallback<base::OnceCallback<void(std::unique_ptr<BrowserBoundKey>)>> |
| mock_callback; |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| GetBrowserBoundKey(fake_credential_id_, fake_relying_party_, |
| /*consumer=*/_)) |
| .WillOnce(DoAll(SaveArg<2>(&web_data_service_consumer), |
| Return(web_data_service_handle))); |
| EXPECT_CALL(mock_callback, Run(IsNull())); |
| |
| binder->GetBoundKeyForPasskey(fake_credential_id_, fake_relying_party_, |
| mock_callback.Get()); |
| ASSERT_TRUE(web_data_service_consumer); |
| web_data_service_consumer->OnWebDataServiceRequestDone( |
| web_data_service_handle, |
| std::make_unique<WDResult<std::optional<std::vector<uint8_t>>>>( |
| WDResultType::BROWSER_BOUND_KEY, std::nullopt)); |
| histograms.ExpectTotalCount( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreRetrieve", |
| /*expected_count=*/0); |
| histograms.ExpectTotalCount( |
| "PaymentRequest.SecurePaymentConfirmation.BrowserBoundKeyStoreCreate", |
| /*expected_count=*/0); |
| } |
| |
| class PasskeyBrowserBinderDeletionTest : public PasskeyBrowserBinderTest { |
| public: |
| void RespondToGetAllBrowserBoundKeys( |
| std::vector<BrowserBoundKeyMetadata> bbk_metadatas) { |
| std::move(captured_get_all_browser_bound_keys_callback_) |
| .Run(/*handle=*/1234, |
| std::make_unique<WDResult<std::vector<BrowserBoundKeyMetadata>>>( |
| WDResultType::BROWSER_BOUND_KEY_METADATA, |
| std::move(bbk_metadatas))); |
| } |
| |
| // Implements a fake version of |
| // InternalAuthenticator::GetMatchingCredentialIds() which is passed as a |
| // callback to PasskeyBrowserBinder::DeleteAllUnknownBrowserBoundKeys. |
| void GetMatchingCredentialIds( |
| const std::string& relying_party_id, |
| const std::vector<std::vector<uint8_t>>& credential_ids, |
| bool require_third_party_payment_bit_set, |
| base::OnceCallback<void(std::vector<std::vector<uint8_t>>)> callback) { |
| if (require_third_party_payment_bit_set) { |
| // PasskeyBrowserBinder::DeleteAllUnknownBrowserBoundKeys must call |
| // with `require_third_party_payment_bit_set` set to false, since BBKs |
| // can be created for passkeys in the first party context through the |
| // PaymentRequest API. |
| FAIL(); |
| } |
| std::vector<std::vector<uint8_t>> stored_credential_ids = |
| fake_matching_credential_ids_[relying_party_id]; |
| std::erase_if(stored_credential_ids, |
| [&credential_ids](const std::vector<uint8_t>& credential) { |
| return !base::Contains(credential_ids, credential); |
| }); |
| std::move(callback).Run(std::move(stored_credential_ids)); |
| } |
| |
| protected: |
| void SetUp() override { |
| PasskeyBrowserBinderTest::SetUp(); |
| EXPECT_CALL(*mock_web_data_service_, GetAllBrowserBoundKeys) |
| .WillOnce(MoveArgAndReturn<0>( |
| &captured_get_all_browser_bound_keys_callback_, 1234)); |
| } |
| |
| WebDataServiceRequestCallback captured_get_all_browser_bound_keys_callback_; |
| base::flat_map</*relying_party*/ std::string, |
| /*credential_ids*/ std::vector<std::vector<uint8_t>>> |
| fake_matching_credential_ids_; |
| }; |
| |
| TEST_F(PasskeyBrowserBinderDeletionTest, |
| DeleteAllUnknownBrowserBoundKeysWhenNoBBKStored) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| base::MockOnceClosure mock_callback; |
| |
| binder->DeleteAllUnknownBrowserBoundKeys( |
| base::BindRepeating( |
| &PasskeyBrowserBinderDeletionTest::GetMatchingCredentialIds, |
| base::Unretained(this)), |
| mock_callback.Get()); |
| RespondToGetAllBrowserBoundKeys(/*bbk_metadatas=*/{}); |
| } |
| |
| TEST_F(PasskeyBrowserBinderDeletionTest, |
| DeleteAllUnknownBrowserBoundKeysWithInvalidBbkMetadata) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| base::MockOnceClosure mock_callback; |
| std::vector<BrowserBoundKeyMetadata> bbk_metadatas; |
| bbk_metadatas.push_back(MakeBrowserBoundKeyMetadata( |
| fake_credential_id_, fake_relying_party_, fake_bbk_id_)); |
| // This test does not insert any entries into `fake_matching_credential_ids_` |
| |
| testing::Sequence sequence; |
| EXPECT_CALL(*mock_web_data_service_, |
| DeleteBrowserBoundKeys( |
| testing::UnorderedElementsAre(EqRelyingPartyAndCredentialId( |
| fake_relying_party_, fake_credential_id_)), |
| _)) |
| .InSequence(sequence) |
| .WillOnce(base::test::RunOnceCallbackRepeatedly<1>()); |
| EXPECT_CALL(mock_callback, Run()).InSequence(sequence); |
| |
| binder->DeleteAllUnknownBrowserBoundKeys( |
| base::BindRepeating( |
| &PasskeyBrowserBinderDeletionTest::GetMatchingCredentialIds, |
| base::Unretained(this)), |
| mock_callback.Get()); |
| RespondToGetAllBrowserBoundKeys(std::move(bbk_metadatas)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderDeletionTest, |
| DeleteAllUnknownBrowserBoundKeysWithValidBbkMetadata) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| base::MockOnceClosure mock_callback; |
| std::vector<BrowserBoundKeyMetadata> bbk_metadatas; |
| bbk_metadatas.push_back(MakeBrowserBoundKeyMetadata( |
| fake_credential_id_, fake_relying_party_, fake_bbk_id_)); |
| fake_matching_credential_ids_[fake_relying_party_].push_back( |
| fake_credential_id_); |
| |
| EXPECT_CALL(*mock_web_data_service_, DeleteBrowserBoundKeys(_, _)).Times(0); |
| |
| binder->DeleteAllUnknownBrowserBoundKeys( |
| base::BindRepeating( |
| &PasskeyBrowserBinderDeletionTest::GetMatchingCredentialIds, |
| base::Unretained(this)), |
| mock_callback.Get()); |
| RespondToGetAllBrowserBoundKeys(std::move(bbk_metadatas)); |
| } |
| |
| TEST_F( |
| PasskeyBrowserBinderDeletionTest, |
| DeleteAllUnknownBrowserBoundKeysWithInvalidBbkMetadataWhenRelyingPartyIsDifferent) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| base::MockOnceClosure mock_callback; |
| std::vector<BrowserBoundKeyMetadata> bbk_metadatas; |
| bbk_metadatas.push_back(MakeBrowserBoundKeyMetadata( |
| fake_credential_id_, fake_relying_party_, fake_bbk_id_)); |
| fake_matching_credential_ids_["another." + fake_relying_party_].push_back( |
| fake_credential_id_); |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| DeleteBrowserBoundKeys( |
| testing::UnorderedElementsAre(EqRelyingPartyAndCredentialId( |
| fake_relying_party_, fake_credential_id_)), |
| _)) |
| .WillOnce(base::test::RunOnceCallbackRepeatedly<1>()); |
| |
| binder->DeleteAllUnknownBrowserBoundKeys( |
| base::BindRepeating( |
| &PasskeyBrowserBinderDeletionTest::GetMatchingCredentialIds, |
| base::Unretained(this)), |
| mock_callback.Get()); |
| RespondToGetAllBrowserBoundKeys(std::move(bbk_metadatas)); |
| } |
| |
| TEST_F(PasskeyBrowserBinderDeletionTest, |
| DeleteAllUnknownBrowserBoundKeysWithMultipleRelyingParties) { |
| std::unique_ptr<PasskeyBrowserBinder> binder = CreatePasskeyBrowserBinder(); |
| base::MockOnceClosure mock_callback; |
| std::vector<BrowserBoundKeyMetadata> bbk_metadatas; |
| bbk_metadatas.push_back(MakeBrowserBoundKeyMetadata( |
| fake_credential_id_, fake_relying_party_, fake_bbk_id_)); |
| fake_matching_credential_ids_["another." + fake_relying_party_].push_back( |
| fake_credential_id_); |
| |
| EXPECT_CALL(*mock_web_data_service_, |
| DeleteBrowserBoundKeys( |
| testing::UnorderedElementsAre(EqRelyingPartyAndCredentialId( |
| fake_relying_party_, fake_credential_id_)), |
| _)) |
| .WillOnce(base::test::RunOnceCallbackRepeatedly<1>()); |
| |
| binder->DeleteAllUnknownBrowserBoundKeys( |
| base::BindRepeating( |
| &PasskeyBrowserBinderDeletionTest::GetMatchingCredentialIds, |
| base::Unretained(this)), |
| mock_callback.Get()); |
| RespondToGetAllBrowserBoundKeys(std::move(bbk_metadatas)); |
| } |
| |
| } // namespace |
| } // namespace payments |