blob: 56b82258f1b68df465802335332a012e9931ce2a [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2017 The Chromium Authors
kpaulhamus7c9f00942017-06-30 11:08:452// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/webauth/authenticator_impl.h"
6
Adem Derinele6378762024-06-27 06:05:077#include <algorithm>
8#include <array>
9#include <cstddef>
10#include <cstdint>
11#include <cstring>
Adem Derinel620520b42024-11-04 15:45:4412#include <iterator>
Adam Langleyb159c312019-03-05 00:33:1913#include <list>
Adem Derinele6378762024-06-27 06:05:0714#include <map>
Jinho Bangddead4e2018-01-01 10:32:4315#include <memory>
Adem Derinele6378762024-06-27 06:05:0716#include <optional>
kpaulhamus7c9f00942017-06-30 11:08:4517#include <string>
Md Hasibul Hasana963a9342024-04-03 10:15:1418#include <string_view>
Adem Derinele6378762024-06-27 06:05:0719#include <tuple>
Kim Paulhamuse549f842018-01-09 16:12:4420#include <utility>
21#include <vector>
kpaulhamus7c9f00942017-06-30 11:08:4522
Avi Drissman1bade8232025-03-19 18:00:3223#include "base/apple/owned_objc.h"
Adam Langley2e71e7a2020-03-02 21:19:0424#include "base/base64url.h"
Adem Derinele6378762024-06-27 06:05:0725#include "base/check.h"
26#include "base/check_op.h"
Adam Langley2a9355a2019-04-23 00:11:0227#include "base/compiler_specific.h"
Adam Langley9095b4182022-07-20 16:14:5028#include "base/containers/flat_set.h"
Adem Derinele6378762024-06-27 06:05:0729#include "base/containers/span.h"
Elly450ad9fe2025-07-09 17:13:2230#include "base/containers/to_vector.h"
Avi Drissmanadac21992023-01-11 23:46:3931#include "base/functional/bind.h"
Adem Derinele6378762024-06-27 06:05:0732#include "base/functional/callback.h"
33#include "base/functional/callback_forward.h"
Avi Drissmanadac21992023-01-11 23:46:3934#include "base/functional/callback_helpers.h"
Nigel Tao410788e2020-06-24 07:12:2735#include "base/json/json_reader.h"
Adem Derinele6378762024-06-27 06:05:0736#include "base/location.h"
Keishi Hattori0e45c022021-11-27 09:25:5237#include "base/memory/raw_ptr.h"
Adem Derinele6378762024-06-27 06:05:0738#include "base/memory/scoped_refptr.h"
Kevin McNeeab4af6512024-06-19 20:55:5739#include "base/memory/stack_allocated.h"
Adem Derinele6378762024-06-27 06:05:0740#include "base/notreached.h"
Adam Langley0a145aa32021-04-23 20:23:5841#include "base/rand_util.h"
kpaulhamus7c9f00942017-06-30 11:08:4542#include "base/run_loop.h"
Lei Zhang9cc9aad72022-08-25 19:19:1843#include "base/strings/strcat.h"
Adam Langleyb307ff62019-03-27 23:36:3344#include "base/strings/string_number_conversions.h"
45#include "base/strings/string_util.h"
Sean Maher52fa5a72022-11-14 15:53:2546#include "base/task/sequenced_task_runner.h"
Sean Mahere672a662023-01-09 21:42:2847#include "base/task/single_thread_task_runner.h"
Guido Urdanetaef4e91942020-11-09 15:06:2448#include "base/test/bind.h"
Nina Satragno129251c2023-10-23 21:50:4049#include "base/test/metrics/histogram_tester.h"
Martin Kreichgauere255af062022-04-18 19:40:5650#include "base/test/scoped_command_line.h"
Jun Choi17bbafc2018-04-11 08:37:2051#include "base/test/scoped_feature_list.h"
Adem Derinele6378762024-06-27 06:05:0752#include "base/test/task_environment.h"
53#include "base/test/test_future.h"
Kim Paulhamus40de29e42017-12-07 04:14:0854#include "base/time/time.h"
Adem Derinele6378762024-06-27 06:05:0755#include "base/values.h"
Martin Kreichgauerbcfda9cf2018-08-02 00:47:0756#include "build/build_config.h"
Adam Langleye0e46cdf2018-10-29 19:23:1657#include "components/cbor/reader.h"
58#include "components/cbor/values.h"
Ken Buchanan23dce912024-07-11 16:41:2759#include "components/ukm/test_ukm_recorder.h"
Liquan (Max) Gu5710af132021-06-28 07:22:2360#include "components/webauthn/content/browser/internal_authenticator_impl.h"
Nina Satragnocb0406e2024-09-09 19:47:4861#include "content/browser/renderer_host/frame_tree_node.h"
Amos Lim12696e5e32022-09-16 07:37:5862#include "content/browser/webauth/authenticator_common_impl.h"
Adam Langley1e03fb02023-03-16 23:02:0363#include "content/browser/webauth/authenticator_environment.h"
Ken Buchanan1ea549c12024-10-10 21:31:0564#include "content/browser/webauth/authenticator_request_outcome_enums.h"
Liquan (Max) Gu292fc3d2021-07-19 19:03:0365#include "content/browser/webauth/client_data_json.h"
Nina Satragnocb0406e2024-09-09 19:47:4866#include "content/browser/webauth/virtual_authenticator.h"
67#include "content/browser/webauth/virtual_authenticator_manager_impl.h"
Pavel Beloborodovd2dfbaa82025-02-27 20:05:3668#include "content/browser/webauth/webauth_request_security_checker.h"
Balazs Engedya7ff70982018-06-04 18:14:4769#include "content/public/browser/authenticator_request_client_delegate.h"
Nina Satragno8d67dec32023-04-18 22:10:4470#include "content/public/browser/content_browser_client.h"
kpaulhamus7c9f00942017-06-30 11:08:4571#include "content/public/browser/render_frame_host.h"
Adem Derineldae9eff2025-01-23 12:16:0072#include "content/public/browser/web_authentication_delegate.h"
Adem Derinele6378762024-06-27 06:05:0773#include "content/public/browser/web_authentication_request_proxy.h"
Hans Wennborg5ffd1392019-10-16 11:00:0274#include "content/public/common/content_client.h"
Martin Kreichgauere255af062022-04-18 19:40:5675#include "content/public/common/content_switches.h"
Casey Piperd785ef02018-11-16 20:15:4176#include "content/public/test/browser_test_utils.h"
Andrii Natiahlyie480a492024-09-18 15:20:3777#include "content/public/test/navigation_simulator.h"
Adem Derinele6378762024-06-27 06:05:0778#include "content/public/test/test_renderer_host.h"
Elly2f1928d62025-07-14 15:31:2579#include "crypto/evp.h"
80#include "crypto/hash.h"
81#include "crypto/hmac.h"
Jun Choife176682018-08-30 07:49:1482#include "device/bluetooth/bluetooth_adapter_factory.h"
83#include "device/bluetooth/test/mock_bluetooth_adapter.h"
Adam Langley3015ad42018-08-15 02:04:3684#include "device/fido/attested_credential_data.h"
85#include "device/fido/authenticator_data.h"
Adem Derinele6378762024-06-27 06:05:0786#include "device/fido/authenticator_get_assertion_response.h"
87#include "device/fido/authenticator_selection_criteria.h"
88#include "device/fido/cable/cable_discovery_data.h"
Adam Langley8d249872022-03-07 23:16:0689#include "device/fido/cable/fido_tunnel_device.h"
Adam Langley0e3a6422020-09-25 18:36:3890#include "device/fido/cable/v2_authenticator.h"
91#include "device/fido/cable/v2_constants.h"
92#include "device/fido/cable/v2_discovery.h"
93#include "device/fido/cable/v2_handshake.h"
94#include "device/fido/cable/v2_test_util.h"
Martin Kreichgauerb3689d062022-07-12 09:36:5195#include "device/fido/discoverable_credential_metadata.h"
Jun Choi7d7139a2018-07-27 21:02:0496#include "device/fido/fake_fido_discovery.h"
Casey Piperd785ef02018-11-16 20:15:4197#include "device/fido/features.h"
Adam Langley989fa492019-03-28 22:03:1898#include "device/fido/fido_authenticator.h"
Adam Langleyd072d4f2018-10-18 16:46:0699#include "device/fido/fido_constants.h"
Adam Langley2cdc5972024-10-03 12:33:13100#include "device/fido/fido_device_authenticator.h"
Adem Derinele6378762024-06-27 06:05:07101#include "device/fido/fido_discovery_base.h"
Nina Satragno6e0f1ab2024-06-13 22:28:11102#include "device/fido/fido_request_handler_base.h"
Martin Kreichgauer930f341a2022-01-07 20:20:46103#include "device/fido/fido_transport_protocol.h"
Nina Satragno5ba78462020-10-02 17:25:15104#include "device/fido/fido_types.h"
Adam Langley051b97932021-01-11 19:11:23105#include "device/fido/filter.h"
Nina Satragno2bf834342020-10-06 22:05:50106#include "device/fido/large_blob.h"
Jun Choi7d7139a2018-07-27 21:02:04107#include "device/fido/mock_fido_device.h"
Nina Satragnob1870042020-12-03 03:14:01108#include "device/fido/multiple_virtual_fido_device_factory.h"
Martin Kreichgauer0787dc32020-10-05 17:44:11109#include "device/fido/pin.h"
Martin Kreichgauer8d55b452021-10-04 20:51:11110#include "device/fido/public_key_credential_descriptor.h"
Adem Derinele6378762024-06-27 06:05:07111#include "device/fido/public_key_credential_params.h"
112#include "device/fido/public_key_credential_rp_entity.h"
Nina Satragno8824f9a2023-02-02 15:54:46113#include "device/fido/public_key_credential_user_entity.h"
Martin Kreichgauera2503a72021-04-29 20:53:48114#include "device/fido/virtual_ctap2_device.h"
Nina Satragno62fe3e02020-11-16 18:13:26115#include "device/fido/virtual_fido_device.h"
Nina Satragnoacf403f92019-05-23 17:16:52116#include "device/fido/virtual_fido_device_factory.h"
Nina Satragno9ca1c642022-04-21 16:26:47117#include "mojo/public/cpp/base/big_buffer.h"
Mario Sanchez Pradac791f54e2019-07-09 23:38:37118#include "mojo/public/cpp/bindings/remote.h"
Lei Zhang32475bc2022-08-20 18:24:08119#include "mojo/public/cpp/system/functions.h"
Nina Satragno9ca1c642022-04-21 16:26:47120#include "services/data_decoder/gzipper.h"
Nina Satragnoaed99fb2020-10-15 22:21:56121#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
Ken Buchanan23dce912024-07-11 16:41:27122#include "services/metrics/public/cpp/ukm_builders.h"
123#include "services/metrics/public/cpp/ukm_source.h"
Sandor Major878f8352025-02-18 20:16:02124#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
Hans Wennborg78b52182021-06-15 13:42:15125#include "services/network/public/mojom/network_context.mojom.h"
Sandor Major878f8352025-02-18 20:16:02126#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
kpaulhamus7c9f00942017-06-30 11:08:45127#include "testing/gmock/include/gmock/gmock.h"
128#include "testing/gtest/include/gtest/gtest.h"
Martin Kreichgauer8c97189a2022-01-10 20:31:43129#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
Adem Derinele6378762024-06-27 06:05:07130#include "third_party/boringssl/src/include/openssl/base.h"
Adem Derinele6378762024-06-27 06:05:07131#include "third_party/boringssl/src/include/openssl/ec.h"
Adam Langley0e3a6422020-09-25 18:36:38132#include "third_party/boringssl/src/include/openssl/ec_key.h"
Adam Langleya09284eb2020-06-11 18:58:29133#include "third_party/boringssl/src/include/openssl/evp.h"
Adam Langley0e3a6422020-09-25 18:36:38134#include "third_party/boringssl/src/include/openssl/obj.h"
Martin Kreichgauerbf776d72022-04-18 23:38:16135#include "url/origin.h"
Hans Wennborg5ffd1392019-10-16 11:00:02136#include "url/url_util.h"
kpaulhamus7c9f00942017-06-30 11:08:45137
Xiaohan Wang2ba85e32022-01-15 17:19:40138#if BUILDFLAG(IS_MAC)
Adem Derinele6378762024-06-27 06:05:07139#include "base/files/file_path.h"
140#include "base/path_service.h"
Elly Fong-Jones38c29b62025-07-23 22:54:53141#include "crypto/apple/scoped_fake_keychain_v2.h"
Martin Kreichgauereaa8eb9d2019-05-30 19:03:45142#include "device/fido/mac/authenticator_config.h"
Martin Kreichgauer108eb102022-06-29 23:08:41143#include "device/fido/mac/credential_store.h"
Adam Langleyb5b72582023-09-13 19:41:46144#include "device/fido/mac/icloud_keychain.h"
Adam Langley9134ffe2023-05-26 19:14:17145#include "device/fido/mac/scoped_icloud_keychain_test_environment.h"
Martin Kreichgauera3c0f9312018-08-10 19:04:39146#include "device/fido/mac/scoped_touch_id_test_environment.h"
Adem Derinele6378762024-06-27 06:05:07147#include "ui/base/resource/resource_bundle.h"
148#include "ui/base/resource/resource_scale_factor.h"
Martin Kreichgauera3c0f9312018-08-10 19:04:39149#endif
150
Xiaohan Wang2ba85e32022-01-15 17:19:40151#if BUILDFLAG(IS_WIN)
Adem Derinele6378762024-06-27 06:05:07152#include "content/public/test/test_browser_context.h"
153#include "device/fido/fido_test_data.h"
Martin Kreichgauer977c00472018-11-29 00:09:04154#include "device/fido/win/fake_webauthn_api.h"
Ken Buchanan1ffd3912024-07-08 01:16:09155#include "device/fido/win/util.h"
Nina Satragno0cace2392024-09-12 14:32:39156#include "third_party/microsoft_webauthn/webauthn.h" // nogncheck
Martin Kreichgauer977c00472018-11-29 00:09:04157#endif
158
Howard Yang72a1412b2022-04-13 00:16:02159#if BUILDFLAG(IS_CHROMEOS)
Yi Chouf6745842021-07-30 05:38:19160#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
Martin Kreichgauerdc37b752021-02-23 23:12:31161#include "chromeos/dbus/u2f/u2f_client.h"
162#endif
163
kpaulhamus7c9f00942017-06-30 11:08:45164namespace content {
165
166using ::testing::_;
167
Amos Limdddb6992018-07-19 22:14:32168using blink::mojom::AttestationConveyancePreference;
Slobodan Pejicc8ce88b2023-06-19 15:41:12169using blink::mojom::AuthenticationExtensionsClientInputs;
Slobodan Pejica0f2b9d2023-09-28 01:56:25170using blink::mojom::AuthenticationExtensionsClientOutputs;
Amos Limdddb6992018-07-19 22:14:32171using blink::mojom::AuthenticatorSelectionCriteria;
172using blink::mojom::AuthenticatorSelectionCriteriaPtr;
173using blink::mojom::AuthenticatorStatus;
Martin Kreichgauer2c9e5352018-10-30 23:28:53174using blink::mojom::AuthenticatorTransport;
Adam Langley2a9355a2019-04-23 00:11:02175using blink::mojom::CableAuthentication;
176using blink::mojom::CableAuthenticationPtr;
Martin Kreichgauer8c97189a2022-01-10 20:31:43177using blink::mojom::CommonCredentialInfo;
Martin Kreichgauer1beaff02022-02-02 18:58:42178using blink::mojom::GetAssertionAuthenticatorResponse;
Amos Limdddb6992018-07-19 22:14:32179using blink::mojom::GetAssertionAuthenticatorResponsePtr;
Martin Kreichgauer8c97189a2022-01-10 20:31:43180using blink::mojom::MakeCredentialAuthenticatorResponse;
Amos Limdddb6992018-07-19 22:14:32181using blink::mojom::MakeCredentialAuthenticatorResponsePtr;
182using blink::mojom::PublicKeyCredentialCreationOptions;
183using blink::mojom::PublicKeyCredentialCreationOptionsPtr;
184using blink::mojom::PublicKeyCredentialDescriptor;
185using blink::mojom::PublicKeyCredentialDescriptorPtr;
186using blink::mojom::PublicKeyCredentialParameters;
187using blink::mojom::PublicKeyCredentialParametersPtr;
Gabriel Viera7bc08f212024-07-10 15:42:33188using blink::mojom::PublicKeyCredentialReportOptions;
189using blink::mojom::PublicKeyCredentialReportOptionsPtr;
Amos Limdddb6992018-07-19 22:14:32190using blink::mojom::PublicKeyCredentialRequestOptions;
191using blink::mojom::PublicKeyCredentialRequestOptionsPtr;
192using blink::mojom::PublicKeyCredentialRpEntity;
193using blink::mojom::PublicKeyCredentialRpEntityPtr;
194using blink::mojom::PublicKeyCredentialType;
195using blink::mojom::PublicKeyCredentialUserEntity;
196using blink::mojom::PublicKeyCredentialUserEntityPtr;
Martin Kreichgauere255af062022-04-18 19:40:56197using blink::mojom::RemoteDesktopClientOverride;
198using blink::mojom::RemoteDesktopClientOverridePtr;
Martin Kreichgauer6119e842022-01-28 01:52:41199using blink::mojom::WebAuthnDOMExceptionDetails;
200using blink::mojom::WebAuthnDOMExceptionDetailsPtr;
Adam Langleyb4f12f92018-10-26 21:00:02201using cbor::Reader;
Martin Kreichgauer2c9e5352018-10-30 23:28:53202using cbor::Value;
Martin Kreichgauer08be2d72020-10-27 04:26:35203using device::VirtualCtap2Device;
204using device::VirtualFidoDevice;
Adam Langley63b34812023-06-05 23:08:19205using device::cablev2::Event;
kpaulhamus7c9f00942017-06-30 11:08:45206
Kim Paulhamusfbe554b2017-08-22 19:46:34207namespace {
208
Balazs Engedyed4966f72018-08-29 22:07:45209using InterestingFailureReason =
Martin Kreichgauerfefb3772021-04-12 23:02:48210 AuthenticatorRequestClientDelegate::InterestingFailureReason;
Adem Derinele6378762024-06-27 06:05:07211using FailureReasonFuture = base::test::TestFuture<InterestingFailureReason>;
Balazs Engedyed4966f72018-08-29 22:07:45212
Peter Kastinge5a38ed2021-10-02 03:06:35213constexpr base::TimeDelta kTestTimeout = base::Minutes(1);
Kim Paulhamuscd76a26e2018-01-18 16:11:16214
Martin Kreichgaueracef6fd2019-10-21 23:33:04215// The size of credential IDs returned by GetTestCredentials().
216constexpr size_t kTestCredentialIdLength = 32u;
217
Kim Paulhamuscd76a26e2018-01-18 16:11:16218constexpr char kTestOrigin1[] = "https://a.google.com";
Nina Satragno9d6389e2019-06-14 21:21:35219constexpr char kTestOrigin2[] = "https://acme.org";
Kim Paulhamuscd76a26e2018-01-18 16:11:16220constexpr char kTestRelyingPartyId[] = "google.com";
Nina Satragnocb0406e2024-09-09 19:47:48221constexpr char kDifferentTestRelyingPartyId[] = "different-rp.com";
Martin Kreichgauer05a33fb2022-02-18 23:31:29222constexpr char kExtensionScheme[] = "chrome-extension";
Nina Satragno867bca52022-10-13 15:02:02223static constexpr char kCorpCrdOrigin[] =
224 "https://remotedesktop.corp.google.com";
Kim Paulhamusfbe092bf2017-11-21 16:46:08225
Kim Paulhamusfbe092bf2017-11-21 16:46:08226constexpr uint8_t kTestChallengeBytes[] = {
227 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xEC, 0x17, 0x20, 0x2E, 0x42,
228 0x50, 0x5F, 0x8E, 0xD2, 0xB1, 0x6A, 0xE2, 0x2F, 0x16, 0xBB, 0x05,
229 0xB8, 0x8C, 0x25, 0xDB, 0x9E, 0x60, 0x26, 0x45, 0xF1, 0x41};
230
Kim Paulhamus94331812018-01-18 21:17:32231constexpr char kTestRegisterClientDataJsonString[] =
Kim Paulhamus83a16a82018-01-19 11:27:37232 R"({"challenge":"aHE0loIi7BcgLkJQX47SsWriLxa7BbiMJdueYCZF8UE","origin":)"
Adam Langley96986fc82018-05-24 20:08:05233 R"("https://a.google.com", "type":"webauthn.create"})";
Kim Paulhamusfbe092bf2017-11-21 16:46:08234
Kim Paulhamus94331812018-01-18 21:17:32235constexpr char kTestSignClientDataJsonString[] =
Kim Paulhamus83a16a82018-01-19 11:27:37236 R"({"challenge":"aHE0loIi7BcgLkJQX47SsWriLxa7BbiMJdueYCZF8UE","origin":)"
Adam Langley96986fc82018-05-24 20:08:05237 R"("https://a.google.com", "type":"webauthn.get"})";
Kim Paulhamus94331812018-01-18 21:17:32238
Martin Kreichgauer70fc0cf2020-07-17 01:01:00239typedef struct {
Adem Derinel620520b42024-11-04 15:45:44240 std::string_view origin;
Martin Kreichgauer70fc0cf2020-07-17 01:01:00241 // Either a relying party ID or a U2F AppID.
Adem Derinel620520b42024-11-04 15:45:44242 std::string_view claimed_authority;
Martin Kreichgauer70fc0cf2020-07-17 01:01:00243 AuthenticatorStatus expected_status;
244} OriginClaimedAuthorityPair;
245
Kalvin Lee4c50a3fd2025-02-27 15:00:16246constexpr auto kValidRpTestCases = std::to_array<OriginClaimedAuthorityPair>({
Ken Buchanana36345d2019-11-01 17:48:30247 {"http://localhost", "localhost", AuthenticatorStatus::SUCCESS},
248 {"https://myawesomedomain", "myawesomedomain",
249 AuthenticatorStatus::SUCCESS},
250 {"https://foo.bar.google.com", "foo.bar.google.com",
251 AuthenticatorStatus::SUCCESS},
252 {"https://foo.bar.google.com", "bar.google.com",
253 AuthenticatorStatus::SUCCESS},
254 {"https://foo.bar.google.com", "google.com", AuthenticatorStatus::SUCCESS},
255 {"https://earth.login.awesomecompany", "login.awesomecompany",
256 AuthenticatorStatus::SUCCESS},
257 {"https://google.com:1337", "google.com", AuthenticatorStatus::SUCCESS},
Kim Paulhamuscd76a26e2018-01-18 16:11:16258
259 // Hosts with trailing dot valid for rpIds with or without trailing dot.
260 // Hosts without trailing dots only matches rpIDs without trailing dot.
261 // Two trailing dots only matches rpIDs with two trailing dots.
Ken Buchanana36345d2019-11-01 17:48:30262 {"https://google.com.", "google.com", AuthenticatorStatus::SUCCESS},
263 {"https://google.com.", "google.com.", AuthenticatorStatus::SUCCESS},
264 {"https://google.com..", "google.com..", AuthenticatorStatus::SUCCESS},
Kim Paulhamuscd76a26e2018-01-18 16:11:16265
266 // Leading dots are ignored in canonicalized hosts.
Ken Buchanana36345d2019-11-01 17:48:30267 {"https://.google.com", "google.com", AuthenticatorStatus::SUCCESS},
268 {"https://..google.com", "google.com", AuthenticatorStatus::SUCCESS},
269 {"https://.google.com", ".google.com", AuthenticatorStatus::SUCCESS},
270 {"https://..google.com", ".google.com", AuthenticatorStatus::SUCCESS},
271 {"https://accounts.google.com", ".google.com",
272 AuthenticatorStatus::SUCCESS},
Adem Derinel620520b42024-11-04 15:45:44273});
Kim Paulhamuscd76a26e2018-01-18 16:11:16274
Kalvin Lee4c50a3fd2025-02-27 15:00:16275constexpr auto kInvalidRpTestCases = std::to_array<OriginClaimedAuthorityPair>({
Ken Buchanana36345d2019-11-01 17:48:30276 {"https://google.com", "com", AuthenticatorStatus::BAD_RELYING_PARTY_ID},
277 {"http://google.com", "google.com", AuthenticatorStatus::INVALID_DOMAIN},
278 {"http://myawesomedomain", "myawesomedomain",
279 AuthenticatorStatus::INVALID_DOMAIN},
280 {"https://google.com", "foo.bar.google.com",
281 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
282 {"http://myawesomedomain", "randomdomain",
283 AuthenticatorStatus::INVALID_DOMAIN},
284 {"https://myawesomedomain", "randomdomain",
285 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
286 {"https://notgoogle.com", "google.com)",
287 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
288 {"https://not-google.com", "google.com)",
289 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
290 {"https://evil.appspot.com", "appspot.com",
291 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
292 {"https://evil.co.uk", "co.uk", AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Kim Paulhamuscd76a26e2018-01-18 16:11:16293
Ken Buchanana36345d2019-11-01 17:48:30294 {"https://google.com", "google.com.",
295 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
296 {"https://google.com", "google.com..",
297 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
298 {"https://google.com", ".google.com",
299 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
300 {"https://google.com..", "google.com",
301 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
302 {"https://.com", "com.", AuthenticatorStatus::BAD_RELYING_PARTY_ID},
303 {"https://.co.uk", "co.uk.", AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Kim Paulhamuscd76a26e2018-01-18 16:11:16304
Ken Buchanana36345d2019-11-01 17:48:30305 {"https://1.2.3", "1.2.3", AuthenticatorStatus::INVALID_DOMAIN},
306 {"https://1.2.3", "2.3", AuthenticatorStatus::INVALID_DOMAIN},
Kim Paulhamuscd76a26e2018-01-18 16:11:16307
Ken Buchanana36345d2019-11-01 17:48:30308 {"https://127.0.0.1", "127.0.0.1", AuthenticatorStatus::INVALID_DOMAIN},
309 {"https://127.0.0.1", "27.0.0.1", AuthenticatorStatus::INVALID_DOMAIN},
310 {"https://127.0.0.1", ".0.0.1", AuthenticatorStatus::INVALID_DOMAIN},
311 {"https://127.0.0.1", "0.0.1", AuthenticatorStatus::INVALID_DOMAIN},
Kim Paulhamuscd76a26e2018-01-18 16:11:16312
Ken Buchanana36345d2019-11-01 17:48:30313 {"https://[::127.0.0.1]", "127.0.0.1", AuthenticatorStatus::INVALID_DOMAIN},
314 {"https://[::127.0.0.1]", "[127.0.0.1]",
315 AuthenticatorStatus::INVALID_DOMAIN},
Kim Paulhamuscd76a26e2018-01-18 16:11:16316
Ken Buchanana36345d2019-11-01 17:48:30317 {"https://[::1]", "1", AuthenticatorStatus::INVALID_DOMAIN},
318 {"https://[::1]", "1]", AuthenticatorStatus::INVALID_DOMAIN},
319 {"https://[::1]", "::1", AuthenticatorStatus::INVALID_DOMAIN},
320 {"https://[::1]", "[::1]", AuthenticatorStatus::INVALID_DOMAIN},
321 {"https://[1::1]", "::1", AuthenticatorStatus::INVALID_DOMAIN},
322 {"https://[1::1]", "::1]", AuthenticatorStatus::INVALID_DOMAIN},
323 {"https://[1::1]", "[::1]", AuthenticatorStatus::INVALID_DOMAIN},
Kim Paulhamuscd76a26e2018-01-18 16:11:16324
Ken Buchanana36345d2019-11-01 17:48:30325 {"http://google.com:443", "google.com",
326 AuthenticatorStatus::INVALID_DOMAIN},
327 {"data:google.com", "google.com", AuthenticatorStatus::OPAQUE_DOMAIN},
328 {"data:text/html,google.com", "google.com",
329 AuthenticatorStatus::OPAQUE_DOMAIN},
Martin Kreichgauer05a33fb2022-02-18 23:31:29330 {"ws://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL},
Ken Buchanana36345d2019-11-01 17:48:30331 {"gopher://google.com", "google.com", AuthenticatorStatus::OPAQUE_DOMAIN},
Martin Kreichgauer05a33fb2022-02-18 23:31:29332 {"ftp://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL},
Ken Buchanana36345d2019-11-01 17:48:30333 {"file:///google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL},
Adam Langleyd41f65182018-03-21 20:17:27334 // Use of webauthn from a WSS origin may be technically valid, but we
335 // prohibit use on non-HTTPS origins. (At least for now.)
Ken Buchanana36345d2019-11-01 17:48:30336 {"wss://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL},
Kim Paulhamuscd76a26e2018-01-18 16:11:16337
Ken Buchanana36345d2019-11-01 17:48:30338 {"data:,", "", AuthenticatorStatus::OPAQUE_DOMAIN},
339 {"https://google.com", "", AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Martin Kreichgauer05a33fb2022-02-18 23:31:29340 {"ws:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL},
Ken Buchanana36345d2019-11-01 17:48:30341 {"wss:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL},
342 {"gopher://google.com", "", AuthenticatorStatus::OPAQUE_DOMAIN},
Martin Kreichgauer05a33fb2022-02-18 23:31:29343 {"ftp://google.com", "", AuthenticatorStatus::INVALID_PROTOCOL},
Ken Buchanana36345d2019-11-01 17:48:30344 {"file:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL},
Kim Paulhamuscd76a26e2018-01-18 16:11:16345
346 // This case is acceptable according to spec, but both renderer
347 // and browser handling currently do not permit it.
Ken Buchanana36345d2019-11-01 17:48:30348 {"https://login.awesomecompany", "awesomecompany",
349 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Adam Langleya4ace032018-04-04 20:14:27350
351 // These are AppID test cases, but should also be invalid relying party
352 // examples too.
Ken Buchanana36345d2019-11-01 17:48:30353 {"https://example.com", "https://com/",
354 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
355 {"https://example.com", "https://com/foo",
356 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
357 {"https://example.com", "https://foo.com/",
358 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
359 {"https://example.com", "http://example.com",
360 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
361 {"http://example.com", "https://example.com",
362 AuthenticatorStatus::INVALID_DOMAIN},
363 {"https://127.0.0.1", "https://127.0.0.1",
364 AuthenticatorStatus::INVALID_DOMAIN},
Adam Langleya4ace032018-04-04 20:14:27365 {"https://www.notgoogle.com",
Ken Buchanana36345d2019-11-01 17:48:30366 "https://www.gstatic.com/securitykey/origins.json",
367 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Adam Langleya4ace032018-04-04 20:14:27368 {"https://www.google.com",
Ken Buchanana36345d2019-11-01 17:48:30369 "https://www.gstatic.com/securitykey/origins.json#x",
370 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Adam Langleya4ace032018-04-04 20:14:27371 {"https://www.google.com",
Ken Buchanana36345d2019-11-01 17:48:30372 "https://www.gstatic.com/securitykey/origins.json2",
373 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
374 {"https://www.google.com", "https://gstatic.com/securitykey/origins.json",
375 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
376 {"https://ggoogle.com", "https://www.gstatic.com/securitykey/origi",
377 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
378 {"https://com", "https://www.gstatic.com/securitykey/origins.json",
379 AuthenticatorStatus::BAD_RELYING_PARTY_ID},
Adem Derinel620520b42024-11-04 15:45:44380});
Kim Paulhamuscd76a26e2018-01-18 16:11:16381
Andrii Natiahlyi6b2f4b12024-09-03 14:58:42382using TestGetClientCapabilityFuture = base::test::TestFuture<
383 std::vector<blink::mojom::WebAuthnClientCapabilityPtr>>;
Adem Derinele6378762024-06-27 06:05:07384using TestIsUvpaaFuture = base::test::TestFuture<bool>;
385using TestMakeCredentialFuture =
386 base::test::TestFuture<AuthenticatorStatus,
387 MakeCredentialAuthenticatorResponsePtr,
388 WebAuthnDOMExceptionDetailsPtr>;
389using TestGetAssertionFuture =
390 base::test::TestFuture<AuthenticatorStatus,
391 GetAssertionAuthenticatorResponsePtr,
392 WebAuthnDOMExceptionDetailsPtr>;
Adem Derinel72e11db2025-02-11 15:58:00393using TestGetCredentialFuture =
394 base::test::TestFuture<blink::mojom::GetCredentialResponsePtr>;
Adem Derinele6378762024-06-27 06:05:07395using TestRequestStartedFuture = base::test::TestFuture<void>;
Gabriel Viera7bc08f212024-07-10 15:42:33396using TestReportFuture =
397 base::test::TestFuture<AuthenticatorStatus, WebAuthnDOMExceptionDetailsPtr>;
Adam Langley15a15432018-03-27 08:07:37398
Kim Paulhamus35995bb62018-01-05 11:47:04399std::vector<uint8_t> GetTestChallengeBytes() {
400 return std::vector<uint8_t>(std::begin(kTestChallengeBytes),
401 std::end(kTestChallengeBytes));
Kim Paulhamusfbe092bf2017-11-21 16:46:08402}
403
Ken Buchanan4a33ef0d2019-06-20 17:34:04404device::PublicKeyCredentialRpEntity GetTestPublicKeyCredentialRPEntity() {
405 device::PublicKeyCredentialRpEntity entity;
406 entity.id = std::string(kTestRelyingPartyId);
407 entity.name = "[email protected]";
Kim Paulhamusfbe554b2017-08-22 19:46:34408 return entity;
409}
410
Ken Buchanan4a33ef0d2019-06-20 17:34:04411device::PublicKeyCredentialUserEntity GetTestPublicKeyCredentialUserEntity() {
412 device::PublicKeyCredentialUserEntity entity;
413 entity.display_name = "User A. Name";
Kim Paulhamus6549c812017-11-28 22:36:17414 std::vector<uint8_t> id(32, 0x0A);
Ken Buchanan4a33ef0d2019-06-20 17:34:04415 entity.id = id;
416 entity.name = "[email protected]";
Kim Paulhamusfbe554b2017-08-22 19:46:34417 return entity;
418}
419
Ken Buchanan4a33ef0d2019-06-20 17:34:04420std::vector<device::PublicKeyCredentialParams::CredentialInfo>
Kim Paulhamusfbe092bf2017-11-21 16:46:08421GetTestPublicKeyCredentialParameters(int32_t algorithm_identifier) {
Ken Buchanan4a33ef0d2019-06-20 17:34:04422 std::vector<device::PublicKeyCredentialParams::CredentialInfo> parameters;
423 device::PublicKeyCredentialParams::CredentialInfo fake_parameter;
424 fake_parameter.type = device::CredentialType::kPublicKey;
425 fake_parameter.algorithm = algorithm_identifier;
Kim Paulhamusfbe554b2017-08-22 19:46:34426 parameters.push_back(std::move(fake_parameter));
427 return parameters;
428}
429
Ken Buchanan4a33ef0d2019-06-20 17:34:04430device::AuthenticatorSelectionCriteria GetTestAuthenticatorSelectionCriteria() {
431 return device::AuthenticatorSelectionCriteria(
Martin Kreichgauer2e9d8072020-08-31 15:20:47432 device::AuthenticatorAttachment::kAny,
433 device::ResidentKeyRequirement::kDiscouraged,
Ken Buchanan4a33ef0d2019-06-20 17:34:04434 device::UserVerificationRequirement::kPreferred);
Kim Paulhamuse4573992018-03-13 18:46:25435}
436
Ken Buchanan4a33ef0d2019-06-20 17:34:04437std::vector<device::PublicKeyCredentialDescriptor> GetTestCredentials(
Martin Kreichgauer43558c72019-04-06 22:15:37438 size_t num_credentials = 1) {
Ken Buchanan4a33ef0d2019-06-20 17:34:04439 std::vector<device::PublicKeyCredentialDescriptor> descriptors;
Martin Kreichgauer43558c72019-04-06 22:15:37440 for (size_t i = 0; i < num_credentials; i++) {
441 DCHECK(i <= std::numeric_limits<uint8_t>::max());
Martin Kreichgaueracef6fd2019-10-21 23:33:04442 std::vector<uint8_t> id(kTestCredentialIdLength, static_cast<uint8_t>(i));
Ken Buchanan4a33ef0d2019-06-20 17:34:04443 base::flat_set<device::FidoTransportProtocol> transports{
444 device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
445 device::FidoTransportProtocol::kBluetoothLowEnergy};
Adam Langley5b8bda42019-08-29 21:22:59446 descriptors.emplace_back(device::CredentialType::kPublicKey, std::move(id),
447 std::move(transports));
Martin Kreichgauer43558c72019-04-06 22:15:37448 }
Kim Paulhamus6f81d682018-04-11 05:37:03449 return descriptors;
450}
451
Kim Paulhamus31998d22018-02-10 22:28:48452PublicKeyCredentialCreationOptionsPtr
453GetTestPublicKeyCredentialCreationOptions() {
454 auto options = PublicKeyCredentialCreationOptions::New();
Kim Paulhamusfbe554b2017-08-22 19:46:34455 options->relying_party = GetTestPublicKeyCredentialRPEntity();
456 options->user = GetTestPublicKeyCredentialUserEntity();
Adam Langleyd1eb57f2020-06-12 02:07:00457 options->public_key_parameters = GetTestPublicKeyCredentialParameters(
458 static_cast<int32_t>(device::CoseAlgorithmIdentifier::kEs256));
Kim Paulhamus9d87fb0d2018-01-18 18:36:22459 options->challenge.assign(32, 0x0A);
Peter Kastinge5a38ed2021-10-02 03:06:35460 options->timeout = base::Minutes(1);
Kim Paulhamuse4573992018-03-13 18:46:25461 options->authenticator_selection = GetTestAuthenticatorSelectionCriteria();
Kim Paulhamus9d87fb0d2018-01-18 18:36:22462 return options;
463}
464
465PublicKeyCredentialRequestOptionsPtr
466GetTestPublicKeyCredentialRequestOptions() {
467 auto options = PublicKeyCredentialRequestOptions::New();
Slobodan Pejicc8ce88b2023-06-19 15:41:12468 options->extensions = AuthenticationExtensionsClientInputs::New();
Kim Paulhamus94331812018-01-18 21:17:32469 options->relying_party_id = std::string(kTestRelyingPartyId);
Ken Buchananbba66b42024-11-29 16:43:15470 options->challenge = std::vector<uint8_t>(32, 0x0A);
Peter Kastinge5a38ed2021-10-02 03:06:35471 options->timeout = base::Minutes(1);
Ken Buchanan4a33ef0d2019-06-20 17:34:04472 options->user_verification = device::UserVerificationRequirement::kPreferred;
Martin Kreichgauer43558c72019-04-06 22:15:37473 options->allow_credentials = GetTestCredentials();
Kim Paulhamusfbe554b2017-08-22 19:46:34474 return options;
475}
476
Gabriel Viera7bc08f212024-07-10 15:42:33477PublicKeyCredentialReportOptionsPtr GetTestPublicKeyCredentialReportOptions() {
478 auto options = PublicKeyCredentialReportOptions::New();
479 options->relying_party_id = std::string(kTestRelyingPartyId);
Gabriel Viera7bc08f212024-07-10 15:42:33480 return options;
481}
482
Ken Buchanan4a33ef0d2019-06-20 17:34:04483std::vector<device::CableDiscoveryData> GetTestCableExtension() {
484 device::CableDiscoveryData cable;
Adam Langley4ce0c312019-09-10 15:19:43485 cable.version = device::CableDiscoveryData::Version::V1;
486 cable.v1.emplace();
487 cable.v1->client_eid.fill(0x01);
488 cable.v1->authenticator_eid.fill(0x02);
489 cable.v1->session_pre_key.fill(0x03);
Adam Langley2a9355a2019-04-23 00:11:02490
Ken Buchanan4a33ef0d2019-06-20 17:34:04491 std::vector<device::CableDiscoveryData> ret;
Adam Langley2a9355a2019-04-23 00:11:02492 ret.emplace_back(std::move(cable));
493 return ret;
494}
495
Adam Langleyb7d451e022020-06-23 20:18:57496device::AuthenticatorData AuthDataFromMakeCredentialResponse(
497 const MakeCredentialAuthenticatorResponsePtr& response) {
Arthur Sonzognic686e8f2024-01-11 08:36:37498 std::optional<Value> attestation_value =
Adam Langleyb7d451e022020-06-23 20:18:57499 Reader::Read(response->attestation_object);
500 CHECK(attestation_value);
501 const auto& attestation = attestation_value->GetMap();
502
503 const auto auth_data_it = attestation.find(Value(device::kAuthDataKey));
504 CHECK(auth_data_it != attestation.end());
505 const std::vector<uint8_t>& auth_data = auth_data_it->second.GetBytestring();
Arthur Sonzognic686e8f2024-01-11 08:36:37506 std::optional<device::AuthenticatorData> parsed_auth_data =
Adam Langleyb7d451e022020-06-23 20:18:57507 device::AuthenticatorData::DecodeAuthenticatorData(auth_data);
508 return std::move(parsed_auth_data.value());
509}
510
Martin Kreichgauer5f7a707b2023-03-02 22:22:25511bool HasUV(const MakeCredentialAuthenticatorResponsePtr& response) {
512 return AuthDataFromMakeCredentialResponse(response)
513 .obtained_user_verification();
514}
515
516bool HasUV(const GetAssertionAuthenticatorResponsePtr& response) {
Arthur Sonzognic686e8f2024-01-11 08:36:37517 std::optional<device::AuthenticatorData> auth_data =
Martin Kreichgauer5f7a707b2023-03-02 22:22:25518 device::AuthenticatorData::DecodeAuthenticatorData(
519 response->info->authenticator_data);
520 return auth_data->obtained_user_verification();
521}
522
Martin Kreichgauer55834402020-08-03 21:54:31523url::Origin GetTestOrigin() {
524 const GURL test_relying_party_url(kTestOrigin1);
525 CHECK(test_relying_party_url.is_valid());
526 return url::Origin::Create(test_relying_party_url);
527}
528
Martin Kreichgauer0b1f5592021-07-02 22:28:25529std::string GetTestClientDataJSON(ClientDataRequestType type) {
Ken Buchanan4fb3ef12024-08-19 20:19:32530 return BuildClientDataJson({std::move(type), GetTestOrigin(), GetTestOrigin(),
Martin Kreichgauere255af062022-04-18 19:40:56531 GetTestChallengeBytes(),
532 /*is_cross_origin_iframe=*/false});
Martin Kreichgauer55834402020-08-03 21:54:31533}
534
Nina Satragno9ca1c642022-04-21 16:26:47535device::LargeBlob CompressLargeBlob(base::span<const uint8_t> blob) {
536 data_decoder::Gzipper gzipper;
537 std::vector<uint8_t> compressed;
538 base::RunLoop run_loop;
Elly450ad9fe2025-07-09 17:13:22539 gzipper.Deflate(blob, base::BindLambdaForTesting(
540 [&](std::optional<mojo_base::BigBuffer> result) {
541 compressed = base::ToVector(*result);
542 run_loop.Quit();
543 }));
Nina Satragno9ca1c642022-04-21 16:26:47544 run_loop.Run();
545 return device::LargeBlob(std::move(compressed), blob.size());
Nina Satragnoaed99fb2020-10-15 22:21:56546}
547
Nina Satragno9ca1c642022-04-21 16:26:47548std::vector<uint8_t> UncompressLargeBlob(device::LargeBlob blob) {
549 data_decoder::Gzipper gzipper;
550 std::vector<uint8_t> uncompressed;
551 base::RunLoop run_loop;
Martin Kreichgauerca18b432023-04-25 18:58:47552 gzipper.Inflate(
Peter Kasting2b138f12024-10-31 17:10:27553 {blob.compressed_data}, blob.original_size,
Martin Kreichgauerca18b432023-04-25 18:58:47554 base::BindLambdaForTesting(
Arthur Sonzognic686e8f2024-01-11 08:36:37555 [&](std::optional<mojo_base::BigBuffer> result) {
Martin Kreichgauerca18b432023-04-25 18:58:47556 if (result) {
Elly450ad9fe2025-07-09 17:13:22557 uncompressed = base::ToVector(*result);
Martin Kreichgauerca18b432023-04-25 18:58:47558 } else {
559 // Magic value to indicate failure.
560 const char kErrorMsg[] = "decompress error";
561 uncompressed.assign(
562 reinterpret_cast<const uint8_t*>(kErrorMsg),
563 reinterpret_cast<const uint8_t*>(std::end(kErrorMsg)));
564 }
565 run_loop.Quit();
566 }));
Nina Satragno9ca1c642022-04-21 16:26:47567 run_loop.Run();
568 return uncompressed;
Nina Satragnoaed99fb2020-10-15 22:21:56569}
570
Martin Kreichgauerfefb3772021-04-12 23:02:48571// Convert a blink::mojom::AttestationConveyancePreference to a
572// device::AtttestationConveyancePreference.
573device::AttestationConveyancePreference ConvertAttestationConveyancePreference(
574 AttestationConveyancePreference in) {
575 switch (in) {
576 case AttestationConveyancePreference::NONE:
577 return ::device::AttestationConveyancePreference::kNone;
578 case AttestationConveyancePreference::INDIRECT:
579 return ::device::AttestationConveyancePreference::kIndirect;
580 case AttestationConveyancePreference::DIRECT:
581 return ::device::AttestationConveyancePreference::kDirect;
582 case AttestationConveyancePreference::ENTERPRISE:
583 return ::device::AttestationConveyancePreference::
584 kEnterpriseIfRPListedOnAuthenticator;
585 }
586}
587
Kim Paulhamusfbe554b2017-08-22 19:46:34588} // namespace
kpaulhamus7c9f00942017-06-30 11:08:45589
Martin Kreichgauerfefb3772021-04-12 23:02:48590class AuthenticatorTestBase : public RenderViewHostTestHarness {
Martin Kreichgauer4faa9baf2019-07-17 17:57:39591 protected:
Martin Kreichgauer70fc0cf2020-07-17 01:01:00592 AuthenticatorTestBase()
593 : RenderViewHostTestHarness(
594 base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
Nina Satragno20a78cf92020-06-22 18:02:19595 ~AuthenticatorTestBase() override = default;
Nina Satragnoacf403f92019-05-23 17:16:52596
Martin Kreichgauer108eb102022-06-29 23:08:41597 static void SetUpTestSuite() {
598#if BUILDFLAG(IS_MAC)
599 // Load fido_strings, which can be required for exercising the Touch ID
600 // authenticator.
601 base::FilePath path;
602 ASSERT_TRUE(base::PathService::Get(base::DIR_ASSETS, &path));
603 base::FilePath fido_test_strings =
604 path.Append(FILE_PATH_LITERAL("fido_test_strings.pak"));
605 ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
606 fido_test_strings, ui::kScaleFactorNone);
607#endif
608 }
609
Martin Kreichgauer17bc163b2019-09-12 00:38:42610 void SetUp() override {
Martin Kreichgauerfefb3772021-04-12 23:02:48611 RenderViewHostTestHarness::SetUp();
Martin Kreichgauerdc37b752021-02-23 23:12:31612
Pavel Beloborodovd2dfbaa82025-02-27 20:05:36613 WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() =
614 true;
615
Martin Kreichgauer8d55b452021-10-04 20:51:11616 mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
617 &AuthenticatorTestBase::OnMojoError, base::Unretained(this)));
618
Howard Yang72a1412b2022-04-13 00:16:02619#if BUILDFLAG(IS_CHROMEOS)
Yi Chouf6745842021-07-30 05:38:19620 chromeos::TpmManagerClient::InitializeFake();
Martin Kreichgauerdc37b752021-02-23 23:12:31621 chromeos::U2FClient::InitializeFake();
622#endif
623
Xiaohan Wang2ba85e32022-01-15 17:19:40624#if BUILDFLAG(IS_WIN)
Martin Kreichgauer37ace492021-04-08 23:36:46625 // Disable the Windows WebAuthn API integration by default. Individual tests
626 // can modify this.
627 fake_win_webauthn_api_.set_available(false);
Ken Buchanan1ffd3912024-07-08 01:16:09628
629 // Prevent `FidoRequestHandlerBase` from doing a system API call, which can
630 // cause tests to finish early since `RunUntilIdle` won't see it in the task
631 // queue.
632 biometrics_override_ =
633 std::make_unique<device::fido::win::ScopedBiometricsOverride>(false);
Martin Kreichgauer37ace492021-04-08 23:36:46634#endif
635
Martin Kreichgauer17bc163b2019-09-12 00:38:42636 ResetVirtualDevice();
637 }
638
Martin Kreichgauerdc37b752021-02-23 23:12:31639 void TearDown() override {
Martin Kreichgauerfefb3772021-04-12 23:02:48640 RenderViewHostTestHarness::TearDown();
Pavel Beloborodovd2dfbaa82025-02-27 20:05:36641 WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() =
642 false;
Martin Kreichgauerdc37b752021-02-23 23:12:31643
Martin Kreichgauer8d55b452021-10-04 20:51:11644 mojo::SetDefaultProcessErrorHandler(base::NullCallback());
645
Jagadesh P8db17bf2023-11-28 04:14:15646 virtual_device_factory_ = nullptr;
Adam Langley1e03fb02023-03-16 23:02:03647 AuthenticatorEnvironment::GetInstance()->Reset();
Howard Yang72a1412b2022-04-13 00:16:02648#if BUILDFLAG(IS_CHROMEOS)
Martin Kreichgauerdc37b752021-02-23 23:12:31649 chromeos::U2FClient::Shutdown();
Yi Chouf6745842021-07-30 05:38:19650 chromeos::TpmManagerClient::Shutdown();
Martin Kreichgauerdc37b752021-02-23 23:12:31651#endif
652 }
653
Martin Kreichgauer108eb102022-06-29 23:08:41654 virtual void ResetVirtualDevice() {
Nina Satragnoacf403f92019-05-23 17:16:52655 auto virtual_device_factory =
656 std::make_unique<device::test::VirtualFidoDeviceFactory>();
657 virtual_device_factory_ = virtual_device_factory.get();
Adam Langley1e03fb02023-03-16 23:02:03658 AuthenticatorEnvironment::GetInstance()
Nina Satragnoacf403f92019-05-23 17:16:52659 ->ReplaceDefaultDiscoveryFactoryForTesting(
660 std::move(virtual_device_factory));
661 }
662
Jagadesh P8db17bf2023-11-28 04:14:15663 virtual void ReplaceDiscoveryFactory(
664 std::unique_ptr<device::FidoDiscoveryFactory> device_factory) {
665 virtual_device_factory_ = nullptr;
666 AuthenticatorEnvironment::GetInstance()
667 ->ReplaceDefaultDiscoveryFactoryForTesting(std::move(device_factory));
668 }
669
Martin Kreichgauer8d55b452021-10-04 20:51:11670 void SetMojoErrorHandler(
671 base::RepeatingCallback<void(const std::string&)> callback) {
672 mojo_error_handler_ = callback;
673 }
674
Jagadesh P8db17bf2023-11-28 04:14:15675 raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_ =
676 nullptr;
Xiaohan Wang2ba85e32022-01-15 17:19:40677#if BUILDFLAG(IS_WIN)
Martin Kreichgauer37ace492021-04-08 23:36:46678 device::FakeWinWebAuthnApi fake_win_webauthn_api_;
Adam Langley30375442023-06-19 17:20:26679 device::WinWebAuthnApi::ScopedOverride win_webauthn_api_override_{
680 &fake_win_webauthn_api_};
Ken Buchanan1ffd3912024-07-08 01:16:09681 std::unique_ptr<device::fido::win::ScopedBiometricsOverride>
682 biometrics_override_;
Martin Kreichgauer37ace492021-04-08 23:36:46683#endif
Martin Kreichgauer8d55b452021-10-04 20:51:11684
685 private:
686 void OnMojoError(const std::string& error) {
687 if (mojo_error_handler_) {
688 mojo_error_handler_.Run(error);
689 return;
690 }
691 FAIL() << "Unhandled mojo error: " << error;
692 }
693
694 base::RepeatingCallback<void(const std::string&)> mojo_error_handler_;
Nina Satragnoacf403f92019-05-23 17:16:52695};
696
697class AuthenticatorImplTest : public AuthenticatorTestBase {
Martin Kreichgauer4faa9baf2019-07-17 17:57:39698 protected:
Michael Thiessen2add7d442020-02-05 13:49:38699 AuthenticatorImplTest() {
700 url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST);
701 }
702 ~AuthenticatorImplTest() override = default;
Adam Langley72d721e2018-02-23 19:37:08703
Martin Kreichgauer263f6d82020-03-10 05:46:45704 void SetUp() override {
705 AuthenticatorTestBase::SetUp();
Andrii Natiahlyie480a492024-09-18 15:20:37706 SetBluetoothLESupported(true);
Martin Kreichgauer263f6d82020-03-10 05:46:45707 device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
708 }
709
Andrii Natiahlyie480a492024-09-18 15:20:37710 void SetBluetoothLESupported(bool supported) {
711 bluetooth_global_values_->SetLESupported(supported);
712 }
713
Adam Langley2cffe382018-06-12 01:52:54714 void NavigateAndCommit(const GURL& url) {
Martin Kreichgauerfefb3772021-04-12 23:02:48715 RenderViewHostTestHarness::NavigateAndCommit(url);
Adam Langley2cffe382018-06-12 01:52:54716 }
717
Mario Sanchez Pradac791f54e2019-07-09 23:38:37718 mojo::Remote<blink::mojom::Authenticator> ConnectToAuthenticator() {
Mario Sanchez Pradac791f54e2019-07-09 23:38:37719 mojo::Remote<blink::mojom::Authenticator> authenticator;
Martin Kreichgauer7d2b8dbb2021-04-01 16:03:45720 static_cast<RenderFrameHostImpl*>(main_rfh())
721 ->GetWebAuthenticationService(
722 authenticator.BindNewPipeAndPassReceiver());
Adam Langley72d721e2018-02-23 19:37:08723 return authenticator;
724 }
725
Martin Kreichgauerca18b432023-04-25 18:58:47726 bool AuthenticatorIsUvpaa() {
Adem Derinele6378762024-06-27 06:05:07727 TestIsUvpaaFuture future;
Martin Kreichgauerca18b432023-04-25 18:58:47728 mojo::Remote<blink::mojom::Authenticator> authenticator =
729 ConnectToAuthenticator();
Adem Derinele6378762024-06-27 06:05:07730 authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(
731 future.GetCallback());
732 EXPECT_TRUE(future.Wait());
733 return future.Get();
Martin Kreichgauerca18b432023-04-25 18:58:47734 }
735
Andrii Natiahlyi6b2f4b12024-09-03 14:58:42736 using ClientCapabilitiesList =
737 std::vector<blink::mojom::WebAuthnClientCapabilityPtr>;
738
739 ClientCapabilitiesList AuthenticatorGetClientCapabilities() {
740 mojo::Remote<blink::mojom::Authenticator> authenticator =
741 ConnectToAuthenticator();
742 TestGetClientCapabilityFuture future;
743 authenticator->GetClientCapabilities(future.GetCallback());
744 EXPECT_TRUE(future.Wait());
745 return future.Take();
746 }
747
748 void ExpectCapability(
749 const std::vector<blink::mojom::WebAuthnClientCapabilityPtr>&
750 capabilities,
751 std::string_view capability_name,
Adem Derineld5640322025-04-24 09:14:06752 std::optional<bool> supported) {
Andrii Natiahlyi6b2f4b12024-09-03 14:58:42753 auto capability_it =
754 std::find_if(capabilities.begin(), capabilities.end(),
755 [&capability_name](const auto& capability) {
756 return capability->name == capability_name;
757 });
758
Adem Derineld5640322025-04-24 09:14:06759 if (supported.has_value()) {
760 ASSERT_NE(capability_it, capabilities.end());
761 EXPECT_EQ(supported, (*capability_it)->supported);
762 } else {
763 EXPECT_EQ(capability_it, capabilities.end());
764 }
Andrii Natiahlyi6b2f4b12024-09-03 14:58:42765 }
766
Martin Kreichgauerca18b432023-04-25 18:58:47767 bool AuthenticatorIsConditionalMediationAvailable() {
Adem Derinele6378762024-06-27 06:05:07768 TestIsUvpaaFuture future;
Martin Kreichgauerca18b432023-04-25 18:58:47769 mojo::Remote<blink::mojom::Authenticator> authenticator =
770 ConnectToAuthenticator();
Adem Derinele6378762024-06-27 06:05:07771 authenticator->IsConditionalMediationAvailable(future.GetCallback());
772 EXPECT_TRUE(future.Wait());
773 return future.Get();
Martin Kreichgauerca18b432023-04-25 18:58:47774 }
775
Martin Kreichgauer55834402020-08-03 21:54:31776 struct MakeCredentialResult {
777 AuthenticatorStatus status;
778 MakeCredentialAuthenticatorResponsePtr response;
779 };
780
781 MakeCredentialResult AuthenticatorMakeCredential() {
782 return AuthenticatorMakeCredential(
783 GetTestPublicKeyCredentialCreationOptions());
Adam Langley72d721e2018-02-23 19:37:08784 }
785
Martin Kreichgauer55834402020-08-03 21:54:31786 MakeCredentialResult AuthenticatorMakeCredential(
787 PublicKeyCredentialCreationOptionsPtr options) {
788 mojo::Remote<blink::mojom::Authenticator> authenticator =
789 ConnectToAuthenticator();
Adem Derinele6378762024-06-27 06:05:07790 TestMakeCredentialFuture future;
791 authenticator->MakeCredential(std::move(options), future.GetCallback());
792 EXPECT_TRUE(future.Wait());
793 auto [status, response, dom_exception] = future.Take();
Martin Kreichgauer55834402020-08-03 21:54:31794 return {status, std::move(response)};
Adam Langley72d721e2018-02-23 19:37:08795 }
796
Martin Kreichgauer55834402020-08-03 21:54:31797 MakeCredentialResult AuthenticatorMakeCredentialAndWaitForTimeout(
798 PublicKeyCredentialCreationOptionsPtr options) {
799 mojo::Remote<blink::mojom::Authenticator> authenticator =
800 ConnectToAuthenticator();
Adem Derinele6378762024-06-27 06:05:07801 TestMakeCredentialFuture future;
802 authenticator->MakeCredential(std::move(options), future.GetCallback());
Martin Kreichgauer55834402020-08-03 21:54:31803 task_environment()->FastForwardBy(kTestTimeout);
Adem Derinele6378762024-06-27 06:05:07804 EXPECT_TRUE(future.Wait());
805 auto [status, response, dom_exception] = future.Take();
Martin Kreichgauer55834402020-08-03 21:54:31806 return {status, std::move(response)};
807 }
808
809 struct GetAssertionResult {
810 AuthenticatorStatus status;
811 GetAssertionAuthenticatorResponsePtr response;
812 };
813
814 GetAssertionResult AuthenticatorGetAssertion() {
815 return AuthenticatorGetAssertion(
816 GetTestPublicKeyCredentialRequestOptions());
817 }
818
819 GetAssertionResult AuthenticatorGetAssertion(
820 PublicKeyCredentialRequestOptionsPtr options) {
821 mojo::Remote<blink::mojom::Authenticator> authenticator =
822 ConnectToAuthenticator();
Adem Derinel72e11db2025-02-11 15:58:00823 TestGetCredentialFuture future;
824 authenticator->GetCredential(std::move(options), future.GetCallback());
Adem Derinele6378762024-06-27 06:05:07825 EXPECT_TRUE(future.Wait());
Adem Derinel72e11db2025-02-11 15:58:00826 auto get_assertion_response =
827 std::move(future.Take()->get_get_assertion_response());
828 return {get_assertion_response->status,
829 std::move(get_assertion_response->credential)};
Martin Kreichgauer55834402020-08-03 21:54:31830 }
831
832 GetAssertionResult AuthenticatorGetAssertionAndWaitForTimeout(
833 PublicKeyCredentialRequestOptionsPtr options) {
834 mojo::Remote<blink::mojom::Authenticator> authenticator =
835 ConnectToAuthenticator();
Adem Derinel72e11db2025-02-11 15:58:00836 TestGetCredentialFuture future;
837 authenticator->GetCredential(std::move(options), future.GetCallback());
Martin Kreichgauer55834402020-08-03 21:54:31838 task_environment()->FastForwardBy(kTestTimeout);
Adem Derinel72e11db2025-02-11 15:58:00839 auto get_assertion_response =
840 std::move(future.Take()->get_get_assertion_response());
841 return {get_assertion_response->status,
842 std::move(get_assertion_response->credential)};
Adam Langley2e71e7a2020-03-02 21:19:04843 }
844
Gabriel Viera7bc08f212024-07-10 15:42:33845 AuthenticatorStatus AuthenticatorReport(
846 PublicKeyCredentialReportOptionsPtr options) {
847 mojo::Remote<blink::mojom::Authenticator> authenticator =
848 ConnectToAuthenticator();
849 TestReportFuture future;
850 authenticator->Report(std::move(options), future.GetCallback());
851 EXPECT_TRUE(future.Wait());
852 auto [status, dom_exception] = future.Take();
853 return status;
854 }
855
Adem Derinel620520b42024-11-04 15:45:44856 AuthenticatorStatus TryAuthenticationWithAppId(std::string_view origin,
857 std::string_view appid) {
Adam Langleya4ace032018-04-04 20:14:27858 const GURL origin_url(origin);
859 NavigateAndCommit(origin_url);
Martin Kreichgauer55834402020-08-03 21:54:31860
Adam Langleya4ace032018-04-04 20:14:27861 PublicKeyCredentialRequestOptionsPtr options =
862 GetTestPublicKeyCredentialRequestOptions();
863 options->relying_party_id = origin_url.host();
Slobodan Pejicc8ce88b2023-06-19 15:41:12864 options->extensions->appid = appid;
Adam Langleya4ace032018-04-04 20:14:27865
Martin Kreichgauer55834402020-08-03 21:54:31866 return AuthenticatorGetAssertion(std::move(options)).status;
Adam Langleya4ace032018-04-04 20:14:27867 }
868
Adam Langley0616cd42019-08-08 22:31:10869 AuthenticatorStatus TryRegistrationWithAppIdExclude(
Adem Derinel620520b42024-11-04 15:45:44870 std::string_view origin,
871 std::string_view appid_exclude) {
Adam Langley0616cd42019-08-08 22:31:10872 const GURL origin_url(origin);
873 NavigateAndCommit(origin_url);
Martin Kreichgauer55834402020-08-03 21:54:31874
Adam Langley0616cd42019-08-08 22:31:10875 PublicKeyCredentialCreationOptionsPtr options =
876 GetTestPublicKeyCredentialCreationOptions();
877 options->relying_party.id = origin_url.host();
878 options->appid_exclude = appid_exclude;
879
Martin Kreichgauer55834402020-08-03 21:54:31880 return AuthenticatorMakeCredential(std::move(options)).status;
Adam Langley0616cd42019-08-08 22:31:10881 }
882
Ken Buchanan23dce912024-07-11 16:41:27883 ukm::TestUkmRecorder* GetTestUkmRecorder() { return &test_ukm_recorder_; }
884
885 void VerifyGetAssertionOutcomeUkm(uint32_t index,
886 GetAssertionOutcome outcome,
Ken Buchanan1ea549c12024-10-10 21:31:05887 AuthenticationRequestMode mode) {
Ken Buchanan23dce912024-07-11 16:41:27888 auto entries = GetTestUkmRecorder()->GetEntriesByName(
889 ukm::builders::WebAuthn_SignCompletion::kEntryName);
890 ASSERT_GT(entries.size(), index);
891 GetTestUkmRecorder()->ExpectEntryMetric(
892 entries[index], "SignCompletionResult", static_cast<int64_t>(outcome));
893 GetTestUkmRecorder()->ExpectEntryMetric(entries[index], "RequestMode",
894 static_cast<int64_t>(mode));
895 }
896
897 void VerifyMakeCredentialOutcomeUkm(uint32_t index,
898 MakeCredentialOutcome outcome,
Ken Buchanan1ea549c12024-10-10 21:31:05899 AuthenticationRequestMode mode) {
Ken Buchanan23dce912024-07-11 16:41:27900 auto entries = GetTestUkmRecorder()->GetEntriesByName(
901 ukm::builders::WebAuthn_RegisterCompletion::kEntryName);
902 ASSERT_GT(entries.size(), index);
903 GetTestUkmRecorder()->ExpectEntryMetric(entries[index],
904 "RegisterCompletionResult",
905 static_cast<int64_t>(outcome));
906 GetTestUkmRecorder()->ExpectEntryMetric(entries[index], "RequestMode",
907 static_cast<int64_t>(mode));
908 }
909
Nina Satragnoa11a1ff2025-07-07 21:05:30910 // Replaces the virtual authenticator with a multiple discovery for all
911 // transports.
912 void InjectVirtualAuthenticatorForAllTransports() {
913 EXPECT_CALL(*mock_adapter_, IsPresent())
914 .WillRepeatedly(::testing::Return(true));
915 auto discovery =
916 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
917 for (device::FidoTransportProtocol transport : {
918 device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
919 device::FidoTransportProtocol::kNearFieldCommunication,
920 device::FidoTransportProtocol::kBluetoothLowEnergy,
921 device::FidoTransportProtocol::kHybrid,
922 device::FidoTransportProtocol::kInternal,
923 }) {
924 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device;
925 device.transport = transport;
926 device.state->transport = transport;
927 ASSERT_TRUE(device.state->InjectResidentKey(
928 /*credential_id=*/{{1, 2, 3, 4}}, kTestRelyingPartyId,
929 /*user_id=*/{{1, 1, 1, 1}}, "[email protected]", "Test User"));
930 discovery->AddDevice(std::move(device));
931 }
932 ReplaceDiscoveryFactory(std::move(discovery));
933 }
934
Martin Kreichgauer8d55b452021-10-04 20:51:11935 scoped_refptr<::testing::NiceMock<device::MockBluetoothAdapter>>
936 mock_adapter_ = base::MakeRefCounted<
937 ::testing::NiceMock<device::MockBluetoothAdapter>>();
938
939 private:
Alex N. Josef3258bb2024-07-02 20:13:46940 std::unique_ptr<device::BluetoothAdapterFactory::GlobalOverrideValues>
Martin Kreichgauer263f6d82020-03-10 05:46:45941 bluetooth_global_values_ =
Alex N. Josef3258bb2024-07-02 20:13:46942 device::BluetoothAdapterFactory::Get()->InitGlobalOverrideValues();
Nina Satragnoaed99fb2020-10-15 22:21:56943 data_decoder::test::InProcessDataDecoder data_decoder_service_;
Michael Thiessen2add7d442020-02-05 13:49:38944 url::ScopedSchemeRegistryForTests scoped_registry_;
Ken Buchanan23dce912024-07-11 16:41:27945 ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
Adam Langley72d721e2018-02-23 19:37:08946};
947
Adam Langley2e71e7a2020-03-02 21:19:04948TEST_F(AuthenticatorImplTest, ClientDataJSONSerialization) {
949 // First test that the output is in the expected form. Some verifiers may be
950 // depending on the exact JSON serialisation. Since the serialisation may add
951 // extra elements, this can only test that the expected value is a prefix of
952 // the returned value.
953 std::vector<uint8_t> challenge_bytes = {1, 2, 3};
Martin Kreichgauer7d2b8dbb2021-04-01 16:03:45954 EXPECT_EQ(
Martin Kreichgauere255af062022-04-18 19:40:56955 BuildClientDataJson({ClientDataRequestType::kWebAuthnCreate,
Ken Buchanan4fb3ef12024-08-19 20:19:32956 GetTestOrigin(), GetTestOrigin(), challenge_bytes,
957 false})
Martin Kreichgauer0b1f5592021-07-02 22:28:25958 .find(
959 "{\"type\":\"webauthn.create\",\"challenge\":\"AQID\",\"origin\":"
Martin Kreichgauere255af062022-04-18 19:40:56960 "\"https://a.google.com\",\"crossOrigin\":false"),
Martin Kreichgauer7d2b8dbb2021-04-01 16:03:45961 0u);
Adam Langley2e71e7a2020-03-02 21:19:04962
963 // Second, check that a generic JSON parser correctly parses the result.
964 static const struct {
Martin Kreichgauer0b1f5592021-07-02 22:28:25965 const ClientDataRequestType type;
Martin Kreichgauere255af062022-04-18 19:40:56966 url::Origin origin;
Ken Buchanan4fb3ef12024-08-19 20:19:32967 url::Origin top_origin;
Adam Langley2e71e7a2020-03-02 21:19:04968 std::vector<uint8_t> challenge;
969 bool is_cross_origin;
970 } kTestCases[] = {
971 {
Martin Kreichgauer0b1f5592021-07-02 22:28:25972 ClientDataRequestType::kWebAuthnGet,
Martin Kreichgauere255af062022-04-18 19:40:56973 GetTestOrigin(),
Ken Buchanan4fb3ef12024-08-19 20:19:32974 GetTestOrigin(),
Adam Langley2e71e7a2020-03-02 21:19:04975 {1, 2, 3},
976 false,
977 },
978 {
Martin Kreichgauer0b1f5592021-07-02 22:28:25979 ClientDataRequestType::kPaymentGet,
Martin Kreichgauere255af062022-04-18 19:40:56980 GetTestOrigin(),
Ken Buchanan4fb3ef12024-08-19 20:19:32981 GetTestOrigin(),
982 {1, 2, 3},
983 false,
984 },
985 {
986 ClientDataRequestType::kWebAuthnCreate,
987 GetTestOrigin(),
988 url::Origin::Create(GURL("https://toplevel.example")),
Martin Kreichgauer0b1f5592021-07-02 22:28:25989 {1, 2, 3},
990 false,
991 },
Adam Langley2e71e7a2020-03-02 21:19:04992 };
993
994 size_t num = 0;
995 for (const auto& test : kTestCases) {
996 SCOPED_TRACE(num++);
997
Ken Buchanan4fb3ef12024-08-19 20:19:32998 const std::string json =
999 BuildClientDataJson({test.type, test.origin, test.top_origin,
1000 test.challenge, test.is_cross_origin});
Adam Langley2e71e7a2020-03-02 21:19:041001
1002 const auto parsed = base::JSONReader::Read(json);
1003 ASSERT_TRUE(parsed.has_value());
Martin Kreichgauer0b1f5592021-07-02 22:28:251004 std::string type_key;
1005 std::string expected_type;
1006 switch (test.type) {
Martin Kreichgauer0b1f5592021-07-02 22:28:251007 case ClientDataRequestType::kWebAuthnCreate:
1008 type_key = "type";
1009 expected_type = "webauthn.create";
1010 break;
1011 case ClientDataRequestType::kWebAuthnGet:
1012 type_key = "type";
1013 expected_type = "webauthn.get";
1014 break;
Martin Kreichgauer0b1f5592021-07-02 22:28:251015 case ClientDataRequestType::kPaymentGet:
1016 type_key = "type";
1017 expected_type = "payment.get";
1018 break;
1019 }
[email protected]147bc83a2023-02-14 13:11:071020 ASSERT_TRUE(parsed->is_dict());
1021 EXPECT_EQ(*parsed->GetDict().FindString(type_key), expected_type);
1022 EXPECT_EQ(*parsed->GetDict().FindString("origin"), test.origin.Serialize());
Adam Langley2e71e7a2020-03-02 21:19:041023 std::string expected_challenge;
Adem Derinel620520b42024-11-04 15:45:441024 base::Base64UrlEncode(test.challenge,
1025 base::Base64UrlEncodePolicy::OMIT_PADDING,
1026 &expected_challenge);
[email protected]147bc83a2023-02-14 13:11:071027 EXPECT_EQ(*parsed->GetDict().FindString("challenge"), expected_challenge);
1028 EXPECT_EQ(*parsed->GetDict().FindBool("crossOrigin"), test.is_cross_origin);
Ken Buchanan4fb3ef12024-08-19 20:19:321029 if (test.is_cross_origin) {
1030 EXPECT_EQ(*parsed->GetDict().FindString("topOrigin"),
1031 test.top_origin.Serialize());
1032 } else {
1033 EXPECT_EQ(parsed->GetDict().FindString("topOrigin"), nullptr);
1034 }
Adam Langley2e71e7a2020-03-02 21:19:041035 }
1036}
1037
Adam Langleydc639a92018-02-28 17:18:421038// Verify behavior for various combinations of origins and RP IDs.
Kim Paulhamuscd76a26e2018-01-18 16:11:161039TEST_F(AuthenticatorImplTest, MakeCredentialOriginAndRpIds) {
Adem Derinel620520b42024-11-04 15:45:441040 std::vector<OriginClaimedAuthorityPair> tests;
Peter Kasting1557e5f2025-01-28 01:14:081041 std::ranges::copy(kValidRpTestCases, std::back_inserter(tests));
1042 std::ranges::copy(kInvalidRpTestCases, std::back_inserter(tests));
Adam Langley4f0548282020-06-12 18:54:301043
Ken Buchanan23dce912024-07-11 16:41:271044 int test_case_count = 0;
Adam Langley4f0548282020-06-12 18:54:301045 for (const auto& test_case : tests) {
Adem Derinel620520b42024-11-04 15:45:441046 SCOPED_TRACE(
1047 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Adam Langleydc639a92018-02-28 17:18:421048
Kim Paulhamuscd76a26e2018-01-18 16:11:161049 NavigateAndCommit(GURL(test_case.origin));
Kim Paulhamus31998d22018-02-10 22:28:481050 PublicKeyCredentialCreationOptionsPtr options =
1051 GetTestPublicKeyCredentialCreationOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:041052 options->relying_party.id = test_case.claimed_authority;
Martin Kreichgauer55834402020-08-03 21:54:311053
1054 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
1055 test_case.expected_status);
Ken Buchanan23dce912024-07-11 16:41:271056 VerifyMakeCredentialOutcomeUkm(
1057 test_case_count++,
1058 (test_case.expected_status == AuthenticatorStatus::SUCCESS)
1059 ? MakeCredentialOutcome::kSuccess
1060 : MakeCredentialOutcome::kSecurityError,
Ken Buchanan1ea549c12024-10-10 21:31:051061 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamuscd76a26e2018-01-18 16:11:161062 }
kpaulhamus7c9f00942017-06-30 11:08:451063}
Adam Langley31c85e72017-10-17 06:10:351064
Jun Choif45741d2018-06-08 20:58:311065// Test that MakeCredential request times out with NOT_ALLOWED_ERROR if user
1066// verification is required for U2F devices.
Kim Paulhamuse4573992018-03-13 18:46:251067TEST_F(AuthenticatorImplTest, MakeCredentialUserVerification) {
Martin Kreichgauer55834402020-08-03 21:54:311068 NavigateAndCommit(GURL(kTestOrigin1));
Kim Paulhamuse4573992018-03-13 18:46:251069
1070 PublicKeyCredentialCreationOptionsPtr options =
1071 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauerbece642a2021-12-07 21:02:461072 options->authenticator_selection->user_verification_requirement =
1073 device::UserVerificationRequirement::kRequired;
Kim Paulhamuse4573992018-03-13 18:46:251074
Martin Kreichgauer55834402020-08-03 21:54:311075 EXPECT_EQ(
1076 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
1077 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Kim Paulhamuse4573992018-03-13 18:46:251078}
1079
Martin Kreichgauer55834402020-08-03 21:54:311080TEST_F(AuthenticatorImplTest, MakeCredentialResidentKeyUnsupported) {
1081 NavigateAndCommit(GURL(kTestOrigin1));
Kim Paulhamuse4573992018-03-13 18:46:251082
1083 PublicKeyCredentialCreationOptionsPtr options =
1084 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauerbece642a2021-12-07 21:02:461085 options->authenticator_selection->resident_key =
1086 device::ResidentKeyRequirement::kRequired;
Kim Paulhamuse4573992018-03-13 18:46:251087
Martin Kreichgauer55834402020-08-03 21:54:311088 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
1089 AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
Ken Buchanan23dce912024-07-11 16:41:271090 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kRkNotSupported,
Ken Buchanan1ea549c12024-10-10 21:31:051091 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamus3eef95f2018-04-03 10:37:451092}
1093
Jun Choif45741d2018-06-08 20:58:311094// Test that MakeCredential request times out with NOT_ALLOWED_ERROR if a
1095// platform authenticator is requested for U2F devices.
Kim Paulhamus3eef95f2018-04-03 10:37:451096TEST_F(AuthenticatorImplTest, MakeCredentialPlatformAuthenticator) {
Martin Kreichgauer55834402020-08-03 21:54:311097 NavigateAndCommit(GURL(kTestOrigin1));
Kim Paulhamus3eef95f2018-04-03 10:37:451098
1099 PublicKeyCredentialCreationOptionsPtr options =
1100 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauerbece642a2021-12-07 21:02:461101 options->authenticator_selection->authenticator_attachment =
1102 device::AuthenticatorAttachment::kPlatform;
Kim Paulhamus3eef95f2018-04-03 10:37:451103
Martin Kreichgauer55834402020-08-03 21:54:311104 EXPECT_EQ(
1105 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
1106 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanan23dce912024-07-11 16:41:271107 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUiTimeout,
Ken Buchanan1ea549c12024-10-10 21:31:051108 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamuse4573992018-03-13 18:46:251109}
1110
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421111TEST_F(AuthenticatorImplTest, GetClientCapabilities) {
Adem Derinel8601e782025-05-19 08:04:441112 base::test::ScopedFeatureList feature_list;
1113 feature_list.InitWithFeatureState(device::kWebAuthnImmediateGet, false);
1114
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421115 NavigateAndCommit(GURL(kTestOrigin1));
1116
1117 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1118
1119 std::vector<std::string> capability_names;
Peter Kasting1557e5f2025-01-28 01:14:081120 std::ranges::transform(
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421121 capabilities, std::back_inserter(capability_names),
1122 [](const auto& capability) { return capability->name; });
1123
1124 const std::vector<std::string_view> kRequiredCapabilities = {
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421125 client_capabilities::kConditionalGet,
1126 client_capabilities::kHybridTransport,
1127 client_capabilities::kPasskeyPlatformAuthenticator,
1128 client_capabilities::kUserVerifyingPlatformAuthenticator,
1129 client_capabilities::kRelatedOrigins,
Martin Kreichgauera57d2f12025-03-12 16:47:451130 client_capabilities::kConditionalCreate,
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421131 };
1132
1133 // Ensure no extra capabilities
1134 EXPECT_EQ(kRequiredCapabilities.size(), capabilities.size());
1135
1136 // Check that each required capability is present exactly once.
1137 for (const auto& capability : kRequiredCapabilities) {
1138 EXPECT_EQ(1u, static_cast<size_t>(
Peter Kasting1557e5f2025-01-28 01:14:081139 std::ranges::count(capability_names, capability)));
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421140 }
1141}
1142
Andrii Natiahlyie480a492024-09-18 15:20:371143TEST_F(AuthenticatorImplTest, GetClientCapabilities_HybridTransportSupported) {
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421144 NavigateAndCommit(GURL(kTestOrigin1));
Andrii Natiahlyie480a492024-09-18 15:20:371145 EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true));
1146 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1147 ExpectCapability(capabilities, client_capabilities::kHybridTransport, true);
1148}
1149
1150TEST_F(AuthenticatorImplTest,
1151 GetClientCapabilities_HybridTransport_NoBluetoothAdapter) {
1152 NavigateAndCommit(GURL(kTestOrigin1));
1153 EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(false));
1154 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1155 ExpectCapability(capabilities, client_capabilities::kHybridTransport, false);
1156}
1157
1158TEST_F(AuthenticatorImplTest,
Andrii Natiahlyie480a492024-09-18 15:20:371159 GetClientCapabilities_HybridTransport_LowEnergyNotSupported) {
1160 SetBluetoothLESupported(false);
1161
1162 NavigateAndCommit(GURL(kTestOrigin1));
1163 EXPECT_CALL(*mock_adapter_, IsPresent).Times(0);
Andrii Natiahlyi6b2f4b12024-09-03 14:58:421164 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1165 ExpectCapability(capabilities, client_capabilities::kHybridTransport, false);
1166}
1167
1168TEST_F(AuthenticatorImplTest, GetClientCapabilities_RelatedOrigins) {
1169 NavigateAndCommit(GURL(kTestOrigin1));
1170 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1171 ExpectCapability(capabilities, client_capabilities::kRelatedOrigins, true);
1172}
1173
Martin Kreichgauera57d2f12025-03-12 16:47:451174TEST_F(AuthenticatorImplTest, GetClientCapabilities_ConditonalCreate) {
1175 for (const bool enabled : {false, true}) {
1176 base::test::ScopedFeatureList feature_list;
1177 feature_list.InitWithFeatureState(device::kWebAuthnPasskeyUpgrade, enabled);
1178 NavigateAndCommit(GURL(kTestOrigin1));
1179 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1180 ExpectCapability(capabilities, client_capabilities::kConditionalCreate,
1181 enabled);
1182 }
1183}
1184
Adem Derineld5640322025-04-24 09:14:061185TEST_F(AuthenticatorImplTest, GetClientCapabilities_ImmediateGet) {
1186 for (const bool enabled : {false, true}) {
1187 base::test::ScopedFeatureList feature_list;
1188 feature_list.InitWithFeatureState(device::kWebAuthnImmediateGet, enabled);
1189 NavigateAndCommit(GURL(kTestOrigin1));
1190 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
1191 ExpectCapability(capabilities, client_capabilities::kImmediateGet,
1192 enabled ? std::optional<bool>(true) : std::nullopt);
1193 }
1194}
1195
Adam Langley9f4fb5a2018-02-06 17:17:181196// Parses its arguments as JSON and expects that all the keys in the first are
1197// also in the second, and with the same value.
Md Hasibul Hasana963a9342024-04-03 10:15:141198static void CheckJSONIsSubsetOfJSON(std::string_view subset_str,
1199 std::string_view test_str) {
Arthur Sonzognic686e8f2024-01-11 08:36:371200 std::optional<base::Value> subset = base::JSONReader::Read(subset_str);
Adam Langley9f4fb5a2018-02-06 17:17:181201 ASSERT_TRUE(subset);
1202 ASSERT_TRUE(subset->is_dict());
Daniel Cheng249aa4f2022-07-13 18:26:311203 const base::Value::Dict& subset_dict = subset->GetDict();
Arthur Sonzognic686e8f2024-01-11 08:36:371204 std::optional<base::Value> test = base::JSONReader::Read(test_str);
Adam Langley9f4fb5a2018-02-06 17:17:181205 ASSERT_TRUE(test);
1206 ASSERT_TRUE(test->is_dict());
Daniel Cheng249aa4f2022-07-13 18:26:311207 const base::Value::Dict& test_dict = test->GetDict();
Adam Langley9f4fb5a2018-02-06 17:17:181208
Daniel Cheng249aa4f2022-07-13 18:26:311209 for (auto item : subset_dict) {
1210 const base::Value* test_value = test_dict.Find(item.first);
Adam Langley9f4fb5a2018-02-06 17:17:181211 if (test_value == nullptr) {
1212 ADD_FAILURE() << item.first << " does not exist in the test dictionary";
1213 continue;
1214 }
1215
Daniel Cheng249aa4f2022-07-13 18:26:311216 EXPECT_EQ(item.second, *test_value);
Adam Langley9f4fb5a2018-02-06 17:17:181217 }
1218}
1219
Kim Paulhamusfbe092bf2017-11-21 16:46:081220// Test that client data serializes to JSON properly.
Martin Kreichgauer55834402020-08-03 21:54:311221TEST(ClientDataSerializationTest, Register) {
Martin Kreichgauer0b1f5592021-07-02 22:28:251222 CheckJSONIsSubsetOfJSON(
1223 kTestRegisterClientDataJsonString,
1224 GetTestClientDataJSON(ClientDataRequestType::kWebAuthnCreate));
Kim Paulhamusfbe092bf2017-11-21 16:46:081225}
1226
Martin Kreichgauer55834402020-08-03 21:54:311227TEST(ClientDataSerializationTest, Sign) {
Martin Kreichgauer0b1f5592021-07-02 22:28:251228 CheckJSONIsSubsetOfJSON(
1229 kTestSignClientDataJsonString,
1230 GetTestClientDataJSON(ClientDataRequestType::kWebAuthnGet));
Adam Langley72d721e2018-02-23 19:37:081231}
1232
Kim Paulhamus9d87fb0d2018-01-18 18:36:221233TEST_F(AuthenticatorImplTest, TestMakeCredentialTimeout) {
Ken Buchanand5edc0782024-06-10 22:01:221234 base::HistogramTester histogram_tester;
1235
Martin Kreichgauerc5e427a2021-02-24 02:35:591236 // Don't provide an authenticator tap so the request times out.
1237 virtual_device_factory_->mutable_state()->simulate_press_callback =
1238 base::BindLambdaForTesting(
1239 [&](device::VirtualFidoDevice* device) { return false; });
Martin Kreichgauer55834402020-08-03 21:54:311240 NavigateAndCommit(GURL(kTestOrigin1));
1241
Kim Paulhamus31998d22018-02-10 22:28:481242 PublicKeyCredentialCreationOptionsPtr options =
1243 GetTestPublicKeyCredentialCreationOptions();
Kim Paulhamus40de29e42017-12-07 04:14:081244
Martin Kreichgauer55834402020-08-03 21:54:311245 EXPECT_EQ(
1246 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
1247 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanand5edc0782024-06-10 22:01:221248 histogram_tester.ExpectUniqueSample(
1249 "WebAuthentication.MakeCredential.Result",
1250 AuthenticatorCommonImpl::CredentialRequestResult::kTimeout, 1);
Ken Buchanan23dce912024-07-11 16:41:271251 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUiTimeout,
Ken Buchanan1ea549c12024-10-10 21:31:051252 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamus40de29e42017-12-07 04:14:081253}
1254
Adam Langleydc639a92018-02-28 17:18:421255// Verify behavior for various combinations of origins and RP IDs.
Kim Paulhamus94331812018-01-18 21:17:321256TEST_F(AuthenticatorImplTest, GetAssertionOriginAndRpIds) {
1257 // These instances should return security errors (for circumstances
1258 // that would normally crash the renderer).
Adem Derinel620520b42024-11-04 15:45:441259 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
1260 SCOPED_TRACE(
1261 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Adam Langleydc639a92018-02-28 17:18:421262
Kim Paulhamus94331812018-01-18 21:17:321263 NavigateAndCommit(GURL(test_case.origin));
Martin Kreichgauer55834402020-08-03 21:54:311264
Kim Paulhamus94331812018-01-18 21:17:321265 PublicKeyCredentialRequestOptionsPtr options =
1266 GetTestPublicKeyCredentialRequestOptions();
Adam Langleya4ace032018-04-04 20:14:271267 options->relying_party_id = test_case.claimed_authority;
Kim Paulhamus94331812018-01-18 21:17:321268
Martin Kreichgauer55834402020-08-03 21:54:311269 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
1270 test_case.expected_status);
Kim Paulhamus94331812018-01-18 21:17:321271 }
1272}
1273
Gabriel Viera7bc08f212024-07-10 15:42:331274// Verify behavior for various combinations of origins and RP IDs.
1275TEST_F(AuthenticatorImplTest, ReportOriginAndRpIds) {
1276 // These instances should return security errors (for circumstances
1277 // that would normally crash the renderer).
Adem Derinel620520b42024-11-04 15:45:441278 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
1279 SCOPED_TRACE(
1280 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Gabriel Viera7bc08f212024-07-10 15:42:331281
1282 NavigateAndCommit(GURL(test_case.origin));
1283 PublicKeyCredentialReportOptionsPtr options =
1284 GetTestPublicKeyCredentialReportOptions();
1285 options->relying_party_id = test_case.claimed_authority;
Nina Satragnocb0406e2024-09-09 19:47:481286 options->unknown_credential_id = std::vector<uint8_t>(32, 0x0A);
Gabriel Viera7bc08f212024-07-10 15:42:331287
1288 EXPECT_EQ(AuthenticatorReport(std::move(options)),
1289 test_case.expected_status);
1290 }
1291}
1292
Kalvin Lee4c50a3fd2025-02-27 15:00:161293constexpr auto kValidAppIdCases = std::to_array<OriginClaimedAuthorityPair>({
Ken Buchanana36345d2019-11-01 17:48:301294 {"https://example.com", "https://example.com",
1295 AuthenticatorStatus::SUCCESS},
1296 {"https://www.example.com", "https://example.com",
1297 AuthenticatorStatus::SUCCESS},
1298 {"https://example.com", "https://www.example.com",
1299 AuthenticatorStatus::SUCCESS},
1300 {"https://example.com", "https://foo.bar.example.com",
1301 AuthenticatorStatus::SUCCESS},
1302 {"https://example.com", "https://foo.bar.example.com/foo/bar",
1303 AuthenticatorStatus::SUCCESS},
1304 {"https://google.com", "https://www.gstatic.com/securitykey/origins.json",
1305 AuthenticatorStatus::SUCCESS},
Adam Langleydc639a92018-02-28 17:18:421306 {"https://www.google.com",
Ken Buchanana36345d2019-11-01 17:48:301307 "https://www.gstatic.com/securitykey/origins.json",
1308 AuthenticatorStatus::SUCCESS},
Adam Langleydc639a92018-02-28 17:18:421309 {"https://www.google.com",
Ken Buchanana36345d2019-11-01 17:48:301310 "https://www.gstatic.com/securitykey/a/google.com/origins.json",
1311 AuthenticatorStatus::SUCCESS},
Adam Langley42f5dd52018-03-19 21:23:221312 {"https://accounts.google.com",
Ken Buchanana36345d2019-11-01 17:48:301313 "https://www.gstatic.com/securitykey/origins.json",
1314 AuthenticatorStatus::SUCCESS},
Adem Derinel620520b42024-11-04 15:45:441315});
Adam Langleydc639a92018-02-28 17:18:421316
1317// Verify behavior for various combinations of origins and RP IDs.
Adam Langley5a5d5af2018-08-02 15:28:511318TEST_F(AuthenticatorImplTest, AppIdExtensionValues) {
Adam Langleya4ace032018-04-04 20:14:271319 for (const auto& test_case : kValidAppIdCases) {
Adem Derinel620520b42024-11-04 15:45:441320 SCOPED_TRACE(
1321 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Adam Langleydc639a92018-02-28 17:18:421322
Martin Kreichgauer4138eab2019-05-21 07:05:551323 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
Adam Langleya4ace032018-04-04 20:14:271324 TryAuthenticationWithAppId(test_case.origin,
1325 test_case.claimed_authority));
Adam Langley0616cd42019-08-08 22:31:101326
1327 EXPECT_EQ(AuthenticatorStatus::SUCCESS,
1328 TryRegistrationWithAppIdExclude(test_case.origin,
1329 test_case.claimed_authority));
Adam Langleydc639a92018-02-28 17:18:421330 }
1331
Adam Langleya4ace032018-04-04 20:14:271332 // All the invalid relying party test cases should also be invalid as AppIDs.
Adem Derinel620520b42024-11-04 15:45:441333 for (const auto& test_case : kInvalidRpTestCases) {
1334 SCOPED_TRACE(
1335 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Adam Langleya4ace032018-04-04 20:14:271336
Adem Derinel620520b42024-11-04 15:45:441337 if (test_case.claimed_authority.empty()) {
Adam Langleya4ace032018-04-04 20:14:271338 // In this case, no AppID is actually being tested.
1339 continue;
1340 }
1341
Ken Buchanana36345d2019-11-01 17:48:301342 AuthenticatorStatus test_status = TryAuthenticationWithAppId(
1343 test_case.origin, test_case.claimed_authority);
1344 EXPECT_TRUE(test_status == AuthenticatorStatus::INVALID_DOMAIN ||
1345 test_status == test_case.expected_status);
Adam Langley0616cd42019-08-08 22:31:101346
Ken Buchanana36345d2019-11-01 17:48:301347 test_status = TryRegistrationWithAppIdExclude(test_case.origin,
1348 test_case.claimed_authority);
1349 EXPECT_TRUE(test_status == AuthenticatorStatus::INVALID_DOMAIN ||
1350 test_status == test_case.expected_status);
Adam Langleya4ace032018-04-04 20:14:271351 }
Adam Langleydc639a92018-02-28 17:18:421352}
1353
Adam Langley5a5d5af2018-08-02 15:28:511354// Verify that a credential registered with U2F can be used via webauthn.
1355TEST_F(AuthenticatorImplTest, AppIdExtension) {
Martin Kreichgauer55834402020-08-03 21:54:311356 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley5a5d5af2018-08-02 15:28:511357
Adam Langleyce352262018-08-10 17:38:221358 {
1359 // First, test that the appid extension isn't echoed at all when not
1360 // requested.
1361 PublicKeyCredentialRequestOptionsPtr options =
1362 GetTestPublicKeyCredentialRequestOptions();
Nina Satragnoacf403f92019-05-23 17:16:521363 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461364 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langley5a5d5af2018-08-02 15:28:511365
Martin Kreichgauer55834402020-08-03 21:54:311366 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
1367 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:251368 EXPECT_EQ(result.response->extensions->echo_appid_extension, false);
Adam Langleyce352262018-08-10 17:38:221369 }
Adam Langley5a5d5af2018-08-02 15:28:511370
Adam Langleyce352262018-08-10 17:38:221371 {
1372 // Second, test that the appid extension is echoed, but is false, when appid
1373 // is requested but not used.
Nina Satragnoacf403f92019-05-23 17:16:521374 ResetVirtualDevice();
Adam Langleyce352262018-08-10 17:38:221375 PublicKeyCredentialRequestOptionsPtr options =
1376 GetTestPublicKeyCredentialRequestOptions();
Nina Satragnoacf403f92019-05-23 17:16:521377 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461378 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langleyce352262018-08-10 17:38:221379
1380 // This AppID won't be used because the RP ID will be tried (successfully)
1381 // first.
Slobodan Pejicc8ce88b2023-06-19 15:41:121382 options->extensions->appid = kTestOrigin1;
Adam Langleyce352262018-08-10 17:38:221383
Martin Kreichgauer55834402020-08-03 21:54:311384 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
1385 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:251386 EXPECT_EQ(result.response->extensions->echo_appid_extension, true);
1387 EXPECT_EQ(result.response->extensions->appid_extension, false);
Adam Langleyce352262018-08-10 17:38:221388 }
1389
1390 {
1391 // Lastly, when used, the appid extension result should be "true".
Nina Satragnoacf403f92019-05-23 17:16:521392 ResetVirtualDevice();
Adam Langleyce352262018-08-10 17:38:221393 PublicKeyCredentialRequestOptionsPtr options =
1394 GetTestPublicKeyCredentialRequestOptions();
Adam Langleyce352262018-08-10 17:38:221395 // Inject a registration for the URL (which is a U2F AppID).
Nina Satragnoacf403f92019-05-23 17:16:521396 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461397 options->allow_credentials[0].id, kTestOrigin1));
Adam Langleyce352262018-08-10 17:38:221398
Slobodan Pejicc8ce88b2023-06-19 15:41:121399 options->extensions->appid = kTestOrigin1;
Adam Langleyce352262018-08-10 17:38:221400
Martin Kreichgauer55834402020-08-03 21:54:311401 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
1402 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:251403 EXPECT_EQ(result.response->extensions->echo_appid_extension, true);
1404 EXPECT_EQ(result.response->extensions->appid_extension, true);
Adam Langleyce352262018-08-10 17:38:221405 }
Adam Langleyab182012019-05-17 17:22:021406
1407 {
1408 // AppID should still work when the authenticator supports credProtect.
Nina Satragnoacf403f92019-05-23 17:16:521409 ResetVirtualDevice();
Adam Langleyab182012019-05-17 17:22:021410 device::VirtualCtap2Device::Config config;
1411 config.u2f_support = true;
1412 config.pin_support = true;
1413 config.resident_key_support = true;
1414 config.cred_protect_support = true;
1415
Nina Satragnoacf403f92019-05-23 17:16:521416 virtual_device_factory_->SetCtap2Config(config);
Adam Langleyab182012019-05-17 17:22:021417
1418 // Inject a registration for the URL (which is a U2F AppID).
1419 PublicKeyCredentialRequestOptionsPtr options =
1420 GetTestPublicKeyCredentialRequestOptions();
Nina Satragnoacf403f92019-05-23 17:16:521421 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461422 options->allow_credentials[0].id, kTestOrigin1));
Adam Langleyab182012019-05-17 17:22:021423
Slobodan Pejicc8ce88b2023-06-19 15:41:121424 options->extensions->appid = kTestOrigin1;
Adam Langleyab182012019-05-17 17:22:021425
Martin Kreichgauer55834402020-08-03 21:54:311426 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
1427 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:251428 EXPECT_EQ(result.response->extensions->echo_appid_extension, true);
1429 EXPECT_EQ(result.response->extensions->appid_extension, true);
Adam Langleyab182012019-05-17 17:22:021430 }
Adam Langley5a5d5af2018-08-02 15:28:511431}
1432
Adam Langley0616cd42019-08-08 22:31:101433TEST_F(AuthenticatorImplTest, AppIdExcludeExtension) {
Martin Kreichgauer55834402020-08-03 21:54:311434 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley0616cd42019-08-08 22:31:101435
1436 // Attempt to register a credential using the appidExclude extension. It
1437 // should fail when the registration already exists on the authenticator.
1438 for (bool credential_already_exists : {false, true}) {
1439 SCOPED_TRACE(credential_already_exists);
1440
1441 for (bool is_ctap2 : {false, true}) {
1442 SCOPED_TRACE(is_ctap2);
1443
1444 ResetVirtualDevice();
1445 virtual_device_factory_->SetSupportedProtocol(
1446 is_ctap2 ? device::ProtocolVersion::kCtap2
1447 : device::ProtocolVersion::kU2f);
1448
1449 PublicKeyCredentialCreationOptionsPtr options =
1450 GetTestPublicKeyCredentialCreationOptions();
1451 options->appid_exclude = kTestOrigin1;
1452 options->exclude_credentials = GetTestCredentials();
1453
1454 if (credential_already_exists) {
1455 ASSERT_TRUE(
1456 virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461457 options->exclude_credentials[0].id, kTestOrigin1));
Adam Langley0616cd42019-08-08 22:31:101458 }
1459
Martin Kreichgauer55834402020-08-03 21:54:311460 MakeCredentialResult result =
1461 AuthenticatorMakeCredential(std::move(options));
Adam Langley0616cd42019-08-08 22:31:101462
1463 if (credential_already_exists) {
Martin Kreichgauer55834402020-08-03 21:54:311464 ASSERT_EQ(result.status, AuthenticatorStatus::CREDENTIAL_EXCLUDED);
Adam Langley0616cd42019-08-08 22:31:101465 } else {
Martin Kreichgauer55834402020-08-03 21:54:311466 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Adam Langley0616cd42019-08-08 22:31:101467 }
1468 }
1469 }
Adam Langley407d81d02020-02-21 02:18:161470
1471 {
1472 // Using appidExclude with an empty exclude list previously caused a crash.
1473 // See https://bugs.chromium.org/p/chromium/issues/detail?id=1054499.
1474 virtual_device_factory_->SetSupportedProtocol(
1475 device::ProtocolVersion::kCtap2);
1476 PublicKeyCredentialCreationOptionsPtr options =
1477 GetTestPublicKeyCredentialCreationOptions();
1478 options->appid_exclude = kTestOrigin1;
1479 options->exclude_credentials.clear();
Martin Kreichgauer55834402020-08-03 21:54:311480 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
1481 AuthenticatorStatus::SUCCESS);
Adam Langley407d81d02020-02-21 02:18:161482 }
1483
1484 {
1485 // Also test the case where all credential IDs are eliminated because of
1486 // their size.
1487 device::VirtualCtap2Device::Config config;
1488 config.max_credential_count_in_list = 1;
1489 config.max_credential_id_length = 1;
1490 virtual_device_factory_->SetCtap2Config(config);
1491
1492 PublicKeyCredentialCreationOptionsPtr options =
1493 GetTestPublicKeyCredentialCreationOptions();
1494 options->appid_exclude = kTestOrigin1;
1495 options->exclude_credentials = GetTestCredentials();
1496
1497 for (const auto& cred : options->exclude_credentials) {
Martin Kreichgauerbece642a2021-12-07 21:02:461498 ASSERT_GT(cred.id.size(), config.max_credential_id_length);
Adam Langley407d81d02020-02-21 02:18:161499 }
1500
Martin Kreichgauer55834402020-08-03 21:54:311501 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
1502 AuthenticatorStatus::SUCCESS);
Adam Langley407d81d02020-02-21 02:18:161503 }
Adam Langley0616cd42019-08-08 22:31:101504}
1505
Kim Paulhamus94331812018-01-18 21:17:321506TEST_F(AuthenticatorImplTest, TestGetAssertionTimeout) {
Nina Satragnoacf403f92019-05-23 17:16:521507 // The VirtualFidoAuthenticator simulates a tap immediately after it gets the
1508 // request. Replace by the real discovery that will wait until timeout.
Jagadesh P8db17bf2023-11-28 04:14:151509 ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>());
1510
Martin Kreichgauer55834402020-08-03 21:54:311511 NavigateAndCommit(GURL(kTestOrigin1));
Nina Satragno129251c2023-10-23 21:50:401512 base::HistogramTester histogram_tester;
Kim Paulhamus9d87fb0d2018-01-18 18:36:221513 PublicKeyCredentialRequestOptionsPtr options =
1514 GetTestPublicKeyCredentialRequestOptions();
Kim Paulhamus94331812018-01-18 21:17:321515
Martin Kreichgauer55834402020-08-03 21:54:311516 EXPECT_EQ(
1517 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
1518 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragno129251c2023-10-23 21:50:401519 histogram_tester.ExpectUniqueSample(
1520 "WebAuthentication.GetAssertion.Result",
Ken Buchanand5edc0782024-06-10 22:01:221521 AuthenticatorCommonImpl::CredentialRequestResult::kTimeout, 1);
Ken Buchanan23dce912024-07-11 16:41:271522 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUiTimeout,
Ken Buchanan1ea549c12024-10-10 21:31:051523 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamus9d87fb0d2018-01-18 18:36:221524}
Adam Langley31caeef42018-03-22 03:44:101525
Adam Langleyce160592018-04-05 18:38:121526TEST_F(AuthenticatorImplTest, OversizedCredentialId) {
Adam Langleyce160592018-04-05 18:38:121527 // 255 is the maximum size of a U2F credential ID. We also test one greater
1528 // (256) to ensure that nothing untoward happens.
1529 const std::vector<size_t> kSizes = {255, 256};
1530
Adam Langleyb4a2270a2018-04-05 21:47:151531 for (const size_t size : kSizes) {
Adam Langleyce160592018-04-05 18:38:121532 SCOPED_TRACE(size);
1533
Martin Kreichgauer55834402020-08-03 21:54:311534 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langleyce160592018-04-05 18:38:121535 PublicKeyCredentialRequestOptionsPtr options =
1536 GetTestPublicKeyCredentialRequestOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:041537 device::PublicKeyCredentialDescriptor credential;
Martin Kreichgauerbece642a2021-12-07 21:02:461538 credential.credential_type = device::CredentialType::kPublicKey;
1539 credential.id.resize(size);
1540 credential.transports.emplace(
Ken Buchanan4a33ef0d2019-06-20 17:34:041541 device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
Adam Langleyce160592018-04-05 18:38:121542
1543 const bool should_be_valid = size < 256;
1544 if (should_be_valid) {
Nina Satragnoacf403f92019-05-23 17:16:521545 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461546 credential.id, kTestRelyingPartyId));
Adam Langleyce160592018-04-05 18:38:121547 }
1548
Ken Buchanan4a33ef0d2019-06-20 17:34:041549 options->allow_credentials.emplace_back(credential);
Adam Langleyce160592018-04-05 18:38:121550
Martin Kreichgauer55834402020-08-03 21:54:311551 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
1552 should_be_valid ? AuthenticatorStatus::SUCCESS
1553 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langleyce160592018-04-05 18:38:121554 }
1555}
1556
Adam Langleyebfd9872022-07-12 20:26:081557TEST_F(AuthenticatorImplTest, NoSilentAuthenticationForCable) {
Ken Buchanan19fb9c52021-12-03 20:15:511558 // https://crbug.com/954355
Martin Kreichgauer55834402020-08-03 21:54:311559 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley2a9355a2019-04-23 00:11:021560
1561 for (bool is_cable_device : {false, true}) {
Nina Satragnoacf403f92019-05-23 17:16:521562 ResetVirtualDevice();
Adam Langley2a9355a2019-04-23 00:11:021563 device::VirtualCtap2Device::Config config;
1564 config.reject_silent_authentication_requests = true;
Nina Satragnoacf403f92019-05-23 17:16:521565 virtual_device_factory_->SetCtap2Config(config);
Adam Langley2a9355a2019-04-23 00:11:021566
1567 PublicKeyCredentialRequestOptionsPtr options =
1568 GetTestPublicKeyCredentialRequestOptions();
1569 options->allow_credentials = GetTestCredentials(/*num_credentials=*/2);
Slobodan Pejicc8ce88b2023-06-19 15:41:121570 options->extensions->cable_authentication_data = GetTestCableExtension();
Adam Langley2a9355a2019-04-23 00:11:021571
1572 if (is_cable_device) {
Nina Satragnoacf403f92019-05-23 17:16:521573 virtual_device_factory_->SetTransport(
Adam Langleycbafdd4f2022-08-15 02:48:231574 device::FidoTransportProtocol::kHybrid);
Adam Langley2a9355a2019-04-23 00:11:021575 for (auto& cred : options->allow_credentials) {
Martin Kreichgauerbece642a2021-12-07 21:02:461576 cred.transports.clear();
Adam Langleycbafdd4f2022-08-15 02:48:231577 cred.transports.emplace(device::FidoTransportProtocol::kHybrid);
Adam Langley2a9355a2019-04-23 00:11:021578 }
1579 }
1580
Nina Satragnoacf403f92019-05-23 17:16:521581 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461582 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langley2a9355a2019-04-23 00:11:021583
Martin Kreichgauer55834402020-08-03 21:54:311584 // If a caBLE device is not simulated then silent requests should be used.
1585 // The virtual device will return an error because
1586 // |reject_silent_authentication_requests| is true and then it'll
1587 // immediately resolve the touch request.
1588 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
1589 is_cable_device ? AuthenticatorStatus::SUCCESS
1590 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langley2a9355a2019-04-23 00:11:021591 }
1592}
1593
Adam Langley0abb556a2023-06-08 22:39:541594TEST_F(AuthenticatorImplTest, GuessAtTransportsForCable) {
1595 // Even without any reported transports, if the transaction was done over
1596 // hybrid, we should guess at the transports and report them.
1597
1598 NavigateAndCommit(GURL(kTestOrigin1));
1599
1600 ResetVirtualDevice();
1601 device::VirtualCtap2Device::Config config;
1602 config.include_transports_in_attestation_certificate = false;
1603 virtual_device_factory_->SetCtap2Config(config);
1604 virtual_device_factory_->SetTransport(device::FidoTransportProtocol::kHybrid);
1605
1606 const auto result = AuthenticatorMakeCredential();
1607 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
1608 EXPECT_THAT(
1609 result.response->transports,
1610 testing::UnorderedElementsAre(device::FidoTransportProtocol::kHybrid,
1611 device::FidoTransportProtocol::kInternal));
1612}
1613
Jun Choi0e56e5dd2018-06-08 21:27:341614TEST_F(AuthenticatorImplTest, TestGetAssertionU2fDeviceBackwardsCompatibility) {
Martin Kreichgauer55834402020-08-03 21:54:311615 NavigateAndCommit(GURL(kTestOrigin1));
Jun Choi17bbafc2018-04-11 08:37:201616 PublicKeyCredentialRequestOptionsPtr options =
1617 GetTestPublicKeyCredentialRequestOptions();
Jun Choi0e56e5dd2018-06-08 21:27:341618 // Inject credential ID to the virtual device so that successful sign in is
1619 // possible.
Nina Satragnoacf403f92019-05-23 17:16:521620 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461621 options->allow_credentials[0].id, kTestRelyingPartyId));
Jun Choi0e56e5dd2018-06-08 21:27:341622
Martin Kreichgauer55834402020-08-03 21:54:311623 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
1624 AuthenticatorStatus::SUCCESS);
Kim Paulhamus19b1cd52018-05-14 20:50:051625}
1626
Kim Paulhamus595abc652018-04-12 20:42:511627TEST_F(AuthenticatorImplTest, GetAssertionWithEmptyAllowCredentials) {
Martin Kreichgauer55834402020-08-03 21:54:311628 NavigateAndCommit(GURL(kTestOrigin1));
Kim Paulhamus595abc652018-04-12 20:42:511629 PublicKeyCredentialRequestOptionsPtr options =
1630 GetTestPublicKeyCredentialRequestOptions();
1631 options->allow_credentials.clear();
Kim Paulhamus595abc652018-04-12 20:42:511632
Martin Kreichgauer55834402020-08-03 21:54:311633 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
1634 AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
Ken Buchanan23dce912024-07-11 16:41:271635 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kRkNotSupported,
Ken Buchanan1ea549c12024-10-10 21:31:051636 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamus183d1922018-04-20 23:27:091637}
1638
1639TEST_F(AuthenticatorImplTest, MakeCredentialAlreadyRegistered) {
Martin Kreichgauer55834402020-08-03 21:54:311640 NavigateAndCommit(GURL(kTestOrigin1));
Kim Paulhamus183d1922018-04-20 23:27:091641 PublicKeyCredentialCreationOptionsPtr options =
1642 GetTestPublicKeyCredentialCreationOptions();
1643
1644 // Exclude the one already registered credential.
Martin Kreichgauer43558c72019-04-06 22:15:371645 options->exclude_credentials = GetTestCredentials();
Nina Satragnoacf403f92019-05-23 17:16:521646 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461647 options->exclude_credentials[0].id, kTestRelyingPartyId));
Kim Paulhamus183d1922018-04-20 23:27:091648
Martin Kreichgauer55834402020-08-03 21:54:311649 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
1650 AuthenticatorStatus::CREDENTIAL_EXCLUDED);
Ken Buchanan23dce912024-07-11 16:41:271651 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kCredentialExcluded,
Ken Buchanan1ea549c12024-10-10 21:31:051652 AuthenticationRequestMode::kModalWebAuthn);
Kim Paulhamus183d1922018-04-20 23:27:091653}
1654
1655TEST_F(AuthenticatorImplTest, MakeCredentialPendingRequest) {
Martin Kreichgauer55834402020-08-03 21:54:311656 NavigateAndCommit(GURL(kTestOrigin1));
Mario Sanchez Pradac791f54e2019-07-09 23:38:371657 mojo::Remote<blink::mojom::Authenticator> authenticator =
1658 ConnectToAuthenticator();
Kim Paulhamus183d1922018-04-20 23:27:091659
1660 // Make first request.
1661 PublicKeyCredentialCreationOptionsPtr options =
1662 GetTestPublicKeyCredentialCreationOptions();
Adem Derinele6378762024-06-27 06:05:071663 TestMakeCredentialFuture future;
1664 authenticator->MakeCredential(std::move(options), future.GetCallback());
Kim Paulhamus183d1922018-04-20 23:27:091665
1666 // Make second request.
Alison Gale770f3fc2024-04-27 00:39:581667 // TODO(crbug.com/41355992): Rework to ensure there are potential race
Kim Paulhamus183d1922018-04-20 23:27:091668 // conditions once we have VirtualAuthenticatorEnvironment.
1669 PublicKeyCredentialCreationOptionsPtr options2 =
1670 GetTestPublicKeyCredentialCreationOptions();
Adem Derinele6378762024-06-27 06:05:071671 TestMakeCredentialFuture future2;
1672 authenticator->MakeCredential(std::move(options2), future2.GetCallback());
1673 EXPECT_TRUE(future2.Wait());
Kim Paulhamus183d1922018-04-20 23:27:091674
Adem Derinele6378762024-06-27 06:05:071675 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, std::get<0>(future2.Get()));
Adam Langley2cffe382018-06-12 01:52:541676
Adem Derinele6378762024-06-27 06:05:071677 EXPECT_TRUE(future.Wait());
Kim Paulhamus183d1922018-04-20 23:27:091678}
1679
1680TEST_F(AuthenticatorImplTest, GetAssertionPendingRequest) {
Martin Kreichgauer55834402020-08-03 21:54:311681 NavigateAndCommit(GURL(kTestOrigin1));
Mario Sanchez Pradac791f54e2019-07-09 23:38:371682 mojo::Remote<blink::mojom::Authenticator> authenticator =
1683 ConnectToAuthenticator();
Kim Paulhamus183d1922018-04-20 23:27:091684
1685 // Make first request.
1686 PublicKeyCredentialRequestOptionsPtr options =
1687 GetTestPublicKeyCredentialRequestOptions();
Adem Derinel72e11db2025-02-11 15:58:001688 TestGetCredentialFuture future;
1689 authenticator->GetCredential(std::move(options), future.GetCallback());
Kim Paulhamus183d1922018-04-20 23:27:091690
1691 // Make second request.
Alison Gale770f3fc2024-04-27 00:39:581692 // TODO(crbug.com/41355992): Rework to ensure there are potential race
Kim Paulhamus183d1922018-04-20 23:27:091693 // conditions once we have VirtualAuthenticatorEnvironment.
1694 PublicKeyCredentialRequestOptionsPtr options2 =
1695 GetTestPublicKeyCredentialRequestOptions();
Adem Derinel72e11db2025-02-11 15:58:001696 TestGetCredentialFuture future2;
1697 authenticator->GetCredential(std::move(options2), future2.GetCallback());
Adem Derinele6378762024-06-27 06:05:071698 EXPECT_TRUE(future2.Wait());
Kim Paulhamus183d1922018-04-20 23:27:091699
Adem Derinel72e11db2025-02-11 15:58:001700 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST,
1701 future2.Get()->get_get_assertion_response()->status);
Adam Langley2cffe382018-06-12 01:52:541702
Adem Derinele6378762024-06-27 06:05:071703 EXPECT_TRUE(future.Wait());
Adam Langley2cffe382018-06-12 01:52:541704}
1705
Gabriel Viera7bc08f212024-07-10 15:42:331706TEST_F(AuthenticatorImplTest, ReportPendingRequest) {
1707 NavigateAndCommit(GURL(kTestOrigin1));
1708 mojo::Remote<blink::mojom::Authenticator> authenticator =
1709 ConnectToAuthenticator();
1710
1711 // Make first request.
1712 PublicKeyCredentialRequestOptionsPtr options =
1713 GetTestPublicKeyCredentialRequestOptions();
Adem Derinel72e11db2025-02-11 15:58:001714 TestGetCredentialFuture future;
1715 authenticator->GetCredential(std::move(options), future.GetCallback());
Gabriel Viera7bc08f212024-07-10 15:42:331716
1717 // Make second request.
1718 PublicKeyCredentialReportOptionsPtr options2 =
1719 GetTestPublicKeyCredentialReportOptions();
1720 TestReportFuture future2;
1721 authenticator->Report(std::move(options2), future2.GetCallback());
1722 EXPECT_TRUE(future2.Wait());
1723
1724 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, std::get<0>(future2.Get()));
1725
1726 EXPECT_TRUE(future.Wait());
1727}
1728
Adam Langley2cffe382018-06-12 01:52:541729TEST_F(AuthenticatorImplTest, NavigationDuringOperation) {
Martin Kreichgauer55834402020-08-03 21:54:311730 NavigateAndCommit(GURL(kTestOrigin1));
Mario Sanchez Pradac791f54e2019-07-09 23:38:371731 mojo::Remote<blink::mojom::Authenticator> authenticator =
1732 ConnectToAuthenticator();
Adam Langley2cffe382018-06-12 01:52:541733
1734 base::RunLoop run_loop;
Mario Sanchez Pradac791f54e2019-07-09 23:38:371735 authenticator.set_disconnect_handler(run_loop.QuitClosure());
Adam Langley2cffe382018-06-12 01:52:541736
1737 // Make first request.
1738 PublicKeyCredentialRequestOptionsPtr options =
1739 GetTestPublicKeyCredentialRequestOptions();
Adem Derinel72e11db2025-02-11 15:58:001740 TestGetCredentialFuture future;
1741 authenticator->GetCredential(std::move(options), future.GetCallback());
Adam Langley2cffe382018-06-12 01:52:541742
Nina Satragno9d6389e2019-06-14 21:21:351743 // Simulate a navigation while waiting for the user to press the token.
Nina Satragnoacf403f92019-05-23 17:16:521744 virtual_device_factory_->mutable_state()->simulate_press_callback =
Nina Satragno9d6389e2019-06-14 21:21:351745 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
Sean Maher5b9af51f2022-11-21 15:32:471746 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
Nina Satragno9d6389e2019-06-14 21:21:351747 FROM_HERE, base::BindLambdaForTesting(
Martin Kreichgauer55834402020-08-03 21:54:311748 [&]() { NavigateAndCommit(GURL(kTestOrigin2)); }));
Nina Satragno9d6389e2019-06-14 21:21:351749 return false;
1750 });
Adam Langley2cffe382018-06-12 01:52:541751
1752 run_loop.Run();
Kim Paulhamus595abc652018-04-12 20:42:511753}
1754
Adam Langleydd978e312018-05-24 18:50:481755TEST_F(AuthenticatorImplTest, InvalidResponse) {
Nina Satragnoacf403f92019-05-23 17:16:521756 virtual_device_factory_->mutable_state()->simulate_invalid_response = true;
Martin Kreichgauer55834402020-08-03 21:54:311757 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langleydd978e312018-05-24 18:50:481758
1759 {
1760 PublicKeyCredentialRequestOptionsPtr options =
1761 GetTestPublicKeyCredentialRequestOptions();
Martin Kreichgauer55834402020-08-03 21:54:311762 EXPECT_EQ(
1763 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
1764 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langleydd978e312018-05-24 18:50:481765 }
1766
1767 {
1768 PublicKeyCredentialCreationOptionsPtr options =
1769 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauer55834402020-08-03 21:54:311770 EXPECT_EQ(
1771 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
1772 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langleydd978e312018-05-24 18:50:481773 }
1774}
1775
Adam Langleydb017192019-03-26 04:47:391776TEST_F(AuthenticatorImplTest, Ctap2AssertionWithUnknownCredential) {
Martin Kreichgauer55834402020-08-03 21:54:311777 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley322908a82019-03-25 21:03:101778
Adam Langleydb017192019-03-26 04:47:391779 for (bool return_immediate_invalid_credential_error : {false, true}) {
1780 SCOPED_TRACE(::testing::Message()
1781 << "return_immediate_invalid_credential_error="
1782 << return_immediate_invalid_credential_error);
Adam Langley322908a82019-03-25 21:03:101783
Adam Langleydb017192019-03-26 04:47:391784 device::VirtualCtap2Device::Config config;
1785 config.return_immediate_invalid_credential_error =
1786 return_immediate_invalid_credential_error;
Nina Satragnoacf403f92019-05-23 17:16:521787 virtual_device_factory_->SetCtap2Config(config);
Adam Langleydb017192019-03-26 04:47:391788
1789 bool pressed = false;
Nina Satragnoacf403f92019-05-23 17:16:521790 virtual_device_factory_->mutable_state()->simulate_press_callback =
Nina Satragno9d6389e2019-06-14 21:21:351791 base::BindRepeating(
1792 [](bool* flag, device::VirtualFidoDevice* device) {
1793 *flag = true;
1794 return true;
1795 },
1796 &pressed);
Adam Langleydb017192019-03-26 04:47:391797
Martin Kreichgauer55834402020-08-03 21:54:311798 EXPECT_EQ(
1799 AuthenticatorGetAssertion(GetTestPublicKeyCredentialRequestOptions())
1800 .status,
1801 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanan23dce912024-07-11 16:41:271802 VerifyGetAssertionOutcomeUkm(0,
1803 GetAssertionOutcome::kCredentialNotRecognized,
Ken Buchanan1ea549c12024-10-10 21:31:051804 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleydb017192019-03-26 04:47:391805 // The user must have pressed the authenticator for the operation to
1806 // resolve.
1807 EXPECT_TRUE(pressed);
1808 }
Adam Langley322908a82019-03-25 21:03:101809}
1810
Martin Kreichgauer3b57e2a02019-03-30 01:13:171811TEST_F(AuthenticatorImplTest, GetAssertionResponseWithAttestedCredentialData) {
Martin Kreichgauer3b57e2a02019-03-30 01:13:171812 device::VirtualCtap2Device::Config config;
1813 config.return_attested_cred_data_in_get_assertion_response = true;
Nina Satragnoacf403f92019-05-23 17:16:521814 virtual_device_factory_->SetCtap2Config(config);
Martin Kreichgauer3b57e2a02019-03-30 01:13:171815 PublicKeyCredentialRequestOptionsPtr options =
1816 GetTestPublicKeyCredentialRequestOptions();
Nina Satragnoacf403f92019-05-23 17:16:521817 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:461818 options->allow_credentials[0].id, kTestRelyingPartyId));
Martin Kreichgauer3b57e2a02019-03-30 01:13:171819
Martin Kreichgauer55834402020-08-03 21:54:311820 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauer3b57e2a02019-03-30 01:13:171821
Martin Kreichgauer55834402020-08-03 21:54:311822 EXPECT_EQ(
1823 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
1824 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauer3b57e2a02019-03-30 01:13:171825}
1826
Xiaohan Wang2ba85e32022-01-15 17:19:401827#if BUILDFLAG(IS_WIN)
Martin Kreichgauera4c7fcf42024-08-19 22:59:451828TEST_F(AuthenticatorImplTest, Win_IsUVPAA) {
Adam Langley30375442023-06-19 17:20:261829 virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true);
Martin Kreichgauer55834402020-08-03 21:54:311830 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauer263f6d82020-03-10 05:46:451831 mojo::Remote<blink::mojom::Authenticator> authenticator =
1832 ConnectToAuthenticator();
1833
1834 for (const bool enable_win_webauthn_api : {false, true}) {
1835 SCOPED_TRACE(enable_win_webauthn_api ? "enable_win_webauthn_api"
1836 : "!enable_win_webauthn_api");
1837 for (const bool is_uvpaa : {false, true}) {
1838 SCOPED_TRACE(is_uvpaa ? "is_uvpaa" : "!is_uvpaa");
Martin Kreichgauera4c7fcf42024-08-19 22:59:451839 for (bool is_off_the_record : {true, false}) {
1840 SCOPED_TRACE(is_off_the_record ? "off the record" : "on the record");
1841 static_cast<TestBrowserContext*>(GetBrowserContext())
1842 ->set_is_off_the_record(is_off_the_record);
1843 fake_win_webauthn_api_.set_available(enable_win_webauthn_api);
1844 fake_win_webauthn_api_.set_is_uvpaa(is_uvpaa);
1845 EXPECT_EQ(AuthenticatorIsUvpaa(), enable_win_webauthn_api && is_uvpaa);
1846 }
Martin Kreichgauer263f6d82020-03-10 05:46:451847 }
1848 }
1849}
Xiaohan Wang2ba85e32022-01-15 17:19:401850#endif // BUILDFLAG(IS_WIN)
Martin Kreichgauer263f6d82020-03-10 05:46:451851
Howard Yang72a1412b2022-04-13 00:16:021852#if BUILDFLAG(IS_CHROMEOS)
Martin Kreichgauerde9c5dee2020-03-11 21:20:411853TEST_F(AuthenticatorImplTest, IsUVPAA) {
Martin Kreichgauer55834402020-08-03 21:54:311854 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerca18b432023-04-25 18:58:471855 EXPECT_FALSE(AuthenticatorIsUvpaa());
Martin Kreichgauerde9c5dee2020-03-11 21:20:411856}
Howard Yang72a1412b2022-04-13 00:16:021857#endif // BUILDFLAG(IS_CHROMEOS)
Martin Kreichgauerde9c5dee2020-03-11 21:20:411858
Martin Kreichgauer165ff722021-08-26 01:33:521859// TestWebAuthenticationRequestProxy is a test fake implementation of the
1860// WebAuthenticationRequestProxy embedder interface.
1861class TestWebAuthenticationRequestProxy : public WebAuthenticationRequestProxy {
1862 public:
1863 struct Config {
Martin Kreichgauer8c97189a2022-01-10 20:31:431864 // If true, resolves all request event callbacks instantly.
1865 bool resolve_callbacks = true;
1866
1867 // The return value of IsActive().
Martin Kreichgauer165ff722021-08-26 01:33:521868 bool is_active = true;
Martin Kreichgauer8c97189a2022-01-10 20:31:431869
1870 // The fake response to SignalIsUVPAARequest().
Martin Kreichgauer165ff722021-08-26 01:33:521871 bool is_uvpaa = true;
Martin Kreichgauer8c97189a2022-01-10 20:31:431872
Martin Kreichgauer6119e842022-01-28 01:52:411873 // Whether the request to SignalCreateRequest() should succeed.
Martin Kreichgauer1beaff02022-02-02 18:58:421874 bool request_success = true;
Martin Kreichgauer6119e842022-01-28 01:52:411875
Martin Kreichgauer1beaff02022-02-02 18:58:421876 // If `request_success` is false, the name of the DOMError to be
Martin Kreichgauer6119e842022-01-28 01:52:411877 // returned.
Martin Kreichgauer1beaff02022-02-02 18:58:421878 std::string request_error_name = "NotAllowedError";
Martin Kreichgauer6119e842022-01-28 01:52:411879
Martin Kreichgauer1beaff02022-02-02 18:58:421880 // If `request_success` is true, the fake response to be returned for an
1881 // onCreateRequest event.
Martin Kreichgauer8c97189a2022-01-10 20:31:431882 blink::mojom::MakeCredentialAuthenticatorResponsePtr
1883 make_credential_response = nullptr;
Martin Kreichgauer1beaff02022-02-02 18:58:421884
1885 // If `request_success` is true, the fake response to be returned for an
1886 // onGetRequest event.
1887 blink::mojom::GetAssertionAuthenticatorResponsePtr get_assertion_response =
1888 nullptr;
Martin Kreichgauer165ff722021-08-26 01:33:521889 };
1890
Martin Kreichgauerbf776d72022-04-18 23:38:161891 struct Observations {
1892 std::vector<PublicKeyCredentialCreationOptionsPtr> create_requests;
1893 std::vector<PublicKeyCredentialRequestOptionsPtr> get_requests;
1894 size_t num_isuvpaa;
1895 size_t num_cancel;
Martin Kreichgauer165ff722021-08-26 01:33:521896 };
1897
Martin Kreichgauerb27f6312022-01-25 00:03:321898 ~TestWebAuthenticationRequestProxy() override {
1899 DCHECK(!HasPendingRequest());
1900 }
1901
Martin Kreichgauer165ff722021-08-26 01:33:521902 Config& config() { return config_; }
1903
Martin Kreichgauer2d878b072022-05-02 18:33:111904 Observations& observations() { return observations_; }
Martin Kreichgauer165ff722021-08-26 01:33:521905
Martin Kreichgauer1f4aa592023-01-06 18:39:371906 bool IsActive(const url::Origin& caller_origin) override {
1907 return config_.is_active;
1908 }
Martin Kreichgauer165ff722021-08-26 01:33:521909
Martin Kreichgauerb27f6312022-01-25 00:03:321910 RequestId SignalCreateRequest(
1911 const PublicKeyCredentialCreationOptionsPtr& options,
1912 CreateCallback callback) override {
1913 DCHECK(!HasPendingRequest());
1914
1915 current_request_id_++;
Martin Kreichgauerbf776d72022-04-18 23:38:161916 observations_.create_requests.push_back(options->Clone());
Martin Kreichgauer6119e842022-01-28 01:52:411917 pending_create_callback_ = std::move(callback);
Martin Kreichgauer8c97189a2022-01-10 20:31:431918 if (config_.resolve_callbacks) {
Martin Kreichgauer6119e842022-01-28 01:52:411919 RunPendingCreateCallback();
Martin Kreichgauerb27f6312022-01-25 00:03:321920 return current_request_id_;
Martin Kreichgauer8c97189a2022-01-10 20:31:431921 }
Martin Kreichgauerb27f6312022-01-25 00:03:321922 return current_request_id_;
Martin Kreichgauer8c97189a2022-01-10 20:31:431923 }
1924
Martin Kreichgauer1beaff02022-02-02 18:58:421925 RequestId SignalGetRequest(
1926 const PublicKeyCredentialRequestOptionsPtr& options,
1927 GetCallback callback) override {
1928 current_request_id_++;
Martin Kreichgauerbf776d72022-04-18 23:38:161929 observations_.get_requests.push_back(options->Clone());
Martin Kreichgauer1beaff02022-02-02 18:58:421930 pending_get_callback_ = std::move(callback);
1931 if (config_.resolve_callbacks) {
1932 RunPendingGetCallback();
1933 return current_request_id_;
1934 }
1935 return current_request_id_;
1936 }
1937
Martin Kreichgauerb27f6312022-01-25 00:03:321938 RequestId SignalIsUvpaaRequest(IsUvpaaCallback callback) override {
1939 DCHECK(!HasPendingRequest());
1940
1941 current_request_id_++;
Martin Kreichgauerbf776d72022-04-18 23:38:161942 observations_.num_isuvpaa++;
Martin Kreichgauer8c97189a2022-01-10 20:31:431943 if (config_.resolve_callbacks) {
Sean Maher52fa5a72022-11-14 15:53:251944 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Martin Kreichgauerb27f6312022-01-25 00:03:321945 FROM_HERE, base::BindOnce(std::move(callback), config_.is_uvpaa));
1946 return current_request_id_;
Martin Kreichgauer8c97189a2022-01-10 20:31:431947 }
1948 DCHECK(!pending_is_uvpaa_callback_);
1949 pending_is_uvpaa_callback_ = std::move(callback);
Martin Kreichgauerb27f6312022-01-25 00:03:321950 return current_request_id_;
1951 }
1952
1953 void CancelRequest(RequestId request_id) override {
1954 DCHECK_EQ(request_id, current_request_id_);
Martin Kreichgauerbf776d72022-04-18 23:38:161955 observations_.num_cancel++;
Martin Kreichgauerb27f6312022-01-25 00:03:321956 if (pending_create_callback_) {
1957 pending_create_callback_.Reset();
1958 }
Martin Kreichgauer1beaff02022-02-02 18:58:421959 if (pending_get_callback_) {
1960 pending_get_callback_.Reset();
1961 }
Martin Kreichgauer8c97189a2022-01-10 20:31:431962 }
1963
1964 void RunPendingCreateCallback() {
1965 DCHECK(pending_create_callback_);
Martin Kreichgauer6119e842022-01-28 01:52:411966 auto callback =
Martin Kreichgauer1beaff02022-02-02 18:58:421967 config_.request_success
Martin Kreichgauer6119e842022-01-28 01:52:411968 ? base::BindOnce(std::move(pending_create_callback_),
1969 current_request_id_, nullptr,
1970 config_.make_credential_response.Clone())
1971 : base::BindOnce(std::move(pending_create_callback_),
1972 current_request_id_,
1973 WebAuthnDOMExceptionDetails::New(
Martin Kreichgauer1beaff02022-02-02 18:58:421974 config_.request_error_name, "message"),
1975 nullptr);
Sean Maher52fa5a72022-11-14 15:53:251976 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
1977 FROM_HERE, std::move(callback));
Martin Kreichgauer1beaff02022-02-02 18:58:421978 }
1979
1980 void RunPendingGetCallback() {
1981 DCHECK(pending_get_callback_);
1982 auto callback =
1983 config_.request_success
1984 ? base::BindOnce(std::move(pending_get_callback_),
1985 current_request_id_, nullptr,
1986 config_.get_assertion_response.Clone())
1987 : base::BindOnce(std::move(pending_create_callback_),
1988 current_request_id_,
1989 WebAuthnDOMExceptionDetails::New(
1990 config_.request_error_name, "message"),
Martin Kreichgauer6119e842022-01-28 01:52:411991 nullptr);
Sean Maher52fa5a72022-11-14 15:53:251992 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
1993 FROM_HERE, std::move(callback));
Martin Kreichgauer8c97189a2022-01-10 20:31:431994 }
1995
1996 void RunPendingIsUvpaaCallback() {
1997 DCHECK(pending_is_uvpaa_callback_);
1998 std::move(pending_is_uvpaa_callback_).Run(config_.is_uvpaa);
Martin Kreichgauer165ff722021-08-26 01:33:521999 }
2000
Martin Kreichgauerb27f6312022-01-25 00:03:322001 bool HasPendingRequest() {
Martin Kreichgauer1beaff02022-02-02 18:58:422002 return pending_create_callback_ || pending_get_callback_ ||
2003 pending_is_uvpaa_callback_;
Martin Kreichgauerb27f6312022-01-25 00:03:322004 }
2005
Martin Kreichgauer165ff722021-08-26 01:33:522006 private:
2007 Config config_;
Martin Kreichgauerbf776d72022-04-18 23:38:162008 Observations observations_;
Martin Kreichgauer8c97189a2022-01-10 20:31:432009
Martin Kreichgauerb27f6312022-01-25 00:03:322010 RequestId current_request_id_ = 0;
Martin Kreichgauer8c97189a2022-01-10 20:31:432011 CreateCallback pending_create_callback_;
Martin Kreichgauer1beaff02022-02-02 18:58:422012 GetCallback pending_get_callback_;
Martin Kreichgauer8c97189a2022-01-10 20:31:432013 IsUvpaaCallback pending_is_uvpaa_callback_;
Martin Kreichgauer165ff722021-08-26 01:33:522014};
2015
Martin Kreichgauerfefb3772021-04-12 23:02:482016// TestWebAuthenticationDelegate is a test fake implementation of the
Martin Kreichgauer05a33fb2022-02-18 23:31:292017// WebAuthenticationDelegate embedder interface.
Martin Kreichgauerfefb3772021-04-12 23:02:482018class TestWebAuthenticationDelegate : public WebAuthenticationDelegate {
Adam Langley5f3963f12020-01-21 19:10:332019 public:
Ken Buchanan90fe29552024-04-26 21:15:482020 void IsUserVerifyingPlatformAuthenticatorAvailableOverride(
2021 RenderFrameHost*,
2022 base::OnceCallback<void(std::optional<bool>)> callback) override {
2023 std::move(callback).Run(is_uvpaa_override);
Martin Kreichgauerfefb3772021-04-12 23:02:482024 }
Martin Kreichgauere255af062022-04-18 19:40:562025
Martin Kreichgauer05a33fb2022-02-18 23:31:292026 bool OverrideCallerOriginAndRelyingPartyIdValidation(
Martin Kreichgauerd7aa4f92022-02-22 20:46:212027 content::BrowserContext* browser_context,
Martin Kreichgauer05a33fb2022-02-18 23:31:292028 const url::Origin& origin,
2029 const std::string& rp_id) override {
2030 return permit_extensions && origin.scheme() == kExtensionScheme &&
2031 origin.host() == rp_id;
2032 }
2033
Arthur Sonzognic686e8f2024-01-11 08:36:372034 std::optional<std::string> MaybeGetRelyingPartyIdOverride(
Adam Langley5f3963f12020-01-21 19:10:332035 const std::string& claimed_rp_id,
2036 const url::Origin& caller_origin) override {
Martin Kreichgauer05a33fb2022-02-18 23:31:292037 if (permit_extensions && caller_origin.scheme() == kExtensionScheme) {
2038 return caller_origin.Serialize();
2039 }
Arthur Sonzognic686e8f2024-01-11 08:36:372040 return std::nullopt;
Adam Langley5f3963f12020-01-21 19:10:332041 }
2042
Martin Kreichgauerfefb3772021-04-12 23:02:482043 bool ShouldPermitIndividualAttestation(
2044 content::BrowserContext* browser_context,
Martin Kreichgauer4c6070762022-03-29 16:27:222045 const url::Origin& caller_origin,
Martin Kreichgauerfefb3772021-04-12 23:02:482046 const std::string& relying_party_id) override {
Adam Langleyf21a60b52021-08-19 01:39:452047 return permit_individual_attestation ||
2048 (permit_individual_attestation_for_rp_id.has_value() &&
2049 relying_party_id == *permit_individual_attestation_for_rp_id);
Martin Kreichgauerfefb3772021-04-12 23:02:482050 }
Nina Satragno9d64c5b2020-08-21 23:22:082051
Martin Kreichgauerfefb3772021-04-12 23:02:482052 bool SupportsResidentKeys(RenderFrameHost*) override {
2053 return supports_resident_keys;
2054 }
2055
2056 bool IsFocused(WebContents* web_contents) override { return is_focused; }
2057
Xiaohan Wang2ba85e32022-01-15 17:19:402058#if BUILDFLAG(IS_MAC)
Arthur Sonzognic686e8f2024-01-11 08:36:372059 std::optional<TouchIdAuthenticatorConfig> GetTouchIdAuthenticatorConfig(
Martin Kreichgauerfefb3772021-04-12 23:02:482060 BrowserContext* browser_context) override {
2061 return touch_id_authenticator_config;
2062 }
2063#endif
2064
Martin Kreichgauer165ff722021-08-26 01:33:522065 WebAuthenticationRequestProxy* MaybeGetRequestProxy(
Martin Kreichgauer1f4aa592023-01-06 18:39:372066 content::BrowserContext* browser_context,
2067 const url::Origin& caller_origin) override {
2068 return request_proxy && request_proxy->IsActive(caller_origin)
2069 ? request_proxy.get()
2070 : nullptr;
Martin Kreichgauer165ff722021-08-26 01:33:522071 }
2072
Martin Kreichgauere255af062022-04-18 19:40:562073 bool OriginMayUseRemoteDesktopClientOverride(
2074 content::BrowserContext* browser_context,
2075 const url::Origin& caller_origin) override {
2076 return caller_origin == remote_desktop_client_override_origin;
2077 }
2078
Nina Satragnoa11a1ff2025-07-07 21:05:302079 void BrowserProvidedPasskeysAvailable(
2080 BrowserContext* browser_context,
2081 base::OnceCallback<void(bool)> callback) override {
2082 std::move(callback).Run(browser_provided_passkeys_available);
2083 }
2084
Martin Kreichgauerfefb3772021-04-12 23:02:482085 // If set, the return value of IsUVPAA() will be overridden with this value.
2086 // Platform-specific implementations will not be invoked.
Arthur Sonzognic686e8f2024-01-11 08:36:372087 std::optional<bool> is_uvpaa_override;
Martin Kreichgauerfefb3772021-04-12 23:02:482088
Martin Kreichgauer05a33fb2022-02-18 23:31:292089 // If set, the delegate will permit WebAuthn requests from chrome-extension
2090 // origins.
2091 bool permit_extensions = false;
Martin Kreichgauerfefb3772021-04-12 23:02:482092
2093 // Indicates whether individual attestation should be permitted by the
2094 // delegate.
2095 bool permit_individual_attestation = false;
2096
Adam Langleyf21a60b52021-08-19 01:39:452097 // A specific RP ID for which individual attestation will be permitted.
Arthur Sonzognic686e8f2024-01-11 08:36:372098 std::optional<std::string> permit_individual_attestation_for_rp_id;
Adam Langleyf21a60b52021-08-19 01:39:452099
Martin Kreichgauerfefb3772021-04-12 23:02:482100 // Indicates whether resident key operations should be permitted by the
2101 // delegate.
2102 bool supports_resident_keys = false;
2103
2104 // The return value of the focus check issued at the end of a request.
2105 bool is_focused = true;
2106
Xiaohan Wang2ba85e32022-01-15 17:19:402107#if BUILDFLAG(IS_MAC)
Martin Kreichgauerfefb3772021-04-12 23:02:482108 // Configuration data for the macOS platform authenticator.
Arthur Sonzognic686e8f2024-01-11 08:36:372109 std::optional<TouchIdAuthenticatorConfig> touch_id_authenticator_config;
Martin Kreichgauerfefb3772021-04-12 23:02:482110#endif
Martin Kreichgauer165ff722021-08-26 01:33:522111
2112 // The WebAuthenticationRequestProxy returned by |MaybeGetRequestProxy|.
2113 std::unique_ptr<TestWebAuthenticationRequestProxy> request_proxy = nullptr;
Martin Kreichgauere255af062022-04-18 19:40:562114
2115 // The origin permitted to use the RemoteDesktopClientOverride extension.
Arthur Sonzognic686e8f2024-01-11 08:36:372116 std::optional<url::Origin> remote_desktop_client_override_origin;
Nina Satragnoa11a1ff2025-07-07 21:05:302117
2118 // Return value of `BrowserProvidedPasskeysAvailable()`.
2119 bool browser_provided_passkeys_available = false;
Adam Langley5f3963f12020-01-21 19:10:332120};
2121
Adam Langley1d65a2a2020-06-08 22:13:242122enum class EnterprisePolicy {
2123 LISTED,
2124 NOT_LISTED,
Adam Langley31caeef42018-03-22 03:44:102125};
2126
Adam Langley3015ad42018-08-15 02:04:362127enum class AttestationType {
2128 ANY,
2129 NONE,
Martin Kreichgauer2c9e5352018-10-30 23:28:532130 NONE_WITH_NONZERO_AAGUID,
Adam Langley3015ad42018-08-15 02:04:362131 U2F,
2132 SELF,
Martin Kreichgauer2c9e5352018-10-30 23:28:532133 SELF_WITH_NONZERO_AAGUID,
Adam Langleyf1c8b742020-07-22 19:36:062134 PACKED,
Adam Langley3015ad42018-08-15 02:04:362135};
2136
Adam Langley70a24152022-08-30 02:01:042137const char* AttestationConveyancePreferenceToString(
2138 AttestationConveyancePreference v) {
2139 switch (v) {
2140 case AttestationConveyancePreference::NONE:
2141 return "none";
2142 case AttestationConveyancePreference::INDIRECT:
2143 return "indirect";
2144 case AttestationConveyancePreference::DIRECT:
2145 return "direct";
2146 case AttestationConveyancePreference::ENTERPRISE:
2147 return "enterprise";
2148 default:
Peter Boströmfc7ddc182024-10-31 19:37:212149 NOTREACHED();
Adam Langley70a24152022-08-30 02:01:042150 }
2151}
2152
2153const char* AttestationConveyancePreferenceToString(
2154 device::AttestationConveyancePreference v) {
2155 switch (v) {
2156 case device::AttestationConveyancePreference::kNone:
2157 return "none";
2158 case device::AttestationConveyancePreference::kIndirect:
2159 return "indirect";
2160 case device::AttestationConveyancePreference::kDirect:
2161 return "direct";
2162 case device::AttestationConveyancePreference::
2163 kEnterpriseIfRPListedOnAuthenticator:
2164 return "enterprise(ep=1)";
2165 case device::AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
2166 return "enterprise(ep=2)";
2167 }
2168}
2169
Martin Kreichgauerfefb3772021-04-12 23:02:482170// TestAuthenticatorRequestDelegate is a test fake implementation of the
2171// AuthenticatorRequestClientDelegate embedder interface.
Balazs Engedya7ff70982018-06-04 18:14:472172class TestAuthenticatorRequestDelegate
2173 : public AuthenticatorRequestClientDelegate {
Adam Langley31caeef42018-03-22 03:44:102174 public:
Jun Choid53a039c2018-08-16 23:38:152175 TestAuthenticatorRequestDelegate(
2176 RenderFrameHost* render_frame_host,
2177 base::OnceClosure action_callbacks_registered_callback,
Nina Satragno129251c2023-10-23 21:50:402178 base::OnceClosure started_over_callback,
Nina Satragnoa11a1ff2025-07-07 21:05:302179 bool simulate_user_cancelled,
2180 std::optional<bool>* enclave_authenticator_should_be_discovered,
2181 base::flat_set<device::FidoTransportProtocol>* discovered_transports)
Jun Choid53a039c2018-08-16 23:38:152182 : action_callbacks_registered_callback_(
2183 std::move(action_callbacks_registered_callback)),
Adam Langley39ca22f2022-01-19 01:15:322184 started_over_callback_(std::move(started_over_callback)),
Nina Satragno129251c2023-10-23 21:50:402185 does_block_request_on_failure_(!started_over_callback_.is_null()),
Nina Satragnoa11a1ff2025-07-07 21:05:302186 simulate_user_cancelled_(simulate_user_cancelled),
2187 enclave_authenticator_should_be_discovered_(
2188 enclave_authenticator_should_be_discovered),
2189 discovered_transports_(discovered_transports) {}
Adam Langleyf1c8b742020-07-22 19:36:062190
Peter Boström828b9022021-09-21 02:28:432191 TestAuthenticatorRequestDelegate(const TestAuthenticatorRequestDelegate&) =
2192 delete;
2193 TestAuthenticatorRequestDelegate& operator=(
2194 const TestAuthenticatorRequestDelegate&) = delete;
2195
Jun Choife176682018-08-30 07:49:142196 void RegisterActionCallbacks(
2197 base::OnceClosure cancel_callback,
Adem Derinel2154d4b12025-02-04 12:29:552198 base::OnceClosure immediate_not_found_callback,
danakjd46bdf42019-11-27 22:07:272199 base::RepeatingClosure start_over_callback,
Nina Satragnofe6e52ad72022-06-01 14:04:142200 AccountPreselectedCallback account_preselected_callback,
Adem Derinel72e11db2025-02-11 15:58:002201 PasswordSelectedCallback password_selected_callback,
Jun Choife176682018-08-30 07:49:142202 device::FidoRequestHandlerBase::RequestCallback request_callback,
Adem Derinel38626c22025-05-22 13:31:582203 base::OnceClosure cancel_ui_timeout_callback,
Nina Satragno6e0f1ab2024-06-13 22:28:112204 base::RepeatingClosure bluetooth_adapter_power_on_callback,
2205 base::RepeatingCallback<
2206 void(device::FidoRequestHandlerBase::BlePermissionCallback)>
2207 ble_status_callback) override {
Jun Choid53a039c2018-08-16 23:38:152208 ASSERT_TRUE(action_callbacks_registered_callback_)
2209 << "RegisterActionCallbacks called twice.";
Martin Kreichgauerb3689d062022-07-12 09:36:512210 cancel_callback_ = std::move(cancel_callback);
Jun Choid53a039c2018-08-16 23:38:152211 std::move(action_callbacks_registered_callback_).Run();
Nina Satragno7782b1032020-11-05 17:59:262212 if (started_over_callback_) {
2213 action_callbacks_registered_callback_ = std::move(started_over_callback_);
Adam Langley39ca22f2022-01-19 01:15:322214 start_over_callback_ = start_over_callback;
Nina Satragno7782b1032020-11-05 17:59:262215 }
Balazs Engedya2750182018-06-13 07:45:262216 }
2217
Adam Langley8bf3e4f2019-08-29 21:08:112218 void OnTransportAvailabilityEnumerated(
2219 device::FidoRequestHandlerBase::TransportAvailabilityInfo transport_info)
2220 override {
Nina Satragnoa11a1ff2025-07-07 21:05:302221 if (discovered_transports_) {
2222 *discovered_transports_ = transport_info.available_transports;
2223 }
Adam Langley8bf3e4f2019-08-29 21:08:112224 // Simulate the behaviour of Chrome's |AuthenticatorRequestDialogModel|
2225 // which shows a specific error when no transports are available and lets
2226 // the user cancel the request.
Nina Satragno129251c2023-10-23 21:50:402227 if (transport_info.available_transports.empty() ||
2228 simulate_user_cancelled_) {
Martin Kreichgauerb3689d062022-07-12 09:36:512229 std::move(cancel_callback_).Run();
Adam Langley8bf3e4f2019-08-29 21:08:112230 }
2231 }
2232
Adam Langley39ca22f2022-01-19 01:15:322233 bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
2234 if (!does_block_request_on_failure_) {
2235 return false;
2236 }
2237
2238 std::move(start_over_callback_).Run();
2239 does_block_request_on_failure_ = false;
2240 return true;
2241 }
2242
Nina Satragnoa11a1ff2025-07-07 21:05:302243 void ConfigureDiscoveries(
2244 const url::Origin& origin,
2245 const std::string& rp_id,
2246 RequestSource request_source,
2247 device::FidoRequestType request_type,
2248 std::optional<device::ResidentKeyRequirement> resident_key_requirement,
2249 device::UserVerificationRequirement user_verification_requirement,
2250 std::optional<std::string_view> user_name,
2251 base::span<const device::CableDiscoveryData> pairings_from_extension,
2252 bool is_enclave_authenticator_available,
2253 device::FidoDiscoveryFactory* fido_discovery_factory) override {
2254 if (enclave_authenticator_should_be_discovered_) {
2255 *enclave_authenticator_should_be_discovered_ =
2256 is_enclave_authenticator_available;
2257 }
2258 }
2259
Jun Choid53a039c2018-08-16 23:38:152260 base::OnceClosure action_callbacks_registered_callback_;
Martin Kreichgauerb3689d062022-07-12 09:36:512261 base::OnceClosure cancel_callback_;
Nina Satragno7782b1032020-11-05 17:59:262262 base::OnceClosure started_over_callback_;
Adam Langley39ca22f2022-01-19 01:15:322263 base::OnceClosure start_over_callback_;
2264 bool does_block_request_on_failure_ = false;
Nina Satragno129251c2023-10-23 21:50:402265 bool simulate_user_cancelled_ = false;
Nina Satragnoa11a1ff2025-07-07 21:05:302266 bool browser_provided_passkeys_available_ = false;
2267 raw_ptr<std::optional<bool>> enclave_authenticator_should_be_discovered_;
2268 raw_ptr<base::flat_set<device::FidoTransportProtocol>> discovered_transports_;
Balazs Engedya7ff70982018-06-04 18:14:472269};
2270
Martin Kreichgauerfefb3772021-04-12 23:02:482271// TestAuthenticatorContentBrowserClient is a test fake implementation of the
2272// ContentBrowserClient interface that injects |TestWebAuthenticationDelegate|
2273// and |TestAuthenticatorRequestDelegate| instances into |AuthenticatorImpl|.
Balazs Engedya7ff70982018-06-04 18:14:472274class TestAuthenticatorContentBrowserClient : public ContentBrowserClient {
2275 public:
Martin Kreichgauer37ace492021-04-08 23:36:462276 TestWebAuthenticationDelegate* GetTestWebAuthenticationDelegate() {
2277 return &web_authentication_delegate;
2278 }
2279
Martin Kreichgauerfefb3772021-04-12 23:02:482280 // ContentBrowserClient:
Martin Kreichgauer37ace492021-04-08 23:36:462281 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
2282 return &web_authentication_delegate;
2283 }
2284
Ken Buchanan6e9929372023-10-31 14:05:432285 bool IsSecurityLevelAcceptableForWebAuthn(
2286 content::RenderFrameHost* rfh,
2287 const url::Origin& origin) override {
2288 return is_webauthn_security_level_acceptable;
2289 }
2290
Balazs Engedya7ff70982018-06-04 18:14:472291 std::unique_ptr<AuthenticatorRequestClientDelegate>
2292 GetWebAuthenticationRequestDelegate(
Adam Langley5f3963f12020-01-21 19:10:332293 RenderFrameHost* render_frame_host) override {
Martin Kreichgauerca18b432023-04-25 18:58:472294 if (return_null_delegate) {
Balazs Engedy5b1088762018-06-05 09:30:462295 return nullptr;
Martin Kreichgauerca18b432023-04-25 18:58:472296 }
Balazs Engedya7ff70982018-06-04 18:14:472297 return std::make_unique<TestAuthenticatorRequestDelegate>(
Balazs Engedya2750182018-06-13 07:45:262298 render_frame_host,
Jun Choid53a039c2018-08-16 23:38:152299 action_callbacks_registered_callback
2300 ? std::move(action_callbacks_registered_callback)
2301 : base::DoNothing(),
Nina Satragnoa11a1ff2025-07-07 21:05:302302 std::move(started_over_callback_), simulate_user_cancelled_,
2303 &enclave_authenticator_should_be_discovered_, &discovered_transports_);
Balazs Engedya7ff70982018-06-04 18:14:472304 }
2305
Martin Kreichgauer37ace492021-04-08 23:36:462306 TestWebAuthenticationDelegate web_authentication_delegate;
2307
Balazs Engedya2750182018-06-13 07:45:262308 // If set, this closure will be called when the subsequently constructed
2309 // delegate is informed that the request has started.
Jun Choid53a039c2018-08-16 23:38:152310 base::OnceClosure action_callbacks_registered_callback;
Balazs Engedya2750182018-06-13 07:45:262311
Balazs Engedy5b1088762018-06-05 09:30:462312 // This emulates scenarios where a nullptr RequestClientDelegate is returned
2313 // because a request is already in progress.
2314 bool return_null_delegate = false;
Nina Satragno7782b1032020-11-05 17:59:262315
2316 // If started_over_callback_ is set to a non-null callback, the request will
2317 // be restarted after action callbacks are registered, and
2318 // |started_over_callback| will replace
2319 // |action_callbacks_registered_callback|. This should then be called the
Adam Langley39ca22f2022-01-19 01:15:322320 // second time action callbacks are registered. It also causes
2321 // DoesBlockRequestOnFailure to return true, once.
Nina Satragno7782b1032020-11-05 17:59:262322 base::OnceClosure started_over_callback_;
Nina Satragno129251c2023-10-23 21:50:402323
2324 // This simulates the user immediately cancelling the request after transport
2325 // availability info is enumerated.
2326 bool simulate_user_cancelled_ = false;
Ken Buchanan6e9929372023-10-31 14:05:432327
2328 // The return value of IsSecurityLevelAcceptableForWebAuthn.
2329 bool is_webauthn_security_level_acceptable = true;
Nina Satragnoa11a1ff2025-07-07 21:05:302330
2331 // Whether discovery of the enclave authenticator was requested or not.
2332 std::optional<bool> enclave_authenticator_should_be_discovered_;
2333
2334 // The set of transports allowed for a request.
2335 base::flat_set<device::FidoTransportProtocol> discovered_transports_;
Adam Langley31caeef42018-03-22 03:44:102336};
2337
2338// A test class that installs and removes an
Martin Kreichgauerfefb3772021-04-12 23:02:482339// |TestAuthenticatorContentBrowserClient| automatically and can run tests
Adam Langley88415c812018-03-22 20:20:222340// against simulated attestation results.
Adam Langley31caeef42018-03-22 03:44:102341class AuthenticatorContentBrowserClientTest : public AuthenticatorImplTest {
2342 public:
2343 AuthenticatorContentBrowserClientTest() = default;
2344
Peter Boström9b036532021-10-28 23:37:282345 AuthenticatorContentBrowserClientTest(
2346 const AuthenticatorContentBrowserClientTest&) = delete;
2347 AuthenticatorContentBrowserClientTest& operator=(
2348 const AuthenticatorContentBrowserClientTest&) = delete;
2349
Adam Langley88415c812018-03-22 20:20:222350 struct TestCase {
2351 AttestationConveyancePreference attestation_requested;
Adam Langley1d65a2a2020-06-08 22:13:242352 EnterprisePolicy enterprise_policy;
Adam Langley88415c812018-03-22 20:20:222353 AuthenticatorStatus expected_status;
Adam Langley3015ad42018-08-15 02:04:362354 AttestationType expected_attestation;
Adem Derinel620520b42024-11-04 15:45:442355 std::string_view expected_certificate_substring;
Adam Langley88415c812018-03-22 20:20:222356 };
2357
Adam Langley31caeef42018-03-22 03:44:102358 void SetUp() override {
2359 AuthenticatorImplTest::SetUp();
2360 old_client_ = SetBrowserClientForTesting(&test_client_);
2361 }
2362
2363 void TearDown() override {
2364 SetBrowserClientForTesting(old_client_);
2365 AuthenticatorImplTest::TearDown();
2366 }
2367
Adam Langley88415c812018-03-22 20:20:222368 void RunTestCases(const std::vector<TestCase>& tests) {
Adam Langley88415c812018-03-22 20:20:222369 for (size_t i = 0; i < tests.size(); i++) {
2370 const auto& test = tests[i];
Adam Langley1d65a2a2020-06-08 22:13:242371 SCOPED_TRACE(test.enterprise_policy == EnterprisePolicy::LISTED
Adam Langley88415c812018-03-22 20:20:222372 ? "individual attestation"
2373 : "no individual attestation");
2374 SCOPED_TRACE(
2375 AttestationConveyancePreferenceToString(test.attestation_requested));
2376 SCOPED_TRACE(i);
2377
Martin Kreichgauerfefb3772021-04-12 23:02:482378 test_client_.GetTestWebAuthenticationDelegate()
2379 ->permit_individual_attestation =
2380 test.enterprise_policy == EnterprisePolicy::LISTED;
Adam Langley88415c812018-03-22 20:20:222381
2382 PublicKeyCredentialCreationOptionsPtr options =
2383 GetTestPublicKeyCredentialCreationOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:042384 options->relying_party.id = "example.com";
Peter Kastinge5a38ed2021-10-02 03:06:352385 options->timeout = base::Seconds(1);
Ken Buchanan4a33ef0d2019-06-20 17:34:042386 options->attestation =
2387 ConvertAttestationConveyancePreference(test.attestation_requested);
Martin Kreichgauer55834402020-08-03 21:54:312388
2389 MakeCredentialResult result =
2390 AuthenticatorMakeCredential(std::move(options));
2391 EXPECT_EQ(result.status, test.expected_status);
Adam Langley88415c812018-03-22 20:20:222392
2393 if (test.expected_status != AuthenticatorStatus::SUCCESS) {
Adam Langley3015ad42018-08-15 02:04:362394 ASSERT_EQ(AttestationType::ANY, test.expected_attestation);
Adam Langley88415c812018-03-22 20:20:222395 continue;
2396 }
2397
Adam Langleyb7d451e022020-06-23 20:18:572398 const device::AuthenticatorData auth_data =
Martin Kreichgauer55834402020-08-03 21:54:312399 AuthDataFromMakeCredentialResponse(result.response);
Adam Langleyb7d451e022020-06-23 20:18:572400
Arthur Sonzognic686e8f2024-01-11 08:36:372401 std::optional<Value> attestation_value =
Martin Kreichgauer55834402020-08-03 21:54:312402 Reader::Read(result.response->attestation_object);
Adam Langley88415c812018-03-22 20:20:222403 ASSERT_TRUE(attestation_value);
2404 ASSERT_TRUE(attestation_value->is_map());
2405 const auto& attestation = attestation_value->GetMap();
Adam Langley3015ad42018-08-15 02:04:362406
2407 switch (test.expected_attestation) {
2408 case AttestationType::ANY:
Adem Derinel620520b42024-11-04 15:45:442409 ASSERT_TRUE(test.expected_certificate_substring.empty());
Adam Langley3015ad42018-08-15 02:04:362410 break;
2411
2412 case AttestationType::NONE:
Adem Derinel620520b42024-11-04 15:45:442413 ASSERT_TRUE(test.expected_certificate_substring.empty());
Adam Langley3015ad42018-08-15 02:04:362414 ExpectMapHasKeyWithStringValue(attestation, "fmt", "none");
Adam Langleyb7d451e022020-06-23 20:18:572415 EXPECT_TRUE(auth_data.attested_data()->IsAaguidZero());
Martin Kreichgauer2c9e5352018-10-30 23:28:532416 break;
2417
2418 case AttestationType::NONE_WITH_NONZERO_AAGUID:
Adem Derinel620520b42024-11-04 15:45:442419 ASSERT_TRUE(test.expected_certificate_substring.empty());
Martin Kreichgauer2c9e5352018-10-30 23:28:532420 ExpectMapHasKeyWithStringValue(attestation, "fmt", "none");
Adam Langleyb7d451e022020-06-23 20:18:572421 EXPECT_FALSE(auth_data.attested_data()->IsAaguidZero());
Adam Langley3015ad42018-08-15 02:04:362422 break;
2423
2424 case AttestationType::U2F:
2425 ExpectMapHasKeyWithStringValue(attestation, "fmt", "fido-u2f");
Adem Derinel620520b42024-11-04 15:45:442426 if (!test.expected_certificate_substring.empty()) {
Adam Langley3015ad42018-08-15 02:04:362427 ExpectCertificateContainingSubstring(
2428 attestation, test.expected_certificate_substring);
2429 }
2430 break;
2431
Adam Langleyf1c8b742020-07-22 19:36:062432 case AttestationType::PACKED:
2433 ExpectMapHasKeyWithStringValue(attestation, "fmt", "packed");
Adem Derinel620520b42024-11-04 15:45:442434 if (!test.expected_certificate_substring.empty()) {
Adam Langleyf1c8b742020-07-22 19:36:062435 ExpectCertificateContainingSubstring(
2436 attestation, test.expected_certificate_substring);
2437 }
2438 break;
2439
Martin Kreichgauer2c9e5352018-10-30 23:28:532440 case AttestationType::SELF: {
Adem Derinel620520b42024-11-04 15:45:442441 ASSERT_TRUE(test.expected_certificate_substring.empty());
Adam Langley3015ad42018-08-15 02:04:362442 ExpectMapHasKeyWithStringValue(attestation, "fmt", "packed");
2443
2444 // A self-attestation should not include an X.509 chain nor ECDAA key.
2445 const auto attestation_statement_it =
Adam Langleyb4f12f92018-10-26 21:00:022446 attestation.find(Value("attStmt"));
Adam Langley3015ad42018-08-15 02:04:362447 ASSERT_TRUE(attestation_statement_it != attestation.end());
2448 ASSERT_TRUE(attestation_statement_it->second.is_map());
2449 const auto& attestation_statement =
2450 attestation_statement_it->second.GetMap();
2451
Adam Langleyb4f12f92018-10-26 21:00:022452 ASSERT_TRUE(attestation_statement.find(Value("x5c")) ==
Adam Langley3015ad42018-08-15 02:04:362453 attestation_statement.end());
Adam Langleyb4f12f92018-10-26 21:00:022454 ASSERT_TRUE(attestation_statement.find(Value("ecdaaKeyId")) ==
Adam Langley3015ad42018-08-15 02:04:362455 attestation_statement.end());
Adam Langleyb7d451e022020-06-23 20:18:572456 EXPECT_TRUE(auth_data.attested_data()->IsAaguidZero());
Adam Langley3015ad42018-08-15 02:04:362457 break;
Martin Kreichgauer2c9e5352018-10-30 23:28:532458 }
2459 case AttestationType::SELF_WITH_NONZERO_AAGUID: {
Adem Derinel620520b42024-11-04 15:45:442460 ASSERT_TRUE(test.expected_certificate_substring.empty());
Martin Kreichgauer2c9e5352018-10-30 23:28:532461 ExpectMapHasKeyWithStringValue(attestation, "fmt", "packed");
2462
2463 // A self-attestation should not include an X.509 chain nor ECDAA key.
2464 const auto attestation_statement_it =
2465 attestation.find(Value("attStmt"));
2466 ASSERT_TRUE(attestation_statement_it != attestation.end());
2467 ASSERT_TRUE(attestation_statement_it->second.is_map());
2468 const auto& attestation_statement =
2469 attestation_statement_it->second.GetMap();
2470
2471 ASSERT_TRUE(attestation_statement.find(Value("x5c")) ==
2472 attestation_statement.end());
2473 ASSERT_TRUE(attestation_statement.find(Value("ecdaaKeyId")) ==
2474 attestation_statement.end());
Adam Langleyb7d451e022020-06-23 20:18:572475 EXPECT_FALSE(auth_data.attested_data()->IsAaguidZero());
Martin Kreichgauer2c9e5352018-10-30 23:28:532476 break;
2477 }
Adam Langley88415c812018-03-22 20:20:222478 }
2479 }
2480 }
2481
Adam Langley31caeef42018-03-22 03:44:102482 protected:
Balazs Engedya7ff70982018-06-04 18:14:472483 TestAuthenticatorContentBrowserClient test_client_;
Adam Langley31caeef42018-03-22 03:44:102484
Adam Langley88415c812018-03-22 20:20:222485 // Expects that |map| contains the given key with a string-value equal to
2486 // |expected|.
Adam Langleyb4f12f92018-10-26 21:00:022487 static void ExpectMapHasKeyWithStringValue(const Value::MapValue& map,
Adam Langley88415c812018-03-22 20:20:222488 const char* key,
2489 const char* expected) {
Adam Langleyb4f12f92018-10-26 21:00:022490 const auto it = map.find(Value(key));
Adam Langley88415c812018-03-22 20:20:222491 ASSERT_TRUE(it != map.end()) << "No such key '" << key << "'";
2492 const auto& value = it->second;
2493 EXPECT_TRUE(value.is_string())
2494 << "Value of '" << key << "' has type "
2495 << static_cast<int>(value.type()) << ", but expected to find a string";
2496 EXPECT_EQ(std::string(expected), value.GetString())
2497 << "Value of '" << key << "' is '" << value.GetString()
2498 << "', but expected to find '" << expected << "'";
2499 }
2500
Martin Kreichgauer2c9e5352018-10-30 23:28:532501 // Asserts that the webauthn attestation CBOR map in |attestation| contains a
2502 // single X.509 certificate containing |substring|.
Adam Langley88415c812018-03-22 20:20:222503 static void ExpectCertificateContainingSubstring(
Adam Langleyb4f12f92018-10-26 21:00:022504 const Value::MapValue& attestation,
Adem Derinel620520b42024-11-04 15:45:442505 std::string_view substring) {
Adam Langleyb4f12f92018-10-26 21:00:022506 const auto& attestation_statement_it = attestation.find(Value("attStmt"));
Adam Langley88415c812018-03-22 20:20:222507 ASSERT_TRUE(attestation_statement_it != attestation.end());
2508 ASSERT_TRUE(attestation_statement_it->second.is_map());
2509 const auto& attestation_statement =
2510 attestation_statement_it->second.GetMap();
Adam Langleyb4f12f92018-10-26 21:00:022511 const auto& x5c_it = attestation_statement.find(Value("x5c"));
Adam Langley88415c812018-03-22 20:20:222512 ASSERT_TRUE(x5c_it != attestation_statement.end());
2513 ASSERT_TRUE(x5c_it->second.is_array());
2514 const auto& x5c = x5c_it->second.GetArray();
2515 ASSERT_EQ(1u, x5c.size());
2516 ASSERT_TRUE(x5c[0].is_bytestring());
Md Hasibul Hasana963a9342024-04-03 10:15:142517 std::string_view cert = x5c[0].GetBytestringAsString();
Adam Langley88415c812018-03-22 20:20:222518 EXPECT_TRUE(cert.find(substring) != cert.npos);
2519 }
2520
Keishi Hattori0e45c022021-11-27 09:25:522521 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langley31caeef42018-03-22 03:44:102522};
2523
Nina Satragno8d8e5cc2022-11-08 18:29:062524TEST_F(AuthenticatorContentBrowserClientTest, MakeCredentialTLSError) {
Nina Satragno52163de2022-11-23 23:03:102525 NavigateAndCommit(GURL(kTestOrigin1));
Ken Buchanan6e9929372023-10-31 14:05:432526 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno8d8e5cc2022-11-08 18:29:062527 PublicKeyCredentialCreationOptionsPtr options =
2528 GetTestPublicKeyCredentialCreationOptions();
2529 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
2530 AuthenticatorStatus::CERTIFICATE_ERROR);
Ken Buchanan23dce912024-07-11 16:41:272531 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kOtherFailure,
Ken Buchanan1ea549c12024-10-10 21:31:052532 AuthenticationRequestMode::kModalWebAuthn);
Nina Satragno8d8e5cc2022-11-08 18:29:062533}
2534
2535TEST_F(AuthenticatorContentBrowserClientTest, GetAssertionTLSError) {
Nina Satragno52163de2022-11-23 23:03:102536 NavigateAndCommit(GURL(kTestOrigin1));
Ken Buchanan6e9929372023-10-31 14:05:432537 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno8d8e5cc2022-11-08 18:29:062538 PublicKeyCredentialRequestOptionsPtr options =
2539 GetTestPublicKeyCredentialRequestOptions();
2540 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
2541 AuthenticatorStatus::CERTIFICATE_ERROR);
Ken Buchanan23dce912024-07-11 16:41:272542 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kOtherFailure,
Ken Buchanan1ea549c12024-10-10 21:31:052543 AuthenticationRequestMode::kModalWebAuthn);
Nina Satragno8d8e5cc2022-11-08 18:29:062544}
2545
Nina Satragno52163de2022-11-23 23:03:102546TEST_F(AuthenticatorContentBrowserClientTest,
2547 MakeCredentialSkipTLSCheckWithVirtualEnvironment) {
2548 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley1e03fb02023-03-16 23:02:032549 content::AuthenticatorEnvironment::GetInstance()
Nina Satragno52163de2022-11-23 23:03:102550 ->EnableVirtualAuthenticatorFor(
2551 static_cast<content::RenderFrameHostImpl*>(main_rfh())
2552 ->frame_tree_node(),
2553 /*enable_ui=*/false);
Ken Buchanan6e9929372023-10-31 14:05:432554 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno52163de2022-11-23 23:03:102555 PublicKeyCredentialCreationOptionsPtr options =
2556 GetTestPublicKeyCredentialCreationOptions();
2557 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
2558 AuthenticatorStatus::SUCCESS);
2559}
2560
2561TEST_F(AuthenticatorContentBrowserClientTest,
2562 GetAssertionSkipTLSCheckWithVirtualEnvironment) {
2563 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley1e03fb02023-03-16 23:02:032564 content::AuthenticatorEnvironment::GetInstance()
Nina Satragno52163de2022-11-23 23:03:102565 ->EnableVirtualAuthenticatorFor(
2566 static_cast<content::RenderFrameHostImpl*>(main_rfh())
2567 ->frame_tree_node(),
2568 /*enable_ui=*/false);
Ken Buchanan6e9929372023-10-31 14:05:432569 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno52163de2022-11-23 23:03:102570 PublicKeyCredentialRequestOptionsPtr options =
2571 GetTestPublicKeyCredentialRequestOptions();
2572 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
2573 options->allow_credentials[0].id, kTestRelyingPartyId));
2574 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
2575 AuthenticatorStatus::SUCCESS);
2576}
2577
Nina Satragno129251c2023-10-23 21:50:402578TEST_F(AuthenticatorContentBrowserClientTest, TestGetAssertionCancel) {
2579 NavigateAndCommit(GURL(kTestOrigin1));
2580 test_client_.simulate_user_cancelled_ = true;
2581 base::HistogramTester histogram_tester;
Nina Satragno129251c2023-10-23 21:50:402582
2583 EXPECT_EQ(AuthenticatorGetAssertion().status,
2584 AuthenticatorStatus::NOT_ALLOWED_ERROR);
2585 histogram_tester.ExpectUniqueSample(
2586 "WebAuthentication.GetAssertion.Result",
Ken Buchanand5edc0782024-06-10 22:01:222587 AuthenticatorCommonImpl::CredentialRequestResult::kUserCancelled, 1);
Ken Buchanan23dce912024-07-11 16:41:272588 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUserCancellation,
Ken Buchanan1ea549c12024-10-10 21:31:052589 AuthenticationRequestMode::kModalWebAuthn);
Ken Buchanand5edc0782024-06-10 22:01:222590}
2591
2592TEST_F(AuthenticatorContentBrowserClientTest, TestMakeCredentialCancel) {
2593 NavigateAndCommit(GURL(kTestOrigin1));
2594 test_client_.simulate_user_cancelled_ = true;
2595 base::HistogramTester histogram_tester;
2596
2597 EXPECT_EQ(AuthenticatorMakeCredential().status,
2598 AuthenticatorStatus::NOT_ALLOWED_ERROR);
2599 histogram_tester.ExpectUniqueSample(
2600 "WebAuthentication.MakeCredential.Result",
2601 AuthenticatorCommonImpl::CredentialRequestResult::kUserCancelled, 1);
Ken Buchanan23dce912024-07-11 16:41:272602 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUserCancellation,
Ken Buchanan1ea549c12024-10-10 21:31:052603 AuthenticationRequestMode::kModalWebAuthn);
Nina Satragno129251c2023-10-23 21:50:402604}
2605
Nina Satragnoa11a1ff2025-07-07 21:05:302606// Tests that the enclave authenticator should only be discovered for make
2607// credential requests it can fulfill.
2608TEST_F(AuthenticatorContentBrowserClientTest,
2609 DiscoverEnclaveAuthenticatorMakeCredential) {
2610 NavigateAndCommit(GURL(kTestOrigin1));
2611 struct TestCase {
2612 bool available;
2613 device::AuthenticatorAttachment attachment;
2614 bool expected_discovered;
2615 } kTestCases[] = {
2616 {false, device::AuthenticatorAttachment::kAny, false},
2617 {true, device::AuthenticatorAttachment::kCrossPlatform, false},
2618 {true, device::AuthenticatorAttachment::kAny, true},
2619 {true, device::AuthenticatorAttachment::kPlatform, true},
2620 };
2621 for (const auto& test_case : kTestCases) {
2622 SCOPED_TRACE(testing::Message() << "available=" << test_case.available);
2623 SCOPED_TRACE(testing::Message()
2624 << "attachment=" << static_cast<int>(test_case.attachment));
2625 test_client_.GetTestWebAuthenticationDelegate()
2626 ->browser_provided_passkeys_available = test_case.available;
2627 PublicKeyCredentialCreationOptionsPtr options =
2628 GetTestPublicKeyCredentialCreationOptions();
2629 options->authenticator_selection->authenticator_attachment =
2630 test_case.attachment;
2631 AuthenticatorMakeCredential(std::move(options));
2632 ASSERT_TRUE(
2633 test_client_.enclave_authenticator_should_be_discovered_.has_value());
2634 EXPECT_EQ(*test_client_.enclave_authenticator_should_be_discovered_,
2635 test_case.expected_discovered);
2636 }
2637}
2638
2639// Tests that the enclave authenticator should only be discovered for get
2640// assertion requests it can fulfill.
2641TEST_F(AuthenticatorContentBrowserClientTest,
2642 DiscoverEnclaveAuthenticatorGetAssertion) {
2643 NavigateAndCommit(GURL(kTestOrigin1));
2644 device::PublicKeyCredentialDescriptor internal_cred(
2645 device::CredentialType::kPublicKey,
2646 std::vector<uint8_t>(kTestCredentialIdLength, 1),
2647 {device::FidoTransportProtocol::kInternal});
2648 device::PublicKeyCredentialDescriptor sk_cred(
2649 device::CredentialType::kPublicKey,
2650 std::vector<uint8_t>(kTestCredentialIdLength, 2),
2651 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
2652 device::PublicKeyCredentialDescriptor unknown_cred(
2653 device::CredentialType::kPublicKey,
2654 std::vector<uint8_t>(kTestCredentialIdLength, 3), {});
2655 struct TestCase {
2656 bool available;
2657 std::vector<device::PublicKeyCredentialDescriptor> creds;
2658 bool expected_discovered;
2659 } kTestCases[] = {
2660 {false, {internal_cred}, false},
2661 {true, {sk_cred}, false},
2662 {true, {internal_cred}, true},
2663 {true, {unknown_cred}, true},
2664 };
2665 for (const auto& test_case : kTestCases) {
2666 SCOPED_TRACE(testing::Message() << "available=" << test_case.available);
2667 testing::Message creds_trace;
2668 creds_trace << "creds=[";
2669 if (test_case.creds.empty()) {
2670 creds_trace << "empty]";
2671 } else {
2672 creds_trace << test_case.creds.at(0).id.at(0) << "]";
2673 }
2674 SCOPED_TRACE(creds_trace);
2675 test_client_.GetTestWebAuthenticationDelegate()
2676 ->browser_provided_passkeys_available = test_case.available;
2677 PublicKeyCredentialRequestOptionsPtr options =
2678 GetTestPublicKeyCredentialRequestOptions();
2679 options->allow_credentials = std::move(test_case.creds);
2680 AuthenticatorGetAssertion(std::move(options));
2681 ASSERT_TRUE(
2682 test_client_.enclave_authenticator_should_be_discovered_.has_value());
2683 EXPECT_EQ(*test_client_.enclave_authenticator_should_be_discovered_,
2684 test_case.expected_discovered);
2685 }
2686}
2687
2688TEST_F(AuthenticatorContentBrowserClientTest, TransportsFromAllowList) {
2689 NavigateAndCommit(GURL(kTestOrigin1));
2690 InjectVirtualAuthenticatorForAllTransports();
2691 device::PublicKeyCredentialDescriptor internal_cred(
2692 device::CredentialType::kPublicKey, {1, 2, 3, 4},
2693 {device::FidoTransportProtocol::kInternal});
2694 device::PublicKeyCredentialDescriptor sk_cred(
2695 device::CredentialType::kPublicKey, {1, 2, 3, 4},
2696 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
2697 PublicKeyCredentialRequestOptionsPtr options =
2698 GetTestPublicKeyCredentialRequestOptions();
2699 options->allow_credentials = {std::move(internal_cred), std::move(sk_cred)};
2700 AuthenticatorGetAssertion(std::move(options));
2701 EXPECT_THAT(test_client_.discovered_transports_,
2702 testing::UnorderedElementsAre(
2703 device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
2704 device::FidoTransportProtocol::kInternal));
2705}
2706
2707TEST_F(AuthenticatorContentBrowserClientTest,
2708 AllTransportsAllowedIfHasAllowedCredentialWithEmptyTransportsList) {
2709 NavigateAndCommit(GURL(kTestOrigin1));
2710 InjectVirtualAuthenticatorForAllTransports();
2711 device::PublicKeyCredentialDescriptor cred(device::CredentialType::kPublicKey,
2712 {1, 2, 3, 4}, {});
2713 PublicKeyCredentialRequestOptionsPtr options =
2714 GetTestPublicKeyCredentialRequestOptions();
2715 options->allow_credentials = {std::move(cred)};
2716 AuthenticatorGetAssertion(std::move(options));
2717 EXPECT_THAT(test_client_.discovered_transports_,
2718 testing::UnorderedElementsAre(
2719 device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
2720 device::FidoTransportProtocol::kNearFieldCommunication,
2721 device::FidoTransportProtocol::kBluetoothLowEnergy,
2722 device::FidoTransportProtocol::kHybrid,
2723 device::FidoTransportProtocol::kInternal));
2724}
2725
2726TEST_F(AuthenticatorContentBrowserClientTest,
2727 AllTransportsAllowedIfAllowCredentialsListIsEmpty) {
2728 test_client_.web_authentication_delegate.supports_resident_keys = true;
2729 NavigateAndCommit(GURL(kTestOrigin1));
2730 InjectVirtualAuthenticatorForAllTransports();
2731 PublicKeyCredentialRequestOptionsPtr options =
2732 GetTestPublicKeyCredentialRequestOptions();
2733 options->allow_credentials.clear();
2734 AuthenticatorGetAssertion(std::move(options));
2735 EXPECT_THAT(test_client_.discovered_transports_,
2736 testing::UnorderedElementsAre(
2737 device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
2738 device::FidoTransportProtocol::kNearFieldCommunication,
2739 device::FidoTransportProtocol::kBluetoothLowEnergy,
2740 device::FidoTransportProtocol::kHybrid,
2741 device::FidoTransportProtocol::kInternal));
2742}
2743
Martin Kreichgauerfefb3772021-04-12 23:02:482744// Test that credentials can be created and used from an extension origin when
2745// permitted by the delegate.
2746TEST_F(AuthenticatorContentBrowserClientTest, ChromeExtensions) {
Martin Kreichgauer05a33fb2022-02-18 23:31:292747 constexpr char kExtensionId[] = "abcdefg";
Martin Kreichgauerfefb3772021-04-12 23:02:482748 static const std::string kExtensionOrigin =
Martin Kreichgauer05a33fb2022-02-18 23:31:292749 std::string(kExtensionScheme) + "://" + kExtensionId;
Martin Kreichgauerfefb3772021-04-12 23:02:482750
2751 NavigateAndCommit(GURL(kExtensionOrigin + "/test.html"));
2752
Martin Kreichgauer05a33fb2022-02-18 23:31:292753 for (bool permit_webauthn_in_extensions : {false, true}) {
2754 SCOPED_TRACE(testing::Message()
2755 << "permit=" << permit_webauthn_in_extensions);
2756 test_client_.GetTestWebAuthenticationDelegate()->permit_extensions =
2757 permit_webauthn_in_extensions;
Martin Kreichgauerfefb3772021-04-12 23:02:482758
2759 std::vector<uint8_t> credential_id;
2760 {
2761 PublicKeyCredentialCreationOptionsPtr options =
2762 GetTestPublicKeyCredentialCreationOptions();
2763 options->relying_party.id = kExtensionId;
2764
2765 MakeCredentialResult result =
2766 AuthenticatorMakeCredential(std::move(options));
Martin Kreichgauer05a33fb2022-02-18 23:31:292767 if (permit_webauthn_in_extensions) {
Martin Kreichgauerfefb3772021-04-12 23:02:482768 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
2769 credential_id = result.response->info->raw_id;
2770 } else {
Martin Kreichgauer05a33fb2022-02-18 23:31:292771 EXPECT_EQ(result.status, AuthenticatorStatus::INVALID_PROTOCOL);
Martin Kreichgauerfefb3772021-04-12 23:02:482772 }
2773 }
2774
2775 {
2776 PublicKeyCredentialRequestOptionsPtr options =
2777 GetTestPublicKeyCredentialRequestOptions();
2778 options->relying_party_id = kExtensionId;
2779 options->allow_credentials[0] = device::PublicKeyCredentialDescriptor(
2780 device::CredentialType::kPublicKey, std::move(credential_id));
2781
2782 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
Martin Kreichgauer05a33fb2022-02-18 23:31:292783 permit_webauthn_in_extensions
2784 ? AuthenticatorStatus::SUCCESS
2785 : AuthenticatorStatus::INVALID_PROTOCOL);
2786 }
2787 }
2788}
2789
2790TEST_F(AuthenticatorContentBrowserClientTest, ChromeExtensionBadRpIds) {
2791 // Permit WebAuthn in extensions.
Martin Kreichgauer05a33fb2022-02-18 23:31:292792 static const std::string kExtensionOrigin =
Lei Zhang9cc9aad72022-08-25 19:19:182793 base::StrCat({kExtensionScheme, "://abcdefg"});
Martin Kreichgauer05a33fb2022-02-18 23:31:292794 test_client_.GetTestWebAuthenticationDelegate()->permit_extensions = true;
2795
2796 // Extensions are not permitted to assert RP IDs different from their
2797 // extension ID.
2798 for (auto* rp_id : {"", "xyz", "localhost", "xyz.com",
2799 "chrome-extension://abcdefg", "https://abcdefg"}) {
2800 NavigateAndCommit(GURL(kExtensionOrigin + "/test.html"));
2801 {
2802 PublicKeyCredentialCreationOptionsPtr options =
2803 GetTestPublicKeyCredentialCreationOptions();
2804 options->relying_party.id = rp_id;
2805
2806 MakeCredentialResult result =
2807 AuthenticatorMakeCredential(std::move(options));
2808 EXPECT_EQ(result.status, AuthenticatorStatus::INVALID_PROTOCOL);
2809 }
2810
2811 {
2812 PublicKeyCredentialRequestOptionsPtr options =
2813 GetTestPublicKeyCredentialRequestOptions();
2814 options->relying_party_id = rp_id;
2815 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
2816 EXPECT_EQ(result.status, AuthenticatorStatus::INVALID_PROTOCOL);
Martin Kreichgauerfefb3772021-04-12 23:02:482817 }
2818 }
2819}
2820
Adam Langley31caeef42018-03-22 03:44:102821TEST_F(AuthenticatorContentBrowserClientTest, AttestationBehaviour) {
2822 const char kStandardCommonName[] = "U2F Attestation";
2823 const char kIndividualCommonName[] = "Individual Cert";
2824
Adam Langley88415c812018-03-22 20:20:222825 const std::vector<TestCase> kTests = {
Adam Langley31caeef42018-03-22 03:44:102826 {
2827 AttestationConveyancePreference::NONE,
Adam Langley1d65a2a2020-06-08 22:13:242828 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172829 AuthenticatorStatus::SUCCESS,
2830 AttestationType::NONE,
2831 "",
Adam Langley31caeef42018-03-22 03:44:102832 },
2833 {
2834 AttestationConveyancePreference::NONE,
Adam Langley1d65a2a2020-06-08 22:13:242835 EnterprisePolicy::LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172836 AuthenticatorStatus::SUCCESS,
2837 AttestationType::NONE,
2838 "",
Adam Langley31caeef42018-03-22 03:44:102839 },
2840 {
2841 AttestationConveyancePreference::INDIRECT,
Adam Langley1d65a2a2020-06-08 22:13:242842 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172843 AuthenticatorStatus::SUCCESS,
2844 AttestationType::U2F,
Adam Langley3015ad42018-08-15 02:04:362845 kStandardCommonName,
Adam Langley31caeef42018-03-22 03:44:102846 },
2847 {
2848 AttestationConveyancePreference::INDIRECT,
Adam Langley1d65a2a2020-06-08 22:13:242849 EnterprisePolicy::LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172850 AuthenticatorStatus::SUCCESS,
2851 AttestationType::U2F,
Adam Langley3015ad42018-08-15 02:04:362852 kStandardCommonName,
Adam Langley31caeef42018-03-22 03:44:102853 },
2854 {
2855 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:242856 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172857 AuthenticatorStatus::SUCCESS,
2858 AttestationType::U2F,
Adam Langley3015ad42018-08-15 02:04:362859 kStandardCommonName,
Adam Langley31caeef42018-03-22 03:44:102860 },
2861 {
2862 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:242863 EnterprisePolicy::LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172864 AuthenticatorStatus::SUCCESS,
2865 AttestationType::U2F,
Adam Langley3015ad42018-08-15 02:04:362866 kStandardCommonName,
Adam Langley02b10ce2018-06-09 03:32:002867 },
2868 {
2869 AttestationConveyancePreference::ENTERPRISE,
Adam Langley1d65a2a2020-06-08 22:13:242870 EnterprisePolicy::NOT_LISTED,
Suzy Li48702642019-04-08 20:01:462871 AuthenticatorStatus::SUCCESS,
Adam Langley193cb372024-09-13 20:09:052872 AttestationType::U2F,
2873 kStandardCommonName,
Adam Langley02b10ce2018-06-09 03:32:002874 },
2875 {
2876 AttestationConveyancePreference::ENTERPRISE,
Adam Langley1d65a2a2020-06-08 22:13:242877 EnterprisePolicy::LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:172878 AuthenticatorStatus::SUCCESS,
2879 AttestationType::U2F,
Adam Langley3015ad42018-08-15 02:04:362880 kIndividualCommonName,
Adam Langley31caeef42018-03-22 03:44:102881 },
2882 };
2883
Nina Satragnoacf403f92019-05-23 17:16:522884 virtual_device_factory_->mutable_state()->attestation_cert_common_name =
Adam Langley31caeef42018-03-22 03:44:102885 kStandardCommonName;
Nina Satragnoacf403f92019-05-23 17:16:522886 virtual_device_factory_->mutable_state()
2887 ->individual_attestation_cert_common_name = kIndividualCommonName;
Adam Langley31caeef42018-03-22 03:44:102888 NavigateAndCommit(GURL("https://example.com"));
Adam Langley31caeef42018-03-22 03:44:102889
Adam Langley88415c812018-03-22 20:20:222890 RunTestCases(kTests);
2891}
Adam Langley31caeef42018-03-22 03:44:102892
Adam Langleyf1c8b742020-07-22 19:36:062893TEST_F(AuthenticatorContentBrowserClientTest, Ctap2EnterpriseAttestation) {
2894 const char kStandardCommonName[] = "U2F Attestation";
2895 const char kIndividualCommonName[] = "Individual Cert";
2896 virtual_device_factory_->mutable_state()->attestation_cert_common_name =
2897 kStandardCommonName;
2898 virtual_device_factory_->mutable_state()
2899 ->individual_attestation_cert_common_name = kIndividualCommonName;
2900 NavigateAndCommit(GURL("https://example.com"));
2901
2902 {
2903 SCOPED_TRACE("Without RP listed");
2904
2905 device::VirtualCtap2Device::Config config;
2906 config.support_enterprise_attestation = true;
2907 virtual_device_factory_->SetCtap2Config(config);
2908
2909 const std::vector<TestCase> kTests = {
2910 {
2911 AttestationConveyancePreference::ENTERPRISE,
2912 EnterprisePolicy::LISTED,
Adam Langleyf1c8b742020-07-22 19:36:062913 AuthenticatorStatus::SUCCESS,
2914 AttestationType::PACKED,
2915 kIndividualCommonName,
2916 },
2917 {
Adam Langleyf1c8b742020-07-22 19:36:062918 AttestationConveyancePreference::ENTERPRISE,
2919 EnterprisePolicy::NOT_LISTED,
Adam Langleyf1c8b742020-07-22 19:36:062920 AuthenticatorStatus::SUCCESS,
Adam Langley193cb372024-09-13 20:09:052921 AttestationType::PACKED,
2922 kStandardCommonName,
Adam Langleyf1c8b742020-07-22 19:36:062923 },
2924 };
2925
2926 RunTestCases(kTests);
2927 }
2928
2929 {
2930 SCOPED_TRACE("With RP listed");
2931
2932 device::VirtualCtap2Device::Config config;
2933 config.support_enterprise_attestation = true;
2934 config.enterprise_attestation_rps = {"example.com"};
2935 virtual_device_factory_->SetCtap2Config(config);
2936
2937 const std::vector<TestCase> kTests = {
2938 {
2939 // Despite not being listed in enterprise policy, since the
2940 // authenticator recognises the RP ID, attestation should still be
2941 // returned.
2942 AttestationConveyancePreference::ENTERPRISE,
2943 EnterprisePolicy::NOT_LISTED,
Adam Langleyf1c8b742020-07-22 19:36:062944 AuthenticatorStatus::SUCCESS,
2945 AttestationType::PACKED,
2946 kIndividualCommonName,
2947 },
2948 {
Adam Langleyf1c8b742020-07-22 19:36:062949 AttestationConveyancePreference::ENTERPRISE,
Adam Langley193cb372024-09-13 20:09:052950 EnterprisePolicy::LISTED,
Adam Langleyf1c8b742020-07-22 19:36:062951 AuthenticatorStatus::SUCCESS,
Adam Langley193cb372024-09-13 20:09:052952 AttestationType::PACKED,
2953 kIndividualCommonName,
Adam Langleyf1c8b742020-07-22 19:36:062954 },
2955 };
2956
2957 RunTestCases(kTests);
2958 }
2959}
2960
2961TEST_F(AuthenticatorContentBrowserClientTest,
2962 Ctap2EnterpriseAttestationUnsolicited) {
2963 NavigateAndCommit(GURL(kTestOrigin1));
2964
2965 device::VirtualCtap2Device::Config config;
2966 config.support_enterprise_attestation = true;
2967 virtual_device_factory_->SetCtap2Config(config);
2968
Adam Langleyf1c8b742020-07-22 19:36:062969 {
Martin Kreichgauer55834402020-08-03 21:54:312970 EXPECT_EQ(
2971 AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions())
2972 .status,
2973 AuthenticatorStatus::SUCCESS);
Adam Langleyf1c8b742020-07-22 19:36:062974 }
2975
2976 config.always_return_enterprise_attestation = true;
2977 virtual_device_factory_->SetCtap2Config(config);
2978
2979 {
Martin Kreichgauer55834402020-08-03 21:54:312980 EXPECT_EQ(
2981 AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions())
2982 .status,
2983 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langleyf1c8b742020-07-22 19:36:062984 }
2985}
2986
Adam Langley88415c812018-03-22 20:20:222987TEST_F(AuthenticatorContentBrowserClientTest,
2988 InappropriatelyIdentifyingAttestation) {
2989 // This common name is used by several devices that have inappropriately
2990 // identifying attestation certificates.
2991 const char kCommonName[] = "FT FIDO 0100";
Adam Langley31caeef42018-03-22 03:44:102992
Adam Langley88415c812018-03-22 20:20:222993 const std::vector<TestCase> kTests = {
2994 {
Adam Langley02b10ce2018-06-09 03:32:002995 AttestationConveyancePreference::ENTERPRISE,
Adam Langley1d65a2a2020-06-08 22:13:242996 EnterprisePolicy::NOT_LISTED,
Suzy Li48702642019-04-08 20:01:462997 AuthenticatorStatus::SUCCESS,
2998 AttestationType::NONE,
Martin Kreichgauerdb032682019-02-20 05:05:172999 "",
Adam Langley88415c812018-03-22 20:20:223000 },
3001 {
Adam Langley02b10ce2018-06-09 03:32:003002 AttestationConveyancePreference::ENTERPRISE,
Adam Langley1d65a2a2020-06-08 22:13:243003 EnterprisePolicy::LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:173004 AuthenticatorStatus::SUCCESS,
3005 AttestationType::U2F,
3006 kCommonName,
Adam Langley88415c812018-03-22 20:20:223007 },
3008 };
Adam Langley31caeef42018-03-22 03:44:103009
Nina Satragnoacf403f92019-05-23 17:16:523010 virtual_device_factory_->mutable_state()->attestation_cert_common_name =
Adam Langley88415c812018-03-22 20:20:223011 kCommonName;
Nina Satragnoacf403f92019-05-23 17:16:523012 virtual_device_factory_->mutable_state()
3013 ->individual_attestation_cert_common_name = kCommonName;
Adam Langley88415c812018-03-22 20:20:223014 NavigateAndCommit(GURL("https://example.com"));
Adam Langley31caeef42018-03-22 03:44:103015
Adam Langley88415c812018-03-22 20:20:223016 RunTestCases(kTests);
Adam Langley31caeef42018-03-22 03:44:103017}
3018
Martin Kreichgauer2c9e5352018-10-30 23:28:533019// Test attestation erasure for an authenticator that uses self-attestation
3020// (which requires a zero AAGUID), but has a non-zero AAGUID. This mirrors the
3021// behavior of the Touch ID platform authenticator.
3022TEST_F(AuthenticatorContentBrowserClientTest,
3023 PlatformAuthenticatorAttestation) {
Martin Kreichgauerfefb3772021-04-12 23:02:483024 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override = true;
Nina Satragnoacf403f92019-05-23 17:16:523025 virtual_device_factory_->SetSupportedProtocol(
3026 device::ProtocolVersion::kCtap2);
Nina Satragnof585eca2019-07-29 20:05:323027 virtual_device_factory_->SetTransport(
3028 device::FidoTransportProtocol::kInternal);
Nina Satragnoacf403f92019-05-23 17:16:523029 virtual_device_factory_->mutable_state()->self_attestation = true;
3030 virtual_device_factory_->mutable_state()
3031 ->non_zero_aaguid_with_self_attestation = true;
Martin Kreichgauer2c9e5352018-10-30 23:28:533032 NavigateAndCommit(GURL("https://example.com"));
3033
3034 const std::vector<TestCase> kTests = {
3035 {
3036 // Self-attestation is defined as having a zero AAGUID, but
3037 // |non_zero_aaguid_with_self_attestation| is set above. Thus, if no
3038 // attestation is requested, the self-attestation will be removed but,
3039 // because the transport is kInternal, the AAGUID will be preserved.
3040 AttestationConveyancePreference::NONE,
Adam Langley1d65a2a2020-06-08 22:13:243041 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauer2c9e5352018-10-30 23:28:533042 AuthenticatorStatus::SUCCESS,
Martin Kreichgauerdb032682019-02-20 05:05:173043 AttestationType::NONE_WITH_NONZERO_AAGUID,
3044 "",
Martin Kreichgauer2c9e5352018-10-30 23:28:533045 },
3046 {
Martin Kreichgauerf3c5ed5b2021-01-25 21:19:003047 // Attestation is always returned if requested because it is privacy
3048 // preserving. The AttestationConsent value is irrelevant.
Martin Kreichgauer2c9e5352018-10-30 23:28:533049 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243050 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauer2c9e5352018-10-30 23:28:533051 AuthenticatorStatus::SUCCESS,
Martin Kreichgauerdb032682019-02-20 05:05:173052 AttestationType::SELF_WITH_NONZERO_AAGUID,
3053 "",
Martin Kreichgauer2c9e5352018-10-30 23:28:533054 },
3055 };
3056
3057 RunTestCases(kTests);
3058}
3059
Adam Langley3015ad42018-08-15 02:04:363060TEST_F(AuthenticatorContentBrowserClientTest, Ctap2SelfAttestation) {
Nina Satragnoacf403f92019-05-23 17:16:523061 virtual_device_factory_->SetSupportedProtocol(
3062 device::ProtocolVersion::kCtap2);
3063 virtual_device_factory_->mutable_state()->self_attestation = true;
Adam Langley3015ad42018-08-15 02:04:363064 NavigateAndCommit(GURL("https://example.com"));
3065
3066 const std::vector<TestCase> kTests = {
3067 {
3068 // If no attestation is requested, we'll return the self attestation
3069 // rather than erasing it.
3070 AttestationConveyancePreference::NONE,
Adam Langley1d65a2a2020-06-08 22:13:243071 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:173072 AuthenticatorStatus::SUCCESS,
3073 AttestationType::SELF,
3074 "",
Adam Langley3015ad42018-08-15 02:04:363075 },
3076 {
Adam Langley193cb372024-09-13 20:09:053077 // And if direct attestation was requested.
Adam Langley3015ad42018-08-15 02:04:363078 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243079 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:173080 AuthenticatorStatus::SUCCESS,
3081 AttestationType::SELF,
3082 "",
Adam Langley3015ad42018-08-15 02:04:363083 },
3084 };
3085
3086 RunTestCases(kTests);
3087}
3088
3089TEST_F(AuthenticatorContentBrowserClientTest,
3090 Ctap2SelfAttestationNonZeroAaguid) {
Nina Satragnoacf403f92019-05-23 17:16:523091 virtual_device_factory_->SetSupportedProtocol(
3092 device::ProtocolVersion::kCtap2);
3093 virtual_device_factory_->mutable_state()->self_attestation = true;
3094 virtual_device_factory_->mutable_state()
3095 ->non_zero_aaguid_with_self_attestation = true;
Adam Langley3015ad42018-08-15 02:04:363096 NavigateAndCommit(GURL("https://example.com"));
3097
3098 const std::vector<TestCase> kTests = {
3099 {
Martin Kreichgauer2c9e5352018-10-30 23:28:533100 // Since the virtual device is configured to set a non-zero AAGUID the
3101 // self-attestation should still be replaced with a "none"
Adam Langley3015ad42018-08-15 02:04:363102 // attestation.
3103 AttestationConveyancePreference::NONE,
Adam Langley1d65a2a2020-06-08 22:13:243104 EnterprisePolicy::NOT_LISTED,
Martin Kreichgauerdb032682019-02-20 05:05:173105 AuthenticatorStatus::SUCCESS,
3106 AttestationType::NONE,
3107 "",
Adam Langley3015ad42018-08-15 02:04:363108 },
3109 };
3110
3111 RunTestCases(kTests);
3112}
3113
Adam Langleyf6fbd032020-03-16 23:44:233114TEST_F(AuthenticatorContentBrowserClientTest, BlockedAttestation) {
3115 NavigateAndCommit(GURL("https://foo.example.com"));
3116
3117 static constexpr struct {
Adam Langley051b97932021-01-11 19:11:233118 const char* filter_json;
Adam Langleyf6fbd032020-03-16 23:44:233119 AttestationConveyancePreference attestation;
Adam Langley1d65a2a2020-06-08 22:13:243120 EnterprisePolicy enterprise_policy;
Adam Langleyf6fbd032020-03-16 23:44:233121 AttestationType result;
3122 } kTests[] = {
Adam Langley051b97932021-01-11 19:11:233123 // Empty or nonsense filter doesn't block anything.
Adam Langleyf6fbd032020-03-16 23:44:233124 {
3125 "",
3126 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243127 EnterprisePolicy::NOT_LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233128 AttestationType::U2F,
3129 },
3130 {
Adam Langley051b97932021-01-11 19:11:233131 R"({"filters": []})",
Adam Langleyf6fbd032020-03-16 23:44:233132 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243133 EnterprisePolicy::NOT_LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233134 AttestationType::U2F,
3135 },
3136 // Direct listing of domain blocks...
3137 {
Adam Langley051b97932021-01-11 19:11:233138 R"({"filters": [{
3139 "operation": "mc",
3140 "rp_id": "example.com",
3141 "action": "no-attestation"
3142 }]})",
Adam Langleyf6fbd032020-03-16 23:44:233143 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243144 EnterprisePolicy::NOT_LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233145 AttestationType::NONE,
3146 },
3147 // ... unless attestation is permitted by policy.
3148 {
Adam Langley051b97932021-01-11 19:11:233149 R"({"filters": [{
3150 "operation": "mc",
3151 "rp_id": "example.com",
3152 "action": "no-attestation"
3153 }]})",
Adam Langleyf6fbd032020-03-16 23:44:233154 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243155 EnterprisePolicy::LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233156 AttestationType::U2F,
3157 },
Adam Langley051b97932021-01-11 19:11:233158 // The whole domain can be blocked. (Note, blocking a domain would
3159 // normally want to list both the base domain and a pattern for
3160 // subdomains because the below also matches fooexample.com.)
Adam Langleyf6fbd032020-03-16 23:44:233161 {
Adam Langley051b97932021-01-11 19:11:233162 R"({"filters": [{
3163 "operation": "mc",
3164 "rp_id": "*example.com",
3165 "action": "no-attestation"
3166 }]})",
Adam Langleyf6fbd032020-03-16 23:44:233167 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243168 EnterprisePolicy::NOT_LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233169 AttestationType::NONE,
3170 },
3171 // Policy again overrides
3172 {
Adam Langley051b97932021-01-11 19:11:233173 R"({"filters": [{
3174 "operation": "mc",
3175 "rp_id": "*example.com",
3176 "action": "no-attestation"
3177 }]})",
Adam Langleyf6fbd032020-03-16 23:44:233178 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243179 EnterprisePolicy::LISTED,
Adam Langleyf6fbd032020-03-16 23:44:233180 AttestationType::U2F,
3181 },
Adam Langley051b97932021-01-11 19:11:233182 // An explicit wildcard will match everything, be careful. (Omitting
3183 // both RP ID and device is a parse error, however.)
Adam Langleyf6fbd032020-03-16 23:44:233184 {
Adam Langley051b97932021-01-11 19:11:233185 R"({"filters": [{
3186 "operation": "mc",
3187 "rp_id": "*",
3188 "action": "no-attestation"
3189 }]})",
Adam Langleyf6fbd032020-03-16 23:44:233190 AttestationConveyancePreference::DIRECT,
Adam Langley1d65a2a2020-06-08 22:13:243191 EnterprisePolicy::NOT_LISTED,
Adam Langley051b97932021-01-11 19:11:233192 AttestationType::NONE,
Adam Langleyf6fbd032020-03-16 23:44:233193 },
3194 };
3195
3196 int test_num = 0;
3197 for (const auto& test : kTests) {
3198 SCOPED_TRACE(test_num++);
Adam Langley051b97932021-01-11 19:11:233199 SCOPED_TRACE(test.filter_json);
Adam Langleyf6fbd032020-03-16 23:44:233200
Adam Langley051b97932021-01-11 19:11:233201 device::fido_filter::ScopedFilterForTesting filter(test.filter_json);
Adam Langleyf6fbd032020-03-16 23:44:233202
3203 const std::vector<TestCase> kTestCase = {
3204 {
3205 test.attestation,
Adam Langley1d65a2a2020-06-08 22:13:243206 test.enterprise_policy,
Adam Langleyf6fbd032020-03-16 23:44:233207 AuthenticatorStatus::SUCCESS,
3208 test.result,
3209 "",
3210 },
3211 };
3212
3213 RunTestCases(kTestCase);
3214 }
3215}
3216
Adam Langley051b97932021-01-11 19:11:233217TEST_F(AuthenticatorContentBrowserClientTest, FilteringMakeCredential) {
3218 static const struct {
3219 const char* filter_json;
3220 bool expect_make_credential_success;
3221 } kTests[] = {
3222 {
3223 R"()",
3224 true,
3225 },
3226 // Block by device.
3227 {
3228 R"({"filters": [{
3229 "operation": "mc",
3230 "device": "VirtualFidoDevice-*",
3231 "action": "block",
3232 }]})",
3233 false,
3234 },
3235 // Shouldn't block when the device is unrelated.
3236 {
3237 R"({"filters": [{
3238 "operation": "mc",
3239 "device": "OtherDevice-*",
3240 "action": "block",
3241 }]})",
3242 true,
3243 },
3244 // Block by RP ID.
3245 {
3246 R"({"filters": [{
3247 "operation": "mc",
3248 "rp_id": "google.com",
3249 "action": "block",
3250 }]})",
3251 false,
3252 },
3253 // Unrelated RP ID.
3254 {
3255 R"({"filters": [{
3256 "operation": "mc",
3257 "rp_id": "other.com",
3258 "action": "block",
3259 }]})",
3260 true,
3261 },
3262 // Block specific user ID.
3263 {
3264 R"({"filters": [{
3265 "operation": "mc",
3266 "rp_id": "*",
3267 "id_type": "user",
3268 "id": "0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A",
3269 "action": "block",
3270 }]})",
3271 false,
3272 },
3273 // Different user ID.
3274 {
3275 R"({"filters": [{
3276 "operation": "mc",
3277 "rp_id": "*",
3278 "id_type": "user",
3279 "id": "FF0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A0A",
3280 "action": "block",
3281 }]})",
3282 true,
3283 },
3284 // Block by user ID length.
3285 {
3286 R"({"filters": [{
3287 "operation": "mc",
3288 "rp_id": "*",
3289 "id_type": "user",
3290 "id_min_size": 32,
3291 "id_max_size": 32,
3292 "action": "block",
3293 }]})",
3294 false,
3295 },
3296 // Block user IDs that are longer than specified by
3297 // |GetTestPublicKeyCredentialUserEntity|.
3298 {
3299 R"({"filters": [{
3300 "operation": "mc",
3301 "rp_id": "*",
3302 "id_type": "user",
3303 "id_min_size": 33,
3304 "action": "block",
3305 }]})",
3306 true,
3307 },
3308 // Block excluded credential ID.
3309 {
3310 R"({"filters": [{
3311 "operation": "mc",
3312 "rp_id": "*",
3313 "id_type": "cred",
3314 "id": "0000000000000000000000000000000000000000000000000000000000000000",
3315 "action": "block",
3316 }]})",
3317 false,
3318 },
3319 // Block different credential ID.
3320 {
3321 R"({"filters": [{
3322 "operation": "mc",
3323 "rp_id": "*",
3324 "id_type": "cred",
3325 "id": "FF00000000000000000000000000000000000000000000000000000000000000",
3326 "action": "block",
3327 }]})",
3328 true,
3329 },
3330 // Block by excluded credential ID length.
3331 {
3332 R"({"filters": [{
3333 "operation": "mc",
3334 "rp_id": "*",
3335 "id_type": "cred",
3336 "id_min_size": 32,
3337 "id_max_size": 32,
3338 "action": "block",
3339 }]})",
3340 false,
3341 },
3342 // Block longer credentials IDs than are used.
3343 {
3344 R"({"filters": [{
3345 "operation": "mc",
3346 "rp_id": "*",
3347 "id_type": "cred",
3348 "id_min_size": 33,
3349 "action": "block",
3350 }]})",
3351 true,
3352 },
3353 };
3354
3355 NavigateAndCommit(GURL(kTestOrigin1));
3356
3357 int test_num = 0;
3358 for (const auto& test : kTests) {
3359 SCOPED_TRACE(test_num++);
3360 SCOPED_TRACE(test.filter_json);
3361 device::fido_filter::ScopedFilterForTesting filter(test.filter_json);
3362
3363 PublicKeyCredentialCreationOptionsPtr options =
3364 GetTestPublicKeyCredentialCreationOptions();
3365 options->exclude_credentials = GetTestCredentials();
3366 EXPECT_EQ(AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options))
3367 .status == AuthenticatorStatus::SUCCESS,
3368 test.expect_make_credential_success);
3369 }
3370}
3371
3372TEST_F(AuthenticatorContentBrowserClientTest, FilteringGetAssertion) {
3373 static const struct {
3374 const char* filter_json;
3375 bool expect_get_assertion_success;
3376 } kTests[] = {
3377 {
3378 R"()",
3379 true,
3380 },
3381 // Block by device.
3382 {
3383 R"({"filters": [{
3384 "operation": "ga",
3385 "device": "VirtualFidoDevice-*",
3386 "action": "block",
3387 }]})",
3388 false,
3389 },
3390 // Shouldn't block when the device is unrelated.
3391 {
3392 R"({"filters": [{
3393 "operation": "ga",
3394 "device": "OtherDevice-*",
3395 "action": "block",
3396 }]})",
3397 true,
3398 },
3399 // Block by RP ID.
3400 {
3401 R"({"filters": [{
3402 "operation": "ga",
3403 "rp_id": "google.com",
3404 "action": "block",
3405 }]})",
3406 false,
3407 },
3408 // Unrelated RP ID.
3409 {
3410 R"({"filters": [{
3411 "operation": "ga",
3412 "rp_id": "other.com",
3413 "action": "block",
3414 }]})",
3415 true,
3416 },
3417 // Block allowList credential ID.
3418 {
3419 R"({"filters": [{
3420 "operation": "ga",
3421 "rp_id": "*",
3422 "id_type": "cred",
3423 "id": "0000000000000000000000000000000000000000000000000000000000000000",
3424 "action": "block",
3425 }]})",
3426 false,
3427 },
3428 // Block different credential ID.
3429 {
3430 R"({"filters": [{
3431 "operation": "ga",
3432 "rp_id": "*",
3433 "id_type": "cred",
3434 "id": "FF00000000000000000000000000000000000000000000000000000000000000",
3435 "action": "block",
3436 }]})",
3437 true,
3438 },
3439 // Block by allowList credential ID length for credentials returned by
3440 // |GetTestCredentials|.
3441 {
3442 R"({"filters": [{
3443 "operation": "ga",
3444 "rp_id": "*",
3445 "id_type": "cred",
3446 "id_min_size": 32,
3447 "id_max_size": 32,
3448 "action": "block",
3449 }]})",
3450 false,
3451 },
3452 // Block longer credentials IDs than are used.
3453 {
3454 R"({"filters": [{
3455 "operation": "ga",
3456 "rp_id": "*",
3457 "id_type": "cred",
3458 "id_min_size": 33,
3459 "action": "block",
3460 }]})",
3461 true,
3462 },
3463 };
3464
3465 NavigateAndCommit(GURL(kTestOrigin1));
3466
3467 int test_num = 0;
3468 bool credential_added = false;
3469 for (const auto& test : kTests) {
3470 SCOPED_TRACE(test_num++);
3471 SCOPED_TRACE(test.filter_json);
3472 device::fido_filter::ScopedFilterForTesting filter(test.filter_json);
3473
3474 PublicKeyCredentialRequestOptionsPtr options =
3475 GetTestPublicKeyCredentialRequestOptions();
3476 if (!credential_added) {
3477 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:463478 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langley051b97932021-01-11 19:11:233479 credential_added = true;
3480 }
3481
3482 EXPECT_EQ(
3483 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status ==
3484 AuthenticatorStatus::SUCCESS,
3485 test.expect_get_assertion_success);
3486 }
3487}
3488
3489TEST_F(AuthenticatorContentBrowserClientTest, FilteringFailsOpen) {
3490 // Setting the filter to invalid JSON should not filter anything.
3491 device::fido_filter::ScopedFilterForTesting filter(
3492 "nonsense",
3493 device::fido_filter::ScopedFilterForTesting::PermitInvalidJSON::kYes);
3494
3495 NavigateAndCommit(GURL(kTestOrigin1));
3496 PublicKeyCredentialCreationOptionsPtr options =
3497 GetTestPublicKeyCredentialCreationOptions();
3498 options->exclude_credentials = GetTestCredentials();
3499 EXPECT_EQ(
3500 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
3501 AuthenticatorStatus::SUCCESS);
3502}
3503
Balazs Engedya2750182018-06-13 07:45:263504TEST_F(AuthenticatorContentBrowserClientTest,
3505 MakeCredentialRequestStartedCallback) {
Balazs Engedya2750182018-06-13 07:45:263506 NavigateAndCommit(GURL(kTestOrigin1));
Mario Sanchez Pradac791f54e2019-07-09 23:38:373507 mojo::Remote<blink::mojom::Authenticator> authenticator =
3508 ConnectToAuthenticator();
Balazs Engedya2750182018-06-13 07:45:263509
3510 PublicKeyCredentialCreationOptionsPtr options =
3511 GetTestPublicKeyCredentialCreationOptions();
3512
Adem Derinele6378762024-06-27 06:05:073513 TestRequestStartedFuture request_started_future;
Jun Choid53a039c2018-08-16 23:38:153514 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073515 request_started_future.GetCallback();
Balazs Engedya2750182018-06-13 07:45:263516 authenticator->MakeCredential(std::move(options), base::DoNothing());
Adem Derinele6378762024-06-27 06:05:073517 EXPECT_TRUE(request_started_future.Wait());
Balazs Engedya2750182018-06-13 07:45:263518}
3519
3520TEST_F(AuthenticatorContentBrowserClientTest,
3521 GetAssertionRequestStartedCallback) {
Balazs Engedya2750182018-06-13 07:45:263522 NavigateAndCommit(GURL(kTestOrigin1));
Mario Sanchez Pradac791f54e2019-07-09 23:38:373523 mojo::Remote<blink::mojom::Authenticator> authenticator =
3524 ConnectToAuthenticator();
Balazs Engedya2750182018-06-13 07:45:263525
3526 PublicKeyCredentialRequestOptionsPtr options =
3527 GetTestPublicKeyCredentialRequestOptions();
3528
Adem Derinele6378762024-06-27 06:05:073529 TestRequestStartedFuture request_started_future;
Jun Choid53a039c2018-08-16 23:38:153530 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073531 request_started_future.GetCallback();
Adem Derinel72e11db2025-02-11 15:58:003532 authenticator->GetCredential(std::move(options), base::DoNothing());
Adem Derinele6378762024-06-27 06:05:073533 EXPECT_TRUE(request_started_future.Wait());
Balazs Engedya2750182018-06-13 07:45:263534}
3535
Nina Satragno7782b1032020-11-05 17:59:263536TEST_F(AuthenticatorContentBrowserClientTest, MakeCredentialStartOver) {
3537 NavigateAndCommit(GURL(kTestOrigin1));
3538 mojo::Remote<blink::mojom::Authenticator> authenticator =
3539 ConnectToAuthenticator();
3540
3541 PublicKeyCredentialCreationOptionsPtr options =
3542 GetTestPublicKeyCredentialCreationOptions();
Adam Langley39ca22f2022-01-19 01:15:323543 // Make the request fail so that it's started over.
3544 options->authenticator_selection->user_verification_requirement =
3545 device::UserVerificationRequirement::kRequired;
Nina Satragno7782b1032020-11-05 17:59:263546
Adem Derinele6378762024-06-27 06:05:073547 TestRequestStartedFuture request_started_future;
Nina Satragno7782b1032020-11-05 17:59:263548 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073549 request_started_future.GetCallback();
3550 TestRequestStartedFuture request_restarted_future;
3551 test_client_.started_over_callback_ = request_restarted_future.GetCallback();
Nina Satragno7782b1032020-11-05 17:59:263552
3553 authenticator->MakeCredential(std::move(options), base::DoNothing());
Adem Derinele6378762024-06-27 06:05:073554 EXPECT_TRUE(request_started_future.Wait());
3555 EXPECT_TRUE(request_restarted_future.Wait());
Adam Langley39ca22f2022-01-19 01:15:323556
3557 const auto& discoveries_trace = virtual_device_factory_->trace()->discoveries;
3558 ASSERT_EQ(discoveries_trace.size(), 2u);
3559 EXPECT_TRUE(discoveries_trace[0].is_stopped);
3560 EXPECT_TRUE(discoveries_trace[0].is_destroyed);
3561 EXPECT_FALSE(discoveries_trace[1].is_stopped);
3562 EXPECT_FALSE(discoveries_trace[1].is_destroyed);
Nina Satragno7782b1032020-11-05 17:59:263563}
3564
3565TEST_F(AuthenticatorContentBrowserClientTest, GetAssertionStartOver) {
3566 NavigateAndCommit(GURL(kTestOrigin1));
3567 mojo::Remote<blink::mojom::Authenticator> authenticator =
3568 ConnectToAuthenticator();
3569
3570 PublicKeyCredentialRequestOptionsPtr options =
3571 GetTestPublicKeyCredentialRequestOptions();
3572
Adem Derinele6378762024-06-27 06:05:073573 TestRequestStartedFuture request_started_future;
Nina Satragno7782b1032020-11-05 17:59:263574 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073575 request_started_future.GetCallback();
3576 TestRequestStartedFuture request_restarted_future;
3577 test_client_.started_over_callback_ = request_restarted_future.GetCallback();
Nina Satragno7782b1032020-11-05 17:59:263578
Adem Derinel72e11db2025-02-11 15:58:003579 authenticator->GetCredential(std::move(options), base::DoNothing());
Adem Derinele6378762024-06-27 06:05:073580 EXPECT_TRUE(request_started_future.Wait());
3581 EXPECT_TRUE(request_restarted_future.Wait());
Adam Langley39ca22f2022-01-19 01:15:323582
3583 const auto& discoveries_trace = virtual_device_factory_->trace()->discoveries;
3584 ASSERT_EQ(discoveries_trace.size(), 2u);
3585 EXPECT_TRUE(discoveries_trace[0].is_stopped);
3586 EXPECT_TRUE(discoveries_trace[0].is_destroyed);
3587 EXPECT_FALSE(discoveries_trace[1].is_stopped);
3588 EXPECT_FALSE(discoveries_trace[1].is_destroyed);
Nina Satragno7782b1032020-11-05 17:59:263589}
3590
Adam Langley573d3ac2018-04-28 00:32:133591TEST_F(AuthenticatorContentBrowserClientTest, Unfocused) {
3592 // When the |ContentBrowserClient| considers the tab to be unfocused,
3593 // registration requests should fail with a |NOT_FOCUSED| error, but getting
3594 // assertions should still work.
Martin Kreichgauerfefb3772021-04-12 23:02:483595 test_client_.GetTestWebAuthenticationDelegate()->is_focused = false;
Adam Langley573d3ac2018-04-28 00:32:133596
3597 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langley573d3ac2018-04-28 00:32:133598
3599 {
Adem Derinele6378762024-06-27 06:05:073600 TestRequestStartedFuture request_started_future;
Jun Choid53a039c2018-08-16 23:38:153601 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073602 request_started_future.GetCallback();
Balazs Engedya2750182018-06-13 07:45:263603
Martin Kreichgauer55834402020-08-03 21:54:313604 EXPECT_EQ(
3605 AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions())
3606 .status,
3607 AuthenticatorStatus::NOT_FOCUSED);
Adem Derinele6378762024-06-27 06:05:073608 EXPECT_FALSE(request_started_future.IsReady());
Adam Langley573d3ac2018-04-28 00:32:133609 }
3610
3611 {
Ken Buchanan4a33ef0d2019-06-20 17:34:043612 device::PublicKeyCredentialDescriptor credential;
Martin Kreichgauerbece642a2021-12-07 21:02:463613 credential.credential_type = device::CredentialType::kPublicKey;
3614 credential.id.resize(16);
3615 credential.transports = {
Ken Buchanan4a33ef0d2019-06-20 17:34:043616 device::FidoTransportProtocol::kUsbHumanInterfaceDevice};
Jun Choie4aee4ff32018-08-13 19:35:073617
Nina Satragnoacf403f92019-05-23 17:16:523618 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:463619 credential.id, kTestRelyingPartyId));
Adam Langley4f0548282020-06-12 18:54:303620 PublicKeyCredentialRequestOptionsPtr options =
3621 GetTestPublicKeyCredentialRequestOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:043622 options->allow_credentials.emplace_back(credential);
Adam Langley573d3ac2018-04-28 00:32:133623
Adem Derinele6378762024-06-27 06:05:073624 TestRequestStartedFuture request_started_future;
Jun Choid53a039c2018-08-16 23:38:153625 test_client_.action_callbacks_registered_callback =
Adem Derinele6378762024-06-27 06:05:073626 request_started_future.GetCallback();
Balazs Engedya2750182018-06-13 07:45:263627
Martin Kreichgauer55834402020-08-03 21:54:313628 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
3629 AuthenticatorStatus::SUCCESS);
Adem Derinele6378762024-06-27 06:05:073630 EXPECT_TRUE(request_started_future.IsReady());
Adam Langley573d3ac2018-04-28 00:32:133631 }
3632}
3633
Balazs Engedy5b1088762018-06-05 09:30:463634TEST_F(AuthenticatorContentBrowserClientTest,
3635 NullDelegate_RejectsWithPendingRequest) {
3636 test_client_.return_null_delegate = true;
Balazs Engedy5b1088762018-06-05 09:30:463637 NavigateAndCommit(GURL(kTestOrigin1));
Balazs Engedy5b1088762018-06-05 09:30:463638
3639 {
3640 PublicKeyCredentialCreationOptionsPtr options =
3641 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauer55834402020-08-03 21:54:313642 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
3643 AuthenticatorStatus::PENDING_REQUEST);
Balazs Engedy5b1088762018-06-05 09:30:463644 }
3645
3646 {
3647 PublicKeyCredentialRequestOptionsPtr options =
3648 GetTestPublicKeyCredentialRequestOptions();
Martin Kreichgauer55834402020-08-03 21:54:313649 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
3650 AuthenticatorStatus::PENDING_REQUEST);
Balazs Engedy5b1088762018-06-05 09:30:463651 }
3652}
3653
Martin Kreichgauer263f6d82020-03-10 05:46:453654TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAOverride) {
Martin Kreichgauer55834402020-08-03 21:54:313655 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauer263f6d82020-03-10 05:46:453656
Nina Satragnof585eca2019-07-29 20:05:323657 for (const bool is_uvpaa : {false, true}) {
3658 SCOPED_TRACE(::testing::Message() << "is_uvpaa=" << is_uvpaa);
Martin Kreichgauerfefb3772021-04-12 23:02:483659 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override =
3660 is_uvpaa;
Martin Kreichgauer977c00472018-11-29 00:09:043661
Martin Kreichgauerca18b432023-04-25 18:58:473662 EXPECT_EQ(AuthenticatorIsUvpaa(), is_uvpaa);
Martin Kreichgauer977c00472018-11-29 00:09:043663 }
3664}
Martin Kreichgauerbcfda9cf2018-08-02 00:47:073665
Nina Satragno365759f2023-10-11 20:01:293666TEST_F(AuthenticatorContentBrowserClientTest,
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423667 GetClientCapabilities_CheckUvpaaPlumbing) {
3668 NavigateAndCommit(GURL(kTestOrigin1));
3669
3670 for (const bool is_uvpaa : {false, true}) {
3671 SCOPED_TRACE(::testing::Message() << "is_uvpaa=" << is_uvpaa);
3672 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override =
3673 is_uvpaa;
3674
3675 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
3676 ExpectCapability(capabilities,
3677 client_capabilities::kUserVerifyingPlatformAuthenticator,
3678 is_uvpaa);
Andrii Natiahlyie480a492024-09-18 15:20:373679 }
3680}
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423681
Andrii Natiahlyie480a492024-09-18 15:20:373682TEST_F(AuthenticatorContentBrowserClientTest,
3683 GetClientCapabilities_CheckPPAAPlumbing) {
3684 NavigateAndCommit(GURL(kTestOrigin1));
3685
3686 // Verify: PPAA == `is_uvpaa` || HybridTransport (false).
3687 for (const bool is_uvpaa : {false, true}) {
3688 SCOPED_TRACE(::testing::Message() << "is_uvpaa=" << is_uvpaa);
3689 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override =
3690 is_uvpaa;
3691 // Simulate `hybrid_transport = false`.
3692 EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(false));
3693
3694 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423695 ExpectCapability(capabilities,
3696 client_capabilities::kPasskeyPlatformAuthenticator,
3697 is_uvpaa);
3698 }
Andrii Natiahlyie480a492024-09-18 15:20:373699
3700 // Verify: PPAA == isUVPAA (false) || `hybrid_transport`.
3701 for (const bool hybrid_transport : {false, true}) {
3702 SCOPED_TRACE(::testing::Message()
3703 << "hybrid_transport=" << hybrid_transport);
3704 // Simulate `isUVPAA = false`.
3705 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override = false;
3706
3707 EXPECT_CALL(*mock_adapter_, IsPresent())
3708 .WillOnce(::testing::Return(hybrid_transport));
3709
3710 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
3711 ExpectCapability(capabilities,
3712 client_capabilities::kPasskeyPlatformAuthenticator,
3713 hybrid_transport);
3714 }
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423715}
3716
3717TEST_F(AuthenticatorContentBrowserClientTest,
3718 GetClientCapabilities_ConditionalGet_ReturnsFalse) {
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423719 NavigateAndCommit(GURL(kTestOrigin1));
Andrii Natiahlyi6b2f4b12024-09-03 14:58:423720 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
3721 ExpectCapability(capabilities, client_capabilities::kConditionalGet, true);
3722}
3723
3724TEST_F(AuthenticatorContentBrowserClientTest,
Nina Satragno365759f2023-10-11 20:01:293725 GPMPasskeys_IsConditionalMediationAvailable) {
Nina Satragno365759f2023-10-11 20:01:293726 NavigateAndCommit(GURL(kTestOrigin1));
3727 ASSERT_TRUE(AuthenticatorIsConditionalMediationAvailable());
3728}
3729
Martin Kreichgauerf43496d72022-04-26 03:21:553730// AuthenticatorImplRemoteDesktopClientOverrideTest exercises the
3731// RemoteDesktopClientOverride extension, which is used by remote desktop
3732// applications exercising requests on behalf of other origins.
Martin Kreichgauere255af062022-04-18 19:40:563733class AuthenticatorImplRemoteDesktopClientOverrideTest
3734 : public AuthenticatorContentBrowserClientTest {
3735 protected:
Martin Kreichgauere255af062022-04-18 19:40:563736 static constexpr char kOtherRdpOrigin[] = "https://myrdp.test";
3737 static constexpr char kExampleOrigin[] = "https://example.test";
3738 static constexpr char kExampleRpId[] = "example.test";
Martin Kreichgauerf43496d72022-04-26 03:21:553739 static constexpr char kExampleAppid[] = "https://example.test/appid.json";
Martin Kreichgauere255af062022-04-18 19:40:563740 static constexpr char kOtherRpId[] = "other.test";
Martin Kreichgauerf43496d72022-04-26 03:21:553741 static constexpr char kOtherAppid[] = "https://other.test/appid.json";
Martin Kreichgauere255af062022-04-18 19:40:563742
3743 void SetUp() override {
Nina Satragno867bca52022-10-13 15:02:023744 AuthenticatorContentBrowserClientTest::SetUp();
Martin Kreichgauerf43496d72022-04-26 03:21:553745 // Authorize `kCorpCrdOrigin` to exercise the extension. In //chrome, this
3746 // is controlled by the `WebAuthenticationRemoteProxiedRequestsAllowed`
3747 // enterprise policy.
Martin Kreichgauere255af062022-04-18 19:40:563748 test_client_.GetTestWebAuthenticationDelegate()
3749 ->remote_desktop_client_override_origin =
3750 url::Origin::Create(GURL(kCorpCrdOrigin));
Martin Kreichgauerf43496d72022-04-26 03:21:553751 // Controls the Blink feature gating the extension.
3752 scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
3753 switches::kWebAuthRemoteDesktopSupport);
Martin Kreichgauere255af062022-04-18 19:40:563754 }
3755
3756 base::test::ScopedCommandLine scoped_command_line_;
Martin Kreichgauere255af062022-04-18 19:40:563757};
3758
3759TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, MakeCredential) {
Martin Kreichgauerf43496d72022-04-26 03:21:553760 // Verify that an authorized origin may use the extension. Regular RP ID
3761 // processing applies, i.e. the origin override must be authorized to claim
3762 // the specified RP ID.
Martin Kreichgauere255af062022-04-18 19:40:563763 const struct TestCase {
3764 std::string local_origin;
3765 std::string remote_origin;
3766 std::string rp_id;
3767 bool success;
3768 } test_cases[] = {
3769 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, true},
3770 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, false},
3771 {kOtherRdpOrigin, kExampleOrigin, kOtherRpId, false},
3772 {kExampleOrigin, kExampleOrigin, kExampleRpId, false},
3773 };
3774
3775 for (const auto& test : test_cases) {
3776 SCOPED_TRACE(testing::Message()
3777 << "local=" << test.local_origin
3778 << " remote=" << test.remote_origin << " rp=" << test.rp_id);
3779 NavigateAndCommit(GURL(test.local_origin));
3780
3781 PublicKeyCredentialCreationOptionsPtr options =
3782 GetTestPublicKeyCredentialCreationOptions();
3783 options->relying_party.id = test.rp_id;
3784 options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
3785 url::Origin::Create(GURL(test.remote_origin)), true);
3786
3787 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
3788 test.success ? AuthenticatorStatus::SUCCESS
Martin Kreichgauer00b04dd2022-05-04 14:04:223789 : AuthenticatorStatus::
3790 REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
Martin Kreichgauere255af062022-04-18 19:40:563791 }
3792}
3793
3794TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, GetAssertion) {
Martin Kreichgauerf43496d72022-04-26 03:21:553795 // Verify that an authorized origin may use the extension. Regular RP ID
3796 // processing applies, i.e. the origin override must be authorized to claim
3797 // the specified RP ID.
Martin Kreichgauere255af062022-04-18 19:40:563798 const struct TestCase {
3799 std::string local_origin;
3800 std::string remote_origin;
3801 std::string rp_id;
3802 bool success;
3803 } test_cases[] = {
3804 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, true},
3805 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, false},
3806 {kOtherRdpOrigin, kExampleOrigin, kOtherRpId, false},
3807 {kExampleOrigin, kExampleOrigin, kExampleRpId, false},
3808 };
3809
3810 for (const auto& test : test_cases) {
3811 SCOPED_TRACE(testing::Message()
3812 << "local=" << test.local_origin
3813 << " remote=" << test.remote_origin << " rp=" << test.rp_id);
3814 ResetVirtualDevice();
3815 NavigateAndCommit(GURL(test.local_origin));
3816
3817 PublicKeyCredentialRequestOptionsPtr options =
3818 GetTestPublicKeyCredentialRequestOptions();
3819 options->relying_party_id = test.rp_id;
Slobodan Pejicc8ce88b2023-06-19 15:41:123820 options->extensions->remote_desktop_client_override =
3821 RemoteDesktopClientOverride::New(
3822 url::Origin::Create(GURL(test.remote_origin)), true);
Martin Kreichgauere255af062022-04-18 19:40:563823
3824 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
3825 options->allow_credentials[0].id, test.rp_id));
3826
3827 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
3828 test.success ? AuthenticatorStatus::SUCCESS
Martin Kreichgauer00b04dd2022-05-04 14:04:223829 : AuthenticatorStatus::
3830 REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
Martin Kreichgauere255af062022-04-18 19:40:563831 }
3832}
3833
Martin Kreichgauerf43496d72022-04-26 03:21:553834TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, MakeCredentialAppid) {
3835 // Verify that origin overriding extends to the appidExclude extension. If the
3836 // caller origin is authorized to use the extension, App ID processing is
3837 // applied to the overridden origin.
3838 const struct TestCase {
3839 std::string local_origin;
3840 std::string remote_origin;
3841 std::string rp_id;
3842 std::string app_id;
Martin Kreichgauer00b04dd2022-05-04 14:04:223843 AuthenticatorStatus expected;
Martin Kreichgauerf43496d72022-04-26 03:21:553844 } test_cases[] = {
Martin Kreichgauer00b04dd2022-05-04 14:04:223845 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3846 AuthenticatorStatus::SUCCESS},
3847 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3848 AuthenticatorStatus::INVALID_DOMAIN},
3849 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3850 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3851 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3852 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3853 {kExampleOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3854 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3855 {kExampleOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3856 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
Martin Kreichgauerf43496d72022-04-26 03:21:553857 };
3858
3859 for (const auto& test : test_cases) {
3860 SCOPED_TRACE(testing::Message()
3861 << "local=" << test.local_origin
3862 << " remote=" << test.remote_origin << " rp=" << test.rp_id
3863 << " appid=" << test.app_id);
3864 NavigateAndCommit(GURL(test.local_origin));
3865
3866 PublicKeyCredentialCreationOptionsPtr options =
3867 GetTestPublicKeyCredentialCreationOptions();
3868 options->relying_party.id = test.rp_id;
3869 options->appid_exclude = test.app_id;
3870 options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
3871 url::Origin::Create(GURL(test.remote_origin)), true);
3872
3873 auto result = AuthenticatorMakeCredential(std::move(options));
Martin Kreichgauer00b04dd2022-05-04 14:04:223874 EXPECT_EQ(result.status, test.expected);
Martin Kreichgauerf43496d72022-04-26 03:21:553875 }
3876}
3877
3878TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, GetAssertionAppid) {
3879 // Verify that origin overriding extends to the appid extension. If the
3880 // caller origin is authorized to use the extension, App ID processing is
3881 // applied to the overridden origin.
3882 const struct TestCase {
3883 std::string local_origin;
3884 std::string remote_origin;
3885 std::string rp_id;
3886 std::string app_id;
Martin Kreichgauer00b04dd2022-05-04 14:04:223887 AuthenticatorStatus expected;
Martin Kreichgauerf43496d72022-04-26 03:21:553888 } test_cases[] = {
Martin Kreichgauer00b04dd2022-05-04 14:04:223889 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3890 AuthenticatorStatus::SUCCESS},
3891 {kCorpCrdOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3892 AuthenticatorStatus::INVALID_DOMAIN},
3893 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3894 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3895 {kOtherRdpOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3896 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3897 {kExampleOrigin, kExampleOrigin, kExampleRpId, kExampleAppid,
3898 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
3899 {kExampleOrigin, kExampleOrigin, kExampleRpId, kOtherAppid,
3900 AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED},
Martin Kreichgauerf43496d72022-04-26 03:21:553901 };
3902
3903 for (const auto& test : test_cases) {
3904 SCOPED_TRACE(testing::Message()
3905 << "local=" << test.local_origin
3906 << " remote=" << test.remote_origin << " rp=" << test.rp_id
3907 << " appid=" << test.app_id);
3908 ResetVirtualDevice();
3909 NavigateAndCommit(GURL(test.local_origin));
3910
3911 PublicKeyCredentialRequestOptionsPtr options =
3912 GetTestPublicKeyCredentialRequestOptions();
3913 options->relying_party_id = test.rp_id;
Slobodan Pejicc8ce88b2023-06-19 15:41:123914 options->extensions->appid = test.app_id;
3915 options->extensions->remote_desktop_client_override =
3916 RemoteDesktopClientOverride::New(
3917 url::Origin::Create(GURL(test.remote_origin)), true);
Martin Kreichgauerf43496d72022-04-26 03:21:553918
3919 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
3920 options->allow_credentials[0].id, test.rp_id));
3921
3922 auto result = AuthenticatorGetAssertion(std::move(options));
Martin Kreichgauer00b04dd2022-05-04 14:04:223923 EXPECT_EQ(result.status, test.expected);
Martin Kreichgauerf43496d72022-04-26 03:21:553924 }
3925}
3926
Jun Choi7d7139a2018-07-27 21:02:043927class MockAuthenticatorRequestDelegateObserver
3928 : public TestAuthenticatorRequestDelegate {
3929 public:
Balazs Engedyed4966f72018-08-29 22:07:453930 using InterestingFailureReasonCallback =
3931 base::OnceCallback<void(InterestingFailureReason)>;
3932
Nina Satragno20a78cf92020-06-22 18:02:193933 explicit MockAuthenticatorRequestDelegateObserver(
Balazs Engedyed4966f72018-08-29 22:07:453934 InterestingFailureReasonCallback failure_reasons_callback =
3935 base::DoNothing())
Jun Choi7d7139a2018-07-27 21:02:043936 : TestAuthenticatorRequestDelegate(
3937 nullptr /* render_frame_host */,
3938 base::DoNothing() /* did_start_request_callback */,
Nina Satragno129251c2023-10-23 21:50:403939 /*started_over_callback=*/base::OnceClosure(),
Nina Satragnoa11a1ff2025-07-07 21:05:303940 /*simulate_user_cancelled=*/false,
3941 /*enclave_authenticator_should_be_discovered=*/nullptr,
3942 /*discovered_transports=*/nullptr),
Balazs Engedyed4966f72018-08-29 22:07:453943 failure_reasons_callback_(std::move(failure_reasons_callback)) {}
Peter Boström828b9022021-09-21 02:28:433944
3945 MockAuthenticatorRequestDelegateObserver(
3946 const MockAuthenticatorRequestDelegateObserver&) = delete;
3947 MockAuthenticatorRequestDelegateObserver& operator=(
3948 const MockAuthenticatorRequestDelegateObserver&) = delete;
3949
Jun Choi7d7139a2018-07-27 21:02:043950 ~MockAuthenticatorRequestDelegateObserver() override = default;
3951
Martin Kreichgauerf45542a2019-08-30 16:45:503952 bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
Kim Paulhamus0aa02f82019-02-06 23:43:553953 CHECK(failure_reasons_callback_);
Balazs Engedyed4966f72018-08-29 22:07:453954 std::move(failure_reasons_callback_).Run(reason);
Kim Paulhamus0aa02f82019-02-06 23:43:553955 return false;
Balazs Engedyed4966f72018-08-29 22:07:453956 }
3957
Jun Choi84abf8e2018-08-14 00:03:003958 MOCK_METHOD1(
3959 OnTransportAvailabilityEnumerated,
3960 void(device::FidoRequestHandlerBase::TransportAvailabilityInfo data));
Martin Kreichgauer23355d592018-08-23 22:40:313961 MOCK_METHOD1(EmbedderControlsAuthenticatorDispatch,
3962 bool(const device::FidoAuthenticator&));
3963 MOCK_METHOD1(FidoAuthenticatorAdded, void(const device::FidoAuthenticator&));
Md Hasibul Hasana963a9342024-04-03 10:15:143964 MOCK_METHOD1(FidoAuthenticatorRemoved, void(std::string_view));
Jun Choi7d7139a2018-07-27 21:02:043965
3966 private:
Balazs Engedyed4966f72018-08-29 22:07:453967 InterestingFailureReasonCallback failure_reasons_callback_;
Jun Choi7d7139a2018-07-27 21:02:043968};
3969
Amos Lim12696e5e32022-09-16 07:37:583970// Fake test construct that shares all other behavior with
3971// AuthenticatorCommonImpl except that:
3972// - FakeAuthenticatorCommonImpl does not trigger UI activity.
Jun Choi7d7139a2018-07-27 21:02:043973// - MockAuthenticatorRequestDelegateObserver is injected to
3974// |request_delegate_|
3975// instead of ChromeAuthenticatorRequestDelegate.
Amos Lim12696e5e32022-09-16 07:37:583976class FakeAuthenticatorCommonImpl : public AuthenticatorCommonImpl {
Jun Choi7d7139a2018-07-27 21:02:043977 public:
Amos Lim12696e5e32022-09-16 07:37:583978 explicit FakeAuthenticatorCommonImpl(
Jun Choi7d7139a2018-07-27 21:02:043979 RenderFrameHost* render_frame_host,
Jun Choi7d7139a2018-07-27 21:02:043980 std::unique_ptr<MockAuthenticatorRequestDelegateObserver> mock_delegate)
Adam Langley3ec44c22023-08-10 01:04:013981 : AuthenticatorCommonImpl(render_frame_host,
3982 ServingRequestsFor::kWebContents),
Jun Choi7d7139a2018-07-27 21:02:043983 mock_delegate_(std::move(mock_delegate)) {}
Amos Lim12696e5e32022-09-16 07:37:583984 ~FakeAuthenticatorCommonImpl() override = default;
Jun Choi7d7139a2018-07-27 21:02:043985
Martin Kreichgauer37ace492021-04-08 23:36:463986 std::unique_ptr<AuthenticatorRequestClientDelegate>
3987 MaybeCreateRequestDelegate() override {
Jun Choi7d7139a2018-07-27 21:02:043988 DCHECK(mock_delegate_);
Nina Satragnof3b63e72019-08-20 16:44:383989 return std::move(mock_delegate_);
Jun Choi7d7139a2018-07-27 21:02:043990 }
3991
3992 private:
3993 friend class AuthenticatorImplRequestDelegateTest;
3994
3995 std::unique_ptr<MockAuthenticatorRequestDelegateObserver> mock_delegate_;
3996};
3997
3998class AuthenticatorImplRequestDelegateTest : public AuthenticatorImplTest {
3999 public:
Nina Satragno20a78cf92020-06-22 18:02:194000 AuthenticatorImplRequestDelegateTest() = default;
4001 ~AuthenticatorImplRequestDelegateTest() override = default;
Jun Choi7d7139a2018-07-27 21:02:044002
Mario Sanchez Pradac791f54e2019-07-09 23:38:374003 mojo::Remote<blink::mojom::Authenticator> ConnectToFakeAuthenticator(
Martin Kreichgauer70fc0cf2020-07-17 01:01:004004 std::unique_ptr<MockAuthenticatorRequestDelegateObserver> delegate) {
Mario Sanchez Pradac791f54e2019-07-09 23:38:374005 mojo::Remote<blink::mojom::Authenticator> authenticator;
Martin Kreichgauer7d2b8dbb2021-04-01 16:03:454006 // AuthenticatorImpl owns itself. It self-destructs when the RenderFrameHost
4007 // navigates or is deleted.
danakjc70aec1f2022-07-07 15:48:194008 AuthenticatorImpl::CreateForTesting(
4009 *main_rfh(), authenticator.BindNewPipeAndPassReceiver(),
Amos Lim12696e5e32022-09-16 07:37:584010 std::make_unique<FakeAuthenticatorCommonImpl>(main_rfh(),
4011 std::move(delegate)));
Jun Choi7d7139a2018-07-27 21:02:044012 return authenticator;
4013 }
Jun Choi7d7139a2018-07-27 21:02:044014};
4015
4016TEST_F(AuthenticatorImplRequestDelegateTest,
4017 TestRequestDelegateObservesFidoRequestHandler) {
Martin Kreichgauerdb032682019-02-20 05:05:174018 EXPECT_CALL(*mock_adapter_, IsPresent())
Jun Choife176682018-08-30 07:49:144019 .WillRepeatedly(::testing::Return(true));
Balazs Engedy5b4891f2018-08-29 23:08:004020
Nina Satragnoacf403f92019-05-23 17:16:524021 auto discovery_factory =
4022 std::make_unique<device::test::FakeFidoDiscoveryFactory>();
Ken Buchanan0228d322020-05-04 21:20:584023 auto* fake_hid_discovery = discovery_factory->ForgeNextHidDiscovery();
Jagadesh P8db17bf2023-11-28 04:14:154024 ReplaceDiscoveryFactory(std::move(discovery_factory));
Jun Choi7d7139a2018-07-27 21:02:044025
Martin Kreichgauer55834402020-08-03 21:54:314026 NavigateAndCommit(GURL(kTestOrigin1));
Jun Choi7d7139a2018-07-27 21:02:044027 PublicKeyCredentialRequestOptionsPtr options =
4028 GetTestPublicKeyCredentialRequestOptions();
Adem Derinel72e11db2025-02-11 15:58:004029 TestGetCredentialFuture future;
Jun Choi7d7139a2018-07-27 21:02:044030
4031 auto mock_delegate =
4032 std::make_unique<MockAuthenticatorRequestDelegateObserver>();
4033 auto* const mock_delegate_ptr = mock_delegate.get();
Martin Kreichgauer70fc0cf2020-07-17 01:01:004034 auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate));
Jun Choi7d7139a2018-07-27 21:02:044035
Ken Buchanan0228d322020-05-04 21:20:584036 auto mock_usb_device = device::MockFidoDevice::MakeCtap();
4037 mock_usb_device->StubGetId();
4038 mock_usb_device->SetDeviceTransport(
4039 device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
4040 const auto device_id = mock_usb_device->GetId();
Jun Choi7d7139a2018-07-27 21:02:044041
Jun Choi84abf8e2018-08-14 00:03:004042 EXPECT_CALL(*mock_delegate_ptr, OnTransportAvailabilityEnumerated(_));
Jun Choife176682018-08-30 07:49:144043 EXPECT_CALL(*mock_delegate_ptr, EmbedderControlsAuthenticatorDispatch(_))
4044 .WillOnce(testing::Return(true));
Jun Choi7d7139a2018-07-27 21:02:044045
Ken Buchanan0228d322020-05-04 21:20:584046 base::RunLoop usb_device_found_done;
Martin Kreichgauer23355d592018-08-23 22:40:314047 EXPECT_CALL(*mock_delegate_ptr, FidoAuthenticatorAdded(_))
Jun Choi7d7139a2018-07-27 21:02:044048 .WillOnce(testing::InvokeWithoutArgs(
Ken Buchanan0228d322020-05-04 21:20:584049 [&usb_device_found_done]() { usb_device_found_done.Quit(); }));
Jun Choi7d7139a2018-07-27 21:02:044050
Ken Buchanan0228d322020-05-04 21:20:584051 base::RunLoop usb_device_lost_done;
Jun Choi7d7139a2018-07-27 21:02:044052 EXPECT_CALL(*mock_delegate_ptr, FidoAuthenticatorRemoved(_))
4053 .WillOnce(testing::InvokeWithoutArgs(
Ken Buchanan0228d322020-05-04 21:20:584054 [&usb_device_lost_done]() { usb_device_lost_done.Quit(); }));
Jun Choi7d7139a2018-07-27 21:02:044055
Adem Derinel72e11db2025-02-11 15:58:004056 authenticator->GetCredential(std::move(options), future.GetCallback());
Martin Kreichgauer2ef78692021-06-24 17:41:494057 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
Ken Buchanan0228d322020-05-04 21:20:584058 fake_hid_discovery->AddDevice(std::move(mock_usb_device));
4059 usb_device_found_done.Run();
Jun Choi7d7139a2018-07-27 21:02:044060
Ken Buchanan0228d322020-05-04 21:20:584061 fake_hid_discovery->RemoveDevice(device_id);
4062 usb_device_lost_done.Run();
Jun Choife176682018-08-30 07:49:144063 base::RunLoop().RunUntilIdle();
Jun Choi7d7139a2018-07-27 21:02:044064}
4065
Balazs Engedyed4966f72018-08-29 22:07:454066TEST_F(AuthenticatorImplRequestDelegateTest, FailureReasonForTimeout) {
Nina Satragnoacf403f92019-05-23 17:16:524067 // The VirtualFidoAuthenticator simulates a tap immediately after it gets the
4068 // request. Replace by the real discovery that will wait until timeout.
Jagadesh P8db17bf2023-11-28 04:14:154069 ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>());
4070
Martin Kreichgauer55834402020-08-03 21:54:314071 NavigateAndCommit(GURL(kTestOrigin1));
Balazs Engedyed4966f72018-08-29 22:07:454072
Adem Derinele6378762024-06-27 06:05:074073 FailureReasonFuture failure_reason_future;
Balazs Engedyed4966f72018-08-29 22:07:454074 auto mock_delegate = std::make_unique<
4075 ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>(
Adem Derinele6378762024-06-27 06:05:074076 failure_reason_future.GetCallback());
Martin Kreichgauer70fc0cf2020-07-17 01:01:004077 auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate));
Balazs Engedyed4966f72018-08-29 22:07:454078
Adem Derinel72e11db2025-02-11 15:58:004079 TestGetCredentialFuture future;
4080 authenticator->GetCredential(GetTestPublicKeyCredentialRequestOptions(),
4081 future.GetCallback());
Balazs Engedyed4966f72018-08-29 22:07:454082
Martin Kreichgauer70fc0cf2020-07-17 01:01:004083 task_environment()->FastForwardBy(kTestTimeout);
Balazs Engedyed4966f72018-08-29 22:07:454084
Adem Derinele6378762024-06-27 06:05:074085 EXPECT_TRUE(future.Wait());
Adem Derinel72e11db2025-02-11 15:58:004086 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
4087 future.Get()->get_get_assertion_response()->status);
Balazs Engedyed4966f72018-08-29 22:07:454088
Adem Derinele6378762024-06-27 06:05:074089 ASSERT_TRUE(failure_reason_future.IsReady());
Martin Kreichgauerfefb3772021-04-12 23:02:484090 EXPECT_EQ(
4091 AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout,
Adem Derinele6378762024-06-27 06:05:074092 failure_reason_future.Get());
Balazs Engedyed4966f72018-08-29 22:07:454093}
4094
4095TEST_F(AuthenticatorImplRequestDelegateTest,
4096 FailureReasonForDuplicateRegistration) {
Martin Kreichgauer55834402020-08-03 21:54:314097 NavigateAndCommit(GURL(kTestOrigin1));
Balazs Engedyed4966f72018-08-29 22:07:454098
Adem Derinele6378762024-06-27 06:05:074099 FailureReasonFuture failure_reason_future;
Balazs Engedyed4966f72018-08-29 22:07:454100 auto mock_delegate = std::make_unique<
4101 ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>(
Adem Derinele6378762024-06-27 06:05:074102 failure_reason_future.GetCallback());
Martin Kreichgauer70fc0cf2020-07-17 01:01:004103 auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate));
Balazs Engedyed4966f72018-08-29 22:07:454104
4105 PublicKeyCredentialCreationOptionsPtr options =
4106 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauer43558c72019-04-06 22:15:374107 options->exclude_credentials = GetTestCredentials();
Nina Satragnoacf403f92019-05-23 17:16:524108 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464109 options->exclude_credentials[0].id, kTestRelyingPartyId));
Balazs Engedyed4966f72018-08-29 22:07:454110
Adem Derinele6378762024-06-27 06:05:074111 TestMakeCredentialFuture future;
4112 authenticator->MakeCredential(std::move(options), future.GetCallback());
Balazs Engedyed4966f72018-08-29 22:07:454113
Adem Derinele6378762024-06-27 06:05:074114 EXPECT_TRUE(future.Wait());
Balazs Engedyed4966f72018-08-29 22:07:454115 EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_EXCLUDED,
Adem Derinele6378762024-06-27 06:05:074116 std::get<0>(future.Get()));
Balazs Engedyed4966f72018-08-29 22:07:454117
Adem Derinele6378762024-06-27 06:05:074118 ASSERT_TRUE(failure_reason_future.IsReady());
Martin Kreichgauerfefb3772021-04-12 23:02:484119 EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason::
4120 kKeyAlreadyRegistered,
Adem Derinele6378762024-06-27 06:05:074121 failure_reason_future.Get());
Balazs Engedyed4966f72018-08-29 22:07:454122}
4123
4124TEST_F(AuthenticatorImplRequestDelegateTest,
4125 FailureReasonForMissingRegistration) {
Martin Kreichgauer55834402020-08-03 21:54:314126 NavigateAndCommit(GURL(kTestOrigin1));
Balazs Engedyed4966f72018-08-29 22:07:454127
Adem Derinele6378762024-06-27 06:05:074128 FailureReasonFuture failure_reason_future;
Balazs Engedyed4966f72018-08-29 22:07:454129 auto mock_delegate = std::make_unique<
4130 ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>(
Adem Derinele6378762024-06-27 06:05:074131 failure_reason_future.GetCallback());
Martin Kreichgauer70fc0cf2020-07-17 01:01:004132 auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate));
Balazs Engedyed4966f72018-08-29 22:07:454133
Adem Derinel72e11db2025-02-11 15:58:004134 TestGetCredentialFuture future;
4135 authenticator->GetCredential(GetTestPublicKeyCredentialRequestOptions(),
4136 future.GetCallback());
Balazs Engedyed4966f72018-08-29 22:07:454137
Adem Derinele6378762024-06-27 06:05:074138 EXPECT_TRUE(future.Wait());
Adem Derinel72e11db2025-02-11 15:58:004139 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
4140 future.Get()->get_get_assertion_response()->status);
Balazs Engedyed4966f72018-08-29 22:07:454141
Adem Derinele6378762024-06-27 06:05:074142 ASSERT_TRUE(failure_reason_future.IsReady());
Martin Kreichgauerfefb3772021-04-12 23:02:484143 EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason::
4144 kKeyNotRegistered,
Adem Derinele6378762024-06-27 06:05:074145 failure_reason_future.Get());
Balazs Engedyed4966f72018-08-29 22:07:454146}
4147
Adam Langley9095b4182022-07-20 16:14:504148TEST_F(AuthenticatorImplTest, NoNonAuthoritativeTransports) {
4149 NavigateAndCommit(GURL(kTestOrigin1));
4150 virtual_device_factory_->SetSupportedProtocol(
4151 device::ProtocolVersion::kCtap2);
4152 device::VirtualCtap2Device::Config config;
4153 // If there are no transports in the attestation certificate, and none from
4154 // getInfo, then none should be reported because there isn't enough
4155 // information to say.
4156 config.include_transports_in_attestation_certificate = false;
4157 virtual_device_factory_->SetCtap2Config(config);
4158
4159 MakeCredentialResult result = AuthenticatorMakeCredential();
4160 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
4161
4162 EXPECT_TRUE(result.response->transports.empty());
4163}
4164
4165TEST_F(AuthenticatorImplTest, TransportsFromGetInfo) {
4166 NavigateAndCommit(GURL(kTestOrigin1));
4167 virtual_device_factory_->SetSupportedProtocol(
4168 device::ProtocolVersion::kCtap2);
4169 device::VirtualCtap2Device::Config config;
4170 config.include_transports_in_attestation_certificate = false;
4171 config.transports_in_get_info = {
4172 device::FidoTransportProtocol::kBluetoothLowEnergy};
4173 virtual_device_factory_->SetCtap2Config(config);
4174
4175 MakeCredentialResult result = AuthenticatorMakeCredential();
4176 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
4177
4178 base::flat_set<device::FidoTransportProtocol> reported(
4179 result.response->transports.begin(), result.response->transports.end());
4180 EXPECT_EQ(reported.size(), 2u);
4181 // The transports from the getInfo are authoritative and so they should be
4182 // reported. In addition to 'ble' from getInfo, 'usb' should be included
4183 // because that's what was used to communicate with the virtual authenticator.
4184 EXPECT_TRUE(
4185 reported.contains(device::FidoTransportProtocol::kBluetoothLowEnergy));
4186 EXPECT_TRUE(reported.contains(
4187 device::FidoTransportProtocol::kUsbHumanInterfaceDevice));
4188}
4189
4190TEST_F(AuthenticatorImplTest, TransportsInAttestationCertificate) {
Adam Langley28254a42018-09-07 18:33:274191 NavigateAndCommit(GURL(kTestOrigin1));
4192
4193 for (auto protocol :
Matthew Webb62661b62019-05-21 19:07:574194 {device::ProtocolVersion::kU2f, device::ProtocolVersion::kCtap2}) {
Adam Langley28254a42018-09-07 18:33:274195 SCOPED_TRACE(static_cast<int>(protocol));
Nina Satragnoacf403f92019-05-23 17:16:524196 virtual_device_factory_->SetSupportedProtocol(protocol);
Adam Langley28254a42018-09-07 18:33:274197
Nina Satragnof8ed79a2019-06-05 01:09:324198 for (const auto transport : std::map<device::FidoTransportProtocol,
4199 blink::mojom::AuthenticatorTransport>(
4200 {{device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
4201 blink::mojom::AuthenticatorTransport::USB},
4202 {device::FidoTransportProtocol::kBluetoothLowEnergy,
4203 blink::mojom::AuthenticatorTransport::BLE},
4204 {device::FidoTransportProtocol::kNearFieldCommunication,
4205 blink::mojom::AuthenticatorTransport::NFC}})) {
4206 virtual_device_factory_->SetTransport(transport.first);
Adam Langley28254a42018-09-07 18:33:274207
Martin Kreichgauer55834402020-08-03 21:54:314208 MakeCredentialResult result = AuthenticatorMakeCredential();
4209 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Nina Satragnof8ed79a2019-06-05 01:09:324210
Ken Buchanan4a33ef0d2019-06-20 17:34:044211 const std::vector<device::FidoTransportProtocol>& transports(
Martin Kreichgauer55834402020-08-03 21:54:314212 result.response->transports);
Nina Satragnof8ed79a2019-06-05 01:09:324213 ASSERT_EQ(1u, transports.size());
Ken Buchanan4a33ef0d2019-06-20 17:34:044214 EXPECT_EQ(transport.first, transports[0]);
Nina Satragnof8ed79a2019-06-05 01:09:324215 }
Adam Langley28254a42018-09-07 18:33:274216 }
4217}
4218
Adam Langleyd072d4f2018-10-18 16:46:064219TEST_F(AuthenticatorImplTest, ExtensionHMACSecret) {
Adam Langleyd072d4f2018-10-18 16:46:064220 NavigateAndCommit(GURL(kTestOrigin1));
4221
4222 for (const bool include_extension : {false, true}) {
Adam Langleyf04665c2020-06-23 19:57:514223 for (const bool authenticator_support : {false, true}) {
Adam Langley2d92e612022-12-29 14:43:014224 for (const bool pin_support : {false, true}) {
4225 SCOPED_TRACE(include_extension);
4226 SCOPED_TRACE(authenticator_support);
4227 SCOPED_TRACE(pin_support);
Adam Langleyd072d4f2018-10-18 16:46:064228
Adam Langley2d92e612022-12-29 14:43:014229 device::VirtualCtap2Device::Config config;
4230 config.hmac_secret_support = authenticator_support;
4231 config.pin_support = pin_support;
4232 virtual_device_factory_->SetCtap2Config(config);
Adam Langleyd072d4f2018-10-18 16:46:064233
Adam Langley2d92e612022-12-29 14:43:014234 PublicKeyCredentialCreationOptionsPtr options =
4235 GetTestPublicKeyCredentialCreationOptions();
4236 options->hmac_create_secret = include_extension;
4237 MakeCredentialResult result =
4238 AuthenticatorMakeCredential(std::move(options));
4239 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Adam Langleyd072d4f2018-10-18 16:46:064240
Adam Langley2d92e612022-12-29 14:43:014241 device::AuthenticatorData parsed_auth_data =
4242 AuthDataFromMakeCredentialResponse(result.response);
Adam Langleyd072d4f2018-10-18 16:46:064243
Adam Langley2d92e612022-12-29 14:43:014244 // The virtual CTAP2 device always echos the hmac-secret extension on
4245 // registrations. Therefore, if |hmac_secret| was set above it should be
4246 // serialised in the CBOR and correctly passed all the way back around
4247 // to the reply's authenticator data.
4248 bool has_hmac_secret = false;
4249 const auto& extensions = parsed_auth_data.extensions();
4250 if (extensions) {
4251 CHECK(extensions->is_map());
4252 const cbor::Value::MapValue& extensions_map = extensions->GetMap();
4253 const auto hmac_secret_it =
4254 extensions_map.find(cbor::Value(device::kExtensionHmacSecret));
4255 if (hmac_secret_it != extensions_map.end()) {
4256 ASSERT_TRUE(hmac_secret_it->second.is_bool());
4257 EXPECT_TRUE(hmac_secret_it->second.GetBool());
4258 has_hmac_secret = true;
4259 }
Adam Langleyf04665c2020-06-23 19:57:514260 }
Adam Langleyd072d4f2018-10-18 16:46:064261
Adam Langley2d92e612022-12-29 14:43:014262 EXPECT_EQ(include_extension && authenticator_support && pin_support,
4263 has_hmac_secret);
4264 }
Adam Langleyf04665c2020-06-23 19:57:514265 }
Adam Langleyd072d4f2018-10-18 16:46:064266 }
4267}
4268
Martin Kreichgaueracef6fd2019-10-21 23:33:044269// Tests that for an authenticator that does not support batching, credential
4270// lists get probed silently to work around authenticators rejecting exclude
4271// lists exceeding a certain size.
Martin Kreichgauer43558c72019-04-06 22:15:374272TEST_F(AuthenticatorImplTest, MakeCredentialWithLargeExcludeList) {
Martin Kreichgauer43558c72019-04-06 22:15:374273 NavigateAndCommit(GURL(kTestOrigin1));
4274
4275 for (bool has_excluded_credential : {false, true}) {
4276 SCOPED_TRACE(::testing::Message()
4277 << "has_excluded_credential=" << has_excluded_credential);
4278
Nina Satragnoacf403f92019-05-23 17:16:524279 ResetVirtualDevice();
Martin Kreichgauer43558c72019-04-06 22:15:374280 device::VirtualCtap2Device::Config config;
4281 config.reject_large_allow_and_exclude_lists = true;
Nina Satragnoacf403f92019-05-23 17:16:524282 virtual_device_factory_->SetCtap2Config(config);
Martin Kreichgauer43558c72019-04-06 22:15:374283
4284 PublicKeyCredentialCreationOptionsPtr options =
4285 GetTestPublicKeyCredentialCreationOptions();
4286 options->exclude_credentials = GetTestCredentials(/*num_credentials=*/10);
4287 if (has_excluded_credential) {
Nina Satragnoacf403f92019-05-23 17:16:524288 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464289 options->exclude_credentials.back().id, kTestRelyingPartyId));
Martin Kreichgauer43558c72019-04-06 22:15:374290 }
Martin Kreichgauer43558c72019-04-06 22:15:374291
Martin Kreichgauer55834402020-08-03 21:54:314292 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
Martin Kreichgauer43558c72019-04-06 22:15:374293 has_excluded_credential ? AuthenticatorStatus::CREDENTIAL_EXCLUDED
4294 : AuthenticatorStatus::SUCCESS);
4295 }
4296}
4297
Nina Satragno129251c2023-10-23 21:50:404298TEST_F(AuthenticatorImplTest, GetAssertionResultMetricError) {
4299 NavigateAndCommit(GURL(kTestOrigin1));
4300
4301 base::HistogramTester histogram_tester;
4302 PublicKeyCredentialRequestOptionsPtr options =
4303 GetTestPublicKeyCredentialRequestOptions();
4304 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4305 AuthenticatorStatus::NOT_ALLOWED_ERROR);
4306 histogram_tester.ExpectUniqueSample(
4307 "WebAuthentication.GetAssertion.Result",
Ken Buchanand5edc0782024-06-10 22:01:224308 AuthenticatorCommonImpl::CredentialRequestResult::kOtherError, 1);
Nina Satragno129251c2023-10-23 21:50:404309}
4310
4311TEST_F(AuthenticatorImplTest, GetAssertionResultMetricSuccess) {
4312 NavigateAndCommit(GURL(kTestOrigin1));
4313
4314 base::HistogramTester histogram_tester;
4315 PublicKeyCredentialRequestOptionsPtr options =
4316 GetTestPublicKeyCredentialRequestOptions();
4317 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
4318 options->allow_credentials.back().id, kTestRelyingPartyId));
4319 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4320 AuthenticatorStatus::SUCCESS);
4321 histogram_tester.ExpectUniqueSample(
4322 "WebAuthentication.GetAssertion.Result",
Ken Buchanand5edc0782024-06-10 22:01:224323 AuthenticatorCommonImpl::CredentialRequestResult::kOtherSuccess, 1);
4324}
4325
4326TEST_F(AuthenticatorImplTest, MakeCredentialResultMetricError) {
4327 NavigateAndCommit(GURL(kTestOrigin1));
4328
4329 base::HistogramTester histogram_tester;
4330 PublicKeyCredentialCreationOptionsPtr options =
4331 GetTestPublicKeyCredentialCreationOptions();
4332 options->exclude_credentials = GetTestCredentials();
4333 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
4334 options->exclude_credentials[0].id, kTestRelyingPartyId));
4335 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
4336 AuthenticatorStatus::CREDENTIAL_EXCLUDED);
4337 histogram_tester.ExpectUniqueSample(
4338 "WebAuthentication.MakeCredential.Result",
4339 AuthenticatorCommonImpl::CredentialRequestResult::kOtherError, 1);
4340}
4341
4342TEST_F(AuthenticatorImplTest, MakeCredentialResultMetricSuccess) {
4343 NavigateAndCommit(GURL(kTestOrigin1));
4344
4345 base::HistogramTester histogram_tester;
4346 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
4347 histogram_tester.ExpectUniqueSample(
4348 "WebAuthentication.MakeCredential.Result",
4349 AuthenticatorCommonImpl::CredentialRequestResult::kOtherSuccess, 1);
Nina Satragno129251c2023-10-23 21:50:404350}
4351
Martin Kreichgaueracef6fd2019-10-21 23:33:044352// Tests that for an authenticator that does not support batching, credential
4353// lists get probed silently to work around authenticators rejecting allow lists
4354// exceeding a certain size.
Martin Kreichgauer43558c72019-04-06 22:15:374355TEST_F(AuthenticatorImplTest, GetAssertionWithLargeAllowList) {
Martin Kreichgauer43558c72019-04-06 22:15:374356 NavigateAndCommit(GURL(kTestOrigin1));
4357
4358 for (bool has_allowed_credential : {false, true}) {
4359 SCOPED_TRACE(::testing::Message()
4360 << "has_allowed_credential=" << has_allowed_credential);
4361
Nina Satragnoacf403f92019-05-23 17:16:524362 ResetVirtualDevice();
Martin Kreichgauer43558c72019-04-06 22:15:374363 device::VirtualCtap2Device::Config config;
4364 config.reject_large_allow_and_exclude_lists = true;
Nina Satragnoacf403f92019-05-23 17:16:524365 virtual_device_factory_->SetCtap2Config(config);
Martin Kreichgauer43558c72019-04-06 22:15:374366
Martin Kreichgauer43558c72019-04-06 22:15:374367 PublicKeyCredentialRequestOptionsPtr options =
4368 GetTestPublicKeyCredentialRequestOptions();
4369 options->allow_credentials = GetTestCredentials(/*num_credentials=*/10);
4370 if (has_allowed_credential) {
Nina Satragnoacf403f92019-05-23 17:16:524371 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464372 options->allow_credentials.back().id, kTestRelyingPartyId));
Martin Kreichgauer43558c72019-04-06 22:15:374373 }
4374
Martin Kreichgauer55834402020-08-03 21:54:314375 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
Martin Kreichgauer4138eab2019-05-21 07:05:554376 has_allowed_credential ? AuthenticatorStatus::SUCCESS
4377 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauer43558c72019-04-06 22:15:374378 }
4379}
4380
Adam Langleyf530c072021-05-17 20:49:154381// Tests that, regardless of batching support, GetAssertion requests with a
4382// single allowed credential ID don't result in a silent probing request.
4383TEST_F(AuthenticatorImplTest, GetAssertionSingleElementAllowListDoesNotProbe) {
4384 NavigateAndCommit(GURL(kTestOrigin1));
4385
4386 for (bool supports_batching : {false, true}) {
4387 SCOPED_TRACE(::testing::Message()
4388 << "supports_batching=" << supports_batching);
4389
4390 ResetVirtualDevice();
4391 device::VirtualCtap2Device::Config config;
4392 if (supports_batching) {
4393 config.max_credential_id_length = kTestCredentialIdLength;
4394 config.max_credential_count_in_list = 10;
4395 }
4396 config.reject_silent_authentication_requests = true;
4397 virtual_device_factory_->SetCtap2Config(config);
4398
4399 auto test_credentials = GetTestCredentials(/*num_credentials=*/1);
4400 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464401 test_credentials.front().id, kTestRelyingPartyId));
Adam Langleyf530c072021-05-17 20:49:154402
4403 PublicKeyCredentialRequestOptionsPtr options =
4404 GetTestPublicKeyCredentialRequestOptions();
4405 options->allow_credentials = std::move(test_credentials);
4406
4407 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4408 AuthenticatorStatus::SUCCESS);
4409 }
4410}
4411
4412// Tests that an allow list that fits into a single batch does not result in a
4413// silent probing request.
4414TEST_F(AuthenticatorImplTest, GetAssertionSingleBatchListDoesNotProbe) {
4415 NavigateAndCommit(GURL(kTestOrigin1));
4416
4417 for (bool allow_list_fits_single_batch : {false, true}) {
4418 SCOPED_TRACE(::testing::Message() << "allow_list_fits_single_batch="
4419 << allow_list_fits_single_batch);
4420
4421 ResetVirtualDevice();
4422 device::VirtualCtap2Device::Config config;
4423 config.max_credential_id_length = kTestCredentialIdLength;
4424 constexpr size_t kBatchSize = 10;
4425 config.max_credential_count_in_list = kBatchSize;
4426 config.reject_silent_authentication_requests = true;
4427 virtual_device_factory_->SetCtap2Config(config);
4428
4429 auto test_credentials = GetTestCredentials(
4430 /*num_credentials=*/kBatchSize +
4431 (allow_list_fits_single_batch ? 0 : 1));
4432 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464433 test_credentials.back().id, kTestRelyingPartyId));
Adam Langleyf530c072021-05-17 20:49:154434
4435 PublicKeyCredentialRequestOptionsPtr options =
4436 GetTestPublicKeyCredentialRequestOptions();
4437 options->allow_credentials = std::move(test_credentials);
4438
4439 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4440 allow_list_fits_single_batch
4441 ? AuthenticatorStatus::SUCCESS
4442 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
4443 }
4444}
4445
Adam Langleyafba11f62020-07-10 23:49:494446TEST_F(AuthenticatorImplTest, OptionalCredentialInAssertionResponse) {
4447 // This test exercises the unfortunate optionality in the CTAP2 spec r.e.
4448 // whether an authenticator returns credential information when the allowlist
4449 // only has a single entry.
4450 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langleyafba11f62020-07-10 23:49:494451
4452 for (const auto behavior :
4453 {device::VirtualCtap2Device::Config::IncludeCredential::ONLY_IF_NEEDED,
4454 device::VirtualCtap2Device::Config::IncludeCredential::ALWAYS,
4455 device::VirtualCtap2Device::Config::IncludeCredential::NEVER}) {
4456 SCOPED_TRACE(static_cast<int>(behavior));
4457
4458 ResetVirtualDevice();
4459 device::VirtualCtap2Device::Config config;
4460 config.include_credential_in_assertion_response = behavior;
4461 config.max_credential_count_in_list = 10;
4462 config.max_credential_id_length = 256;
4463 virtual_device_factory_->SetCtap2Config(config);
4464
4465 size_t num_credentials;
4466 bool should_timeout = false;
4467 switch (behavior) {
4468 case device::VirtualCtap2Device::Config::IncludeCredential::
4469 ONLY_IF_NEEDED:
4470 // The behaviour to test for |ONLY_IF_NEEDED| is that an omitted
4471 // credential in the response is handled correctly.
4472 num_credentials = 1;
4473 break;
4474 case device::VirtualCtap2Device::Config::IncludeCredential::ALWAYS:
4475 // Also test that a technically-superfluous credential in the response
4476 // is handled.
4477 num_credentials = 1;
4478 break;
4479 case device::VirtualCtap2Device::Config::IncludeCredential::NEVER:
4480 // Test that omitting a credential in an ambiguous context causes a
4481 // failure.
4482 num_credentials = 2;
4483 should_timeout = true;
4484 break;
4485 }
4486
4487 auto test_credentials = GetTestCredentials(num_credentials);
4488 for (const auto& cred : test_credentials) {
4489 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464490 cred.id, kTestRelyingPartyId));
Adam Langleyafba11f62020-07-10 23:49:494491 }
4492
4493 PublicKeyCredentialRequestOptionsPtr options =
4494 GetTestPublicKeyCredentialRequestOptions();
4495 options->allow_credentials = std::move(test_credentials);
4496
Adam Langleyafba11f62020-07-10 23:49:494497 if (should_timeout) {
Martin Kreichgauer55834402020-08-03 21:54:314498 EXPECT_EQ(
4499 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
4500 AuthenticatorStatus::NOT_ALLOWED_ERROR);
4501 } else {
4502 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4503 AuthenticatorStatus::SUCCESS);
Adam Langleyafba11f62020-07-10 23:49:494504 }
Adam Langleyafba11f62020-07-10 23:49:494505 }
4506}
4507
Adam Langley2723c8e2020-07-11 00:11:404508// Tests that an allowList with only credential IDs of a length exceeding the
4509// maxCredentialIdLength parameter is not mistakenly interpreted as an empty
4510// allow list.
4511TEST_F(AuthenticatorImplTest, AllowListWithOnlyOversizedCredentialIds) {
4512 NavigateAndCommit(GURL(kTestOrigin1));
4513
4514 device::VirtualCtap2Device::Config config;
4515 config.u2f_support = true;
4516 config.max_credential_id_length = kTestCredentialIdLength;
4517 config.max_credential_count_in_list = 10;
4518 virtual_device_factory_->SetCtap2Config(config);
4519
4520 const std::vector<uint8_t> cred_id(kTestCredentialIdLength + 1, 0);
4521 // Inject registration so that the test will fail (because of a successful
4522 // response) if the oversized credential ID is sent.
4523 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
4524 cred_id, kTestRelyingPartyId));
4525
Adam Langley2723c8e2020-07-11 00:11:404526 for (const bool has_app_id : {false, true}) {
4527 SCOPED_TRACE(has_app_id);
Martin Kreichgauer8d55b452021-10-04 20:51:114528 virtual_device_factory_->mutable_state()->allow_list_history.clear();
Adam Langley2723c8e2020-07-11 00:11:404529
4530 PublicKeyCredentialRequestOptionsPtr options =
4531 GetTestPublicKeyCredentialRequestOptions();
4532 if (has_app_id) {
Slobodan Pejicc8ce88b2023-06-19 15:41:124533 options->extensions->appid = kTestOrigin1;
Adam Langley2723c8e2020-07-11 00:11:404534 }
4535 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
4536 device::CredentialType::kPublicKey, cred_id)};
4537
Martin Kreichgauer55834402020-08-03 21:54:314538 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4539 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauer8d55b452021-10-04 20:51:114540 const auto& allow_list_history =
4541 virtual_device_factory_->mutable_state()->allow_list_history;
Adam Langley2723c8e2020-07-11 00:11:404542 // No empty allow-list requests should have been made.
Peter Kasting1557e5f2025-01-28 01:14:084543 EXPECT_TRUE(std::ranges::none_of(
Peter Kastingd5685942022-09-02 17:52:174544 allow_list_history,
Martin Kreichgauer8d55b452021-10-04 20:51:114545 [](const std::vector<device::PublicKeyCredentialDescriptor>&
4546 allow_list) { return allow_list.empty(); }));
Adam Langley2723c8e2020-07-11 00:11:404547 }
4548}
4549
Martin Kreichgauer8d55b452021-10-04 20:51:114550// Tests that duplicate credential IDs are filtered from an assertion allow_list
4551// parameter.
4552TEST_F(AuthenticatorImplTest, AllowListWithDuplicateCredentialIds) {
4553 NavigateAndCommit(GURL(kTestOrigin1));
4554
4555 device::VirtualCtap2Device::Config config;
4556 config.u2f_support = true;
4557 config.max_credential_id_length = kTestCredentialIdLength;
4558 config.max_credential_count_in_list = 10;
4559 virtual_device_factory_->SetCtap2Config(config);
4560
4561 device::PublicKeyCredentialDescriptor cred_a(
4562 device::CredentialType::kPublicKey,
4563 std::vector<uint8_t>(kTestCredentialIdLength, 1), {});
4564 device::PublicKeyCredentialDescriptor cred_b(
4565 device::CredentialType::kPublicKey,
4566 std::vector<uint8_t>(kTestCredentialIdLength, 2),
4567 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
Martin Kreichgauer73e1efb2021-11-17 01:34:484568 // Same ID as `cred_a` and `cred_b` but with different transports. Transport
4569 // hints from descriptors with equal IDs should be merged.
Martin Kreichgauer8d55b452021-10-04 20:51:114570 device::PublicKeyCredentialDescriptor cred_c(
4571 device::CredentialType::kPublicKey,
4572 std::vector<uint8_t>(kTestCredentialIdLength, 1),
4573 {device::FidoTransportProtocol::kBluetoothLowEnergy});
4574 device::PublicKeyCredentialDescriptor cred_d(
4575 device::CredentialType::kPublicKey,
4576 std::vector<uint8_t>(kTestCredentialIdLength, 2),
4577 {device::FidoTransportProtocol::kBluetoothLowEnergy});
4578
4579 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464580 cred_b.id, kTestRelyingPartyId));
Martin Kreichgauer8d55b452021-10-04 20:51:114581
4582 PublicKeyCredentialRequestOptionsPtr options =
4583 GetTestPublicKeyCredentialRequestOptions();
4584 options->allow_credentials.clear();
4585 options->allow_credentials.insert(options->allow_credentials.end(), 5,
4586 cred_a);
4587 options->allow_credentials.push_back(cred_b);
4588 options->allow_credentials.insert(options->allow_credentials.end(), 3,
4589 cred_c);
4590 options->allow_credentials.insert(options->allow_credentials.end(), 2,
4591 cred_d);
4592
4593 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4594 AuthenticatorStatus::SUCCESS);
4595 EXPECT_EQ(virtual_device_factory_->mutable_state()->allow_list_history.size(),
4596 1u);
Martin Kreichgauer8d55b452021-10-04 20:51:114597 device::PublicKeyCredentialDescriptor cred_a_and_c(
4598 device::CredentialType::kPublicKey,
Martin Kreichgauer73e1efb2021-11-17 01:34:484599 std::vector<uint8_t>(kTestCredentialIdLength, 1));
Martin Kreichgauer8d55b452021-10-04 20:51:114600 device::PublicKeyCredentialDescriptor cred_b_and_d(
4601 device::CredentialType::kPublicKey,
Martin Kreichgauer73e1efb2021-11-17 01:34:484602 std::vector<uint8_t>(kTestCredentialIdLength, 2));
Martin Kreichgauer8d55b452021-10-04 20:51:114603 EXPECT_THAT(
4604 virtual_device_factory_->mutable_state()->allow_list_history.at(0),
4605 testing::UnorderedElementsAre(cred_a_and_c, cred_b_and_d));
4606}
4607
4608// Tests that duplicate credential IDs are filtered from a registration
4609// exclude_list parameter.
4610TEST_F(AuthenticatorImplTest, ExcludeListWithDuplicateCredentialIds) {
4611 NavigateAndCommit(GURL(kTestOrigin1));
4612
4613 device::VirtualCtap2Device::Config config;
4614 config.u2f_support = true;
4615 config.max_credential_id_length = kTestCredentialIdLength;
4616 config.max_credential_count_in_list = 100;
4617 virtual_device_factory_->SetCtap2Config(config);
4618
4619 device::PublicKeyCredentialDescriptor cred_a(
4620 device::CredentialType::kPublicKey,
4621 std::vector<uint8_t>(kTestCredentialIdLength, 1), {});
4622 device::PublicKeyCredentialDescriptor cred_b(
4623 device::CredentialType::kPublicKey,
4624 std::vector<uint8_t>(kTestCredentialIdLength, 2),
4625 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
Martin Kreichgauer73e1efb2021-11-17 01:34:484626 // Same ID as `cred_a` and `cred_b` but with different transports. Transport
4627 // hints from descriptors with equal IDs should be merged.
Martin Kreichgauer8d55b452021-10-04 20:51:114628 device::PublicKeyCredentialDescriptor cred_c(
4629 device::CredentialType::kPublicKey,
4630 std::vector<uint8_t>(kTestCredentialIdLength, 1),
4631 {device::FidoTransportProtocol::kBluetoothLowEnergy});
4632 device::PublicKeyCredentialDescriptor cred_d(
4633 device::CredentialType::kPublicKey,
4634 std::vector<uint8_t>(kTestCredentialIdLength, 2),
4635 {device::FidoTransportProtocol::kBluetoothLowEnergy});
4636
4637 PublicKeyCredentialCreationOptionsPtr options =
4638 GetTestPublicKeyCredentialCreationOptions();
4639 options->exclude_credentials.clear();
4640 options->exclude_credentials.insert(options->exclude_credentials.end(), 5,
4641 cred_a);
4642 options->exclude_credentials.push_back(cred_b);
4643 options->exclude_credentials.insert(options->exclude_credentials.end(), 3,
4644 cred_c);
4645 options->exclude_credentials.insert(options->exclude_credentials.end(), 2,
4646 cred_d);
4647
4648 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
4649 AuthenticatorStatus::SUCCESS);
4650 EXPECT_EQ(
4651 virtual_device_factory_->mutable_state()->exclude_list_history.size(),
4652 1u);
Martin Kreichgauer8d55b452021-10-04 20:51:114653 device::PublicKeyCredentialDescriptor cred_a_and_c(
4654 device::CredentialType::kPublicKey,
Martin Kreichgauer73e1efb2021-11-17 01:34:484655 std::vector<uint8_t>(kTestCredentialIdLength, 1));
Martin Kreichgauer8d55b452021-10-04 20:51:114656 device::PublicKeyCredentialDescriptor cred_b_and_d(
4657 device::CredentialType::kPublicKey,
Martin Kreichgauer73e1efb2021-11-17 01:34:484658 std::vector<uint8_t>(kTestCredentialIdLength, 2));
Martin Kreichgauer8d55b452021-10-04 20:51:114659 EXPECT_THAT(
4660 virtual_device_factory_->mutable_state()->exclude_list_history.at(0),
4661 testing::UnorderedElementsAre(cred_a_and_c, cred_b_and_d));
4662}
4663
4664// Test that allow lists over 64 entries are verboten.
4665TEST_F(AuthenticatorImplTest, OversizedAllowList) {
4666 NavigateAndCommit(GURL(kTestOrigin1));
4667
4668 device::VirtualCtap2Device::Config config;
4669 config.u2f_support = true;
4670 config.max_credential_id_length = kTestCredentialIdLength;
4671 config.max_credential_count_in_list = 100;
4672 virtual_device_factory_->SetCtap2Config(config);
4673
4674 auto test_credentials = GetTestCredentials(
4675 /*num_credentials=*/blink::mojom::
4676 kPublicKeyCredentialDescriptorListMaxSize +
4677 1);
4678 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464679 test_credentials.at(0).id, kTestRelyingPartyId));
Martin Kreichgauer8d55b452021-10-04 20:51:114680
4681 PublicKeyCredentialRequestOptionsPtr options =
4682 GetTestPublicKeyCredentialRequestOptions();
4683 options->allow_credentials = test_credentials;
4684
4685 bool has_mojo_error = false;
4686 SetMojoErrorHandler(base::BindLambdaForTesting(
4687 [&](const std::string& error) { has_mojo_error = true; }));
4688
4689 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
4690 AuthenticatorStatus::NOT_ALLOWED_ERROR);
4691 EXPECT_TRUE(has_mojo_error);
4692}
4693
4694// Test that exclude lists over 64 entries are verboten.
4695TEST_F(AuthenticatorImplTest, OversizedExcludeList) {
4696 NavigateAndCommit(GURL(kTestOrigin1));
4697
4698 device::VirtualCtap2Device::Config config;
4699 config.u2f_support = true;
4700 config.max_credential_id_length = kTestCredentialIdLength;
4701 config.max_credential_count_in_list = 100;
4702 virtual_device_factory_->SetCtap2Config(config);
4703
4704 auto test_credentials = GetTestCredentials(
4705 /*num_credentials=*/blink::mojom::
4706 kPublicKeyCredentialDescriptorListMaxSize +
4707 1);
4708
4709 PublicKeyCredentialCreationOptionsPtr options =
4710 GetTestPublicKeyCredentialCreationOptions();
4711 options->exclude_credentials = test_credentials;
4712
4713 bool has_mojo_error = false;
4714 SetMojoErrorHandler(base::BindLambdaForTesting(
4715 [&](const std::string& error) { has_mojo_error = true; }));
4716
4717 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
4718 AuthenticatorStatus::NOT_ALLOWED_ERROR);
4719 EXPECT_TRUE(has_mojo_error);
4720}
4721
Adam Langley98b6a292019-09-06 17:23:264722TEST_F(AuthenticatorImplTest, NoUnexpectedAuthenticatorExtensions) {
Adam Langley98b6a292019-09-06 17:23:264723 NavigateAndCommit(GURL(kTestOrigin1));
4724
4725 device::VirtualCtap2Device::Config config;
4726 config.add_extra_extension = true;
4727 virtual_device_factory_->SetCtap2Config(config);
4728
Adam Langley98b6a292019-09-06 17:23:264729 // Check that extra authenticator extensions are rejected when creating a
4730 // credential.
Martin Kreichgauer55834402020-08-03 21:54:314731 EXPECT_EQ(AuthenticatorMakeCredential().status,
4732 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langley98b6a292019-09-06 17:23:264733
4734 // Extensions should also be rejected when getting an assertion.
4735 PublicKeyCredentialRequestOptionsPtr assertion_options =
4736 GetTestPublicKeyCredentialRequestOptions();
4737 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464738 assertion_options->allow_credentials.back().id, kTestRelyingPartyId));
Martin Kreichgauer55834402020-08-03 21:54:314739 EXPECT_EQ(AuthenticatorGetAssertion(std::move(assertion_options)).status,
Adam Langley98b6a292019-09-06 17:23:264740 AuthenticatorStatus::NOT_ALLOWED_ERROR);
4741}
4742
Martin Kreichgauerc2f2cfc2020-04-25 15:23:074743TEST_F(AuthenticatorImplTest, NoUnexpectedClientExtensions) {
4744 NavigateAndCommit(GURL(kTestOrigin1));
4745
4746 device::VirtualCtap2Device::Config config;
4747 config.reject_all_extensions = true;
4748 virtual_device_factory_->SetCtap2Config(config);
4749
Martin Kreichgauerc2f2cfc2020-04-25 15:23:074750 // Check that no unexpected client extensions are sent to the authenticator.
Martin Kreichgauer55834402020-08-03 21:54:314751 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
Martin Kreichgauerc2f2cfc2020-04-25 15:23:074752
4753 // No extensions should be sent when getting an assertion either.
4754 PublicKeyCredentialRequestOptionsPtr assertion_options =
4755 GetTestPublicKeyCredentialRequestOptions();
4756 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464757 assertion_options->allow_credentials.back().id, kTestRelyingPartyId));
Martin Kreichgauer55834402020-08-03 21:54:314758 EXPECT_EQ(AuthenticatorGetAssertion(std::move(assertion_options)).status,
4759 AuthenticatorStatus::SUCCESS);
Martin Kreichgauerc2f2cfc2020-04-25 15:23:074760}
4761
Martin Kreichgaueracef6fd2019-10-21 23:33:044762// Tests that on an authenticator that supports batching, exclude lists that fit
4763// into a single batch are sent without probing.
4764TEST_F(AuthenticatorImplTest, ExcludeListBatching) {
Martin Kreichgaueracef6fd2019-10-21 23:33:044765 NavigateAndCommit(GURL(kTestOrigin1));
4766
4767 for (bool authenticator_has_excluded_credential : {false, true}) {
4768 SCOPED_TRACE(::testing::Message()
4769 << "authenticator_has_excluded_credential="
4770 << authenticator_has_excluded_credential);
4771
4772 ResetVirtualDevice();
4773 device::VirtualCtap2Device::Config config;
4774 config.max_credential_id_length = kTestCredentialIdLength;
4775 constexpr size_t kBatchSize = 10;
4776 config.max_credential_count_in_list = kBatchSize;
4777 // Reject silent authentication requests to ensure we are not probing
4778 // credentials silently, since the exclude list should fit into a single
4779 // batch.
4780 config.reject_silent_authentication_requests = true;
4781 virtual_device_factory_->SetCtap2Config(config);
4782
4783 auto test_credentials = GetTestCredentials(kBatchSize);
4784 test_credentials.insert(
4785 test_credentials.end() - 1,
4786 {device::CredentialType::kPublicKey,
4787 std::vector<uint8_t>(kTestCredentialIdLength + 1, 1)});
4788 if (authenticator_has_excluded_credential) {
4789 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:464790 test_credentials.back().id, kTestRelyingPartyId));
Martin Kreichgaueracef6fd2019-10-21 23:33:044791 }
4792
Martin Kreichgaueracef6fd2019-10-21 23:33:044793 PublicKeyCredentialCreationOptionsPtr options =
4794 GetTestPublicKeyCredentialCreationOptions();
4795 options->exclude_credentials = std::move(test_credentials);
Martin Kreichgauer55834402020-08-03 21:54:314796 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
4797 authenticator_has_excluded_credential
4798 ? AuthenticatorStatus::CREDENTIAL_EXCLUDED
4799 : AuthenticatorStatus::SUCCESS);
Martin Kreichgaueracef6fd2019-10-21 23:33:044800 }
4801}
4802
Adam Langleya09284eb2020-06-11 18:58:294803TEST_F(AuthenticatorImplTest, GetPublicKey) {
Adam Langleya09284eb2020-06-11 18:58:294804 NavigateAndCommit(GURL(kTestOrigin1));
4805
Adam Langleya09284eb2020-06-11 18:58:294806 static constexpr struct {
4807 device::CoseAlgorithmIdentifier algo;
Arthur Sonzognic686e8f2024-01-11 08:36:374808 std::optional<int> evp_id;
Adam Langleya09284eb2020-06-11 18:58:294809 } kTests[] = {
Adam Langleyd1eb57f2020-06-12 02:07:004810 {device::CoseAlgorithmIdentifier::kEs256, EVP_PKEY_EC},
4811 {device::CoseAlgorithmIdentifier::kRs256, EVP_PKEY_RSA},
4812 {device::CoseAlgorithmIdentifier::kEdDSA, EVP_PKEY_ED25519},
Arthur Sonzognic686e8f2024-01-11 08:36:374813 {device::CoseAlgorithmIdentifier::kInvalidForTesting, std::nullopt},
Adam Langleya09284eb2020-06-11 18:58:294814 };
4815
Adam Langleycbe07b72021-07-16 19:32:224816 std::vector<device::CoseAlgorithmIdentifier> advertised_algorithms;
4817 for (const auto& test : kTests) {
4818 advertised_algorithms.push_back(test.algo);
4819 }
4820
4821 device::VirtualCtap2Device::Config config;
4822 config.advertised_algorithms = std::move(advertised_algorithms);
4823 virtual_device_factory_->SetCtap2Config(config);
4824
Adam Langleya09284eb2020-06-11 18:58:294825 for (const auto& test : kTests) {
4826 PublicKeyCredentialCreationOptionsPtr options =
4827 GetTestPublicKeyCredentialCreationOptions();
4828 options->public_key_parameters =
4829 GetTestPublicKeyCredentialParameters(static_cast<int32_t>(test.algo));
Adam Langleya09284eb2020-06-11 18:58:294830
Martin Kreichgauer55834402020-08-03 21:54:314831 MakeCredentialResult result =
4832 AuthenticatorMakeCredential(std::move(options));
4833 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
4834 const auto& response = result.response;
Adam Langleya09284eb2020-06-11 18:58:294835 EXPECT_EQ(response->public_key_algo, static_cast<int32_t>(test.algo));
Adam Langley1ab57462022-08-23 03:29:004836
4837 // The value of the parsed authenticator data should match what's in
4838 // the attestation object.
Arthur Sonzognic686e8f2024-01-11 08:36:374839 std::optional<Value> attestation_value =
Adam Langley1ab57462022-08-23 03:29:004840 Reader::Read(response->attestation_object);
4841 CHECK(attestation_value);
4842 const auto& attestation = attestation_value->GetMap();
4843 const auto auth_data_it = attestation.find(Value(device::kAuthDataKey));
4844 CHECK(auth_data_it != attestation.end());
4845 const std::vector<uint8_t>& auth_data =
4846 auth_data_it->second.GetBytestring();
4847 EXPECT_EQ(auth_data, response->info->authenticator_data);
Adam Langleya09284eb2020-06-11 18:58:294848
4849 ASSERT_EQ(test.evp_id.has_value(), response->public_key_der.has_value());
4850 if (!test.evp_id) {
4851 continue;
4852 }
4853
Elly2f1928d62025-07-14 15:31:254854 bssl::UniquePtr<EVP_PKEY> pkey =
4855 crypto::evp::PublicKeyFromBytes(response->public_key_der.value());
Adam Langleya09284eb2020-06-11 18:58:294856 ASSERT_TRUE(pkey.get());
4857
4858 EXPECT_EQ(test.evp_id.value(), EVP_PKEY_id(pkey.get()));
4859 }
4860}
4861
Adam Langleye3777f7d2021-07-14 23:13:064862TEST_F(AuthenticatorImplTest, AlgorithmsOmitted) {
4863 // Some CTAP 2.0 security keys shipped support for algorithms other than
4864 // ECDSA P-256 but the algorithms field didn't exist then. makeCredential
4865 // requests should get routed to them anyway.
4866
4867 device::VirtualCtap2Device::Config config;
4868 // Remove the algorithms field from the getInfo.
Adam Langleycbe07b72021-07-16 19:32:224869 config.advertised_algorithms.clear();
Adam Langleye3777f7d2021-07-14 23:13:064870 virtual_device_factory_->SetCtap2Config(config);
4871 NavigateAndCommit(GURL(kTestOrigin1));
4872
4873 // Test that an Ed25519 credential can still be created. (The virtual
4874 // authenticator supports that algorithm.)
4875 {
4876 const int32_t algo =
4877 static_cast<int32_t>(device::CoseAlgorithmIdentifier::kEdDSA);
4878 PublicKeyCredentialCreationOptionsPtr options =
4879 GetTestPublicKeyCredentialCreationOptions();
4880 options->public_key_parameters = GetTestPublicKeyCredentialParameters(algo);
4881 MakeCredentialResult result =
4882 AuthenticatorMakeCredential(std::move(options));
4883 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
4884 const auto& response = result.response;
4885 EXPECT_EQ(response->public_key_algo, algo);
4886 }
4887
4888 // Test that requesting an unsupported algorithm still collects a touch.
4889 {
4890 bool touched = false;
4891 virtual_device_factory_->mutable_state()->simulate_press_callback =
4892 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
4893 touched = true;
4894 return true;
4895 });
4896
4897 const int32_t algo = static_cast<int32_t>(
4898 device::CoseAlgorithmIdentifier::kInvalidForTesting);
4899 PublicKeyCredentialCreationOptionsPtr options =
4900 GetTestPublicKeyCredentialCreationOptions();
4901 options->public_key_parameters = GetTestPublicKeyCredentialParameters(algo);
4902 MakeCredentialResult result =
4903 AuthenticatorMakeCredential(std::move(options));
4904 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanan23dce912024-07-11 16:41:274905 VerifyMakeCredentialOutcomeUkm(
4906 1, MakeCredentialOutcome::kAlgorithmNotSupported,
Ken Buchanan1ea549c12024-10-10 21:31:054907 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleye3777f7d2021-07-14 23:13:064908 EXPECT_TRUE(touched);
4909 }
4910}
4911
Adam Langley0ab76e5af2020-09-01 16:47:064912TEST_F(AuthenticatorImplTest, VirtualAuthenticatorPublicKeyAlgos) {
4913 // Exercise all the public key types in the virtual authenticator for create()
4914 // and get().
Adam Langley0ab76e5af2020-09-01 16:47:064915 NavigateAndCommit(GURL(kTestOrigin1));
4916
4917 static const struct {
Kevin McNeeab4af6512024-06-19 20:55:574918 STACK_ALLOCATED();
4919
4920 public:
Adam Langley0ab76e5af2020-09-01 16:47:064921 device::CoseAlgorithmIdentifier algo;
Kevin McNeeab4af6512024-06-19 20:55:574922 const EVP_MD* digest;
Adam Langley0ab76e5af2020-09-01 16:47:064923 } kTests[] = {
4924 {device::CoseAlgorithmIdentifier::kEs256, EVP_sha256()},
4925 {device::CoseAlgorithmIdentifier::kRs256, EVP_sha256()},
4926 {device::CoseAlgorithmIdentifier::kEdDSA, nullptr},
4927 };
4928
Adam Langleycbe07b72021-07-16 19:32:224929 std::vector<device::CoseAlgorithmIdentifier> advertised_algorithms;
4930 for (const auto& test : kTests) {
4931 advertised_algorithms.push_back(test.algo);
4932 }
4933
4934 device::VirtualCtap2Device::Config config;
4935 config.advertised_algorithms = std::move(advertised_algorithms);
4936 virtual_device_factory_->SetCtap2Config(config);
4937
Adam Langley0ab76e5af2020-09-01 16:47:064938 for (const auto& test : kTests) {
4939 SCOPED_TRACE(static_cast<int>(test.algo));
4940
4941 PublicKeyCredentialCreationOptionsPtr create_options =
4942 GetTestPublicKeyCredentialCreationOptions();
4943 create_options->public_key_parameters =
4944 GetTestPublicKeyCredentialParameters(static_cast<int32_t>(test.algo));
4945
4946 MakeCredentialResult create_result =
4947 AuthenticatorMakeCredential(std::move(create_options));
4948 ASSERT_EQ(create_result.status, AuthenticatorStatus::SUCCESS);
4949 EXPECT_EQ(create_result.response->public_key_algo,
4950 static_cast<int32_t>(test.algo));
4951
Elly2f1928d62025-07-14 15:31:254952 bssl::UniquePtr<EVP_PKEY> pkey = crypto::evp::PublicKeyFromBytes(
4953 create_result.response->public_key_der.value());
Adam Langley0ab76e5af2020-09-01 16:47:064954 ASSERT_TRUE(pkey.get());
4955
4956 PublicKeyCredentialRequestOptionsPtr get_options =
4957 GetTestPublicKeyCredentialRequestOptions();
4958 device::PublicKeyCredentialDescriptor public_key(
4959 device::CredentialType::kPublicKey,
4960 create_result.response->info->raw_id,
4961 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
4962 get_options->allow_credentials = {std::move(public_key)};
4963 GetAssertionResult get_result =
4964 AuthenticatorGetAssertion(std::move(get_options));
4965 ASSERT_EQ(get_result.status, AuthenticatorStatus::SUCCESS);
4966 base::span<const uint8_t> signature(get_result.response->signature);
4967 std::vector<uint8_t> signed_data(
4968 get_result.response->info->authenticator_data);
Elly2f1928d62025-07-14 15:31:254969 std::array<uint8_t, crypto::hash::kSha256Size> client_data_json_hash =
4970 crypto::hash::Sha256(get_result.response->info->client_data_json);
Adam Langley0ab76e5af2020-09-01 16:47:064971 signed_data.insert(signed_data.end(), client_data_json_hash.begin(),
4972 client_data_json_hash.end());
4973
4974 bssl::ScopedEVP_MD_CTX md_ctx;
4975 ASSERT_EQ(EVP_DigestVerifyInit(md_ctx.get(), /*pctx=*/nullptr, test.digest,
Nina Satragno164bc112020-12-04 02:44:154976 /*e=*/nullptr, pkey.get()),
Adam Langley0ab76e5af2020-09-01 16:47:064977 1);
4978 EXPECT_EQ(EVP_DigestVerify(md_ctx.get(), signature.data(), signature.size(),
4979 signed_data.data(), signed_data.size()),
4980 1);
4981 }
4982}
4983
Zakaria Ridouhae30c6012021-09-15 17:30:134984TEST_F(AuthenticatorImplTest, TestAuthenticationTransport) {
Alison Gale81f4f2c72024-04-22 19:33:314985 // TODO(crbug.com/40197472): handle case where the transport is unknown.
Zakaria Ridouhae30c6012021-09-15 17:30:134986 NavigateAndCommit(GURL(kTestOrigin1));
4987 // Verify transport used during authentication is correctly being returned
4988 // to the renderer layer.
4989 for (const device::FidoTransportProtocol transport :
4990 {device::FidoTransportProtocol::kUsbHumanInterfaceDevice,
4991 device::FidoTransportProtocol::kBluetoothLowEnergy,
zakaria ridouh15ce79e12021-09-24 20:20:144992 device::FidoTransportProtocol::kNearFieldCommunication,
4993 device::FidoTransportProtocol::kInternal}) {
Martin Kreichgauer930f341a2022-01-07 20:20:464994 device::AuthenticatorAttachment attachment =
4995 (transport == device::FidoTransportProtocol::kInternal
4996 ? device::AuthenticatorAttachment::kPlatform
4997 : device::AuthenticatorAttachment::kCrossPlatform);
Zakaria Ridouhae30c6012021-09-15 17:30:134998 ResetVirtualDevice();
4999 virtual_device_factory_->SetSupportedProtocol(
zakaria ridouh15ce79e12021-09-24 20:20:145000 device::ProtocolVersion::kCtap2);
Zakaria Ridouhae30c6012021-09-15 17:30:135001 virtual_device_factory_->SetTransport(transport);
5002 virtual_device_factory_->mutable_state()->transport = transport;
5003
5004 PublicKeyCredentialCreationOptionsPtr create_options =
5005 GetTestPublicKeyCredentialCreationOptions();
5006 MakeCredentialResult create_result =
5007 AuthenticatorMakeCredential(std::move(create_options));
5008 ASSERT_EQ(create_result.status, AuthenticatorStatus::SUCCESS);
Martin Kreichgauer930f341a2022-01-07 20:20:465009 EXPECT_EQ(create_result.response->authenticator_attachment, attachment);
Zakaria Ridouhae30c6012021-09-15 17:30:135010
5011 PublicKeyCredentialRequestOptionsPtr get_options =
5012 GetTestPublicKeyCredentialRequestOptions();
5013 device::PublicKeyCredentialDescriptor public_key(
5014 device::CredentialType::kPublicKey,
5015 create_result.response->info->raw_id, {transport});
5016 get_options->allow_credentials = {std::move(public_key)};
Zakaria Ridouhae30c6012021-09-15 17:30:135017 GetAssertionResult get_result =
5018 AuthenticatorGetAssertion(std::move(get_options));
5019 ASSERT_EQ(get_result.status, AuthenticatorStatus::SUCCESS);
Martin Kreichgauer930f341a2022-01-07 20:20:465020 EXPECT_EQ(get_result.response->authenticator_attachment, attachment);
Zakaria Ridouhae30c6012021-09-15 17:30:135021 }
5022}
5023
Martin Kreichgauer85a723bb2020-06-08 17:25:195024TEST_F(AuthenticatorImplTest, ResetDiscoveryFactoryOverride) {
5025 // This is a regression test for crbug.com/1087158.
5026 NavigateAndCommit(GURL(kTestOrigin1));
5027
Martin Kreichgauer70fc0cf2020-07-17 01:01:005028 // Make the entire discovery factory disappear mid-request.
Adem Derinele6378762024-06-27 06:05:075029 bool IsReady = false;
Martin Kreichgauer85a723bb2020-06-08 17:25:195030 virtual_device_factory_->SetSupportedProtocol(
5031 device::ProtocolVersion::kCtap2);
5032 virtual_device_factory_->mutable_state()->simulate_press_callback =
5033 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
Adem Derinele6378762024-06-27 06:05:075034 IsReady = true;
Martin Kreichgauer70fc0cf2020-07-17 01:01:005035 ResetVirtualDevice();
Martin Kreichgauer85a723bb2020-06-08 17:25:195036 return false;
5037 });
5038
Martin Kreichgauer85a723bb2020-06-08 17:25:195039 PublicKeyCredentialCreationOptionsPtr options =
5040 GetTestPublicKeyCredentialCreationOptions();
Martin Kreichgauer55834402020-08-03 21:54:315041 EXPECT_EQ(
5042 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
5043 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauer85a723bb2020-06-08 17:25:195044}
5045
Adam Langley07ef8d732021-03-02 00:37:175046TEST_F(AuthenticatorImplTest, InvalidU2FPublicKey) {
5047 NavigateAndCommit(GURL(kTestOrigin1));
5048 virtual_device_factory_->SetSupportedProtocol(device::ProtocolVersion::kU2f);
5049 virtual_device_factory_->mutable_state()->u2f_invalid_public_key = true;
5050
5051 PublicKeyCredentialCreationOptionsPtr options =
5052 GetTestPublicKeyCredentialCreationOptions();
5053 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
5054 AuthenticatorStatus::NOT_ALLOWED_ERROR);
5055}
5056
5057TEST_F(AuthenticatorImplTest, InvalidU2FSignature) {
5058 NavigateAndCommit(GURL(kTestOrigin1));
5059 virtual_device_factory_->SetSupportedProtocol(device::ProtocolVersion::kU2f);
5060 virtual_device_factory_->mutable_state()->u2f_invalid_signature = true;
5061
5062 PublicKeyCredentialRequestOptionsPtr options =
5063 GetTestPublicKeyCredentialRequestOptions();
5064 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:465065 options->allow_credentials[0].id, kTestOrigin1));
Slobodan Pejicc8ce88b2023-06-19 15:41:125066 options->extensions->appid = kTestOrigin1;
Adam Langley07ef8d732021-03-02 00:37:175067
5068 EXPECT_EQ(
5069 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
5070 AuthenticatorStatus::NOT_ALLOWED_ERROR);
5071}
5072
Adam Langley77bbb1f2021-04-16 16:44:105073TEST_F(AuthenticatorImplTest, CredBlob) {
5074 NavigateAndCommit(GURL(kTestOrigin1));
5075
5076 device::VirtualCtap2Device::Config config;
5077 config.cred_blob_support = true;
5078 // credProtect is required for credBlob per CTAP 2.1.
5079 config.cred_protect_support = true;
5080 virtual_device_factory_->SetCtap2Config(config);
5081
5082 const std::vector<uint8_t> cred_blob = {1, 2, 3, 4};
5083
5084 std::vector<uint8_t> credential_id;
5085 // Create a credential with a credBlob set.
5086 {
5087 PublicKeyCredentialCreationOptionsPtr options =
5088 GetTestPublicKeyCredentialCreationOptions();
5089 options->cred_blob = cred_blob;
5090 auto result = AuthenticatorMakeCredential(std::move(options));
5091 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5092 credential_id = std::move(result.response->info->raw_id);
5093 EXPECT_TRUE(result.response->echo_cred_blob);
5094 EXPECT_TRUE(result.response->cred_blob);
5095 }
5096
5097 // Expect to be able to fetch the credBlob with an assertion.
5098 {
5099 PublicKeyCredentialRequestOptionsPtr options =
5100 GetTestPublicKeyCredentialRequestOptions();
5101 options->allow_credentials[0] = device::PublicKeyCredentialDescriptor(
5102 device::CredentialType::kPublicKey, std::move(credential_id));
Slobodan Pejicc8ce88b2023-06-19 15:41:125103 options->extensions->get_cred_blob = true;
Adam Langley77bbb1f2021-04-16 16:44:105104
5105 auto result = AuthenticatorGetAssertion(std::move(options));
5106 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:255107 EXPECT_EQ(result.response->extensions->get_cred_blob, cred_blob);
Adam Langley77bbb1f2021-04-16 16:44:105108 }
5109}
5110
Adam Langley4b0ca3e22021-11-29 20:51:185111TEST_F(AuthenticatorImplTest, MinPINLength) {
5112 NavigateAndCommit(GURL(kTestOrigin1));
5113
Adam Langley84220e92021-12-23 18:33:385114 for (const bool min_pin_length_supported : {false, true}) {
5115 device::VirtualCtap2Device::Config config;
5116 config.min_pin_length_extension_support = min_pin_length_supported;
5117 virtual_device_factory_->SetCtap2Config(config);
Adam Langley4b0ca3e22021-11-29 20:51:185118
Adam Langley84220e92021-12-23 18:33:385119 for (const bool min_pin_length_requested : {false, true}) {
5120 PublicKeyCredentialCreationOptionsPtr options =
5121 GetTestPublicKeyCredentialCreationOptions();
5122 options->min_pin_length_requested = min_pin_length_requested;
5123 auto result = AuthenticatorMakeCredential(std::move(options));
5124 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Adam Langley4b0ca3e22021-11-29 20:51:185125
Adam Langley84220e92021-12-23 18:33:385126 const device::AuthenticatorData auth_data =
5127 AuthDataFromMakeCredentialResponse(result.response);
5128 bool has_min_pin_length = false;
5129 if (auth_data.extensions().has_value()) {
5130 const cbor::Value::MapValue& extensions =
5131 auth_data.extensions()->GetMap();
5132 const auto it =
5133 extensions.find(cbor::Value(device::kExtensionMinPINLength));
5134 has_min_pin_length = it != extensions.end() && it->second.is_unsigned();
5135 }
5136 ASSERT_EQ(has_min_pin_length,
5137 min_pin_length_supported && min_pin_length_requested);
Adam Langley4b0ca3e22021-11-29 20:51:185138 }
Adam Langley4b0ca3e22021-11-29 20:51:185139 }
5140}
5141
Nina Satragnod18d2062021-10-08 16:00:495142// Regression test for crbug.com/1257281.
5143// Tests that a request is not cancelled when an authenticator returns
5144// CTAP2_ERR_KEEPALIVE_CANCEL after selecting another authenticator for a
5145// request.
5146TEST_F(AuthenticatorImplTest, CancellingAuthenticatorDoesNotTerminateRequest) {
5147 NavigateAndCommit(GURL(kTestOrigin1));
5148 for (auto request_type : {device::FidoRequestType::kMakeCredential,
5149 device::FidoRequestType::kGetAssertion}) {
5150 SCOPED_TRACE(::testing::Message()
5151 << "request_type="
5152 << (request_type == device::FidoRequestType::kMakeCredential
5153 ? "make_credential"
5154 : "get_assertion"));
5155 // Make a device that supports getting a PUAT with UV.
5156 auto discovery =
5157 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
5158 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
5159 device_1.config.internal_uv_support = true;
5160 device_1.config.pin_uv_auth_token_support = true;
5161 device_1.config.user_verification_succeeds = true;
5162 device_1.config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
5163 device_1.state->fingerprints_enrolled = true;
5164 PublicKeyCredentialRequestOptionsPtr dummy_options =
5165 GetTestPublicKeyCredentialRequestOptions();
5166 ASSERT_TRUE(device_1.state->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:465167 dummy_options->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnod18d2062021-10-08 16:00:495168 discovery->AddDevice(std::move(device_1));
5169
5170 // Make a device that does not support PUATs but can still handle the
5171 // request. This device will not respond to the request.
5172 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
5173 device_2.config.internal_uv_support = false;
5174 device_2.config.pin_uv_auth_token_support = false;
5175 device_2.config.ctap2_versions = {device::Ctap2Version::kCtap2_0};
5176 device_2.state->simulate_press_callback =
5177 base::BindRepeating([](VirtualFidoDevice* ignore) { return false; });
5178 discovery->AddDevice(std::move(device_2));
Jagadesh P8db17bf2023-11-28 04:14:155179 ReplaceDiscoveryFactory(std::move(discovery));
Nina Satragnod18d2062021-10-08 16:00:495180
5181 if (request_type == device::FidoRequestType::kMakeCredential) {
5182 MakeCredentialResult result = AuthenticatorMakeCredential();
5183 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5184 } else {
5185 GetAssertionResult result = AuthenticatorGetAssertion();
5186 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5187 }
5188 }
5189}
5190
Adam Langley9da727ca2022-12-22 18:48:005191TEST_F(AuthenticatorImplTest, PRFWithoutSupport) {
5192 // This tests that the PRF extension doesn't trigger any DCHECKs or crashes
5193 // when used with an authenticator doesn't doesn't support hmac-secret.
5194 NavigateAndCommit(GURL(kTestOrigin1));
5195
5196 auto prf_value = blink::mojom::PRFValues::New();
5197 const std::vector<uint8_t> salt1(32, 1);
5198 prf_value->first = salt1;
5199 std::vector<blink::mojom::PRFValuesPtr> prf_inputs;
5200 prf_inputs.emplace_back(std::move(prf_value));
5201
5202 PublicKeyCredentialRequestOptionsPtr options =
5203 GetTestPublicKeyCredentialRequestOptions();
Slobodan Pejicc8ce88b2023-06-19 15:41:125204 options->extensions->prf = true;
5205 options->extensions->prf_inputs = std::move(prf_inputs);
Adam Langley9da727ca2022-12-22 18:48:005206
5207 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
5208
5209 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
5210}
5211
Nina Satragnocb0406e2024-09-09 19:47:485212// These test verify that the virtual authenticator supports the Signal API.
5213class VirtualAuthenticatorSignalTest : public AuthenticatorImplTest {
5214 public:
5215 static constexpr char kUsername[] = "reimu";
5216 static constexpr char kDisplayName[] = "Reimu Hakurei";
5217 const std::vector<uint8_t> kUserId = {2};
5218
5219 void SetUp() override {
5220 AuthenticatorImplTest::SetUp();
5221 NavigateAndCommit(GURL(kTestOrigin1));
5222
5223 // These tests need an AuthenticatorEnvironment set up.
5224 virtual_device_factory_ = nullptr;
5225 content::AuthenticatorEnvironment* authenticator_environment =
5226 content::AuthenticatorEnvironment::GetInstance();
5227 authenticator_environment->Reset();
5228 FrameTreeNode* frame_tree_node =
5229 static_cast<content::RenderFrameHostImpl*>(main_rfh())
5230 ->frame_tree_node();
5231 authenticator_environment->EnableVirtualAuthenticatorFor(
5232 frame_tree_node,
5233 /*enable_ui=*/false);
5234 VirtualAuthenticatorManagerImpl* virtual_authenticator_manager =
5235 authenticator_environment->MaybeGetVirtualAuthenticatorManager(
5236 frame_tree_node);
Nina Satragno0d0e1ba2024-10-15 19:10:075237 VirtualAuthenticator::Options virt_auth_options;
5238 virt_auth_options.protocol = device::ProtocolVersion::kCtap2;
5239 virt_auth_options.transport = device::FidoTransportProtocol::kInternal;
5240 virt_auth_options.has_resident_key = true;
Nina Satragnocb0406e2024-09-09 19:47:485241 authenticator_ =
5242 virtual_authenticator_manager
Nina Satragno0d0e1ba2024-10-15 19:10:075243 ->AddAuthenticatorAndReturnNonOwningPointer(virt_auth_options);
Nina Satragnocb0406e2024-09-09 19:47:485244
5245 // Make a credential.
5246 PublicKeyCredentialCreationOptionsPtr options =
5247 GetTestPublicKeyCredentialCreationOptions();
5248 options->user.id = kUserId;
5249 options->user.name = kUsername;
5250 options->user.display_name = kDisplayName;
5251 options->authenticator_selection->resident_key =
5252 device::ResidentKeyRequirement::kRequired;
5253 MakeCredentialResult result =
5254 AuthenticatorMakeCredential(std::move(options));
5255 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5256 credential_id_ = result.response->info->raw_id;
5257 }
5258
5259 void TearDown() override {
5260 authenticator_ = nullptr;
5261 AuthenticatorImplTest::TearDown();
5262 }
5263
5264 protected:
5265 // The id of the credential created during test setup.
5266 std::vector<uint8_t> credential_id_;
5267
5268 raw_ptr<VirtualAuthenticator> authenticator_;
5269};
5270
5271TEST_F(VirtualAuthenticatorSignalTest, SignalUnknownCredentialId) {
5272 {
5273 // Verify that we do not remove passkeys that don't match the rp id.
5274 PublicKeyCredentialReportOptionsPtr options =
5275 GetTestPublicKeyCredentialReportOptions();
5276 options->relying_party_id = kDifferentTestRelyingPartyId;
5277 options->unknown_credential_id = credential_id_;
5278 AuthenticatorReport(std::move(options));
5279 EXPECT_TRUE(
5280 base::Contains(authenticator_->registrations(), credential_id_));
5281 }
5282 {
5283 // Verify that we do not remove passkeys that don't match the cred id.
5284 PublicKeyCredentialReportOptionsPtr options =
5285 GetTestPublicKeyCredentialReportOptions();
5286 options->relying_party_id = kTestRelyingPartyId;
5287 options->unknown_credential_id = std::vector<uint8_t>{4, 3, 2, 1};
5288 AuthenticatorReport(std::move(options));
5289 EXPECT_TRUE(
5290 base::Contains(authenticator_->registrations(), credential_id_));
5291 }
5292 {
5293 // Remove the passkey when the rp id and credential id match.
5294 PublicKeyCredentialReportOptionsPtr options =
5295 GetTestPublicKeyCredentialReportOptions();
5296 options->relying_party_id = kTestRelyingPartyId;
5297 options->unknown_credential_id = credential_id_;
5298 AuthenticatorReport(std::move(options));
5299 EXPECT_FALSE(
5300 base::Contains(authenticator_->registrations(), credential_id_));
5301 }
5302}
5303
5304TEST_F(VirtualAuthenticatorSignalTest, SignalAllAcceptableCredentials) {
5305 {
5306 // Verify that we do not remove passkeys that don't match the rp id.
5307 PublicKeyCredentialReportOptionsPtr options =
5308 GetTestPublicKeyCredentialReportOptions();
5309 options->relying_party_id = kDifferentTestRelyingPartyId;
5310 options->all_accepted_credentials =
5311 blink::mojom::AllAcceptedCredentialsOptions::New(
5312 kUserId, std::vector<std::vector<uint8_t>>{});
5313 AuthenticatorReport(std::move(options));
5314 EXPECT_TRUE(
5315 base::Contains(authenticator_->registrations(), credential_id_));
5316 }
5317 {
5318 // Verify that we do not remove passkeys that don't match the user id.
5319 PublicKeyCredentialReportOptionsPtr options =
5320 GetTestPublicKeyCredentialReportOptions();
5321 options->relying_party_id = kTestRelyingPartyId;
5322 options->all_accepted_credentials =
5323 blink::mojom::AllAcceptedCredentialsOptions::New(
5324 std::vector<uint8_t>{99}, std::vector<std::vector<uint8_t>>{});
5325 AuthenticatorReport(std::move(options));
5326 EXPECT_TRUE(
5327 base::Contains(authenticator_->registrations(), credential_id_));
5328 }
5329 {
5330 // Verify that we do not remove passkeys that are present on the list.
5331 PublicKeyCredentialReportOptionsPtr options =
5332 GetTestPublicKeyCredentialReportOptions();
5333 options->relying_party_id = kTestRelyingPartyId;
5334 options->all_accepted_credentials =
5335 blink::mojom::AllAcceptedCredentialsOptions::New(
5336 kUserId, std::vector<std::vector<uint8_t>>{credential_id_});
5337 AuthenticatorReport(std::move(options));
5338 EXPECT_TRUE(
5339 base::Contains(authenticator_->registrations(), credential_id_));
5340 }
5341 {
5342 // Verify that we remove passkeys that are not present on the list.
5343 PublicKeyCredentialReportOptionsPtr options =
5344 GetTestPublicKeyCredentialReportOptions();
5345 options->relying_party_id = kTestRelyingPartyId;
5346 options->all_accepted_credentials =
5347 blink::mojom::AllAcceptedCredentialsOptions::New(
5348 kUserId, std::vector<std::vector<uint8_t>>{});
5349 AuthenticatorReport(std::move(options));
5350 EXPECT_FALSE(
5351 base::Contains(authenticator_->registrations(), credential_id_));
5352 }
5353}
5354
5355TEST_F(VirtualAuthenticatorSignalTest, SignalCurrentUserDetails) {
5356 constexpr char kNewUsername[] = "marisa";
5357 constexpr char kNewDisplayName[] = "Marisa Kirisame";
5358 {
5359 // Verify that we do not update passkeys that don't match the rp id.
5360 PublicKeyCredentialReportOptionsPtr options =
5361 GetTestPublicKeyCredentialReportOptions();
5362 options->relying_party_id = kDifferentTestRelyingPartyId;
5363 options->current_user_details =
5364 blink::mojom::CurrentUserDetailsOptions::New(kUserId, kNewUsername,
5365 kNewDisplayName);
5366 AuthenticatorReport(std::move(options));
5367 const auto& cred =
5368 authenticator_->registrations().find(credential_id_)->second;
5369 EXPECT_EQ(cred.user->name, kUsername);
5370 EXPECT_EQ(cred.user->display_name, kDisplayName);
5371 }
5372 {
5373 // Verify that we do not update passkeys that don't match the user id.
5374 PublicKeyCredentialReportOptionsPtr options =
5375 GetTestPublicKeyCredentialReportOptions();
5376 options->relying_party_id = kTestRelyingPartyId;
5377 options->current_user_details =
5378 blink::mojom::CurrentUserDetailsOptions::New(
5379 std::vector<uint8_t>{9}, kNewUsername, kNewDisplayName);
5380 AuthenticatorReport(std::move(options));
5381 const auto& cred =
5382 authenticator_->registrations().find(credential_id_)->second;
5383 EXPECT_EQ(cred.user->name, kUsername);
5384 EXPECT_EQ(cred.user->display_name, kDisplayName);
5385 }
5386 {
5387 // Verify that we do update passkeys that match.
5388 PublicKeyCredentialReportOptionsPtr options =
5389 GetTestPublicKeyCredentialReportOptions();
5390 options->relying_party_id = kTestRelyingPartyId;
5391 options->current_user_details =
5392 blink::mojom::CurrentUserDetailsOptions::New(kUserId, kNewUsername,
5393 kNewDisplayName);
5394 AuthenticatorReport(std::move(options));
5395 const auto& cred =
5396 authenticator_->registrations().find(credential_id_)->second;
5397 EXPECT_EQ(cred.user->name, kNewUsername);
5398 EXPECT_EQ(cred.user->display_name, kNewDisplayName);
5399 }
5400}
5401
Nina Satragno356ffd782020-03-10 03:48:265402static constexpr char kTestPIN[] = "1234";
Peter Kastingcc07b332021-05-07 22:58:255403static constexpr char16_t kTestPIN16[] = u"1234";
Nina Satragno356ffd782020-03-10 03:48:265404
5405class UVTestAuthenticatorClientDelegate
5406 : public AuthenticatorRequestClientDelegate {
5407 public:
Nina Satragno11971dd2020-04-22 21:01:465408 explicit UVTestAuthenticatorClientDelegate(bool* collected_pin,
Nina Satragno62fe3e02020-11-16 18:13:265409 uint32_t* min_pin_length,
Nina Satragno11971dd2020-04-22 21:01:465410 bool* did_bio_enrollment,
5411 bool cancel_bio_enrollment)
5412 : collected_pin_(collected_pin),
Nina Satragno62fe3e02020-11-16 18:13:265413 min_pin_length_(min_pin_length),
Nina Satragno11971dd2020-04-22 21:01:465414 did_bio_enrollment_(did_bio_enrollment),
5415 cancel_bio_enrollment_(cancel_bio_enrollment) {
Nina Satragno356ffd782020-03-10 03:48:265416 *collected_pin_ = false;
Nina Satragno11971dd2020-04-22 21:01:465417 *did_bio_enrollment_ = false;
Nina Satragno356ffd782020-03-10 03:48:265418 }
5419
5420 bool SupportsPIN() const override { return true; }
5421
5422 void CollectPIN(
Nina Satragno164bc112020-12-04 02:44:155423 CollectPINOptions options,
Jan Wilken Dörrieaace0cfef2021-03-11 22:01:585424 base::OnceCallback<void(std::u16string)> provide_pin_cb) override {
Nina Satragno356ffd782020-03-10 03:48:265425 *collected_pin_ = true;
Nina Satragno164bc112020-12-04 02:44:155426 *min_pin_length_ = options.min_pin_length;
Sean Maher52fa5a72022-11-14 15:53:255427 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Peter Kastingcc07b332021-05-07 22:58:255428 FROM_HERE, base::BindOnce(std::move(provide_pin_cb), kTestPIN16));
Nina Satragno356ffd782020-03-10 03:48:265429 }
5430
Nina Satragno11971dd2020-04-22 21:01:465431 void StartBioEnrollment(base::OnceClosure next_callback) override {
5432 *did_bio_enrollment_ = true;
5433 if (cancel_bio_enrollment_) {
Sean Maher52fa5a72022-11-14 15:53:255434 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Nina Satragno11971dd2020-04-22 21:01:465435 FROM_HERE, std::move(next_callback));
5436 return;
5437 }
5438 bio_callback_ = std::move(next_callback);
5439 }
5440
5441 void OnSampleCollected(int remaining_samples) override {
5442 if (remaining_samples <= 0) {
Sean Maher52fa5a72022-11-14 15:53:255443 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Nina Satragno11971dd2020-04-22 21:01:465444 FROM_HERE, std::move(bio_callback_));
5445 }
5446 }
5447
Nina Satragno356ffd782020-03-10 03:48:265448 void FinishCollectToken() override {}
5449
5450 private:
Keishi Hattori0e45c022021-11-27 09:25:525451 raw_ptr<bool> collected_pin_;
5452 raw_ptr<uint32_t> min_pin_length_;
Nina Satragno11971dd2020-04-22 21:01:465453 base::OnceClosure bio_callback_;
Keishi Hattori0e45c022021-11-27 09:25:525454 raw_ptr<bool> did_bio_enrollment_;
Nina Satragno11971dd2020-04-22 21:01:465455 bool cancel_bio_enrollment_;
Nina Satragno356ffd782020-03-10 03:48:265456};
5457
5458class UVTestAuthenticatorContentBrowserClient : public ContentBrowserClient {
5459 public:
Martin Kreichgauer339413a82021-05-05 03:02:555460 // ContentBrowserClient:
5461 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
5462 return &web_authentication_delegate;
5463 }
5464
Nina Satragno356ffd782020-03-10 03:48:265465 std::unique_ptr<AuthenticatorRequestClientDelegate>
5466 GetWebAuthenticationRequestDelegate(
5467 RenderFrameHost* render_frame_host) override {
Nina Satragno11971dd2020-04-22 21:01:465468 return std::make_unique<UVTestAuthenticatorClientDelegate>(
Martin Kreichgauer339413a82021-05-05 03:02:555469 &collected_pin, &min_pin_length, &did_bio_enrollment,
5470 cancel_bio_enrollment);
Nina Satragno356ffd782020-03-10 03:48:265471 }
5472
Martin Kreichgauer339413a82021-05-05 03:02:555473 TestWebAuthenticationDelegate web_authentication_delegate;
Nina Satragno356ffd782020-03-10 03:48:265474
Martin Kreichgauer339413a82021-05-05 03:02:555475 bool collected_pin;
5476 uint32_t min_pin_length = 0;
5477 bool did_bio_enrollment;
5478 bool cancel_bio_enrollment = false;
Nina Satragno356ffd782020-03-10 03:48:265479};
5480
Adam Langleyc5ae37d2019-03-08 20:32:315481class UVAuthenticatorImplTest : public AuthenticatorImplTest {
5482 public:
5483 UVAuthenticatorImplTest() = default;
5484
Peter Boström9b036532021-10-28 23:37:285485 UVAuthenticatorImplTest(const UVAuthenticatorImplTest&) = delete;
5486 UVAuthenticatorImplTest& operator=(const UVAuthenticatorImplTest&) = delete;
5487
Nina Satragno356ffd782020-03-10 03:48:265488 void SetUp() override {
5489 AuthenticatorImplTest::SetUp();
5490 old_client_ = SetBrowserClientForTesting(&test_client_);
5491 }
5492
5493 void TearDown() override {
5494 SetBrowserClientForTesting(old_client_);
5495 AuthenticatorImplTest::TearDown();
5496 }
5497
Adam Langleyc5ae37d2019-03-08 20:32:315498 protected:
5499 static PublicKeyCredentialCreationOptionsPtr make_credential_options(
Ken Buchanan4a33ef0d2019-06-20 17:34:045500 device::UserVerificationRequirement uv =
Adam Langley8348a0bc2021-06-07 23:45:155501 device::UserVerificationRequirement::kRequired,
5502 bool exclude_credentials = false,
5503 bool appid_exclude = false) {
Adam Langleyc5ae37d2019-03-08 20:32:315504 PublicKeyCredentialCreationOptionsPtr options =
5505 GetTestPublicKeyCredentialCreationOptions();
Adam Langley8348a0bc2021-06-07 23:45:155506 if (exclude_credentials) {
5507 options->exclude_credentials = GetTestCredentials(/*num_credentials=*/1);
5508 }
5509 if (appid_exclude) {
5510 CHECK(exclude_credentials);
5511 options->appid_exclude = kTestOrigin1;
5512 }
Martin Kreichgauerbece642a2021-12-07 21:02:465513 options->authenticator_selection->user_verification_requirement = uv;
Adam Langleyc5ae37d2019-03-08 20:32:315514 return options;
5515 }
5516
5517 static PublicKeyCredentialRequestOptionsPtr get_credential_options(
Ken Buchanan4a33ef0d2019-06-20 17:34:045518 device::UserVerificationRequirement uv =
5519 device::UserVerificationRequirement::kRequired) {
Adam Langleyc5ae37d2019-03-08 20:32:315520 PublicKeyCredentialRequestOptionsPtr options =
5521 GetTestPublicKeyCredentialRequestOptions();
5522 options->user_verification = uv;
5523 return options;
5524 }
5525
Ken Buchanan4a33ef0d2019-06-20 17:34:045526 static const char* UVToString(device::UserVerificationRequirement uv) {
Adam Langleyc5ae37d2019-03-08 20:32:315527 switch (uv) {
Ken Buchanan4a33ef0d2019-06-20 17:34:045528 case device::UserVerificationRequirement::kDiscouraged:
Adam Langleyc5ae37d2019-03-08 20:32:315529 return "discouraged";
Ken Buchanan4a33ef0d2019-06-20 17:34:045530 case device::UserVerificationRequirement::kPreferred:
Adam Langleyc5ae37d2019-03-08 20:32:315531 return "preferred";
Ken Buchanan4a33ef0d2019-06-20 17:34:045532 case device::UserVerificationRequirement::kRequired:
Adam Langleyc5ae37d2019-03-08 20:32:315533 return "required";
5534 }
5535 }
5536
Nina Satragno356ffd782020-03-10 03:48:265537 UVTestAuthenticatorContentBrowserClient test_client_;
5538
Adam Langleyc5ae37d2019-03-08 20:32:315539 private:
Keishi Hattori0e45c022021-11-27 09:25:525540 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langleyc5ae37d2019-03-08 20:32:315541};
5542
Nina Satragno21e40a72020-12-17 17:00:145543using PINReason = device::pin::PINEntryReason;
5544using PINError = device::pin::PINEntryError;
Nina Satragno164bc112020-12-04 02:44:155545
5546// PINExpectation represent expected |mode|, |attempts|, |min_pin_length| and
Nina Satragno62fe3e02020-11-16 18:13:265547// the PIN to answer with.
5548struct PINExpectation {
Nina Satragno21e40a72020-12-17 17:00:145549 PINReason reason;
Peter Kastingcc07b332021-05-07 22:58:255550 std::u16string pin;
Nina Satragno164bc112020-12-04 02:44:155551 int attempts;
Nina Satragno62fe3e02020-11-16 18:13:265552 uint32_t min_pin_length = device::kMinPinLength;
Nina Satragno21e40a72020-12-17 17:00:145553 PINError error = PINError::kNoError;
Nina Satragno62fe3e02020-11-16 18:13:265554};
Adam Langleyb159c312019-03-05 00:33:195555
5556class PINTestAuthenticatorRequestDelegate
5557 : public AuthenticatorRequestClientDelegate {
5558 public:
Nina Satragno62fe3e02020-11-16 18:13:265559 PINTestAuthenticatorRequestDelegate(
Adam Langley989fa492019-03-28 22:03:185560 bool supports_pin,
Nina Satragno62fe3e02020-11-16 18:13:265561 const std::list<PINExpectation>& pins,
Adam Langley2cdc5972024-10-03 12:33:135562 std::optional<InterestingFailureReason>* failure_reason,
5563 base::RepeatingCallback<bool()> collect_pin_cb)
Adam Langley989fa492019-03-28 22:03:185564 : supports_pin_(supports_pin),
5565 expected_(pins),
Adam Langley2cdc5972024-10-03 12:33:135566 failure_reason_(failure_reason),
5567 collect_pin_cb_(collect_pin_cb) {}
Peter Boström828b9022021-09-21 02:28:435568
5569 PINTestAuthenticatorRequestDelegate(
5570 const PINTestAuthenticatorRequestDelegate&) = delete;
5571 PINTestAuthenticatorRequestDelegate& operator=(
5572 const PINTestAuthenticatorRequestDelegate&) = delete;
5573
Martin Kreichgauera2503a72021-04-29 20:53:485574 ~PINTestAuthenticatorRequestDelegate() override {
5575 DCHECK(expected_.empty())
5576 << expected_.size() << " unsatisifed PIN expectations";
5577 }
Adam Langleyb159c312019-03-05 00:33:195578
Adam Langley989fa492019-03-28 22:03:185579 bool SupportsPIN() const override { return supports_pin_; }
5580
Adam Langleyb159c312019-03-05 00:33:195581 void CollectPIN(
Nina Satragno164bc112020-12-04 02:44:155582 CollectPINOptions options,
Jan Wilken Dörrieaace0cfef2021-03-11 22:01:585583 base::OnceCallback<void(std::u16string)> provide_pin_cb) override {
Adam Langley2cdc5972024-10-03 12:33:135584 if (collect_pin_cb_ && !collect_pin_cb_.Run()) {
5585 return;
5586 }
Adam Langley989fa492019-03-28 22:03:185587 DCHECK(supports_pin_);
Martin Kreichgauera2503a72021-04-29 20:53:485588 DCHECK(!expected_.empty()) << "unexpected PIN request";
Nina Satragno21e40a72020-12-17 17:00:145589 if (expected_.front().reason == PINReason::kChallenge) {
Nina Satragno164bc112020-12-04 02:44:155590 DCHECK(options.attempts == expected_.front().attempts)
5591 << "got: " << options.attempts
5592 << " expected: " << expected_.front().attempts;
5593 }
5594 DCHECK_EQ(expected_.front().min_pin_length, options.min_pin_length);
Nina Satragno21e40a72020-12-17 17:00:145595 DCHECK_EQ(expected_.front().reason, options.reason);
5596 DCHECK_EQ(expected_.front().error, options.error);
Peter Kastingcc07b332021-05-07 22:58:255597 std::u16string pin = std::move(expected_.front().pin);
Adam Langleyb159c312019-03-05 00:33:195598 expected_.pop_front();
5599
Sean Maher52fa5a72022-11-14 15:53:255600 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Peter Kastingcc07b332021-05-07 22:58:255601 FROM_HERE, base::BindOnce(std::move(provide_pin_cb), std::move(pin)));
Adam Langleyb159c312019-03-05 00:33:195602 }
5603
Nina Satragnod976d1b2020-01-06 22:45:435604 void FinishCollectToken() override {}
Adam Langley989fa492019-03-28 22:03:185605
Martin Kreichgauerf45542a2019-08-30 16:45:505606 bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
Adam Langleyb159c312019-03-05 00:33:195607 *failure_reason_ = reason;
5608 return AuthenticatorRequestClientDelegate::DoesBlockRequestOnFailure(
Martin Kreichgauerf45542a2019-08-30 16:45:505609 reason);
Adam Langleyb159c312019-03-05 00:33:195610 }
5611
5612 private:
Adam Langley989fa492019-03-28 22:03:185613 const bool supports_pin_;
Nina Satragno62fe3e02020-11-16 18:13:265614 std::list<PINExpectation> expected_;
Arthur Sonzognic686e8f2024-01-11 08:36:375615 const raw_ptr<std::optional<InterestingFailureReason>> failure_reason_;
Adam Langley2cdc5972024-10-03 12:33:135616 // collect_pin_cb_ is optional. If present, it returns whether `CollectPIN`
5617 // should continue and invoke its main callback.
5618 base::RepeatingCallback<bool()> collect_pin_cb_;
Adam Langleyb159c312019-03-05 00:33:195619};
5620
5621class PINTestAuthenticatorContentBrowserClient : public ContentBrowserClient {
5622 public:
Martin Kreichgauera2503a72021-04-29 20:53:485623 // ContentBrowserClient:
5624 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
5625 return &web_authentication_delegate;
5626 }
5627
Adam Langleyb159c312019-03-05 00:33:195628 std::unique_ptr<AuthenticatorRequestClientDelegate>
5629 GetWebAuthenticationRequestDelegate(
Adam Langley5f3963f12020-01-21 19:10:335630 RenderFrameHost* render_frame_host) override {
Adam Langleyb159c312019-03-05 00:33:195631 return std::make_unique<PINTestAuthenticatorRequestDelegate>(
Adam Langley2cdc5972024-10-03 12:33:135632 supports_pin, expected, &failure_reason, collect_pin_cb);
Adam Langleyb159c312019-03-05 00:33:195633 }
5634
Martin Kreichgauera2503a72021-04-29 20:53:485635 TestWebAuthenticationDelegate web_authentication_delegate;
5636
Adam Langley989fa492019-03-28 22:03:185637 bool supports_pin = true;
Nina Satragno62fe3e02020-11-16 18:13:265638 std::list<PINExpectation> expected;
Arthur Sonzognic686e8f2024-01-11 08:36:375639 std::optional<InterestingFailureReason> failure_reason;
Adam Langley2cdc5972024-10-03 12:33:135640 base::RepeatingCallback<bool()> collect_pin_cb;
Adam Langleyb159c312019-03-05 00:33:195641};
5642
Adam Langleyc5ae37d2019-03-08 20:32:315643class PINAuthenticatorImplTest : public UVAuthenticatorImplTest {
Adam Langleyb159c312019-03-05 00:33:195644 public:
Adam Langleyc5ae37d2019-03-08 20:32:315645 PINAuthenticatorImplTest() = default;
Adam Langleyb159c312019-03-05 00:33:195646
Peter Boström9b036532021-10-28 23:37:285647 PINAuthenticatorImplTest(const PINAuthenticatorImplTest&) = delete;
5648 PINAuthenticatorImplTest& operator=(const PINAuthenticatorImplTest&) = delete;
5649
Adam Langleyb159c312019-03-05 00:33:195650 void SetUp() override {
Adam Langleyc5ae37d2019-03-08 20:32:315651 UVAuthenticatorImplTest::SetUp();
Adam Langleyb159c312019-03-05 00:33:195652 old_client_ = SetBrowserClientForTesting(&test_client_);
Adam Langleyc5ae37d2019-03-08 20:32:315653 device::VirtualCtap2Device::Config config;
5654 config.pin_support = true;
Nina Satragnoacf403f92019-05-23 17:16:525655 virtual_device_factory_->SetCtap2Config(config);
Adam Langleyb159c312019-03-05 00:33:195656 NavigateAndCommit(GURL(kTestOrigin1));
5657 }
5658
5659 void TearDown() override {
5660 SetBrowserClientForTesting(old_client_);
Martin Kreichgauer85a723bb2020-06-08 17:25:195661 UVAuthenticatorImplTest::TearDown();
Adam Langleyb159c312019-03-05 00:33:195662 }
5663
5664 protected:
Adam Langleyb159c312019-03-05 00:33:195665 PINTestAuthenticatorContentBrowserClient test_client_;
Adam Langleyb159c312019-03-05 00:33:195666
Adam Langleybf92fdc2019-03-27 21:13:545667 // An enumerate of outcomes for PIN tests.
5668 enum {
5669 kFailure,
5670 kNoPIN,
5671 kSetPIN,
5672 kUsePIN,
5673 };
5674
Martin Kreichgauer0787dc32020-10-05 17:44:115675 void ConfigureVirtualDevice(device::PINUVAuthProtocol pin_protocol,
5676 bool pin_uv_auth_token,
5677 int support_level) {
Adam Langleybf92fdc2019-03-27 21:13:545678 device::VirtualCtap2Device::Config config;
Martin Kreichgauer0787dc32020-10-05 17:44:115679 config.pin_protocol = pin_protocol;
Nina Satragno20a78cf92020-06-22 18:02:195680 config.pin_uv_auth_token_support = pin_uv_auth_token;
5681 config.ctap2_versions = {device::Ctap2Version::kCtap2_0,
5682 device::Ctap2Version::kCtap2_1};
Adam Langleybf92fdc2019-03-27 21:13:545683 switch (support_level) {
5684 case 0:
5685 // No support.
5686 config.pin_support = false;
Nina Satragnoacf403f92019-05-23 17:16:525687 virtual_device_factory_->mutable_state()->pin = "";
Nina Satragnod976d1b2020-01-06 22:45:435688 virtual_device_factory_->mutable_state()->pin_retries = 0;
Adam Langleybf92fdc2019-03-27 21:13:545689 break;
5690
5691 case 1:
5692 // PIN supported, but no PIN set.
5693 config.pin_support = true;
Nina Satragnoacf403f92019-05-23 17:16:525694 virtual_device_factory_->mutable_state()->pin = "";
Nina Satragnod976d1b2020-01-06 22:45:435695 virtual_device_factory_->mutable_state()->pin_retries = 0;
Adam Langleybf92fdc2019-03-27 21:13:545696 break;
5697
5698 case 2:
5699 // PIN set.
5700 config.pin_support = true;
Nina Satragnoacf403f92019-05-23 17:16:525701 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:435702 virtual_device_factory_->mutable_state()->pin_retries =
5703 device::kMaxPinRetries;
Adam Langleybf92fdc2019-03-27 21:13:545704 break;
5705
5706 default:
Peter Boströmfc7ddc182024-10-31 19:37:215707 NOTREACHED();
Adam Langleybf92fdc2019-03-27 21:13:545708 }
5709
Nina Satragnoacf403f92019-05-23 17:16:525710 virtual_device_factory_->SetCtap2Config(config);
Adam Langleybf92fdc2019-03-27 21:13:545711 }
5712
Adam Langleyb159c312019-03-05 00:33:195713 private:
Keishi Hattori0e45c022021-11-27 09:25:525714 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langleyb159c312019-03-05 00:33:195715};
5716
Adem Derinel620520b42024-11-04 15:45:445717static constexpr std::array<device::UserVerificationRequirement, 3> kUVLevel = {
Ken Buchanan4a33ef0d2019-06-20 17:34:045718 device::UserVerificationRequirement::kDiscouraged,
5719 device::UserVerificationRequirement::kPreferred,
5720 device::UserVerificationRequirement::kRequired,
Adam Langleybf92fdc2019-03-27 21:13:545721};
Adam Langleyb159c312019-03-05 00:33:195722
Adem Derinel620520b42024-11-04 15:45:445723static const std::array<std::string_view, 3> kUVDescription = {
5724 "discouraged", "preferred", "required"};
Adam Langleybf92fdc2019-03-27 21:13:545725
Adem Derinel620520b42024-11-04 15:45:445726static const std::array<std::string_view, 3> kPINSupportDescription = {
5727 "no PIN support", "PIN not set", "PIN set"};
Adam Langleybf92fdc2019-03-27 21:13:545728
5729TEST_F(PINAuthenticatorImplTest, MakeCredential) {
Adem Derinel620520b42024-11-04 15:45:445730 typedef std::array<int, 3> UvRequirement;
5731 typedef std::array<UvRequirement, 3> Expectations;
Adam Langley989fa492019-03-28 22:03:185732 // kExpectedWithUISupport enumerates the expected behaviour when the embedder
5733 // supports prompting the user for a PIN.
Adam Langleybf92fdc2019-03-27 21:13:545734 // clang-format off
Adem Derinel620520b42024-11-04 15:45:445735 const Expectations kExpectedWithUISupport = std::to_array<UvRequirement>({
Adam Langleybf92fdc2019-03-27 21:13:545736 // discouraged | preferred | required
5737 /* No support */ { kNoPIN, kNoPIN, kFailure },
5738 /* PIN not set */ { kNoPIN, kNoPIN, kSetPIN },
5739 /* PIN set */ { kUsePIN, kUsePIN, kUsePIN },
5740 // ^
5741 // |
5742 // VirtualCtap2Device cannot fall back to U2F.
Adem Derinel620520b42024-11-04 15:45:445743 });
Adam Langleybf92fdc2019-03-27 21:13:545744 // clang-format on
Adam Langleyb159c312019-03-05 00:33:195745
Adam Langley989fa492019-03-28 22:03:185746 // kExpectedWithoutUISupport enumerates the expected behaviour when the
5747 // embedder cannot prompt the user for a PIN.
5748 // clang-format off
Adem Derinel620520b42024-11-04 15:45:445749 const Expectations kExpectedWithoutUISupport = std::to_array<UvRequirement>({
Adam Langley989fa492019-03-28 22:03:185750 // discouraged | preferred | required
5751 /* No support */ { kNoPIN, kNoPIN, kFailure },
5752 /* PIN not set */ { kNoPIN, kNoPIN, kFailure },
5753 /* PIN set */ { kFailure, kFailure, kFailure },
5754 // ^ ^
5755 // | |
5756 // VirtualCtap2Device cannot fall back to U2F and
5757 // a PIN is required to create credentials once set
5758 // in CTAP 2.0.
Adem Derinel620520b42024-11-04 15:45:445759 });
Adam Langley989fa492019-03-28 22:03:185760 // clang-format on
Adam Langleyb159c312019-03-05 00:33:195761
Nina Satragno20a78cf92020-06-22 18:02:195762 for (bool pin_uv_auth_token : {false, true}) {
5763 SCOPED_TRACE(::testing::Message()
5764 << "pin_uv_auth_token=" << pin_uv_auth_token);
5765 for (bool ui_support : {false, true}) {
5766 SCOPED_TRACE(::testing::Message() << "ui_support=" << ui_support);
5767 const Expectations& expected =
5768 ui_support ? kExpectedWithUISupport : kExpectedWithoutUISupport;
5769 test_client_.supports_pin = ui_support;
Adam Langley6b067102019-03-16 21:18:055770
Nina Satragno20a78cf92020-06-22 18:02:195771 for (int support_level = 0; support_level <= 2; support_level++) {
Martin Kreichgauer0787dc32020-10-05 17:44:115772 for (const auto pin_protocol :
5773 {device::PINUVAuthProtocol::kV1, device::PINUVAuthProtocol::kV2}) {
5774 SCOPED_TRACE(testing::Message()
5775 << "support_level="
5776 << kPINSupportDescription[support_level]
5777 << ", pin_protocol=" << static_cast<int>(pin_protocol));
Adam Langley8348a0bc2021-06-07 23:45:155778 for (const bool excluded_credentials : {false, true}) {
5779 SCOPED_TRACE(::testing::Message()
5780 << "excluded_credentials=" << excluded_credentials);
5781 for (const bool appid_exclude : {false, true}) {
5782 if (appid_exclude && !excluded_credentials) {
5783 continue;
5784 }
5785 SCOPED_TRACE(::testing::Message()
5786 << "appid_exclude=" << appid_exclude);
Adam Langley6b067102019-03-16 21:18:055787
Adam Langley3e9ecfd2021-06-15 17:10:135788 for (const bool always_uv : {false, true}) {
5789 if (always_uv &&
5790 (!ui_support ||
5791 virtual_device_factory_->mutable_state()->pin.empty())) {
5792 continue;
Adam Langley8348a0bc2021-06-07 23:45:155793 }
Adam Langley3e9ecfd2021-06-15 17:10:135794 SCOPED_TRACE(::testing::Message() << "always_uv=" << always_uv);
Adam Langleybf92fdc2019-03-27 21:13:545795
Adam Langley3e9ecfd2021-06-15 17:10:135796 ConfigureVirtualDevice(pin_protocol, pin_uv_auth_token,
5797 support_level);
Adam Langleybf92fdc2019-03-27 21:13:545798
Adam Langley3e9ecfd2021-06-15 17:10:135799 for (int uv_level = 0; uv_level <= 2; uv_level++) {
5800 SCOPED_TRACE(kUVDescription[uv_level]);
Adam Langley989fa492019-03-28 22:03:185801
Adam Langley3e9ecfd2021-06-15 17:10:135802 switch (expected[support_level][uv_level]) {
5803 case kNoPIN:
5804 case kFailure:
5805 // There shouldn't be any PIN prompts.
5806 test_client_.expected.clear();
5807 break;
Adam Langley989fa492019-03-28 22:03:185808
Adam Langley3e9ecfd2021-06-15 17:10:135809 case kSetPIN:
5810 // A single PIN prompt to set a PIN is expected.
5811 test_client_.expected = {{PINReason::kSet, kTestPIN16}};
5812 break;
Adam Langley8348a0bc2021-06-07 23:45:155813
Adam Langley3e9ecfd2021-06-15 17:10:135814 case kUsePIN:
5815 // A single PIN prompt to get the PIN is expected.
5816 test_client_.expected = {
5817 {PINReason::kChallenge, kTestPIN16, 8}};
5818 break;
5819
5820 default:
Peter Boströmfc7ddc182024-10-31 19:37:215821 NOTREACHED();
Adam Langley3e9ecfd2021-06-15 17:10:135822 }
5823
5824 MakeCredentialResult result =
5825 AuthenticatorMakeCredential(make_credential_options(
5826 kUVLevel[uv_level], excluded_credentials,
5827 appid_exclude));
5828
5829 switch (expected[support_level][uv_level]) {
5830 case kFailure:
5831 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
5832 result.status);
5833 break;
5834
5835 case kNoPIN:
5836 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
5837 EXPECT_EQ("",
5838 virtual_device_factory_->mutable_state()->pin);
5839 EXPECT_FALSE(HasUV(result.response));
5840 break;
5841
5842 case kSetPIN:
5843 case kUsePIN:
5844 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
5845 EXPECT_EQ(kTestPIN,
5846 virtual_device_factory_->mutable_state()->pin);
5847 EXPECT_TRUE(HasUV(result.response));
5848 break;
5849
5850 default:
Peter Boströmfc7ddc182024-10-31 19:37:215851 NOTREACHED();
Adam Langley3e9ecfd2021-06-15 17:10:135852 }
Adam Langley8348a0bc2021-06-07 23:45:155853 }
5854 }
Martin Kreichgauer0787dc32020-10-05 17:44:115855 }
Nina Satragno20a78cf92020-06-22 18:02:195856 }
Adam Langley989fa492019-03-28 22:03:185857 }
Adam Langleybf92fdc2019-03-27 21:13:545858 }
5859 }
Adam Langley6b067102019-03-16 21:18:055860 }
5861}
5862
Adam Langleyc5ae37d2019-03-08 20:32:315863TEST_F(PINAuthenticatorImplTest, MakeCredentialSoftLock) {
Nina Satragnoacf403f92019-05-23 17:16:525864 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:435865 virtual_device_factory_->mutable_state()->pin_retries =
5866 device::kMaxPinRetries;
Adam Langleyb159c312019-03-05 00:33:195867
Peter Kastingcc07b332021-05-07 22:58:255868 test_client_.expected = {{PINReason::kChallenge, u"wrong", 8},
5869 {PINReason::kChallenge, u"wrong", 7,
Nina Satragno21e40a72020-12-17 17:00:145870 device::kMinPinLength, PINError::kWrongPIN},
Peter Kastingcc07b332021-05-07 22:58:255871 {PINReason::kChallenge, u"wrong", 6,
Nina Satragno21e40a72020-12-17 17:00:145872 device::kMinPinLength, PINError::kWrongPIN}};
Martin Kreichgauer55834402020-08-03 21:54:315873 EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status,
5874 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragnod976d1b2020-01-06 22:45:435875 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->pin_retries);
Nina Satragnoacf403f92019-05-23 17:16:525876 EXPECT_TRUE(virtual_device_factory_->mutable_state()->soft_locked);
Adam Langleyb159c312019-03-05 00:33:195877 ASSERT_TRUE(test_client_.failure_reason.has_value());
5878 EXPECT_EQ(InterestingFailureReason::kSoftPINBlock,
5879 *test_client_.failure_reason);
Ken Buchanan23dce912024-07-11 16:41:275880 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kSoftPinBlock,
Ken Buchanan1ea549c12024-10-10 21:31:055881 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleyb159c312019-03-05 00:33:195882}
5883
Adam Langleyc5ae37d2019-03-08 20:32:315884TEST_F(PINAuthenticatorImplTest, MakeCredentialHardLock) {
Nina Satragnoacf403f92019-05-23 17:16:525885 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:435886 virtual_device_factory_->mutable_state()->pin_retries = 1;
Adam Langleyb159c312019-03-05 00:33:195887
Peter Kastingcc07b332021-05-07 22:58:255888 test_client_.expected = {{PINReason::kChallenge, u"wrong", 1}};
Martin Kreichgauer55834402020-08-03 21:54:315889 EXPECT_EQ(AuthenticatorMakeCredential().status,
5890 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragnod976d1b2020-01-06 22:45:435891 EXPECT_EQ(0, virtual_device_factory_->mutable_state()->pin_retries);
Adam Langleyb159c312019-03-05 00:33:195892 ASSERT_TRUE(test_client_.failure_reason.has_value());
5893 EXPECT_EQ(InterestingFailureReason::kHardPINBlock,
5894 *test_client_.failure_reason);
Ken Buchanan23dce912024-07-11 16:41:275895 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kHardPinBlock,
Ken Buchanan1ea549c12024-10-10 21:31:055896 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleyb159c312019-03-05 00:33:195897}
5898
Adam Langley790bea22020-09-24 22:52:475899TEST_F(PINAuthenticatorImplTest, MakeCredentialWrongPINFirst) {
5900 virtual_device_factory_->mutable_state()->pin = kTestPIN;
5901 virtual_device_factory_->mutable_state()->pin_retries =
5902 device::kMaxPinRetries;
5903
5904 // Test that we can successfully get a PIN token after a failure.
Peter Kastingcc07b332021-05-07 22:58:255905 test_client_.expected = {{PINReason::kChallenge, u"wrong", 8},
5906 {PINReason::kChallenge, kTestPIN16, 7,
Nina Satragno21e40a72020-12-17 17:00:145907 device::kMinPinLength, PINError::kWrongPIN}};
Adam Langley790bea22020-09-24 22:52:475908 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
5909 EXPECT_EQ(static_cast<int>(device::kMaxPinRetries),
5910 virtual_device_factory_->mutable_state()->pin_retries);
5911}
5912
Nina Satragnob1870042020-12-03 03:14:015913TEST_F(PINAuthenticatorImplTest, MakeCredentialSkipPINTouch) {
5914 virtual_device_factory_->mutable_state()->pin = kTestPIN;
5915 int taps = 0;
5916 virtual_device_factory_->mutable_state()->simulate_press_callback =
5917 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
5918 ++taps;
5919 return true;
5920 });
5921 virtual_device_factory_->mutable_state()->pin_retries =
5922 device::kMaxPinRetries;
Nina Satragno164bc112020-12-04 02:44:155923 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:255924 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragnob1870042020-12-03 03:14:015925 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
5926 EXPECT_EQ(taps, 1);
5927}
5928
5929TEST_F(PINAuthenticatorImplTest, MakeCredentialDontSkipPINTouch) {
5930 // Create two devices. Both are candidates but only the second one will
5931 // respond to touches.
5932 auto discovery =
5933 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
5934 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
5935 device_1.config.pin_support = true;
5936 device_1.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:355937 base::BindRepeating([](VirtualFidoDevice* ignore) { return false; });
Nina Satragnob1870042020-12-03 03:14:015938 discovery->AddDevice(std::move(device_1));
5939
5940 int taps = 0;
5941 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
5942 device_2.state->pin = kTestPIN;
5943 device_2.config.pin_support = true;
5944 device_2.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:355945 base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) {
Nina Satragnob1870042020-12-03 03:14:015946 ++taps;
5947 return true;
5948 });
5949 discovery->AddDevice(std::move(device_2));
5950
Jagadesh P8db17bf2023-11-28 04:14:155951 ReplaceDiscoveryFactory(std::move(discovery));
Nina Satragnob1870042020-12-03 03:14:015952
Nina Satragno164bc112020-12-04 02:44:155953 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:255954 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragnob1870042020-12-03 03:14:015955 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
5956 EXPECT_EQ(taps, 2);
5957}
5958
Nina Satragno0410d092020-10-29 22:39:355959TEST_F(PINAuthenticatorImplTest, MakeCredentialAlwaysUv) {
5960 // Test that if an authenticator is reporting alwaysUv = 1, UV is attempted
5961 // even if the user verification requirement is discouraged.
5962 device::VirtualCtap2Device::Config config;
5963 config.pin_support = true;
5964 config.always_uv = true;
Nina Satragno75cff102020-12-01 00:16:255965
5966 // Enable u2f support. Normally, this would allow chrome to create a
5967 // credential without internal user verification, but we should not attempt
5968 // that with the alwaysUv flag on.
5969 config.u2f_support = true;
Nina Satragno0410d092020-10-29 22:39:355970 virtual_device_factory_->SetCtap2Config(config);
5971 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragno164bc112020-12-04 02:44:155972 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:255973 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragno0410d092020-10-29 22:39:355974
5975 MakeCredentialResult result =
5976 AuthenticatorMakeCredential(make_credential_options(
5977 device::UserVerificationRequirement::kDiscouraged));
5978 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5979 EXPECT_TRUE(HasUV(result.response));
5980}
5981
Nina Satragno62fe3e02020-11-16 18:13:265982TEST_F(PINAuthenticatorImplTest, MakeCredentialMinPINLengthNewPIN) {
5983 // Test that an authenticator advertising a min PIN length other than the
5984 // default makes it all the way to CollectPIN when setting a new PIN.
5985 device::VirtualCtap2Device::Config config;
5986 config.pin_support = true;
5987 config.min_pin_length_support = true;
5988 config.pin_uv_auth_token_support = true;
5989 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
5990 virtual_device_factory_->SetCtap2Config(config);
5991 virtual_device_factory_->mutable_state()->min_pin_length = 6;
Peter Kastingcc07b332021-05-07 22:58:255992 test_client_.expected = {{PINReason::kSet, u"123456", 0, 6}};
Nina Satragno62fe3e02020-11-16 18:13:265993
5994 MakeCredentialResult result =
5995 AuthenticatorMakeCredential(make_credential_options());
5996 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
5997}
5998
5999TEST_F(PINAuthenticatorImplTest, MakeCredentialMinPINLengthExistingPIN) {
6000 // Test that an authenticator advertising a min PIN length other than the
6001 // default makes it all the way to CollectPIN when using an existing PIN and
6002 // the forcePINChange flag is false.
6003 device::VirtualCtap2Device::Config config;
6004 config.pin_support = true;
6005 config.min_pin_length_support = true;
6006 config.pin_uv_auth_token_support = true;
6007 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6008 virtual_device_factory_->SetCtap2Config(config);
6009 virtual_device_factory_->mutable_state()->min_pin_length = 6;
6010 virtual_device_factory_->mutable_state()->pin = "123456";
Nina Satragno164bc112020-12-04 02:44:156011 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256012 {PINReason::kChallenge, u"123456", device::kMaxPinRetries, 6}};
Nina Satragno62fe3e02020-11-16 18:13:266013
6014 MakeCredentialResult result =
6015 AuthenticatorMakeCredential(make_credential_options());
6016 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6017}
6018
6019TEST_F(PINAuthenticatorImplTest, MakeCredentialForcePINChange) {
6020 // Test that an authenticator with the forcePINChange flag set to true updates
6021 // the PIN before attempting to make a credential. When querying for an
6022 // existing PIN, the default min PIN length should be asked since there is no
6023 // way to know the current PIN length.
6024 device::VirtualCtap2Device::Config config;
6025 config.pin_support = true;
6026 config.min_pin_length_support = true;
6027 config.pin_uv_auth_token_support = true;
6028 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6029 virtual_device_factory_->SetCtap2Config(config);
6030 virtual_device_factory_->mutable_state()->force_pin_change = true;
6031 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6032 virtual_device_factory_->mutable_state()->pin_retries =
6033 device::kMaxPinRetries;
6034 virtual_device_factory_->mutable_state()->min_pin_length = 6;
Peter Kastingcc07b332021-05-07 22:58:256035 test_client_.expected = {{PINReason::kChallenge, kTestPIN16,
Nina Satragno164bc112020-12-04 02:44:156036 device::kMaxPinRetries, device::kMinPinLength},
Peter Kastingcc07b332021-05-07 22:58:256037 {PINReason::kChange, u"567890", 0, 6}};
Nina Satragno62fe3e02020-11-16 18:13:266038
6039 MakeCredentialResult result =
6040 AuthenticatorMakeCredential(make_credential_options());
6041 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6042 EXPECT_EQ("567890", virtual_device_factory_->mutable_state()->pin);
6043}
6044
Martin Kreichgauera2503a72021-04-29 20:53:486045TEST_F(PINAuthenticatorImplTest, MakeCredUvNotRqd) {
6046 // Test that on an authenticator with the makeCredUvNotRqd option enabled,
6047 // non-discoverable credentials can be created without requiring a PIN.
6048 for (bool discoverable : {false, true}) {
6049 for (bool request_uv : {false, true}) {
6050 SCOPED_TRACE(testing::Message() << "discoverable=" << discoverable
6051 << " request_uv=" << request_uv);
6052
6053 test_client_.web_authentication_delegate.supports_resident_keys = true;
6054 ResetVirtualDevice();
6055 device::VirtualCtap2Device::Config config;
6056 config.u2f_support = true;
6057 config.pin_support = true;
6058 config.resident_key_support = true;
6059 config.pin_uv_auth_token_support = true;
6060 config.allow_non_resident_credential_creation_without_uv = true;
6061 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6062 virtual_device_factory_->SetCtap2Config(config);
6063 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6064 // PIN is still required for discoverable credentials, or if the caller
6065 // requests it.
6066 if (discoverable || request_uv) {
Peter Kastingcc07b332021-05-07 22:58:256067 test_client_.expected = {{PINReason::kChallenge, kTestPIN16,
Martin Kreichgauera2503a72021-04-29 20:53:486068 device::kMaxPinRetries,
6069 device::kMinPinLength}};
6070 } else {
6071 test_client_.expected = {};
6072 }
6073
6074 PublicKeyCredentialCreationOptionsPtr request = make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:466075 request->authenticator_selection->user_verification_requirement =
6076 request_uv ? device::UserVerificationRequirement::kPreferred
6077 : device::UserVerificationRequirement::kDiscouraged;
6078 request->authenticator_selection->resident_key =
Martin Kreichgauera2503a72021-04-29 20:53:486079 discoverable ? device::ResidentKeyRequirement::kPreferred
Martin Kreichgauerbece642a2021-12-07 21:02:466080 : device::ResidentKeyRequirement::kDiscouraged;
Martin Kreichgauera2503a72021-04-29 20:53:486081
6082 MakeCredentialResult result =
6083 AuthenticatorMakeCredential(std::move(request));
6084 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6085 // Requests shouldn't fall back to creating U2F credentials.
6086 EXPECT_FALSE(virtual_device_factory_->mutable_state()
6087 ->registrations.begin()
6088 ->second.is_u2f);
6089 }
6090 }
6091}
6092
6093TEST_F(PINAuthenticatorImplTest, MakeCredUvNotRqdAndAlwaysUv) {
6094 // makeCredUvNotRqd and alwaysUv can be combined even though they contradict
6095 // each other. In that case, makeCredUvNotRqd should be ignored and PIN/UV
6096 // should be collected before creating non-discoverable credentials. If PIN/UV
6097 // isn't configured, that should be taken care of first.
6098 for (bool pin_set : {false, true}) {
6099 SCOPED_TRACE(testing::Message() << "pin_set=" << pin_set);
6100
6101 ResetVirtualDevice();
6102 device::VirtualCtap2Device::Config config;
6103 config.pin_support = true;
6104 config.pin_uv_auth_token_support = true;
6105 config.always_uv = true;
6106 config.allow_non_resident_credential_creation_without_uv = true;
6107 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6108 virtual_device_factory_->SetCtap2Config(config);
6109 if (pin_set) {
6110 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Peter Kastingcc07b332021-05-07 22:58:256111 test_client_.expected = {{PINReason::kChallenge, kTestPIN16,
Martin Kreichgauera2503a72021-04-29 20:53:486112 device::kMaxPinRetries, device::kMinPinLength}};
6113 } else {
Peter Kastingcc07b332021-05-07 22:58:256114 test_client_.expected = {{PINReason::kSet, kTestPIN16,
Martin Kreichgauera2503a72021-04-29 20:53:486115 device::kMaxPinRetries, device::kMinPinLength}};
6116 }
6117
6118 MakeCredentialResult result =
6119 AuthenticatorMakeCredential(make_credential_options());
6120 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6121 }
6122}
6123
Adam Langleyf1ec9642023-01-10 00:01:576124TEST_F(PINAuthenticatorImplTest, MakeCredentialHMACSecret) {
6125 // uv=preferred is more preferred when hmac-secret is in use so that the
6126 // PRF is consistent. (Security keys have two PRFs per credential: one for
6127 // UV and one for non-UV assertions.)
6128 struct TestCase {
6129 device::UserVerificationRequirement uv;
6130 bool hmac_secret;
6131 bool should_configure_uv;
6132 };
6133
6134 constexpr TestCase kTests[] = {
6135 {device::UserVerificationRequirement::kDiscouraged, false, false},
6136 {device::UserVerificationRequirement::kPreferred, false, false},
6137 {device::UserVerificationRequirement::kRequired, false, true},
Adam Langley2bb75bd2023-03-06 01:39:556138 {device::UserVerificationRequirement::kDiscouraged, true, true},
Adam Langleyf1ec9642023-01-10 00:01:576139 {device::UserVerificationRequirement::kPreferred, true, true},
6140 {device::UserVerificationRequirement::kRequired, true, true},
6141 };
6142
6143 NavigateAndCommit(GURL(kTestOrigin1));
6144 unsigned index = 0;
6145 for (const TestCase& test : kTests) {
6146 SCOPED_TRACE(index++);
6147
6148 ResetVirtualDevice();
6149 device::VirtualCtap2Device::Config config;
6150 config.hmac_secret_support = true;
6151 config.pin_support = true;
6152 config.pin_uv_auth_token_support = true;
6153 config.allow_non_resident_credential_creation_without_uv = true;
6154 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6155 virtual_device_factory_->SetCtap2Config(config);
6156
6157 if (test.should_configure_uv) {
6158 test_client_.expected = {{PINReason::kSet, kTestPIN16,
6159 device::kMaxPinRetries, device::kMinPinLength}};
6160 } else {
6161 test_client_.expected.clear();
6162 }
6163
6164 auto options = make_credential_options(test.uv);
6165 options->hmac_create_secret = test.hmac_secret;
6166 MakeCredentialResult result =
6167 AuthenticatorMakeCredential(std::move(options));
6168 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6169 }
6170}
6171
Adam Langleyc5ae37d2019-03-08 20:32:316172TEST_F(PINAuthenticatorImplTest, GetAssertion) {
Adem Derinel620520b42024-11-04 15:45:446173 typedef std::array<int, 3> UvRequirement;
6174 typedef std::array<UvRequirement, 3> Expectations;
Adam Langley989fa492019-03-28 22:03:186175 // kExpectedWithUISupport enumerates the expected behaviour when the embedder
6176 // supports prompting the user for a PIN.
Adam Langleybf92fdc2019-03-27 21:13:546177 // clang-format off
Adem Derinel620520b42024-11-04 15:45:446178 const Expectations kExpectedWithUISupport = std::to_array<UvRequirement>({
Adam Langleybf92fdc2019-03-27 21:13:546179 // discouraged | preferred | required
6180 /* No support */ { kNoPIN, kNoPIN, kFailure },
Adam Langley989fa492019-03-28 22:03:186181 /* PIN not set */ { kNoPIN, kNoPIN, kFailure },
Adam Langleybf92fdc2019-03-27 21:13:546182 /* PIN set */ { kNoPIN, kUsePIN, kUsePIN },
Adem Derinel620520b42024-11-04 15:45:446183 });
Adam Langleybf92fdc2019-03-27 21:13:546184 // clang-format on
Adam Langleyb159c312019-03-05 00:33:196185
Adam Langley989fa492019-03-28 22:03:186186 // kExpectedWithoutUISupport enumerates the expected behaviour when the
6187 // embedder cannot prompt the user for a PIN.
6188 // clang-format off
Adem Derinel620520b42024-11-04 15:45:446189 const Expectations kExpectedWithoutUISupport = std::to_array<UvRequirement>({
Adam Langley989fa492019-03-28 22:03:186190 // discouraged | preferred | required
6191 /* No support */ { kNoPIN, kNoPIN, kFailure },
6192 /* PIN not set */ { kNoPIN, kNoPIN, kFailure },
6193 /* PIN set */ { kNoPIN, kNoPIN, kFailure },
Adem Derinel620520b42024-11-04 15:45:446194 });
Adam Langley989fa492019-03-28 22:03:186195 // clang-format on
6196
Adam Langleybf92fdc2019-03-27 21:13:546197 PublicKeyCredentialRequestOptionsPtr dummy_options = get_credential_options();
Nina Satragnoacf403f92019-05-23 17:16:526198 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466199 dummy_options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langleyb159c312019-03-05 00:33:196200
Nina Satragno20a78cf92020-06-22 18:02:196201 for (bool pin_uv_auth_token : {false, true}) {
6202 for (bool ui_support : {false, true}) {
6203 SCOPED_TRACE(::testing::Message() << "ui_support=" << ui_support);
6204 const Expectations& expected =
6205 ui_support ? kExpectedWithUISupport : kExpectedWithoutUISupport;
6206 test_client_.supports_pin = ui_support;
Adam Langleyb159c312019-03-05 00:33:196207
Nina Satragno20a78cf92020-06-22 18:02:196208 for (int support_level = 0; support_level <= 2; support_level++) {
6209 SCOPED_TRACE(kPINSupportDescription[support_level]);
Martin Kreichgauer0787dc32020-10-05 17:44:116210 for (const auto pin_protocol :
6211 {device::PINUVAuthProtocol::kV1, device::PINUVAuthProtocol::kV2}) {
6212 SCOPED_TRACE(testing::Message()
6213 << "support_level="
6214 << kPINSupportDescription[support_level]
6215 << ", pin_protocol=" << static_cast<int>(pin_protocol));
6216 ConfigureVirtualDevice(pin_protocol, pin_uv_auth_token,
6217 support_level);
Adam Langley6b067102019-03-16 21:18:056218
Martin Kreichgauer0787dc32020-10-05 17:44:116219 for (int uv_level = 0; uv_level <= 2; uv_level++) {
6220 SCOPED_TRACE(kUVDescription[uv_level]);
Adam Langley6b067102019-03-16 21:18:056221
Martin Kreichgauer0787dc32020-10-05 17:44:116222 switch (expected[support_level][uv_level]) {
6223 case kNoPIN:
6224 case kFailure:
6225 // No PIN prompts are expected.
6226 test_client_.expected.clear();
6227 break;
Adam Langley6b067102019-03-16 21:18:056228
Martin Kreichgauer0787dc32020-10-05 17:44:116229 case kUsePIN:
6230 // A single prompt to get the PIN is expected.
Peter Kastingcc07b332021-05-07 22:58:256231 test_client_.expected = {
6232 {PINReason::kChallenge, kTestPIN16, 8}};
Martin Kreichgauer0787dc32020-10-05 17:44:116233 break;
Adam Langleybf92fdc2019-03-27 21:13:546234
Martin Kreichgauer0787dc32020-10-05 17:44:116235 default:
Peter Boströmfc7ddc182024-10-31 19:37:216236 NOTREACHED();
Martin Kreichgauer0787dc32020-10-05 17:44:116237 }
Adam Langleybf92fdc2019-03-27 21:13:546238
Martin Kreichgauer0787dc32020-10-05 17:44:116239 GetAssertionResult result = AuthenticatorGetAssertion(
6240 get_credential_options(kUVLevel[uv_level]));
Adam Langleybf92fdc2019-03-27 21:13:546241
Martin Kreichgauer0787dc32020-10-05 17:44:116242 switch (expected[support_level][uv_level]) {
6243 case kFailure:
6244 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
6245 result.status);
6246 break;
Adam Langleybf92fdc2019-03-27 21:13:546247
Martin Kreichgauer0787dc32020-10-05 17:44:116248 case kNoPIN:
6249 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6250 EXPECT_FALSE(HasUV(result.response));
6251 break;
Adam Langleybf92fdc2019-03-27 21:13:546252
Martin Kreichgauer0787dc32020-10-05 17:44:116253 case kUsePIN:
6254 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6255 EXPECT_EQ(kTestPIN,
6256 virtual_device_factory_->mutable_state()->pin);
6257 EXPECT_TRUE(HasUV(result.response));
6258 break;
Adam Langley989fa492019-03-28 22:03:186259
Martin Kreichgauer0787dc32020-10-05 17:44:116260 default:
Peter Boströmfc7ddc182024-10-31 19:37:216261 NOTREACHED();
Martin Kreichgauer0787dc32020-10-05 17:44:116262 }
Nina Satragno20a78cf92020-06-22 18:02:196263 }
Adam Langley989fa492019-03-28 22:03:186264 }
Adam Langleybf92fdc2019-03-27 21:13:546265 }
6266 }
Adam Langley6b067102019-03-16 21:18:056267 }
6268}
6269
Adam Langleyc5ae37d2019-03-08 20:32:316270TEST_F(PINAuthenticatorImplTest, GetAssertionSoftLock) {
Nina Satragnoacf403f92019-05-23 17:16:526271 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:436272 virtual_device_factory_->mutable_state()->pin_retries =
6273 device::kMaxPinRetries;
Adam Langleyb159c312019-03-05 00:33:196274
6275 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
Nina Satragnoacf403f92019-05-23 17:16:526276 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466277 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langleyb159c312019-03-05 00:33:196278
Peter Kastingcc07b332021-05-07 22:58:256279 test_client_.expected = {{PINReason::kChallenge, u"wrong", 8},
6280 {PINReason::kChallenge, u"wrong", 7,
Nina Satragno21e40a72020-12-17 17:00:146281 device::kMinPinLength, PINError::kWrongPIN},
Peter Kastingcc07b332021-05-07 22:58:256282 {PINReason::kChallenge, u"wrong", 6,
Nina Satragno21e40a72020-12-17 17:00:146283 device::kMinPinLength, PINError::kWrongPIN}};
Martin Kreichgauer55834402020-08-03 21:54:316284 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
6285 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragnod976d1b2020-01-06 22:45:436286 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->pin_retries);
Nina Satragnoacf403f92019-05-23 17:16:526287 EXPECT_TRUE(virtual_device_factory_->mutable_state()->soft_locked);
Adam Langleyb159c312019-03-05 00:33:196288 ASSERT_TRUE(test_client_.failure_reason.has_value());
6289 EXPECT_EQ(InterestingFailureReason::kSoftPINBlock,
6290 *test_client_.failure_reason);
Ken Buchanan23dce912024-07-11 16:41:276291 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kSoftPinBlock,
Ken Buchanan1ea549c12024-10-10 21:31:056292 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleyb159c312019-03-05 00:33:196293}
6294
Adam Langleyc5ae37d2019-03-08 20:32:316295TEST_F(PINAuthenticatorImplTest, GetAssertionHardLock) {
Nina Satragnoacf403f92019-05-23 17:16:526296 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:436297 virtual_device_factory_->mutable_state()->pin_retries = 1;
Adam Langleyb159c312019-03-05 00:33:196298
6299 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
Nina Satragnoacf403f92019-05-23 17:16:526300 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466301 options->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langleyb159c312019-03-05 00:33:196302
Peter Kastingcc07b332021-05-07 22:58:256303 test_client_.expected = {{PINReason::kChallenge, u"wrong", 1}};
Martin Kreichgauer55834402020-08-03 21:54:316304 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
6305 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragnod976d1b2020-01-06 22:45:436306 EXPECT_EQ(0, virtual_device_factory_->mutable_state()->pin_retries);
Adam Langleyb159c312019-03-05 00:33:196307 ASSERT_TRUE(test_client_.failure_reason.has_value());
6308 EXPECT_EQ(InterestingFailureReason::kHardPINBlock,
6309 *test_client_.failure_reason);
Ken Buchanan23dce912024-07-11 16:41:276310 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kHardPinBlock,
Ken Buchanan1ea549c12024-10-10 21:31:056311 AuthenticationRequestMode::kModalWebAuthn);
Adam Langleyb159c312019-03-05 00:33:196312}
6313
Nina Satragnob1870042020-12-03 03:14:016314TEST_F(PINAuthenticatorImplTest, GetAssertionSkipPINTouch) {
6315 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6316 int taps = 0;
6317 virtual_device_factory_->mutable_state()->simulate_press_callback =
6318 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
6319 ++taps;
6320 return true;
6321 });
6322 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
6323 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466324 options->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragno164bc112020-12-04 02:44:156325 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256326 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragnob1870042020-12-03 03:14:016327 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
6328 AuthenticatorStatus::SUCCESS);
6329 EXPECT_EQ(taps, 1);
6330}
6331
6332TEST_F(PINAuthenticatorImplTest, GetAssertionDontSkipPINTouch) {
6333 // Create two devices. Both are candidates but only the second one will
6334 // respond to touches.
6335 auto discovery =
6336 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
6337 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
6338 device_1.config.pin_support = true;
6339 device_1.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:356340 base::BindRepeating([](VirtualFidoDevice* ignore) { return false; });
Nina Satragnob1870042020-12-03 03:14:016341 discovery->AddDevice(std::move(device_1));
6342
6343 int taps = 0;
6344 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
6345 device_2.state->pin = kTestPIN;
6346 device_2.config.pin_support = true;
6347 device_2.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:356348 base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) {
Nina Satragnob1870042020-12-03 03:14:016349 ++taps;
6350 return true;
6351 });
6352 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
6353 ASSERT_TRUE(device_2.state->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466354 options->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnob1870042020-12-03 03:14:016355 discovery->AddDevice(std::move(device_2));
6356
Jagadesh P8db17bf2023-11-28 04:14:156357 ReplaceDiscoveryFactory(std::move(discovery));
Nina Satragnob1870042020-12-03 03:14:016358
Nina Satragno164bc112020-12-04 02:44:156359 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256360 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragnob1870042020-12-03 03:14:016361 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
6362 AuthenticatorStatus::SUCCESS);
6363 EXPECT_EQ(taps, 2);
6364}
6365
Nina Satragno0410d092020-10-29 22:39:356366TEST_F(PINAuthenticatorImplTest, GetAssertionAlwaysUv) {
6367 // Test that if an authenticator is reporting alwaysUv = 1, UV is attempted
6368 // even if the user verification requirement is discouraged.
6369 device::VirtualCtap2Device::Config config;
6370 config.pin_support = true;
6371 config.always_uv = true;
Nina Satragno75cff102020-12-01 00:16:256372 config.u2f_support = true;
Nina Satragno0410d092020-10-29 22:39:356373 virtual_device_factory_->SetCtap2Config(config);
6374 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6375 PublicKeyCredentialRequestOptionsPtr options =
6376 get_credential_options(device::UserVerificationRequirement::kDiscouraged);
6377 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466378 options->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragno164bc112020-12-04 02:44:156379 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256380 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Nina Satragno0410d092020-10-29 22:39:356381
6382 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
6383 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6384 EXPECT_TRUE(HasUV(result.response));
6385}
6386
Adam Langleyb8fdd112020-06-15 21:44:326387TEST_F(PINAuthenticatorImplTest, MakeCredentialNoSupportedAlgorithm) {
Martin Kreichgauer55834402020-08-03 21:54:316388 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langleyb8fdd112020-06-15 21:44:326389
6390 for (int i = 0; i < 3; i++) {
6391 SCOPED_TRACE(i);
6392
6393 test_client_.expected.clear();
6394 bool expected_to_succeed = false;
6395 if (i == 0) {
6396 device::VirtualCtap2Device::Config config;
6397 // The first config is a CTAP2 device that doesn't support the
6398 // kInvalidForTesting algorithm. A dummy touch should be requested in this
6399 // case.
Adam Langleyb8fdd112020-06-15 21:44:326400 virtual_device_factory_->SetCtap2Config(config);
6401 } else if (i == 1) {
6402 device::VirtualCtap2Device::Config config;
6403 // The second config is a device with a PIN set that _does_ support the
6404 // algorithm. Since the PIN is set, we might convert the makeCredential
6405 // request to U2F, but shouldn't because the algorithm cannot be
6406 // represented in U2F.
Adam Langleyb8fdd112020-06-15 21:44:326407 config.u2f_support = true;
6408 config.pin_support = true;
Adam Langleycbe07b72021-07-16 19:32:226409 config.advertised_algorithms = {
6410 device::CoseAlgorithmIdentifier::kInvalidForTesting};
Adam Langleyb8fdd112020-06-15 21:44:326411 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6412 virtual_device_factory_->mutable_state()->pin_retries =
6413 device::kMaxPinRetries;
6414 virtual_device_factory_->SetCtap2Config(config);
Nina Satragno164bc112020-12-04 02:44:156415 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256416 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Adam Langleyb8fdd112020-06-15 21:44:326417 // Since converting to U2F isn't possible, this will trigger a PIN prompt
6418 // and succeed because the device does actually support the algorithm.
6419 expected_to_succeed = true;
6420 } else if (i == 2) {
6421 // The third case is a plain U2F authenticator, which implicitly only
6422 // supports ES256.
6423 virtual_device_factory_->SetSupportedProtocol(
6424 device::ProtocolVersion::kU2f);
6425 }
6426
Adam Langleyb8fdd112020-06-15 21:44:326427 PublicKeyCredentialCreationOptionsPtr options =
6428 GetTestPublicKeyCredentialCreationOptions();
6429 // Set uv=discouraged so that U2F fallback is possible.
Martin Kreichgauerbece642a2021-12-07 21:02:466430 options->authenticator_selection->user_verification_requirement =
6431 device::UserVerificationRequirement::kDiscouraged;
Adam Langleyb8fdd112020-06-15 21:44:326432 options->public_key_parameters =
6433 GetTestPublicKeyCredentialParameters(static_cast<int32_t>(
6434 device::CoseAlgorithmIdentifier::kInvalidForTesting));
6435
Martin Kreichgauer55834402020-08-03 21:54:316436 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
6437 expected_to_succeed ? AuthenticatorStatus::SUCCESS
6438 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
Adam Langleyb8fdd112020-06-15 21:44:326439 }
6440}
6441
Adam Langleyc296f392020-07-16 03:55:246442TEST_F(PINAuthenticatorImplTest, PRFCreatedOnCTAP2) {
6443 // Check that credential creation requests that include the PRF extension use
6444 // CTAP2 if possible.
Martin Kreichgauer55834402020-08-03 21:54:316445 NavigateAndCommit(GURL(kTestOrigin1));
Adam Langleyc296f392020-07-16 03:55:246446
6447 for (int i = 0; i < 3; i++) {
6448 SCOPED_TRACE(i);
6449
6450 device::VirtualCtap2Device::Config config;
6451 config.u2f_support = true;
6452 config.pin_support = true;
6453 config.hmac_secret_support = true;
6454 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6455 virtual_device_factory_->mutable_state()->pin_retries =
6456 device::kMaxPinRetries;
Adam Langleyc296f392020-07-16 03:55:246457
6458 PublicKeyCredentialCreationOptionsPtr options =
6459 GetTestPublicKeyCredentialCreationOptions();
6460 // Set uv=discouraged so that U2F fallback is possible.
Martin Kreichgauerbece642a2021-12-07 21:02:466461 options->authenticator_selection->user_verification_requirement =
6462 device::UserVerificationRequirement::kDiscouraged;
Adam Langleyc296f392020-07-16 03:55:246463
6464 if (i == 0) {
6465 // Sanity check: request should fallback to U2F. (If it doesn't fallback
6466 // to U2F then the PIN test infrastructure will CHECK because
6467 // |test_client_.expected| is empty.)
6468 test_client_.expected.clear();
6469 } else if (i == 1) {
6470 // If PRF is requested, the fallback to U2F should not happen because the
6471 // PRF request is higher priority than avoiding a PIN prompt. (The PIN
6472 // test infrastructure will CHECK if |expected| is set and not used.)
6473 options->prf_enable = true;
Nina Satragno164bc112020-12-04 02:44:156474 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256475 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Adam Langleyc296f392020-07-16 03:55:246476 } else {
6477 // If PRF is requested, but the authenticator doesn't support it, then we
6478 // should still use U2F.
6479 options->prf_enable = true;
6480 config.hmac_secret_support = false;
6481 test_client_.expected.clear();
6482 }
6483
6484 virtual_device_factory_->SetCtap2Config(config);
6485
Martin Kreichgauer55834402020-08-03 21:54:316486 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
6487 AuthenticatorStatus::SUCCESS);
Adam Langleyc296f392020-07-16 03:55:246488 }
6489}
6490
Martin Kreichgauer5c1d09af2021-03-19 22:27:456491// Test that pinUvAuthToken gets sent with every single batch of an exclude
6492// list. If it wasn't, any batch after the first would be unable to match
6493// credProtect=uvRequired credentials.
6494TEST_F(PINAuthenticatorImplTest, ExcludeListBatchesIncludePinToken) {
6495 NavigateAndCommit(GURL(kTestOrigin1));
6496
6497 // Set up a CTAP 2.1 authenticator with pinUvAuthToken and exclude list
6498 // batching.
6499 device::VirtualCtap2Device::Config config;
6500 config.max_credential_id_length = kTestCredentialIdLength;
6501 constexpr size_t kBatchSize = 10;
6502 config.max_credential_count_in_list = kBatchSize;
6503 config.pin_support = true;
6504 config.pin_uv_auth_token_support = true;
6505 config.ctap2_versions = {device::Ctap2Version::kCtap2_0,
6506 device::Ctap2Version::kCtap2_1};
6507 virtual_device_factory_->SetCtap2Config(config);
6508 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6509 virtual_device_factory_->mutable_state()->pin_retries =
6510 device::kMaxPinRetries;
6511
6512 test_client_.expected = {
Peter Kastingcc07b332021-05-07 22:58:256513 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
Martin Kreichgauer5c1d09af2021-03-19 22:27:456514
6515 // Craft an exclude list that is large enough to trigger batched probing and
6516 // includes one match for a credProtect=uvRequired credential.
6517 auto test_credentials = GetTestCredentials(kBatchSize + 1);
6518
6519 device::VirtualFidoDevice::RegistrationData cred_protect_credential(
6520 kTestRelyingPartyId);
6521 cred_protect_credential.protection = device::CredProtect::kUVRequired;
6522 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466523 test_credentials.back().id, std::move(cred_protect_credential)));
Martin Kreichgauer5c1d09af2021-03-19 22:27:456524
6525 // The request should fail because the exclude list matches.
6526 PublicKeyCredentialCreationOptionsPtr options =
6527 GetTestPublicKeyCredentialCreationOptions();
6528 options->exclude_credentials = std::move(test_credentials);
6529 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
6530 AuthenticatorStatus::CREDENTIAL_EXCLUDED);
6531}
6532
Adam Langley8e225502021-07-13 18:19:186533TEST_F(PINAuthenticatorImplTest, RemoveSecondAuthenticator) {
6534 // Create two PIN-capable devices. Touch one of them to trigger a prompt for
6535 // a PIN. Remove the other. Don't crash.
6536 base::RepeatingCallback<void(bool)> disconnect_1, disconnect_2;
6537
6538 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
6539 device_1.state->pin = kTestPIN;
6540 device_1.config.pin_support = true;
6541 std::tie(disconnect_1, device_1.disconnect_events) =
Ken Buchanane7a9a8092023-12-27 17:08:306542 device::FidoDiscoveryBase::EventStream<bool>::New();
Adam Langley8e225502021-07-13 18:19:186543
6544 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
6545 device_2.state->pin = kTestPIN;
6546 device_2.config.pin_support = true;
6547 std::tie(disconnect_2, device_2.disconnect_events) =
Ken Buchanane7a9a8092023-12-27 17:08:306548 device::FidoDiscoveryBase::EventStream<bool>::New();
Adam Langley8e225502021-07-13 18:19:186549
6550 int callbacks = 0;
6551 auto touch_callback = [&](int device_num) -> bool {
6552 callbacks++;
6553 if (callbacks == 1) {
6554 // Wait for the other authenticator to be triggered.
6555 return false;
6556 } else if (callbacks == 2) {
6557 // Touch authenticator to collect a PIN.
6558 return true;
6559 } else {
6560 CHECK_EQ(callbacks, 3);
6561
6562 // Disconnect other authenticator then complete with a touch.
6563 if (device_num == 1) {
6564 disconnect_2.Run(false);
6565 } else {
6566 disconnect_1.Run(false);
6567 }
6568 return true;
6569 }
6570 };
6571
6572 device_1.state->simulate_press_callback = base::BindLambdaForTesting(
Peter Kastingeb8c3ce2021-08-20 04:39:356573 [&](VirtualFidoDevice* ignore) -> bool { return touch_callback(1); });
Adam Langley8e225502021-07-13 18:19:186574 device_2.state->simulate_press_callback = base::BindLambdaForTesting(
Peter Kastingeb8c3ce2021-08-20 04:39:356575 [&](VirtualFidoDevice* ignore) -> bool { return touch_callback(2); });
Adam Langley8e225502021-07-13 18:19:186576
6577 auto discovery =
6578 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
6579 discovery->AddDevice(std::move(device_1));
6580 discovery->AddDevice(std::move(device_2));
Jagadesh P8db17bf2023-11-28 04:14:156581 ReplaceDiscoveryFactory(std::move(discovery));
Adam Langley8e225502021-07-13 18:19:186582
6583 test_client_.expected = {
6584 {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}};
6585 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
6586}
6587
Adam Langley2cdc5972024-10-03 12:33:136588TEST_F(PINAuthenticatorImplTest,
6589 RemoveAuthenticatorDuringRegistrationPINPrompt) {
6590 // Regression test for crbug.com/370000838: removing an authenticator while
6591 // the PIN prompt was showing would crash.
6592 base::RepeatingCallback<void(bool)> disconnect_1;
6593 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
6594 device_1.state->pin = kTestPIN;
6595 device_1.config.pin_support = true;
6596 std::tie(disconnect_1, device_1.disconnect_events) =
6597 device::FidoDiscoveryBase::EventStream<bool>::New();
6598
6599 auto discovery =
6600 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
6601 discovery->AddDevice(std::move(device_1));
6602 ReplaceDiscoveryFactory(std::move(discovery));
6603
6604 test_client_.collect_pin_cb =
6605 base::BindLambdaForTesting([&disconnect_1]() -> bool {
6606 disconnect_1.Run(false);
6607 return false;
6608 });
6609
6610 EXPECT_EQ(AuthenticatorMakeCredential().status,
6611 AuthenticatorStatus::NOT_ALLOWED_ERROR);
6612}
6613
6614TEST_F(PINAuthenticatorImplTest, RemoveAuthenticatorDuringAssertionPINPrompt) {
6615 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
6616 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
6617
6618 base::RepeatingCallback<void(bool)> disconnect_1;
6619 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
6620 device_1.state->pin = kTestPIN;
6621 device_1.config.pin_support = true;
6622 std::tie(disconnect_1, device_1.disconnect_events) =
6623 device::FidoDiscoveryBase::EventStream<bool>::New();
6624
6625 auto discovery =
6626 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
6627 discovery->AddDevice(std::move(device_1));
6628 ReplaceDiscoveryFactory(std::move(discovery));
6629
6630 test_client_.collect_pin_cb =
6631 base::BindLambdaForTesting([&disconnect_1]() -> bool {
6632 disconnect_1.Run(false);
6633 return false;
6634 });
6635
6636 PublicKeyCredentialRequestOptionsPtr options =
6637 GetTestPublicKeyCredentialRequestOptions();
6638 options->user_verification = device::UserVerificationRequirement::kRequired;
6639 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
6640 AuthenticatorStatus::NOT_ALLOWED_ERROR);
6641}
6642
Adam Langleydda3ea82023-05-10 21:14:366643TEST_F(PINAuthenticatorImplTest, AppIdExcludeExtensionWithPinRequiredError) {
6644 // Some alwaysUv authenticators apply the alwaysUv logic even when up=false.
6645 // That causes them to return `kCtap2ErrPinRequired` to appIdExclude probes
6646 // which broke makeCredential at one point. See crbug.com/1443039.
6647 NavigateAndCommit(GURL(kTestOrigin1));
6648
6649 device::VirtualCtap2Device::Config config;
6650 config.always_uv = true;
6651 config.always_uv_for_up_false = true;
6652 config.pin_support = true;
6653 config.pin_uv_auth_token_support = true;
6654 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6655 virtual_device_factory_->SetCtap2Config(config);
6656
6657 test_client_.expected = {{PINReason::kSet, kTestPIN16}};
6658
6659 PublicKeyCredentialCreationOptionsPtr options =
6660 GetTestPublicKeyCredentialCreationOptions();
6661 options->authenticator_selection->user_verification_requirement =
6662 device::UserVerificationRequirement::kRequired;
6663 options->appid_exclude = kTestOrigin1;
6664 options->exclude_credentials = GetTestCredentials();
6665
6666 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
6667 AuthenticatorStatus::SUCCESS);
6668}
6669
Adam Langleyc5ae37d2019-03-08 20:32:316670class InternalUVAuthenticatorImplTest : public UVAuthenticatorImplTest {
6671 public:
Nina Satragno356ffd782020-03-10 03:48:266672 struct TestCase {
6673 const bool fingerprints_enrolled;
6674 const bool supports_pin;
6675 const device::UserVerificationRequirement uv;
6676 };
6677
Adam Langleyc5ae37d2019-03-08 20:32:316678 InternalUVAuthenticatorImplTest() = default;
6679
Peter Boström9b036532021-10-28 23:37:286680 InternalUVAuthenticatorImplTest(const InternalUVAuthenticatorImplTest&) =
6681 delete;
6682 InternalUVAuthenticatorImplTest& operator=(
6683 const InternalUVAuthenticatorImplTest&) = delete;
6684
Adam Langleyc5ae37d2019-03-08 20:32:316685 void SetUp() override {
6686 UVAuthenticatorImplTest::SetUp();
Nina Satragno356ffd782020-03-10 03:48:266687 NavigateAndCommit(GURL(kTestOrigin1));
6688 }
6689
6690 std::vector<TestCase> GetTestCases() {
6691 std::vector<TestCase> test_cases;
6692 for (const bool fingerprints_enrolled : {true, false}) {
6693 for (const bool supports_pin : {true, false}) {
6694 // Avoid just testing for PIN.
Martin Kreichgauerca18b432023-04-25 18:58:476695 if (!fingerprints_enrolled && supports_pin) {
Nina Satragno356ffd782020-03-10 03:48:266696 continue;
Martin Kreichgauerca18b432023-04-25 18:58:476697 }
Nina Satragno356ffd782020-03-10 03:48:266698 for (const auto uv : {device::UserVerificationRequirement::kDiscouraged,
6699 device::UserVerificationRequirement::kPreferred,
6700 device::UserVerificationRequirement::kRequired}) {
6701 test_cases.push_back({fingerprints_enrolled, supports_pin, uv});
6702 }
6703 }
6704 }
6705 return test_cases;
6706 }
6707
6708 void ConfigureDevice(const TestCase& test_case) {
Adam Langleyc5ae37d2019-03-08 20:32:316709 device::VirtualCtap2Device::Config config;
6710 config.internal_uv_support = true;
Adam Langley7dab11d02019-03-29 00:37:166711 config.u2f_support = true;
Nina Satragno356ffd782020-03-10 03:48:266712 config.pin_support = test_case.supports_pin;
6713 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6714 virtual_device_factory_->mutable_state()->pin_retries =
6715 device::kMaxPinRetries;
6716 virtual_device_factory_->mutable_state()->fingerprints_enrolled =
6717 test_case.fingerprints_enrolled;
Nina Satragnoacf403f92019-05-23 17:16:526718 virtual_device_factory_->SetCtap2Config(config);
Nina Satragno356ffd782020-03-10 03:48:266719 SCOPED_TRACE(::testing::Message() << "fingerprints_enrolled="
6720 << test_case.fingerprints_enrolled);
6721 SCOPED_TRACE(::testing::Message()
6722 << "supports_pin=" << test_case.supports_pin);
6723 SCOPED_TRACE(UVToString(test_case.uv));
Adam Langleyc5ae37d2019-03-08 20:32:316724 }
Adam Langleyc5ae37d2019-03-08 20:32:316725};
6726
6727TEST_F(InternalUVAuthenticatorImplTest, MakeCredential) {
Nina Satragno356ffd782020-03-10 03:48:266728 for (const auto test_case : GetTestCases()) {
6729 ConfigureDevice(test_case);
Adam Langleyc5ae37d2019-03-08 20:32:316730
Nina Satragno356ffd782020-03-10 03:48:266731 auto options = make_credential_options(test_case.uv);
6732 // UV cannot be satisfied without fingerprints.
6733 const bool should_timeout =
6734 !test_case.fingerprints_enrolled &&
6735 test_case.uv == device::UserVerificationRequirement::kRequired;
6736 if (should_timeout) {
Peter Kastinge5a38ed2021-10-02 03:06:356737 options->timeout = base::Milliseconds(100);
Nina Satragno356ffd782020-03-10 03:48:266738 }
Adam Langleyc5ae37d2019-03-08 20:32:316739
Martin Kreichgauer55834402020-08-03 21:54:316740 MakeCredentialResult result =
6741 AuthenticatorMakeCredential(std::move(options));
Adam Langleyc5ae37d2019-03-08 20:32:316742
Nina Satragno356ffd782020-03-10 03:48:266743 if (should_timeout) {
Martin Kreichgauer55834402020-08-03 21:54:316744 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
Nina Satragno356ffd782020-03-10 03:48:266745 } else {
Martin Kreichgauer55834402020-08-03 21:54:316746 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6747 EXPECT_EQ(test_case.fingerprints_enrolled, HasUV(result.response));
Adam Langleyc5ae37d2019-03-08 20:32:316748 }
6749 }
6750}
6751
Nina Satragno0775ff12020-03-13 17:27:006752// Test falling back to PIN for devices that support internal user verification
6753// but not uv token.
6754TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialFallBackToPin) {
Nina Satragno0775ff12020-03-13 17:27:006755 device::VirtualCtap2Device::Config config;
6756 config.internal_uv_support = true;
6757 config.pin_support = true;
6758 config.user_verification_succeeds = false;
6759 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6760 virtual_device_factory_->mutable_state()->pin_retries =
6761 device::kMaxPinRetries;
6762 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
6763 virtual_device_factory_->SetCtap2Config(config);
6764
6765 auto options =
6766 make_credential_options(device::UserVerificationRequirement::kRequired);
6767
Martin Kreichgauer55834402020-08-03 21:54:316768 MakeCredentialResult result = AuthenticatorMakeCredential(std::move(options));
Nina Satragno0775ff12020-03-13 17:27:006769
Martin Kreichgauer55834402020-08-03 21:54:316770 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6771 EXPECT_TRUE(HasUV(result.response));
Martin Kreichgauer339413a82021-05-05 03:02:556772 EXPECT_TRUE(test_client_.collected_pin);
6773 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragno0775ff12020-03-13 17:27:006774}
6775
Nina Satragno11971dd2020-04-22 21:01:466776// Test making a credential on an authenticator that supports biometric
6777// enrollment but has no fingerprints enrolled.
6778TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialInlineBioEnrollment) {
Nina Satragno11971dd2020-04-22 21:01:466779 device::VirtualCtap2Device::Config config;
6780 config.internal_uv_support = true;
6781 config.pin_support = true;
6782 config.user_verification_succeeds = true;
6783 config.bio_enrollment_support = true;
6784 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6785 virtual_device_factory_->mutable_state()->pin_retries =
6786 device::kMaxPinRetries;
6787 virtual_device_factory_->mutable_state()->fingerprints_enrolled = false;
6788 virtual_device_factory_->SetCtap2Config(config);
6789
Martin Kreichgauer55834402020-08-03 21:54:316790 MakeCredentialResult result = AuthenticatorMakeCredential(
6791 make_credential_options(device::UserVerificationRequirement::kRequired));
Nina Satragno11971dd2020-04-22 21:01:466792
Martin Kreichgauer55834402020-08-03 21:54:316793 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6794 EXPECT_TRUE(HasUV(result.response));
Martin Kreichgauer339413a82021-05-05 03:02:556795 EXPECT_TRUE(test_client_.collected_pin);
6796 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
6797 EXPECT_TRUE(test_client_.did_bio_enrollment);
Nina Satragno11971dd2020-04-22 21:01:466798 EXPECT_TRUE(virtual_device_factory_->mutable_state()->fingerprints_enrolled);
6799}
6800
Nina Satragno3ca1c4b2025-06-16 20:19:246801// Test having an authenticator report an error during inline biometrics
6802// enrollment. Regression test for crbug.com/410985009.
6803TEST_F(InternalUVAuthenticatorImplTest,
6804 MakeCredentialInlineBioEnrollmentError) {
6805 device::VirtualCtap2Device::Config config;
6806 config.internal_uv_support = true;
6807 config.pin_support = true;
6808 config.user_verification_succeeds = true;
6809 config.bio_enrollment_support = true;
6810 config.override_response_map
6811 [device::CtapRequestCommand::kAuthenticatorBioEnrollment] =
6812 device::CtapDeviceResponseCode::kCtap2ErrUserActionTimeout;
6813 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6814 virtual_device_factory_->mutable_state()->pin_retries =
6815 device::kMaxPinRetries;
6816 virtual_device_factory_->mutable_state()->fingerprints_enrolled = false;
6817 virtual_device_factory_->SetCtap2Config(config);
6818
6819 MakeCredentialResult result = AuthenticatorMakeCredential(
6820 make_credential_options(device::UserVerificationRequirement::kRequired));
6821
6822 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
6823 EXPECT_TRUE(test_client_.collected_pin);
6824 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
6825 EXPECT_TRUE(test_client_.did_bio_enrollment);
6826 EXPECT_FALSE(virtual_device_factory_->mutable_state()->fingerprints_enrolled);
6827}
6828
Nina Satragno11971dd2020-04-22 21:01:466829// Test making a credential skipping biometric enrollment during credential
6830// creation.
6831TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialSkipInlineBioEnrollment) {
Martin Kreichgauer339413a82021-05-05 03:02:556832 test_client_.cancel_bio_enrollment = true;
Nina Satragno11971dd2020-04-22 21:01:466833
6834 device::VirtualCtap2Device::Config config;
6835 config.internal_uv_support = true;
6836 config.pin_support = true;
6837 config.user_verification_succeeds = true;
6838 config.bio_enrollment_support = true;
6839 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6840 virtual_device_factory_->mutable_state()->pin_retries =
6841 device::kMaxPinRetries;
6842 virtual_device_factory_->mutable_state()->fingerprints_enrolled = false;
6843 virtual_device_factory_->SetCtap2Config(config);
6844
Martin Kreichgauer55834402020-08-03 21:54:316845 MakeCredentialResult result = AuthenticatorMakeCredential(
6846 make_credential_options(device::UserVerificationRequirement::kRequired));
Nina Satragno11971dd2020-04-22 21:01:466847
Martin Kreichgauer55834402020-08-03 21:54:316848 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6849 EXPECT_TRUE(HasUV(result.response));
Martin Kreichgauer339413a82021-05-05 03:02:556850 EXPECT_TRUE(test_client_.collected_pin);
6851 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
6852 EXPECT_TRUE(test_client_.did_bio_enrollment);
Nina Satragno11971dd2020-04-22 21:01:466853 EXPECT_FALSE(virtual_device_factory_->mutable_state()->fingerprints_enrolled);
6854}
6855
Martin Kreichgauer339413a82021-05-05 03:02:556856TEST_F(InternalUVAuthenticatorImplTest, MakeCredUvNotRqd) {
6857 // Test that on an authenticator with the makeCredUvNotRqd option enabled,
6858 // non-discoverable credentials can be created without requiring UV or a PIN.
6859 for (bool discoverable : {false, true}) {
6860 for (bool request_uv : {false, true}) {
6861 SCOPED_TRACE(testing::Message() << "discoverable=" << discoverable
6862 << " request_uv=" << request_uv);
6863
6864 test_client_.web_authentication_delegate.supports_resident_keys = true;
6865 ResetVirtualDevice();
6866 device::VirtualCtap2Device::Config config;
6867 config.u2f_support = true;
6868 config.internal_uv_support = true;
6869 config.user_verification_succeeds = true;
6870 config.pin_support = true;
6871 config.resident_key_support = true;
6872 config.pin_uv_auth_token_support = true;
6873 config.allow_non_resident_credential_creation_without_uv = true;
6874 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
6875 virtual_device_factory_->SetCtap2Config(config);
6876 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6877 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
6878
6879 PublicKeyCredentialCreationOptionsPtr request = make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:466880 request->authenticator_selection->user_verification_requirement =
6881 request_uv ? device::UserVerificationRequirement::kPreferred
6882 : device::UserVerificationRequirement::kDiscouraged;
6883 request->authenticator_selection->resident_key =
Martin Kreichgauer339413a82021-05-05 03:02:556884 discoverable ? device::ResidentKeyRequirement::kPreferred
Martin Kreichgauerbece642a2021-12-07 21:02:466885 : device::ResidentKeyRequirement::kDiscouraged;
Martin Kreichgauer339413a82021-05-05 03:02:556886
6887 MakeCredentialResult result =
6888 AuthenticatorMakeCredential(std::move(request));
6889 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
6890 EXPECT_EQ(HasUV(result.response), discoverable || request_uv);
6891 EXPECT_FALSE(test_client_.collected_pin);
6892 // Requests shouldn't fall back to creating U2F credentials.
6893 EXPECT_FALSE(virtual_device_factory_->mutable_state()
6894 ->registrations.begin()
6895 ->second.is_u2f);
6896 }
6897 }
6898}
6899
Adam Langleyc5ae37d2019-03-08 20:32:316900TEST_F(InternalUVAuthenticatorImplTest, GetAssertion) {
Nina Satragnoacf403f92019-05-23 17:16:526901 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466902 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Adam Langleyc5ae37d2019-03-08 20:32:316903
Nina Satragno356ffd782020-03-10 03:48:266904 for (const auto test_case : GetTestCases()) {
6905 ConfigureDevice(test_case);
Nina Satragno356ffd782020-03-10 03:48:266906 // Without a fingerprint enrolled we assume that a UV=required request
6907 // cannot be satisfied by an authenticator that cannot do UV. It is
6908 // possible for a credential to be created without UV and then later
6909 // asserted with UV=required, but that would be bizarre behaviour from
6910 // an RP and we currently don't worry about it.
6911 const bool should_be_unrecognized =
6912 !test_case.fingerprints_enrolled &&
6913 test_case.uv == device::UserVerificationRequirement::kRequired;
Adam Langleyc5ae37d2019-03-08 20:32:316914
Martin Kreichgauer55834402020-08-03 21:54:316915 GetAssertionResult result =
6916 AuthenticatorGetAssertion(get_credential_options(test_case.uv));
Adam Langleyc5ae37d2019-03-08 20:32:316917
Nina Satragno356ffd782020-03-10 03:48:266918 if (should_be_unrecognized) {
Martin Kreichgauer55834402020-08-03 21:54:316919 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
Nina Satragno356ffd782020-03-10 03:48:266920 } else {
Martin Kreichgauer55834402020-08-03 21:54:316921 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Nina Satragno356ffd782020-03-10 03:48:266922 EXPECT_EQ(
6923 test_case.fingerprints_enrolled &&
6924 test_case.uv != device::UserVerificationRequirement::kDiscouraged,
Martin Kreichgauer55834402020-08-03 21:54:316925 HasUV(result.response));
Adam Langleyc5ae37d2019-03-08 20:32:316926 }
6927 }
6928}
6929
Nina Satragno0775ff12020-03-13 17:27:006930// Test falling back to PIN for devices that support internal user verification
6931// but not uv token.
6932TEST_F(InternalUVAuthenticatorImplTest, GetAssertionFallbackToPIN) {
Nina Satragno0775ff12020-03-13 17:27:006933 device::VirtualCtap2Device::Config config;
6934 config.internal_uv_support = true;
6935 config.pin_support = true;
6936 config.user_verification_succeeds = false;
6937 virtual_device_factory_->mutable_state()->pin = kTestPIN;
6938 virtual_device_factory_->mutable_state()->pin_retries =
6939 device::kMaxPinRetries;
6940 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
6941 virtual_device_factory_->SetCtap2Config(config);
6942
6943 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466944 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragno0775ff12020-03-13 17:27:006945
Martin Kreichgauer55834402020-08-03 21:54:316946 GetAssertionResult result = AuthenticatorGetAssertion(
6947 get_credential_options(device::UserVerificationRequirement::kRequired));
Nina Satragno0775ff12020-03-13 17:27:006948
Martin Kreichgauer55834402020-08-03 21:54:316949 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
6950 EXPECT_TRUE(HasUV(result.response));
Martin Kreichgauer339413a82021-05-05 03:02:556951 EXPECT_TRUE(test_client_.collected_pin);
6952 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragno0775ff12020-03-13 17:27:006953}
6954
Nina Satragnod976d1b2020-01-06 22:45:436955class UVTokenAuthenticatorImplTest : public UVAuthenticatorImplTest {
6956 public:
6957 UVTokenAuthenticatorImplTest() = default;
6958 UVTokenAuthenticatorImplTest(const UVTokenAuthenticatorImplTest&) = delete;
6959
6960 void SetUp() override {
6961 UVAuthenticatorImplTest::SetUp();
Nina Satragnod976d1b2020-01-06 22:45:436962 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:576963 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnod976d1b2020-01-06 22:45:436964 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:196965 config.pin_uv_auth_token_support = true;
Nina Satragnod976d1b2020-01-06 22:45:436966 virtual_device_factory_->SetCtap2Config(config);
6967 NavigateAndCommit(GURL(kTestOrigin1));
6968 }
Nina Satragnod976d1b2020-01-06 22:45:436969};
6970
6971TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUVToken) {
Nina Satragnod976d1b2020-01-06 22:45:436972 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:466973 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnod976d1b2020-01-06 22:45:436974
6975 for (const auto fingerprints_enrolled : {false, true}) {
6976 SCOPED_TRACE(::testing::Message()
6977 << "fingerprints_enrolled=" << fingerprints_enrolled);
6978 virtual_device_factory_->mutable_state()->fingerprints_enrolled =
6979 fingerprints_enrolled;
6980
6981 for (auto uv : {device::UserVerificationRequirement::kDiscouraged,
6982 device::UserVerificationRequirement::kPreferred,
6983 device::UserVerificationRequirement::kRequired}) {
6984 SCOPED_TRACE(UVToString(uv));
6985
Nina Satragnod976d1b2020-01-06 22:45:436986 // Without a fingerprint enrolled we assume that a UV=required request
6987 // cannot be satisfied by an authenticator that cannot do UV. It is
6988 // possible for a credential to be created without UV and then later
6989 // asserted with UV=required, but that would be bizarre behaviour from
6990 // an RP and we currently don't worry about it.
6991 const bool should_be_unrecognized =
6992 !fingerprints_enrolled &&
6993 uv == device::UserVerificationRequirement::kRequired;
6994
Martin Kreichgauer55834402020-08-03 21:54:316995 GetAssertionResult result =
6996 AuthenticatorGetAssertion(get_credential_options(uv));
Nina Satragnod976d1b2020-01-06 22:45:436997
6998 if (should_be_unrecognized) {
Martin Kreichgauer55834402020-08-03 21:54:316999 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
Nina Satragnod976d1b2020-01-06 22:45:437000 } else {
Martin Kreichgauer55834402020-08-03 21:54:317001 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Nina Satragnod976d1b2020-01-06 22:45:437002 EXPECT_EQ(fingerprints_enrolled &&
7003 uv != device::UserVerificationRequirement::kDiscouraged,
Martin Kreichgauer55834402020-08-03 21:54:317004 HasUV(result.response));
Nina Satragnod976d1b2020-01-06 22:45:437005 }
7006 }
7007 }
7008}
7009
Nina Satragno427eaca2020-02-27 19:07:277010// Test exhausting all internal user verification attempts on an authenticator
7011// that does not support PINs.
Nina Satragnob4f200b2020-02-28 19:26:037012TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUvFails) {
Nina Satragnod976d1b2020-01-06 22:45:437013 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577014 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnod976d1b2020-01-06 22:45:437015 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197016 config.pin_uv_auth_token_support = true;
Nina Satragnod976d1b2020-01-06 22:45:437017 config.user_verification_succeeds = false;
Nina Satragno427eaca2020-02-27 19:07:277018 config.pin_support = false;
Nina Satragnod976d1b2020-01-06 22:45:437019 virtual_device_factory_->SetCtap2Config(config);
7020 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7021 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467022 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnod976d1b2020-01-06 22:45:437023
Nina Satragno48ce2e622020-02-18 21:30:237024 int expected_retries = 5;
7025 virtual_device_factory_->mutable_state()->uv_retries = expected_retries;
7026 virtual_device_factory_->mutable_state()->simulate_press_callback =
7027 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
7028 EXPECT_EQ(--expected_retries,
7029 virtual_device_factory_->mutable_state()->uv_retries);
7030 return true;
7031 });
7032
Martin Kreichgauer55834402020-08-03 21:54:317033 EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status,
7034 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanan23dce912024-07-11 16:41:277035 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUvNotSupported,
Ken Buchanan1ea549c12024-10-10 21:31:057036 AuthenticationRequestMode::kModalWebAuthn);
Nina Satragno48ce2e622020-02-18 21:30:237037 EXPECT_EQ(0, expected_retries);
Nina Satragnod976d1b2020-01-06 22:45:437038}
7039
Nina Satragno427eaca2020-02-27 19:07:277040// Test exhausting all internal user verification attempts on an authenticator
7041// that supports PINs.
Nina Satragnob4f200b2020-02-28 19:26:037042TEST_F(UVTokenAuthenticatorImplTest, GetAssertionFallBackToPin) {
Nina Satragno427eaca2020-02-27 19:07:277043 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577044 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragno427eaca2020-02-27 19:07:277045 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197046 config.pin_uv_auth_token_support = true;
Nina Satragno427eaca2020-02-27 19:07:277047 config.user_verification_succeeds = false;
7048 config.pin_support = true;
7049 virtual_device_factory_->SetCtap2Config(config);
7050 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7051 virtual_device_factory_->mutable_state()->pin = kTestPIN;
7052 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467053 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragno427eaca2020-02-27 19:07:277054
Nina Satragnob1870042020-12-03 03:14:017055 int taps = 0;
7056 virtual_device_factory_->mutable_state()->uv_retries = 5;
Nina Satragno427eaca2020-02-27 19:07:277057 virtual_device_factory_->mutable_state()->simulate_press_callback =
7058 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
Nina Satragnob1870042020-12-03 03:14:017059 ++taps;
Nina Satragno427eaca2020-02-27 19:07:277060 return true;
7061 });
7062
Martin Kreichgauer55834402020-08-03 21:54:317063 EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status,
7064 AuthenticatorStatus::SUCCESS);
Nina Satragnob1870042020-12-03 03:14:017065 // 5 retries + 1 tap for the actual get assertion request.
7066 EXPECT_EQ(taps, 6);
Martin Kreichgauer339413a82021-05-05 03:02:557067 EXPECT_TRUE(test_client_.collected_pin);
7068 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragno427eaca2020-02-27 19:07:277069 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries);
Nina Satragno427eaca2020-02-27 19:07:277070}
7071
Nina Satragnofa20f272020-05-18 18:19:307072// Tests that a device supporting UV token with UV blocked at the start of a get
7073// assertion request gets a touch and then falls back to PIN.
7074TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUvBlockedFallBackToPin) {
Nina Satragnofa20f272020-05-18 18:19:307075 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577076 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnofa20f272020-05-18 18:19:307077 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197078 config.pin_uv_auth_token_support = true;
Nina Satragnofa20f272020-05-18 18:19:307079 config.user_verification_succeeds = false;
7080 config.pin_support = true;
7081
7082 virtual_device_factory_->SetCtap2Config(config);
7083 virtual_device_factory_->mutable_state()->uv_retries = 0;
7084 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7085 virtual_device_factory_->mutable_state()->pin = kTestPIN;
7086 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467087 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnofa20f272020-05-18 18:19:307088
Martin Kreichgauer55834402020-08-03 21:54:317089 EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status,
7090 AuthenticatorStatus::SUCCESS);
Martin Kreichgauer339413a82021-05-05 03:02:557091 EXPECT_TRUE(test_client_.collected_pin);
7092 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragnofa20f272020-05-18 18:19:307093 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries);
Nina Satragnofa20f272020-05-18 18:19:307094}
7095
Nina Satragnob4f200b2020-02-28 19:26:037096TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUVToken) {
Nina Satragnob4f200b2020-02-28 19:26:037097 for (const auto fingerprints_enrolled : {false, true}) {
7098 SCOPED_TRACE(::testing::Message()
7099 << "fingerprints_enrolled=" << fingerprints_enrolled);
7100 virtual_device_factory_->mutable_state()->fingerprints_enrolled =
7101 fingerprints_enrolled;
7102
7103 for (const auto uv : {device::UserVerificationRequirement::kDiscouraged,
7104 device::UserVerificationRequirement::kPreferred,
7105 device::UserVerificationRequirement::kRequired}) {
7106 SCOPED_TRACE(UVToString(uv));
7107
Nina Satragnob4f200b2020-02-28 19:26:037108 // UV cannot be satisfied without fingerprints.
7109 const bool should_timeout =
7110 !fingerprints_enrolled &&
7111 uv == device::UserVerificationRequirement::kRequired;
Nina Satragnob4f200b2020-02-28 19:26:037112
7113 if (should_timeout) {
7114 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
Martin Kreichgauer55834402020-08-03 21:54:317115 AuthenticatorMakeCredentialAndWaitForTimeout(
7116 make_credential_options(uv))
7117 .status);
Nina Satragnob4f200b2020-02-28 19:26:037118 } else {
Martin Kreichgauer55834402020-08-03 21:54:317119 MakeCredentialResult result =
7120 AuthenticatorMakeCredential(make_credential_options(uv));
7121 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7122 EXPECT_EQ(fingerprints_enrolled, HasUV(result.response));
Nina Satragnob4f200b2020-02-28 19:26:037123 }
7124 }
7125 }
7126}
7127
7128// Test exhausting all internal user verification attempts on an authenticator
7129// that does not support PINs.
7130TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUvFails) {
Nina Satragnob4f200b2020-02-28 19:26:037131 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577132 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnob4f200b2020-02-28 19:26:037133 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197134 config.pin_uv_auth_token_support = true;
Nina Satragnob4f200b2020-02-28 19:26:037135 config.user_verification_succeeds = false;
7136 config.pin_support = false;
7137 virtual_device_factory_->SetCtap2Config(config);
7138 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7139 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467140 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnob4f200b2020-02-28 19:26:037141
7142 int expected_retries = 5;
7143 virtual_device_factory_->mutable_state()->uv_retries = expected_retries;
7144 virtual_device_factory_->mutable_state()->simulate_press_callback =
7145 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
7146 EXPECT_EQ(--expected_retries,
7147 virtual_device_factory_->mutable_state()->uv_retries);
7148 return true;
7149 });
7150
Martin Kreichgauer55834402020-08-03 21:54:317151 EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status,
7152 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Nina Satragnob4f200b2020-02-28 19:26:037153 EXPECT_EQ(0, expected_retries);
Nina Satragnob4f200b2020-02-28 19:26:037154}
7155
7156// Test exhausting all internal user verification attempts on an authenticator
7157// that supports PINs.
7158TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialFallBackToPin) {
Nina Satragnob4f200b2020-02-28 19:26:037159 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577160 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnob4f200b2020-02-28 19:26:037161 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197162 config.pin_uv_auth_token_support = true;
Nina Satragnob4f200b2020-02-28 19:26:037163 config.user_verification_succeeds = false;
7164 config.pin_support = true;
7165 virtual_device_factory_->SetCtap2Config(config);
7166 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7167 virtual_device_factory_->mutable_state()->pin = kTestPIN;
7168 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467169 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnob4f200b2020-02-28 19:26:037170
Nina Satragnob1870042020-12-03 03:14:017171 int taps = 0;
7172 virtual_device_factory_->mutable_state()->uv_retries = 5;
Nina Satragnob4f200b2020-02-28 19:26:037173 virtual_device_factory_->mutable_state()->simulate_press_callback =
7174 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
Nina Satragnob1870042020-12-03 03:14:017175 ++taps;
Nina Satragnob4f200b2020-02-28 19:26:037176 return true;
7177 });
7178
Martin Kreichgauer55834402020-08-03 21:54:317179 EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status,
7180 AuthenticatorStatus::SUCCESS);
Nina Satragnob1870042020-12-03 03:14:017181 // 5 retries + 1 tap for the actual get assertion request.
7182 EXPECT_EQ(taps, 6);
Martin Kreichgauer339413a82021-05-05 03:02:557183 EXPECT_TRUE(test_client_.collected_pin);
7184 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragnob4f200b2020-02-28 19:26:037185 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries);
Nina Satragnob4f200b2020-02-28 19:26:037186}
7187
Nina Satragnofa20f272020-05-18 18:19:307188// Tests that a device supporting UV token with UV blocked at the start of a get
7189// assertion request gets a touch and then falls back to PIN.
7190TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUvBlockedFallBackToPin) {
Nina Satragnofa20f272020-05-18 18:19:307191 device::VirtualCtap2Device::Config config;
Nina Satragnoedad4c52020-06-16 21:31:577192 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
Nina Satragnofa20f272020-05-18 18:19:307193 config.internal_uv_support = true;
Nina Satragno20a78cf92020-06-22 18:02:197194 config.pin_uv_auth_token_support = true;
Nina Satragnofa20f272020-05-18 18:19:307195 config.user_verification_succeeds = false;
7196 config.pin_support = true;
7197
7198 virtual_device_factory_->SetCtap2Config(config);
7199 virtual_device_factory_->mutable_state()->uv_retries = 0;
7200 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7201 virtual_device_factory_->mutable_state()->pin = kTestPIN;
7202 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Martin Kreichgauerbece642a2021-12-07 21:02:467203 get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId));
Nina Satragnofa20f272020-05-18 18:19:307204
Martin Kreichgauer55834402020-08-03 21:54:317205 EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status,
7206 AuthenticatorStatus::SUCCESS);
Martin Kreichgauer339413a82021-05-05 03:02:557207 EXPECT_TRUE(test_client_.collected_pin);
7208 EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length);
Nina Satragnofa20f272020-05-18 18:19:307209 EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries);
Nina Satragnofa20f272020-05-18 18:19:307210}
7211
Adam Langley6b6a13712021-07-14 20:35:317212class BlockingAuthenticatorRequestDelegate
7213 : public AuthenticatorRequestClientDelegate {
7214 public:
Nina Satragnod18d2062021-10-08 16:00:497215 BlockingAuthenticatorRequestDelegate() = default;
Adam Langley6b6a13712021-07-14 20:35:317216
7217 void RegisterActionCallbacks(
7218 base::OnceClosure cancel_callback,
Adem Derinel2154d4b12025-02-04 12:29:557219 base::OnceClosure immediate_not_found_callback,
Adam Langley6b6a13712021-07-14 20:35:317220 base::RepeatingClosure start_over_callback,
Nina Satragnofe6e52ad72022-06-01 14:04:147221 AccountPreselectedCallback account_preselected_callback,
Adem Derinel72e11db2025-02-11 15:58:007222 PasswordSelectedCallback password_selected_callback,
Adam Langley6b6a13712021-07-14 20:35:317223 device::FidoRequestHandlerBase::RequestCallback request_callback,
Adem Derinel38626c22025-05-22 13:31:587224 base::OnceClosure cancel_ui_timeout_callback,
Nina Satragno6e0f1ab2024-06-13 22:28:117225 base::RepeatingClosure bluetooth_adapter_power_on_callback,
7226 base::RepeatingCallback<
7227 void(device::FidoRequestHandlerBase::BlePermissionCallback)>
7228 ble_status_callback) override {
Adam Langley6b6a13712021-07-14 20:35:317229 cancel_callback_ = std::move(cancel_callback);
7230 }
7231
7232 bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
Nina Satragnod18d2062021-10-08 16:00:497233 // Post a task to cancel the request to give the second authenticator a
7234 // chance to return a status from the cancelled request.
Sean Maher52fa5a72022-11-14 15:53:257235 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Nina Satragnod18d2062021-10-08 16:00:497236 FROM_HERE, std::move(cancel_callback_));
Adam Langley6b6a13712021-07-14 20:35:317237 return true;
7238 }
7239
Adam Langley6b6a13712021-07-14 20:35:317240 private:
Adam Langley6b6a13712021-07-14 20:35:317241 base::OnceClosure cancel_callback_;
7242};
7243
7244class BlockingDelegateContentBrowserClient : public ContentBrowserClient {
7245 public:
Nina Satragnod18d2062021-10-08 16:00:497246 BlockingDelegateContentBrowserClient() = default;
Adam Langley6b6a13712021-07-14 20:35:317247
7248 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
7249 return &web_authentication_delegate_;
7250 }
7251
7252 std::unique_ptr<AuthenticatorRequestClientDelegate>
7253 GetWebAuthenticationRequestDelegate(
7254 RenderFrameHost* render_frame_host) override {
Nina Satragnod18d2062021-10-08 16:00:497255 auto ret = std::make_unique<BlockingAuthenticatorRequestDelegate>();
Adam Langley6b6a13712021-07-14 20:35:317256 delegate_ = ret.get();
7257 return ret;
7258 }
7259
Adam Langley6b6a13712021-07-14 20:35:317260 private:
7261 TestWebAuthenticationDelegate web_authentication_delegate_;
Pârise6361d02023-07-19 09:00:437262 raw_ptr<BlockingAuthenticatorRequestDelegate, AcrossTasksDanglingUntriaged>
7263 delegate_ = nullptr;
Adam Langley6b6a13712021-07-14 20:35:317264};
7265
7266class BlockingDelegateAuthenticatorImplTest : public AuthenticatorImplTest {
7267 public:
7268 BlockingDelegateAuthenticatorImplTest() = default;
7269
7270 BlockingDelegateAuthenticatorImplTest(
7271 const BlockingDelegateAuthenticatorImplTest&) = delete;
7272 BlockingDelegateAuthenticatorImplTest& operator=(
7273 const BlockingDelegateAuthenticatorImplTest&) = delete;
7274
7275 void SetUp() override {
7276 AuthenticatorImplTest::SetUp();
7277 old_client_ = SetBrowserClientForTesting(&test_client_);
7278 NavigateAndCommit(GURL(kTestOrigin1));
7279 }
7280
7281 void TearDown() override {
7282 SetBrowserClientForTesting(old_client_);
7283 AuthenticatorImplTest::TearDown();
7284 }
7285
7286 protected:
Nina Satragnod18d2062021-10-08 16:00:497287 BlockingDelegateContentBrowserClient test_client_;
Adam Langley6b6a13712021-07-14 20:35:317288
7289 private:
Keishi Hattori0e45c022021-11-27 09:25:527290 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langley6b6a13712021-07-14 20:35:317291};
7292
7293TEST_F(BlockingDelegateAuthenticatorImplTest, PostCancelMessage) {
7294 // Create a fingerprint-reading device and a UP-only device. Advance the
7295 // first till it's waiting for a fingerprint then simulate a touch on the
7296 // UP device that claims that it failed due to an excluded credential.
Nina Satragnod18d2062021-10-08 16:00:497297 // This will cancel the request on the fingerprint device, which will resolve
Adam Langley6b6a13712021-07-14 20:35:317298 // the UV with an error. Don't crash (crbug.com/1225899).
Adam Langley6b6a13712021-07-14 20:35:317299 PublicKeyCredentialCreationOptionsPtr options =
7300 GetTestPublicKeyCredentialCreationOptions();
7301 options->exclude_credentials = GetTestCredentials();
7302
7303 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_1;
7304 scoped_refptr<VirtualFidoDevice::State> state_1 = device_1.state;
7305 device_1.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:357306 base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) -> bool {
Adam Langley6b6a13712021-07-14 20:35:317307 // Drop all makeCredential requests. The reply will be sent when
7308 // the second authenticator is asked for a fingerprint.
7309 return false;
7310 });
7311
7312 device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
7313 scoped_refptr<VirtualFidoDevice::State> state_2 = device_2.state;
7314 device_2.config.internal_uv_support = true;
7315 device_2.config.pin_support = true;
7316 device_2.config.pin_uv_auth_token_support = true;
7317 device_2.config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
7318 device_2.state->pin = kTestPIN;
7319 device_2.state->fingerprints_enrolled = true;
7320 device_2.state->uv_retries = 8;
Nina Satragnod18d2062021-10-08 16:00:497321 device_2.state->cancel_response_code =
7322 device::CtapDeviceResponseCode::kCtap2ErrOperationDenied;
Adam Langley6b6a13712021-07-14 20:35:317323 device_2.state->simulate_press_callback =
Peter Kastingeb8c3ce2021-08-20 04:39:357324 base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) -> bool {
Adam Langley6b6a13712021-07-14 20:35:317325 // If asked for a fingerprint, fail the makeCredential request by
7326 // simulating a matched excluded credential by the other authenticator.
Sean Maher52fa5a72022-11-14 15:53:257327 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Adam Langley6b6a13712021-07-14 20:35:317328 FROM_HERE, base::BindOnce(std::move(state_1->transact_callback),
7329 std::vector<uint8_t>{static_cast<uint8_t>(
7330 device::CtapDeviceResponseCode::
7331 kCtap2ErrCredentialExcluded)}));
7332 return false;
7333 });
7334
7335 auto discovery =
7336 std::make_unique<device::test::MultipleVirtualFidoDeviceFactory>();
7337 discovery->AddDevice(std::move(device_1));
7338 discovery->AddDevice(std::move(device_2));
Jagadesh P8db17bf2023-11-28 04:14:157339 ReplaceDiscoveryFactory(std::move(discovery));
Adam Langley6b6a13712021-07-14 20:35:317340
Adam Langley6b6a13712021-07-14 20:35:317341 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
7342 AuthenticatorStatus::CREDENTIAL_EXCLUDED);
7343}
7344
Adam Langleyb307ff62019-03-27 23:36:337345// ResidentKeyTestAuthenticatorRequestDelegate is a delegate that:
7346// a) always returns |kTestPIN| when asked for a PIN.
7347// b) sorts potential resident-key accounts by user ID, maps them to a string
7348// form ("<hex user ID>:<user name>:<display name>"), joins the strings
7349// with "/", and compares the result against |expected_accounts|.
7350// c) auto-selects the account with the user ID matching |selected_user_id|.
7351class ResidentKeyTestAuthenticatorRequestDelegate
7352 : public AuthenticatorRequestClientDelegate {
7353 public:
Martin Kreichgauerb3689d062022-07-12 09:36:517354 struct Config {
7355 // A string representation of the accounts expected to be passed to
7356 // `SelectAccount()`.
7357 std::string expected_accounts;
7358
7359 // The user ID of the account that should be selected by `SelectAccount()`.
7360 std::vector<uint8_t> selected_user_id;
7361
Martin Kreichgauer77def30f02024-11-11 20:16:587362 // Indicates whether `SetUIPresentation(kAutofill)` is expected to be
7363 // called.
Martin Kreichgauerb3689d062022-07-12 09:36:517364 bool expect_conditional = false;
7365
Adem Derinel38626c22025-05-22 13:31:587366 // Indicates whether `RegisterActionCallbacks()` should run the cancel UI
7367 // timeout callback.
7368 bool run_cancel_ui_timeout_callback = false;
7369
Martin Kreichgauerb3689d062022-07-12 09:36:517370 // If set, indicates that `DoesBlockRequestOnFailure()` is expected to be
7371 // called with this value.
Arthur Sonzognic686e8f2024-01-11 08:36:377372 std::optional<AuthenticatorRequestClientDelegate::InterestingFailureReason>
Martin Kreichgauerb3689d062022-07-12 09:36:517373 expected_failure_reason;
7374
7375 // If set, indicates that the `AccountPreselectCallback` should be invoked
7376 // with this credential ID at the beginning of the request.
7377 // `preselected_authenticator_id` contains the authenticator ID to which the
7378 // request should be dispatched in this case.
Arthur Sonzognic686e8f2024-01-11 08:36:377379 std::optional<std::vector<uint8_t>> preselected_credential_id;
7380 std::optional<std::string> preselected_authenticator_id;
Martin Kreichgauerb3689d062022-07-12 09:36:517381 };
7382
7383 explicit ResidentKeyTestAuthenticatorRequestDelegate(Config config)
7384 : config_(std::move(config)) {}
7385
7386 ~ResidentKeyTestAuthenticatorRequestDelegate() override {
Martin Kreichgauer77def30f02024-11-11 20:16:587387 CHECK(!config_.expect_conditional || expect_conditional_satisfied_)
7388 << "SetUIPresentation(kAutofill) expected but not called";
Martin Kreichgauerb3689d062022-07-12 09:36:517389 DCHECK(!config_.expected_failure_reason ||
7390 expected_failure_reason_satisfied_)
7391 << "DoesRequestBlockOnFailure() expected but not called";
7392 }
Adam Langleyb307ff62019-03-27 23:36:337393
Martin Kreichgauera97e2a32021-02-05 23:10:597394 ResidentKeyTestAuthenticatorRequestDelegate(
7395 const ResidentKeyTestAuthenticatorRequestDelegate&) = delete;
7396 ResidentKeyTestAuthenticatorRequestDelegate& operator=(
7397 const ResidentKeyTestAuthenticatorRequestDelegate&) = delete;
7398
Adam Langley989fa492019-03-28 22:03:187399 bool SupportsPIN() const override { return true; }
7400
Adam Langleyb307ff62019-03-27 23:36:337401 void CollectPIN(
Nina Satragno164bc112020-12-04 02:44:157402 CollectPINOptions options,
Jan Wilken Dörrieaace0cfef2021-03-11 22:01:587403 base::OnceCallback<void(std::u16string)> provide_pin_cb) override {
Sean Maher52fa5a72022-11-14 15:53:257404 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Peter Kastingcc07b332021-05-07 22:58:257405 FROM_HERE, base::BindOnce(std::move(provide_pin_cb), kTestPIN16));
Adam Langleyb307ff62019-03-27 23:36:337406 }
7407
Nina Satragnod976d1b2020-01-06 22:45:437408 void FinishCollectToken() override {}
Adam Langley989fa492019-03-28 22:03:187409
Martin Kreichgauerb3689d062022-07-12 09:36:517410 void RegisterActionCallbacks(
7411 base::OnceClosure cancel_callback,
Adem Derinel2154d4b12025-02-04 12:29:557412 base::OnceClosure immediate_not_found_callback,
Martin Kreichgauerb3689d062022-07-12 09:36:517413 base::RepeatingClosure start_over_callback,
7414 AccountPreselectedCallback account_preselected_callback,
Adem Derinel72e11db2025-02-11 15:58:007415 PasswordSelectedCallback password_selected_callback,
Martin Kreichgauerb3689d062022-07-12 09:36:517416 device::FidoRequestHandlerBase::RequestCallback request_callback,
Adem Derinel38626c22025-05-22 13:31:587417 base::OnceClosure cancel_ui_timeout_callback,
Nina Satragno6e0f1ab2024-06-13 22:28:117418 base::RepeatingClosure bluetooth_adapter_power_on_callback,
7419 base::RepeatingCallback<
7420 void(device::FidoRequestHandlerBase::BlePermissionCallback)>
7421 ble_status_callback) override {
Martin Kreichgauerb3689d062022-07-12 09:36:517422 account_preselected_callback_ = account_preselected_callback;
7423 request_callback_ = request_callback;
Adem Derinel38626c22025-05-22 13:31:587424 if (config_.run_cancel_ui_timeout_callback) {
7425 std::move(cancel_ui_timeout_callback).Run();
7426 }
Martin Kreichgauerb3689d062022-07-12 09:36:517427 }
7428
Adam Langleyb307ff62019-03-27 23:36:337429 void SelectAccount(
7430 std::vector<device::AuthenticatorGetAssertionResponse> responses,
7431 base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
7432 callback) override {
7433 std::sort(responses.begin(), responses.end(),
7434 [](const device::AuthenticatorGetAssertionResponse& a,
7435 const device::AuthenticatorGetAssertionResponse& b) {
Adam Langleyb1b6ace2021-03-09 20:23:497436 return a.user_entity->id < b.user_entity->id;
Adam Langleyb307ff62019-03-27 23:36:337437 });
7438
7439 std::vector<std::string> string_reps;
Peter Kasting1557e5f2025-01-28 01:14:087440 std::ranges::transform(
Peter Kasting1d48f352023-03-07 18:29:577441 responses, std::back_inserter(string_reps),
Adam Langleyb307ff62019-03-27 23:36:337442 [](const device::AuthenticatorGetAssertionResponse& response) {
7443 const device::PublicKeyCredentialUserEntity& user =
Adam Langleyb1b6ace2021-03-09 20:23:497444 response.user_entity.value();
Tom Sepez80e9e6e52024-02-01 02:34:427445 return base::HexEncode(user.id) + ":" + user.name.value_or("") + ":" +
7446 user.display_name.value_or("");
Adam Langleyb307ff62019-03-27 23:36:337447 });
7448
Martin Kreichgauerb3689d062022-07-12 09:36:517449 EXPECT_EQ(config_.expected_accounts, base::JoinString(string_reps, "/"));
Adam Langleyb307ff62019-03-27 23:36:337450
Peter Kasting1557e5f2025-01-28 01:14:087451 const auto selected = std::ranges::find(
Peter Kasting837e2ccb2022-09-07 14:13:177452 responses, config_.selected_user_id,
7453 [](const device::AuthenticatorGetAssertionResponse& response) {
7454 return response.user_entity->id;
Adam Langleyb307ff62019-03-27 23:36:337455 });
7456 ASSERT_TRUE(selected != responses.end());
7457
Sean Maher52fa5a72022-11-14 15:53:257458 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
Adam Langleyb307ff62019-03-27 23:36:337459 FROM_HERE, base::BindOnce(std::move(callback), std::move(*selected)));
7460 }
7461
Martin Kreichgauerf45542a2019-08-30 16:45:507462 bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
Martin Kreichgauerb3689d062022-07-12 09:36:517463 if (config_.expected_failure_reason) {
7464 EXPECT_EQ(*config_.expected_failure_reason, reason);
7465 expected_failure_reason_satisfied_ = true;
7466 }
Adam Langley77fab612019-05-03 05:19:047467 return AuthenticatorRequestClientDelegate::DoesBlockRequestOnFailure(
Martin Kreichgauerf45542a2019-08-30 16:45:507468 reason);
Adam Langley77fab612019-05-03 05:19:047469 }
7470
Martin Kreichgauer77def30f02024-11-11 20:16:587471 void SetUIPresentation(UIPresentation ui_presentation) override {
7472 if (config_.expect_conditional) {
7473 EXPECT_EQ(ui_presentation, UIPresentation::kAutofill);
7474 } else {
Adem Derinel38626c22025-05-22 13:31:587475 EXPECT_TRUE(ui_presentation == UIPresentation::kModal ||
7476 ui_presentation == UIPresentation::kModalImmediate);
Martin Kreichgauer77def30f02024-11-11 20:16:587477 }
Martin Kreichgauerb3689d062022-07-12 09:36:517478 EXPECT_TRUE(!expect_conditional_satisfied_);
7479 expect_conditional_satisfied_ = true;
7480 }
7481
7482 bool EmbedderControlsAuthenticatorDispatch(
7483 const device::FidoAuthenticator& authenticator) override {
7484 // Don't instantly dispatch platform authenticator requests if the test is
7485 // exercising platform credential preselection.
7486 // `OnTransportAvailabilityEnumerated()` will run the `request_callback_` in
7487 // this case to mimic behavior of the real UI.
7488 return authenticator.AuthenticatorTransport() ==
7489 device::FidoTransportProtocol::kInternal &&
7490 config_.preselected_credential_id;
7491 }
7492
7493 void OnTransportAvailabilityEnumerated(
7494 device::FidoRequestHandlerBase::TransportAvailabilityInfo info) override {
7495 if (config_.preselected_credential_id) {
7496 DCHECK(config_.preselected_authenticator_id);
7497 EXPECT_EQ(info.has_platform_authenticator_credential,
7498 device::FidoRequestHandlerBase::RecognizedCredential::
7499 kHasRecognizedCredential);
Nina Satragno17570452024-03-18 16:48:237500 const auto cred = std::ranges::find(
Nina Satragno775ab712023-06-21 21:07:167501 info.recognized_credentials, *config_.preselected_credential_id,
Nina Satragno17570452024-03-18 16:48:237502 &device::DiscoverableCredentialMetadata::cred_id);
7503 ASSERT_NE(cred, info.recognized_credentials.end());
7504 std::move(account_preselected_callback_).Run(*cred);
Martin Kreichgauerb3689d062022-07-12 09:36:517505 request_callback_.Run(*config_.preselected_authenticator_id);
7506 }
Nina Satragnob93b8422021-02-04 21:50:447507 }
7508
Adam Langleyb307ff62019-03-27 23:36:337509 private:
Martin Kreichgauerb3689d062022-07-12 09:36:517510 const Config config_;
7511 bool expect_conditional_satisfied_ = false;
7512 bool expected_failure_reason_satisfied_ = false;
7513 device::FidoRequestHandlerBase::RequestCallback request_callback_;
7514 AccountPreselectedCallback account_preselected_callback_;
Adem Derinel38626c22025-05-22 13:31:587515 base::OnceClosure cancel_ui_timeout_callback_;
Adam Langleyb307ff62019-03-27 23:36:337516};
7517
7518class ResidentKeyTestAuthenticatorContentBrowserClient
7519 : public ContentBrowserClient {
7520 public:
Martin Kreichgauerfefb3772021-04-12 23:02:487521 ResidentKeyTestAuthenticatorContentBrowserClient() {
7522 web_authentication_delegate.supports_resident_keys = true;
7523 }
7524
7525 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
7526 return &web_authentication_delegate;
7527 }
7528
Adam Langleyb307ff62019-03-27 23:36:337529 std::unique_ptr<AuthenticatorRequestClientDelegate>
7530 GetWebAuthenticationRequestDelegate(
Adam Langley5f3963f12020-01-21 19:10:337531 RenderFrameHost* render_frame_host) override {
Adam Langleyb307ff62019-03-27 23:36:337532 return std::make_unique<ResidentKeyTestAuthenticatorRequestDelegate>(
Martin Kreichgauerb3689d062022-07-12 09:36:517533 delegate_config);
Adam Langleyb307ff62019-03-27 23:36:337534 }
7535
Martin Kreichgauerfefb3772021-04-12 23:02:487536 TestWebAuthenticationDelegate web_authentication_delegate;
7537
Martin Kreichgauerb3689d062022-07-12 09:36:517538 ResidentKeyTestAuthenticatorRequestDelegate::Config delegate_config;
Adam Langleyb307ff62019-03-27 23:36:337539};
7540
7541class ResidentKeyAuthenticatorImplTest : public UVAuthenticatorImplTest {
Nina Satragno867bca52022-10-13 15:02:027542 public:
Peter Boström9b036532021-10-28 23:37:287543 ResidentKeyAuthenticatorImplTest(const ResidentKeyAuthenticatorImplTest&) =
7544 delete;
7545 ResidentKeyAuthenticatorImplTest& operator=(
7546 const ResidentKeyAuthenticatorImplTest&) = delete;
7547
Nina Satragno867bca52022-10-13 15:02:027548 protected:
7549 ResidentKeyAuthenticatorImplTest() = default;
7550
Adam Langleyb307ff62019-03-27 23:36:337551 void SetUp() override {
Adam Langleyb307ff62019-03-27 23:36:337552 UVAuthenticatorImplTest::SetUp();
7553 old_client_ = SetBrowserClientForTesting(&test_client_);
7554 device::VirtualCtap2Device::Config config;
7555 config.pin_support = true;
7556 config.resident_key_support = true;
Nina Satragnoacf403f92019-05-23 17:16:527557 virtual_device_factory_->SetCtap2Config(config);
7558 virtual_device_factory_->mutable_state()->pin = kTestPIN;
Nina Satragnod976d1b2020-01-06 22:45:437559 virtual_device_factory_->mutable_state()->pin_retries =
7560 device::kMaxPinRetries;
Adam Langleyb307ff62019-03-27 23:36:337561 NavigateAndCommit(GURL(kTestOrigin1));
7562 }
7563
7564 void TearDown() override {
7565 SetBrowserClientForTesting(old_client_);
Martin Kreichgauer85a723bb2020-06-08 17:25:197566 UVAuthenticatorImplTest::TearDown();
Adam Langleyb307ff62019-03-27 23:36:337567 }
7568
Martin Kreichgauer2e9d8072020-08-31 15:20:477569 static PublicKeyCredentialCreationOptionsPtr make_credential_options(
7570 device::ResidentKeyRequirement resident_key =
7571 device::ResidentKeyRequirement::kRequired) {
Adam Langleyb307ff62019-03-27 23:36:337572 PublicKeyCredentialCreationOptionsPtr options =
7573 UVAuthenticatorImplTest::make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:467574 options->authenticator_selection->resident_key = resident_key;
Ken Buchanan4a33ef0d2019-06-20 17:34:047575 options->user.id = {1, 2, 3, 4};
Adam Langleyb307ff62019-03-27 23:36:337576 return options;
7577 }
7578
7579 static PublicKeyCredentialRequestOptionsPtr get_credential_options() {
7580 PublicKeyCredentialRequestOptionsPtr options =
7581 UVAuthenticatorImplTest::get_credential_options();
7582 options->allow_credentials.clear();
7583 return options;
7584 }
7585
Martin Kreichgauerb3689d062022-07-12 09:36:517586 ResidentKeyTestAuthenticatorContentBrowserClient test_client_;
7587
Adam Langleyb307ff62019-03-27 23:36:337588 private:
Keishi Hattori0e45c022021-11-27 09:25:527589 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langleyb307ff62019-03-27 23:36:337590};
7591
Martin Kreichgauer2e9d8072020-08-31 15:20:477592TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkRequired) {
Adam Langleyeeac87e2019-04-13 22:58:227593 for (const bool internal_uv : {false, true}) {
7594 SCOPED_TRACE(::testing::Message() << "internal_uv=" << internal_uv);
Adam Langleyeeac87e2019-04-13 22:58:227595
7596 if (internal_uv) {
7597 device::VirtualCtap2Device::Config config;
7598 config.resident_key_support = true;
7599 config.internal_uv_support = true;
Nina Satragnoacf403f92019-05-23 17:16:527600 virtual_device_factory_->SetCtap2Config(config);
7601 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
Adam Langleyeeac87e2019-04-13 22:58:227602 }
7603
Martin Kreichgauer55834402020-08-03 21:54:317604 MakeCredentialResult result =
7605 AuthenticatorMakeCredential(make_credential_options());
Adam Langleyeeac87e2019-04-13 22:58:227606
Martin Kreichgauer55834402020-08-03 21:54:317607 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Martin Kreichgauer55834402020-08-03 21:54:317608 EXPECT_TRUE(HasUV(result.response));
Nina Satragnoacf403f92019-05-23 17:16:527609 ASSERT_EQ(1u,
7610 virtual_device_factory_->mutable_state()->registrations.size());
Adam Langleyeeac87e2019-04-13 22:58:227611 const device::VirtualFidoDevice::RegistrationData& registration =
Nina Satragnoacf403f92019-05-23 17:16:527612 virtual_device_factory_->mutable_state()->registrations.begin()->second;
Adam Langleyeeac87e2019-04-13 22:58:227613 EXPECT_TRUE(registration.is_resident);
7614 ASSERT_TRUE(registration.user.has_value());
7615 const auto options = make_credential_options();
Ken Buchanan4a33ef0d2019-06-20 17:34:047616 EXPECT_EQ(options->user.name, registration.user->name);
7617 EXPECT_EQ(options->user.display_name, registration.user->display_name);
7618 EXPECT_EQ(options->user.id, registration.user->id);
Adam Langleyeeac87e2019-04-13 22:58:227619 }
Adam Langleyb307ff62019-03-27 23:36:337620}
7621
Martin Kreichgauer2e9d8072020-08-31 15:20:477622TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferred) {
7623 for (const bool supports_rk : {false, true}) {
7624 SCOPED_TRACE(::testing::Message() << "supports_rk=" << supports_rk);
7625 ResetVirtualDevice();
Martin Kreichgauer2e9d8072020-08-31 15:20:477626
7627 device::VirtualCtap2Device::Config config;
7628 config.internal_uv_support = true;
7629 config.resident_key_support = supports_rk;
7630 virtual_device_factory_->SetCtap2Config(config);
7631 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7632
7633 MakeCredentialResult result = AuthenticatorMakeCredential(
7634 make_credential_options(device::ResidentKeyRequirement::kPreferred));
7635
7636 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Martin Kreichgauer2e9d8072020-08-31 15:20:477637 EXPECT_TRUE(HasUV(result.response));
7638 ASSERT_EQ(1u,
7639 virtual_device_factory_->mutable_state()->registrations.size());
7640 const device::VirtualFidoDevice::RegistrationData& registration =
7641 virtual_device_factory_->mutable_state()->registrations.begin()->second;
7642 EXPECT_EQ(registration.is_resident, supports_rk);
7643 }
7644}
7645
7646TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferredStorageFull) {
7647 // Making a credential on an authenticator with full storage falls back to
7648 // making a non-resident key.
Martin Kreichgauer08be2d72020-10-27 04:26:357649 for (bool is_ctap_2_1 : {false, true}) {
7650 ResetVirtualDevice();
Martin Kreichgauer2e9d8072020-08-31 15:20:477651
Martin Kreichgauer08be2d72020-10-27 04:26:357652 size_t num_taps = 0;
7653 virtual_device_factory_->mutable_state()->simulate_press_callback =
7654 base::BindLambdaForTesting(
7655 [&num_taps](device::VirtualFidoDevice* device) {
7656 num_taps++;
7657 return true;
7658 });
Martin Kreichgauer2e9d8072020-08-31 15:20:477659
Martin Kreichgauer08be2d72020-10-27 04:26:357660 device::VirtualCtap2Device::Config config;
7661 if (is_ctap_2_1) {
7662 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
7663 std::end(device::kCtap2Versions2_1)};
7664 }
Martin Kreichgauer2e9d8072020-08-31 15:20:477665
Martin Kreichgauer08be2d72020-10-27 04:26:357666 config.internal_uv_support = true;
7667 config.resident_key_support = true;
7668 config.resident_credential_storage = 0;
7669 virtual_device_factory_->SetCtap2Config(config);
7670 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7671
7672 MakeCredentialResult result = AuthenticatorMakeCredential(
7673 make_credential_options(device::ResidentKeyRequirement::kPreferred));
7674
7675 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Martin Kreichgauer08be2d72020-10-27 04:26:357676 EXPECT_TRUE(HasUV(result.response));
7677 ASSERT_EQ(1u,
7678 virtual_device_factory_->mutable_state()->registrations.size());
7679 const device::VirtualFidoDevice::RegistrationData& registration =
7680 virtual_device_factory_->mutable_state()->registrations.begin()->second;
7681 EXPECT_EQ(registration.is_resident, false);
7682 // In CTAP 2.0, the first request with rk=false fails due to exhausted
7683 // storage and then needs to be retried with rk=false, requiring a second
7684 // tap. In 2.1 remaining storage capacity can be checked up front such that
7685 // the request is sent with rk=false right away.
7686 EXPECT_EQ(num_taps, is_ctap_2_1 ? 1u : 2u);
7687 }
Martin Kreichgauer2e9d8072020-08-31 15:20:477688}
7689
Nina Satragno094764772024-11-26 23:35:437690TEST_F(ResidentKeyAuthenticatorImplTest,
7691 MakeCredentialRkPreferredStorageFull_LargeBlob) {
7692 device::VirtualCtap2Device::Config config;
7693 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
7694 std::end(device::kCtap2Versions2_1)};
7695 config.internal_uv_support = true;
7696 config.resident_key_support = true;
7697 config.resident_credential_storage = 0;
7698 config.large_blob_support = true;
7699 config.pin_uv_auth_token_support = true;
7700 virtual_device_factory_->SetCtap2Config(config);
7701 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
7702 {
7703 PublicKeyCredentialCreationOptionsPtr options =
7704 make_credential_options(device::ResidentKeyRequirement::kPreferred);
7705 options->large_blob_enable = device::LargeBlobSupport::kRequired;
7706 MakeCredentialResult result =
7707 AuthenticatorMakeCredential(std::move(options));
7708 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
7709 }
7710 {
7711 PublicKeyCredentialCreationOptionsPtr options =
7712 make_credential_options(device::ResidentKeyRequirement::kPreferred);
7713 options->large_blob_enable = device::LargeBlobSupport::kPreferred;
7714 MakeCredentialResult result =
7715 AuthenticatorMakeCredential(std::move(options));
7716 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7717 EXPECT_TRUE(result.response->echo_large_blob);
7718 EXPECT_FALSE(result.response->supports_large_blob);
7719 EXPECT_EQ(1u,
7720 virtual_device_factory_->mutable_state()->registrations.size());
7721 const device::VirtualFidoDevice::RegistrationData& registration =
7722 virtual_device_factory_->mutable_state()->registrations.begin()->second;
7723 EXPECT_FALSE(registration.is_resident);
7724 EXPECT_FALSE(registration.large_blob);
7725 EXPECT_FALSE(registration.large_blob_key);
7726 }
7727}
7728
Martin Kreichgauer2e9d8072020-08-31 15:20:477729TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferredSetsPIN) {
7730 device::VirtualCtap2Device::Config config;
7731 config.pin_support = true;
7732 config.internal_uv_support = false;
7733 config.resident_key_support = true;
7734 virtual_device_factory_->SetCtap2Config(config);
7735 virtual_device_factory_->mutable_state()->pin = "";
7736
7737 MakeCredentialResult result = AuthenticatorMakeCredential(
7738 make_credential_options(device::ResidentKeyRequirement::kPreferred));
7739
7740 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Martin Kreichgauer2e9d8072020-08-31 15:20:477741 EXPECT_TRUE(HasUV(result.response));
7742 ASSERT_EQ(1u, virtual_device_factory_->mutable_state()->registrations.size());
7743 const device::VirtualFidoDevice::RegistrationData& registration =
7744 virtual_device_factory_->mutable_state()->registrations.begin()->second;
7745 EXPECT_EQ(registration.is_resident, true);
7746 EXPECT_EQ(virtual_device_factory_->mutable_state()->pin, kTestPIN);
7747}
7748
Adam Langley77fab612019-05-03 05:19:047749TEST_F(ResidentKeyAuthenticatorImplTest, StorageFull) {
Adam Langley77fab612019-05-03 05:19:047750 device::VirtualCtap2Device::Config config;
7751 config.resident_key_support = true;
7752 config.internal_uv_support = true;
7753 config.resident_credential_storage = 1;
Nina Satragnoacf403f92019-05-23 17:16:527754 virtual_device_factory_->SetCtap2Config(config);
7755 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
Adam Langley77fab612019-05-03 05:19:047756
7757 // Add a resident key to fill the authenticator.
Nina Satragnoacf403f92019-05-23 17:16:527758 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Adam Langley77fab612019-05-03 05:19:047759 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
7760 /*user_id=*/{{1, 1, 1, 1}}, "[email protected]", "Test User"));
7761
Martin Kreichgauerb3689d062022-07-12 09:36:517762 test_client_.delegate_config.expected_failure_reason =
7763 AuthenticatorRequestClientDelegate::InterestingFailureReason::
7764 kStorageFull;
Martin Kreichgauer55834402020-08-03 21:54:317765 EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status,
7766 AuthenticatorStatus::NOT_ALLOWED_ERROR);
Ken Buchanan23dce912024-07-11 16:41:277767 VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kStorageFull,
Ken Buchanan1ea549c12024-10-10 21:31:057768 AuthenticationRequestMode::kModalWebAuthn);
Adam Langley77fab612019-05-03 05:19:047769}
Adam Langleyaa789952019-05-01 19:20:277770
Nina Satragnobc2c91d12024-06-17 22:55:287771TEST_F(ResidentKeyAuthenticatorImplTest,
7772 MakeCredentialEmptyFields_SecurityKey) {
7773 VirtualCtap2Device::Config config;
7774 config.pin_support = true;
7775 config.resident_key_support = true;
7776 config.reject_empty_display_name = true;
7777 virtual_device_factory_->SetCtap2Config(std::move(config));
7778 virtual_device_factory_->SetTransport(
7779 device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
7780
Adam Langley9a979152022-09-02 22:52:267781 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
Nina Satragnobc2c91d12024-06-17 22:55:287782
Adam Langley9a979152022-09-02 22:52:267783 // This value is perfectly legal, but our VirtualCtap2Device simulates
7784 // some security keys in rejecting empty values. CBOR serialisation should
7785 // omit these values rather than send empty ones.
7786 options->user.display_name = "";
7787
Nina Satragnobc2c91d12024-06-17 22:55:287788 EXPECT_EQ(AuthenticatorStatus::SUCCESS,
7789 AuthenticatorMakeCredential(std::move(options)).status);
7790}
Adam Langley9a979152022-09-02 22:52:267791
Nina Satragnobc2c91d12024-06-17 22:55:287792// Regression test for crbug.com/346835891.
7793TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialEmptyFields_Phone) {
7794 // iPhones reject a request with a missing display name.
7795 VirtualCtap2Device::Config config;
7796 config.pin_support = true;
7797 config.resident_key_support = true;
7798 config.reject_missing_display_name = true;
7799 virtual_device_factory_->SetCtap2Config(std::move(config));
7800 virtual_device_factory_->SetTransport(device::FidoTransportProtocol::kHybrid);
7801
7802 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
7803 options->user.display_name = "";
7804
7805 EXPECT_EQ(AuthenticatorStatus::SUCCESS,
7806 AuthenticatorMakeCredential(std::move(options)).status);
Adam Langley9a979152022-09-02 22:52:267807}
7808
Adam Langley10a207e692019-08-22 01:38:237809TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionSingleNoPII) {
Nina Satragnoacf403f92019-05-23 17:16:527810 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Martin Kreichgauer0fabc5d2019-04-24 19:00:567811 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:377812 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langleyb307ff62019-03-27 23:36:337813
Adam Langley10a207e692019-08-22 01:38:237814 // |SelectAccount| should not be called when there's only a single response
7815 // with no identifying user info because the UI is bad in that case: we can
7816 // only display the single choice of "Unknown user".
Martin Kreichgauerb3689d062022-07-12 09:36:517817 test_client_.delegate_config.expected_accounts = "<invalid>";
Martin Kreichgauer55834402020-08-03 21:54:317818 GetAssertionResult result =
7819 AuthenticatorGetAssertion(get_credential_options());
7820
7821 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7822 EXPECT_TRUE(HasUV(result.response));
Adam Langleyb307ff62019-03-27 23:36:337823}
7824
Adam Langley8abb4722022-02-01 02:06:487825TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionUserSelected) {
7826 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
7827 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
7828 /*user_id=*/{{1, 2, 3, 4}}, "Test", "User"));
7829
7830 for (const bool internal_account_chooser : {false, true}) {
7831 SCOPED_TRACE(internal_account_chooser);
7832
7833 device::VirtualCtap2Device::Config config;
7834 config.pin_support = true;
7835 config.resident_key_support = true;
7836 config.internal_account_chooser = internal_account_chooser;
7837 virtual_device_factory_->SetCtap2Config(config);
7838
7839 // |SelectAccount| should not be called when userSelected is set.
7840 if (internal_account_chooser) {
Martin Kreichgauerb3689d062022-07-12 09:36:517841 test_client_.delegate_config.expected_accounts = "<invalid>";
Adam Langley8abb4722022-02-01 02:06:487842 } else {
Martin Kreichgauerb3689d062022-07-12 09:36:517843 test_client_.delegate_config.expected_accounts = "01020304:Test:User";
7844 test_client_.delegate_config.selected_user_id = {1, 2, 3, 4};
Adam Langley8abb4722022-02-01 02:06:487845 }
7846 GetAssertionResult result =
7847 AuthenticatorGetAssertion(get_credential_options());
7848
7849 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7850 EXPECT_TRUE(HasUV(result.response));
7851 }
7852}
7853
Adam Langley10a207e692019-08-22 01:38:237854TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionSingleWithPII) {
7855 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
7856 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:377857 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, "Test User"));
Adam Langley10a207e692019-08-22 01:38:237858
Adam Langley10a207e692019-08-22 01:38:237859 // |SelectAccount| should be called when PII is available.
Martin Kreichgauerb3689d062022-07-12 09:36:517860 test_client_.delegate_config.expected_accounts = "01020304::Test User";
7861 test_client_.delegate_config.selected_user_id = {1, 2, 3, 4};
Martin Kreichgauer55834402020-08-03 21:54:317862 GetAssertionResult result =
7863 AuthenticatorGetAssertion(get_credential_options());
7864 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7865 EXPECT_TRUE(HasUV(result.response));
Adam Langley10a207e692019-08-22 01:38:237866}
7867
Adam Langleyb307ff62019-03-27 23:36:337868TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionMulti) {
Nina Satragnoacf403f92019-05-23 17:16:527869 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Martin Kreichgauer0fabc5d2019-04-24 19:00:567870 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
7871 /*user_id=*/{{1, 2, 3, 4}}, "[email protected]", "Test User"));
Nina Satragnoacf403f92019-05-23 17:16:527872 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Martin Kreichgauer0fabc5d2019-04-24 19:00:567873 /*credential_id=*/{{4, 3, 2, 2}}, kTestRelyingPartyId,
7874 /*user_id=*/{{5, 6, 7, 8}}, "[email protected]", "Test User 2"));
Adam Langleyb307ff62019-03-27 23:36:337875
Martin Kreichgauerb3689d062022-07-12 09:36:517876 test_client_.delegate_config.expected_accounts =
Adam Langleyb307ff62019-03-27 23:36:337877 "01020304:[email protected]:Test User/"
7878 "05060708:[email protected]:Test User 2";
Martin Kreichgauerb3689d062022-07-12 09:36:517879 test_client_.delegate_config.selected_user_id = {1, 2, 3, 4};
Martin Kreichgauer55834402020-08-03 21:54:317880
7881 GetAssertionResult result =
7882 AuthenticatorGetAssertion(get_credential_options());
7883
7884 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
7885 EXPECT_TRUE(HasUV(result.response));
Adam Langleyb307ff62019-03-27 23:36:337886}
7887
Adam Langley3c77c502019-05-23 08:36:347888TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionUVDiscouraged) {
7889 device::VirtualCtap2Device::Config config;
7890 config.resident_key_support = true;
7891 config.internal_uv_support = true;
7892 config.u2f_support = true;
Nina Satragnoacf403f92019-05-23 17:16:527893 virtual_device_factory_->SetCtap2Config(config);
7894 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
Adam Langley3c77c502019-05-23 08:36:347895
Nina Satragnoacf403f92019-05-23 17:16:527896 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Adam Langley3c77c502019-05-23 08:36:347897 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:377898 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langley3c77c502019-05-23 08:36:347899
Adam Langley10a207e692019-08-22 01:38:237900 // |SelectAccount| should not be called when there's only a single response
7901 // without identifying information.
Martin Kreichgauerb3689d062022-07-12 09:36:517902 test_client_.delegate_config.expected_accounts = "<invalid>";
Adam Langley3c77c502019-05-23 08:36:347903 PublicKeyCredentialRequestOptionsPtr options(get_credential_options());
7904 options->user_verification =
Ken Buchanan4a33ef0d2019-06-20 17:34:047905 device::UserVerificationRequirement::kDiscouraged;
Martin Kreichgauer55834402020-08-03 21:54:317906
7907 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
7908
7909 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Adam Langley3c77c502019-05-23 08:36:347910 // The UV=discouraged should have been ignored for a resident-credential
7911 // request.
Martin Kreichgauer55834402020-08-03 21:54:317912 EXPECT_TRUE(HasUV(result.response));
Adam Langley3c77c502019-05-23 08:36:347913}
7914
Nina Satragno5ba78462020-10-02 17:25:157915static const char* BlobSupportDescription(device::LargeBlobSupport support) {
7916 switch (support) {
7917 case device::LargeBlobSupport::kNotRequested:
7918 return "Blob not requested";
7919 case device::LargeBlobSupport::kPreferred:
7920 return "Blob preferred";
7921 case device::LargeBlobSupport::kRequired:
7922 return "Blob required";
7923 }
7924}
7925
7926TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialLargeBlob) {
Adam Langleya254f0b52023-02-14 03:38:037927 constexpr auto BlobRequired = device::LargeBlobSupport::kRequired;
7928 constexpr auto BlobPreferred = device::LargeBlobSupport::kPreferred;
7929 constexpr auto BlobNotRequested = device::LargeBlobSupport::kNotRequested;
Arthur Sonzognic686e8f2024-01-11 08:36:377930 constexpr auto nullopt = std::nullopt;
Nina Satragno5ba78462020-10-02 17:25:157931
7932 constexpr struct {
Adam Langleya254f0b52023-02-14 03:38:037933 bool large_blob_extension;
Arthur Sonzognic686e8f2024-01-11 08:36:377934 std::optional<bool> large_blob_support;
Nina Satragno5ba78462020-10-02 17:25:157935 bool rk_required;
7936 device::LargeBlobSupport large_blob_enable;
7937 bool request_success;
7938 bool did_create_large_blob;
7939 } kLargeBlobTestCases[] = {
7940 // clang-format off
Adam Langleya254f0b52023-02-14 03:38:037941 // ext, support, rk, enabled, success, did create
7942 { false, true, true, BlobRequired, true, true},
7943 { false, true, true, BlobPreferred, true, true},
7944 { false, true, true, BlobNotRequested, true, false},
7945 { false, true, false, BlobRequired, false, false},
7946 { false, true, false, BlobPreferred, true, false},
7947 { false, true, true, BlobNotRequested, true, false},
7948 { false, false, true, BlobRequired, false, false},
7949 { false, false, true, BlobPreferred, true, false},
7950 { false, true, true, BlobNotRequested, true, false},
7951
7952 { true, true, true, BlobRequired, true, true},
7953 { true, true, true, BlobPreferred, true, true},
7954 { true, true, true, BlobNotRequested, true, false},
7955 { true, true, false, BlobRequired, false, false},
7956 { true, true, false, BlobPreferred, true, false},
7957 { true, true, true, BlobNotRequested, true, false},
7958 { true, nullopt, true, BlobRequired, false, false},
7959 { true, nullopt, true, BlobPreferred, true, false},
7960 { true, true, true, BlobNotRequested, true, false},
7961 { true, false, true, BlobPreferred, true, false},
7962 { true, false, true, BlobRequired, false, false},
Nina Satragno5ba78462020-10-02 17:25:157963 // clang-format on
7964 };
7965 for (auto& test : kLargeBlobTestCases) {
Adam Langleya254f0b52023-02-14 03:38:037966 if (test.large_blob_support) {
7967 SCOPED_TRACE(::testing::Message()
7968 << "support=" << *test.large_blob_support);
7969 } else {
7970 SCOPED_TRACE(::testing::Message() << "support={}");
7971 }
Nina Satragno5ba78462020-10-02 17:25:157972 SCOPED_TRACE(::testing::Message() << "rk_required=" << test.rk_required);
7973 SCOPED_TRACE(::testing::Message()
7974 << "enabled="
7975 << BlobSupportDescription(test.large_blob_enable));
7976 SCOPED_TRACE(::testing::Message() << "success=" << test.request_success);
7977 SCOPED_TRACE(::testing::Message()
7978 << "did create=" << test.did_create_large_blob);
Adam Langleya254f0b52023-02-14 03:38:037979 SCOPED_TRACE(::testing::Message()
7980 << "large_blob_extension=" << test.large_blob_extension);
Nina Satragno5ba78462020-10-02 17:25:157981
7982 device::VirtualCtap2Device::Config config;
7983 config.pin_support = true;
Nina Satragno9de1788d2020-10-08 21:00:517984 config.pin_uv_auth_token_support = true;
Nina Satragno5ba78462020-10-02 17:25:157985 config.resident_key_support = true;
Nina Satragno9de1788d2020-10-08 21:00:517986 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
7987 std::end(device::kCtap2Versions2_1)};
Adam Langleya254f0b52023-02-14 03:38:037988 if (test.large_blob_extension) {
7989 config.large_blob_extension_support = test.large_blob_support;
7990 } else {
7991 config.large_blob_support = *test.large_blob_support;
7992 }
Nina Satragno5ba78462020-10-02 17:25:157993 virtual_device_factory_->SetCtap2Config(config);
7994
7995 PublicKeyCredentialCreationOptionsPtr options = make_credential_options(
7996 test.rk_required ? device::ResidentKeyRequirement::kRequired
7997 : device::ResidentKeyRequirement::kDiscouraged);
7998 options->large_blob_enable = test.large_blob_enable;
7999 MakeCredentialResult result =
8000 AuthenticatorMakeCredential(std::move(options));
8001
8002 if (test.request_success) {
8003 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
8004 ASSERT_EQ(1u,
8005 virtual_device_factory_->mutable_state()->registrations.size());
8006 const device::VirtualFidoDevice::RegistrationData& registration =
8007 virtual_device_factory_->mutable_state()
8008 ->registrations.begin()
8009 ->second;
Adam Langleya254f0b52023-02-14 03:38:038010 EXPECT_EQ(test.did_create_large_blob && !test.large_blob_extension,
Nina Satragno5ba78462020-10-02 17:25:158011 registration.large_blob_key.has_value());
8012 EXPECT_EQ(test.large_blob_enable != BlobNotRequested,
8013 result.response->echo_large_blob);
8014 EXPECT_EQ(test.did_create_large_blob,
8015 result.response->supports_large_blob);
8016 } else {
8017 ASSERT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status);
8018 ASSERT_EQ(0u,
8019 virtual_device_factory_->mutable_state()->registrations.size());
8020 }
8021 virtual_device_factory_->mutable_state()->registrations.clear();
Nina Satragno9de1788d2020-10-08 21:00:518022 virtual_device_factory_->mutable_state()->ClearLargeBlobs();
Nina Satragno5ba78462020-10-02 17:25:158023 }
8024}
8025
Nina Satragno2bf834342020-10-06 22:05:508026TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionLargeBlobRead) {
8027 constexpr struct {
8028 bool large_blob_support;
8029 bool large_blob_set;
8030 bool large_blob_key_set;
8031 bool did_read_large_blob;
8032 } kLargeBlobTestCases[] = {
8033 // clang-format off
8034 // support, set, key_set, did_read
8035 { true, true, true, true },
8036 { true, false, false, false },
8037 { true, false, true, false },
8038 { false, false, false, false },
8039 // clang-format on
8040 };
8041 for (auto& test : kLargeBlobTestCases) {
8042 SCOPED_TRACE(::testing::Message() << "support=" << test.large_blob_support);
8043 SCOPED_TRACE(::testing::Message() << "set=" << test.large_blob_set);
8044 SCOPED_TRACE(::testing::Message() << "key_set=" << test.large_blob_key_set);
8045 SCOPED_TRACE(::testing::Message()
8046 << "did_read=" << test.did_read_large_blob);
8047
8048 const std::vector<uint8_t> large_blob = {'b', 'l', 'o', 'b'};
8049 device::VirtualCtap2Device::Config config;
8050 config.pin_support = true;
Nina Satragno9de1788d2020-10-08 21:00:518051 config.pin_uv_auth_token_support = true;
Nina Satragno2bf834342020-10-06 22:05:508052 config.resident_key_support = true;
Nina Satragno9de1788d2020-10-08 21:00:518053 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
8054 std::end(device::kCtap2Versions2_1)};
Nina Satragno2bf834342020-10-06 22:05:508055 config.large_blob_support = test.large_blob_support;
8056 virtual_device_factory_->SetCtap2Config(config);
8057 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
8058 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:378059 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Nina Satragno2bf834342020-10-06 22:05:508060
8061 if (test.large_blob_set) {
8062 virtual_device_factory_->mutable_state()->InjectLargeBlob(
8063 &virtual_device_factory_->mutable_state()
8064 ->registrations.begin()
8065 ->second,
Nina Satragnoaed99fb2020-10-15 22:21:568066 CompressLargeBlob(large_blob));
Nina Satragno2bf834342020-10-06 22:05:508067 } else if (test.large_blob_key_set) {
8068 virtual_device_factory_->mutable_state()
8069 ->registrations.begin()
8070 ->second.large_blob_key = {{0}};
8071 }
8072
8073 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
Slobodan Pejicc8ce88b2023-06-19 15:41:128074 options->extensions->large_blob_read = true;
Nina Satragno2bf834342020-10-06 22:05:508075 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8076
8077 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258078 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8079 EXPECT_FALSE(result.response->extensions->echo_large_blob_written);
Nina Satragno2bf834342020-10-06 22:05:508080 if (test.did_read_large_blob) {
Slobodan Pejica0f2b9d2023-09-28 01:56:258081 EXPECT_EQ(large_blob, *result.response->extensions->large_blob);
Nina Satragno2bf834342020-10-06 22:05:508082 } else {
Slobodan Pejica0f2b9d2023-09-28 01:56:258083 EXPECT_FALSE(result.response->extensions->large_blob.has_value());
Nina Satragno2bf834342020-10-06 22:05:508084 }
8085 virtual_device_factory_->mutable_state()->registrations.clear();
Nina Satragno9de1788d2020-10-08 21:00:518086 virtual_device_factory_->mutable_state()->ClearLargeBlobs();
8087 }
8088}
8089
8090TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionLargeBlobWrite) {
8091 constexpr struct {
8092 bool large_blob_support;
8093 bool large_blob_set;
8094 bool large_blob_key_set;
8095 bool did_write_large_blob;
8096 } kLargeBlobTestCases[] = {
8097 // clang-format off
8098 // support, set, key_set, did_write
8099 { true, true, true, true },
8100 { true, false, false, false },
8101 { true, false, true, true },
8102 { false, false, false, false },
8103 // clang-format on
8104 };
8105 for (auto& test : kLargeBlobTestCases) {
8106 SCOPED_TRACE(::testing::Message() << "support=" << test.large_blob_support);
8107 SCOPED_TRACE(::testing::Message() << "set=" << test.large_blob_set);
8108 SCOPED_TRACE(::testing::Message() << "key_set=" << test.large_blob_key_set);
8109 SCOPED_TRACE(::testing::Message()
8110 << "did_write=" << test.did_write_large_blob);
8111
8112 const std::vector<uint8_t> large_blob = {'b', 'l', 'o', 'b'};
8113 device::VirtualCtap2Device::Config config;
8114 config.pin_support = true;
8115 config.pin_uv_auth_token_support = true;
8116 config.resident_key_support = true;
8117 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
8118 std::end(device::kCtap2Versions2_1)};
8119 config.large_blob_support = test.large_blob_support;
8120 virtual_device_factory_->SetCtap2Config(config);
8121 const std::vector<uint8_t> cred_id = {4, 3, 2, 1};
8122 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
8123 cred_id, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:378124 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Nina Satragno9de1788d2020-10-08 21:00:518125
8126 if (test.large_blob_set) {
8127 virtual_device_factory_->mutable_state()->InjectLargeBlob(
8128 &virtual_device_factory_->mutable_state()
8129 ->registrations.begin()
8130 ->second,
Nina Satragnoaed99fb2020-10-15 22:21:568131 CompressLargeBlob(large_blob));
Nina Satragno9de1788d2020-10-08 21:00:518132 } else if (test.large_blob_key_set) {
8133 virtual_device_factory_->mutable_state()
8134 ->registrations.begin()
8135 ->second.large_blob_key = {{0}};
8136 }
8137
8138 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8139 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8140 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128141 options->extensions->large_blob_write = large_blob;
Nina Satragno9de1788d2020-10-08 21:00:518142 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8143
8144 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258145 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8146 EXPECT_FALSE(result.response->extensions->large_blob.has_value());
8147 EXPECT_TRUE(result.response->extensions->echo_large_blob_written);
8148 EXPECT_EQ(test.did_write_large_blob,
8149 result.response->extensions->large_blob_written);
Nina Satragno9de1788d2020-10-08 21:00:518150 if (test.did_write_large_blob) {
Arthur Sonzognic686e8f2024-01-11 08:36:378151 std::optional<device::LargeBlob> compressed_blob =
Nina Satragnoaed99fb2020-10-15 22:21:568152 virtual_device_factory_->mutable_state()->GetLargeBlob(
8153 virtual_device_factory_->mutable_state()
8154 ->registrations.begin()
8155 ->second);
8156 EXPECT_EQ(large_blob, UncompressLargeBlob(*compressed_blob));
Nina Satragno9de1788d2020-10-08 21:00:518157 }
8158 virtual_device_factory_->mutable_state()->registrations.clear();
8159 virtual_device_factory_->mutable_state()->ClearLargeBlobs();
Nina Satragno2bf834342020-10-06 22:05:508160 }
8161}
8162
Adam Langleya254f0b52023-02-14 03:38:038163TEST_F(ResidentKeyAuthenticatorImplTest,
8164 GetAssertionLargeBlobExtensionNoSupport) {
8165 device::VirtualCtap2Device::Config config;
8166 config.pin_support = true;
8167 config.pin_uv_auth_token_support = true;
8168 config.resident_key_support = true;
8169 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
8170 std::end(device::kCtap2Versions2_1)};
8171 virtual_device_factory_->SetCtap2Config(config);
8172
8173 const std::vector<uint8_t> cred_id = {4, 3, 2, 1};
8174 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
8175 cred_id, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:378176 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langleya254f0b52023-02-14 03:38:038177
8178 // Try to read a large blob that doesn't exist and couldn't exist because the
8179 // authenticator doesn't support large blobs.
8180 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8181 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8182 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128183 options->extensions->large_blob_read = true;
Adam Langleya254f0b52023-02-14 03:38:038184 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8185 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258186 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8187 EXPECT_FALSE(result.response->extensions->echo_large_blob_written);
8188 ASSERT_FALSE(result.response->extensions->large_blob);
Adam Langleya254f0b52023-02-14 03:38:038189}
8190
8191TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionLargeBlobExtension) {
8192 device::VirtualCtap2Device::Config config;
8193 config.pin_support = true;
8194 config.pin_uv_auth_token_support = true;
8195 config.resident_key_support = true;
8196 config.large_blob_extension_support = true;
8197 config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
8198 std::end(device::kCtap2Versions2_1)};
8199 virtual_device_factory_->SetCtap2Config(config);
8200
8201 const std::vector<uint8_t> large_blob = {'b', 'l', 'o', 'b'};
8202 const std::vector<uint8_t> cred_id = {4, 3, 2, 1};
8203 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
8204 cred_id, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:378205 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langleya254f0b52023-02-14 03:38:038206
8207 {
8208 // Try to read a large blob that doesn't exist.
8209 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8210 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8211 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128212 options->extensions->large_blob_read = true;
Adam Langleya254f0b52023-02-14 03:38:038213 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8214 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258215 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8216 EXPECT_FALSE(result.response->extensions->echo_large_blob_written);
8217 ASSERT_FALSE(result.response->extensions->large_blob);
Adam Langleya254f0b52023-02-14 03:38:038218 }
8219
8220 {
8221 // Write a large blob.
8222 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8223 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8224 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128225 options->extensions->large_blob_write = large_blob;
Adam Langleya254f0b52023-02-14 03:38:038226 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8227 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258228 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8229 EXPECT_TRUE(result.response->extensions->echo_large_blob_written);
8230 EXPECT_FALSE(result.response->extensions->large_blob);
Adam Langleya254f0b52023-02-14 03:38:038231 }
8232
8233 {
8234 // Read it back.
8235 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8236 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8237 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128238 options->extensions->large_blob_read = true;
Adam Langleya254f0b52023-02-14 03:38:038239 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8240 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258241 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8242 EXPECT_FALSE(result.response->extensions->echo_large_blob_written);
8243 ASSERT_TRUE(result.response->extensions->large_blob);
8244 EXPECT_EQ(large_blob, *result.response->extensions->large_blob);
Adam Langleya254f0b52023-02-14 03:38:038245 }
8246
8247 // Corrupt the large blob data and attempt to read it back. The invalid
8248 // large blob should be ignored.
8249 virtual_device_factory_->mutable_state()
8250 ->registrations.begin()
8251 ->second.large_blob->compressed_data = {1, 2, 3, 4};
8252
8253 {
8254 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8255 options->allow_credentials = {device::PublicKeyCredentialDescriptor(
8256 device::CredentialType::kPublicKey, cred_id)};
Slobodan Pejicc8ce88b2023-06-19 15:41:128257 options->extensions->large_blob_read = true;
Adam Langleya254f0b52023-02-14 03:38:038258 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8259 ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Slobodan Pejica0f2b9d2023-09-28 01:56:258260 EXPECT_TRUE(result.response->extensions->echo_large_blob);
8261 EXPECT_FALSE(result.response->extensions->echo_large_blob_written);
8262 ASSERT_FALSE(result.response->extensions->large_blob);
Adam Langleya254f0b52023-02-14 03:38:038263 }
8264}
8265
Adam Langleyaa789952019-05-01 19:20:278266static const char* ProtectionPolicyDescription(
8267 blink::mojom::ProtectionPolicy p) {
8268 switch (p) {
8269 case blink::mojom::ProtectionPolicy::UNSPECIFIED:
8270 return "UNSPECIFIED";
8271 case blink::mojom::ProtectionPolicy::NONE:
8272 return "NONE";
8273 case blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED:
8274 return "UV_OR_CRED_ID_REQUIRED";
8275 case blink::mojom::ProtectionPolicy::UV_REQUIRED:
8276 return "UV_REQUIRED";
8277 }
8278}
8279
Adam Langley6f8b030d2020-04-06 20:10:578280static const char* CredProtectDescription(device::CredProtect cred_protect) {
8281 switch (cred_protect) {
8282 case device::CredProtect::kUVOptional:
8283 return "UV optional";
8284 case device::CredProtect::kUVOrCredIDRequired:
8285 return "UV or cred ID required";
8286 case device::CredProtect::kUVRequired:
8287 return "UV required";
8288 }
8289}
8290
Adam Langleyaa789952019-05-01 19:20:278291TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) {
Adam Langleyaa789952019-05-01 19:20:278292 const auto UNSPECIFIED = blink::mojom::ProtectionPolicy::UNSPECIFIED;
8293 const auto NONE = blink::mojom::ProtectionPolicy::NONE;
8294 const auto UV_OR_CRED =
8295 blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED;
8296 const auto UV_REQ = blink::mojom::ProtectionPolicy::UV_REQUIRED;
8297 const int kOk = 0;
8298 const int kNonsense = 1;
8299 const int kNotAllow = 2;
Adam Langleyafd522f2023-01-27 21:12:338300 const device::UserVerificationRequirement kUV =
8301 device::UserVerificationRequirement::kRequired;
8302 const device::UserVerificationRequirement kUP =
8303 device::UserVerificationRequirement::kDiscouraged;
8304 const device::UserVerificationRequirement kUVPref =
8305 device::UserVerificationRequirement::kPreferred;
Adam Langleyaa789952019-05-01 19:20:278306
8307 const struct {
8308 bool supported_by_authenticator;
8309 bool is_resident;
8310 blink::mojom::ProtectionPolicy protection;
8311 bool enforce;
Adam Langleyafd522f2023-01-27 21:12:338312 device::UserVerificationRequirement uv;
Adam Langleyaa789952019-05-01 19:20:278313 int expected_outcome;
8314 blink::mojom::ProtectionPolicy resulting_policy;
8315 } kExpectations[] = {
8316 // clang-format off
8317 // Support | Resdnt | Level | Enf | UV || Result | Prot level
Adam Langleyafd522f2023-01-27 21:12:338318 { false, false, UNSPECIFIED, false, kUP, kOk, NONE},
8319 { false, false, UNSPECIFIED, true, kUP, kNonsense, UNSPECIFIED},
8320 { false, false, UNSPECIFIED, false, kUVPref, kOk, NONE},
8321 { false, false, NONE, false, kUP, kNonsense, UNSPECIFIED},
8322 { false, false, NONE, true, kUP, kNonsense, UNSPECIFIED},
8323 { false, false, UV_OR_CRED, false, kUP, kOk, NONE},
8324 { false, false, UV_OR_CRED, true, kUP, kNotAllow, UNSPECIFIED},
8325 { false, false, UV_OR_CRED, false, kUV, kOk, NONE},
8326 { false, false, UV_OR_CRED, true, kUV, kNotAllow, UNSPECIFIED},
8327 { false, false, UV_REQ, false, kUP, kNonsense, UNSPECIFIED},
8328 { false, false, UV_REQ, false, kUV, kOk, NONE},
8329 { false, false, UV_REQ, true, kUP, kNonsense, UNSPECIFIED},
8330 { false, false, UV_REQ, true, kUV, kNotAllow, UNSPECIFIED},
8331 { false, true, UNSPECIFIED, false, kUP, kOk, NONE},
8332 { false, true, UNSPECIFIED, true, kUP, kNonsense, UNSPECIFIED},
8333 { false, true, NONE, false, kUP, kOk, NONE},
8334 { false, true, NONE, true, kUP, kNonsense, UNSPECIFIED},
8335 { false, true, UV_OR_CRED, false, kUP, kOk, NONE},
8336 { false, true, UV_OR_CRED, true, kUP, kNotAllow, UNSPECIFIED},
8337 { false, true, UV_REQ, false, kUP, kNonsense, UNSPECIFIED},
8338 { false, true, UV_REQ, false, kUV, kOk, NONE},
8339 { false, true, UV_REQ, true, kUP, kNonsense, UNSPECIFIED},
8340 { false, true, UV_REQ, true, kUV, kNotAllow, UNSPECIFIED},
Adam Langleyaa789952019-05-01 19:20:278341
8342 // For the case where the authenticator supports credProtect we do not
8343 // repeat the cases above that are |kNonsense| on the assumption that
8344 // authenticator support is irrelevant. Therefore these are just the non-
8345 // kNonsense cases from the prior block.
Adam Langleyafd522f2023-01-27 21:12:338346 { true, false, UNSPECIFIED, false, kUP, kOk, NONE},
8347 { true, false, UV_OR_CRED, false, kUP, kOk, UV_OR_CRED},
8348 { true, false, UV_OR_CRED, true, kUP, kOk, UV_OR_CRED},
8349 { true, false, UV_OR_CRED, false, kUV, kOk, UV_OR_CRED},
8350 { true, false, UV_OR_CRED, true, kUV, kOk, UV_OR_CRED},
8351 { true, false, UV_REQ, false, kUV, kOk, UV_REQ},
8352 { true, false, UV_REQ, true, kUV, kOk, UV_REQ},
8353 { true, true, UNSPECIFIED, false, kUP, kOk, UV_OR_CRED},
8354 { true, true, UNSPECIFIED, false, kUVPref, kOk, UV_REQ},
8355 { true, true, NONE, false, kUP, kOk, NONE},
8356 { true, true, NONE, false, kUVPref, kOk, NONE},
8357 { true, true, UV_OR_CRED, false, kUP, kOk, UV_OR_CRED},
8358 { true, true, UV_OR_CRED, true, kUP, kOk, UV_OR_CRED},
8359 { true, true, UV_OR_CRED, false, kUVPref, kOk, UV_OR_CRED},
8360 { true, true, UV_REQ, false, kUV, kOk, UV_REQ},
8361 { true, true, UV_REQ, true, kUV, kOk, UV_REQ},
Adam Langleyaa789952019-05-01 19:20:278362 // clang-format on
8363 };
8364
8365 for (const auto& test : kExpectations) {
8366 device::VirtualCtap2Device::Config config;
8367 config.pin_support = true;
8368 config.resident_key_support = true;
8369 config.cred_protect_support = test.supported_by_authenticator;
Nina Satragnoacf403f92019-05-23 17:16:528370 virtual_device_factory_->SetCtap2Config(config);
8371 virtual_device_factory_->mutable_state()->registrations.clear();
Adam Langleyaa789952019-05-01 19:20:278372
Adam Langleyafd522f2023-01-27 21:12:338373 SCOPED_TRACE(::testing::Message() << "uv=" << UVToString(test.uv));
Adam Langleyaa789952019-05-01 19:20:278374 SCOPED_TRACE(::testing::Message() << "enforce=" << test.enforce);
8375 SCOPED_TRACE(::testing::Message()
8376 << "level=" << ProtectionPolicyDescription(test.protection));
8377 SCOPED_TRACE(::testing::Message() << "resident=" << test.is_resident);
8378 SCOPED_TRACE(::testing::Message()
8379 << "support=" << test.supported_by_authenticator);
8380
8381 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:468382 options->authenticator_selection->resident_key =
Martin Kreichgauer2e9d8072020-08-31 15:20:478383 test.is_resident ? device::ResidentKeyRequirement::kRequired
Martin Kreichgauerbece642a2021-12-07 21:02:468384 : device::ResidentKeyRequirement::kDiscouraged;
Adam Langleyaa789952019-05-01 19:20:278385 options->protection_policy = test.protection;
8386 options->enforce_protection_policy = test.enforce;
Adam Langleyafd522f2023-01-27 21:12:338387 options->authenticator_selection->user_verification_requirement = test.uv;
Adam Langleyaa789952019-05-01 19:20:278388
Martin Kreichgauer55834402020-08-03 21:54:318389 AuthenticatorStatus status =
8390 AuthenticatorMakeCredential(std::move(options)).status;
Adam Langleyaa789952019-05-01 19:20:278391
8392 switch (test.expected_outcome) {
8393 case kOk: {
Martin Kreichgauer55834402020-08-03 21:54:318394 EXPECT_EQ(AuthenticatorStatus::SUCCESS, status);
Nina Satragnoacf403f92019-05-23 17:16:528395 ASSERT_EQ(
8396 1u, virtual_device_factory_->mutable_state()->registrations.size());
Adam Langley6f8b030d2020-04-06 20:10:578397 const device::CredProtect result =
Nina Satragnoacf403f92019-05-23 17:16:528398 virtual_device_factory_->mutable_state()
Adam Langleyaa789952019-05-01 19:20:278399 ->registrations.begin()
8400 ->second.protection;
8401
8402 switch (test.resulting_policy) {
8403 case UNSPECIFIED:
Peter Boströmfc7ddc182024-10-31 19:37:218404 NOTREACHED();
Adam Langleyaa789952019-05-01 19:20:278405 case NONE:
Adam Langley6f8b030d2020-04-06 20:10:578406 EXPECT_EQ(device::CredProtect::kUVOptional, result);
Adam Langleyaa789952019-05-01 19:20:278407 break;
8408 case UV_OR_CRED:
Adam Langley6f8b030d2020-04-06 20:10:578409 EXPECT_EQ(device::CredProtect::kUVOrCredIDRequired, result);
Adam Langleyaa789952019-05-01 19:20:278410 break;
8411 case UV_REQ:
Adam Langley6f8b030d2020-04-06 20:10:578412 EXPECT_EQ(device::CredProtect::kUVRequired, result);
Adam Langleyaa789952019-05-01 19:20:278413 break;
8414 }
8415 break;
8416 }
8417 case kNonsense:
Martin Kreichgauer55834402020-08-03 21:54:318418 EXPECT_EQ(AuthenticatorStatus::PROTECTION_POLICY_INCONSISTENT, status);
Adam Langleyaa789952019-05-01 19:20:278419 break;
8420 case kNotAllow:
Martin Kreichgauer55834402020-08-03 21:54:318421 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, status);
Adam Langleyaa789952019-05-01 19:20:278422 break;
8423 default:
Peter Boströmfc7ddc182024-10-31 19:37:218424 NOTREACHED();
Adam Langleyaa789952019-05-01 19:20:278425 }
8426 }
8427}
8428
Adam Langleye6327a52020-01-03 22:36:108429TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) {
8430 // Some authenticators are expected to set the credProtect extension ad
8431 // libitum. Therefore we should only require that the returned extension is at
8432 // least as restrictive as requested, but perhaps not exactly equal.
Adem Derinel620520b42024-11-04 15:45:448433 constexpr std::array<blink::mojom::ProtectionPolicy, 3> kMojoLevels = {
Adam Langleye6327a52020-01-03 22:36:108434 blink::mojom::ProtectionPolicy::NONE,
8435 blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED,
8436 blink::mojom::ProtectionPolicy::UV_REQUIRED,
8437 };
Adem Derinel620520b42024-11-04 15:45:448438 constexpr std::array<device::CredProtect, 3> kDeviceLevels = {
Adam Langley6f8b030d2020-04-06 20:10:578439 device::CredProtect::kUVOptional,
Adam Langleye6327a52020-01-03 22:36:108440 device::CredProtect::kUVOrCredIDRequired,
8441 device::CredProtect::kUVRequired,
8442 };
8443
8444 for (int requested_level = 0; requested_level < 3; requested_level++) {
8445 for (int forced_level = 1; forced_level < 3; forced_level++) {
8446 SCOPED_TRACE(::testing::Message() << "requested=" << requested_level);
8447 SCOPED_TRACE(::testing::Message() << "forced=" << forced_level);
8448 device::VirtualCtap2Device::Config config;
8449 config.pin_support = true;
8450 config.resident_key_support = true;
8451 config.cred_protect_support = true;
8452 config.force_cred_protect = kDeviceLevels[forced_level];
8453 virtual_device_factory_->SetCtap2Config(config);
8454 virtual_device_factory_->mutable_state()->registrations.clear();
8455
8456 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:468457 options->authenticator_selection->resident_key =
8458 device::ResidentKeyRequirement::kRequired;
Adam Langleye6327a52020-01-03 22:36:108459 options->protection_policy = kMojoLevels[requested_level];
Martin Kreichgauerbece642a2021-12-07 21:02:468460 options->authenticator_selection->user_verification_requirement =
8461 device::UserVerificationRequirement::kRequired;
Adam Langleye6327a52020-01-03 22:36:108462
Martin Kreichgauer55834402020-08-03 21:54:318463 AuthenticatorStatus status =
8464 AuthenticatorMakeCredential(std::move(options)).status;
Adam Langleye6327a52020-01-03 22:36:108465
8466 if (requested_level <= forced_level) {
Martin Kreichgauer55834402020-08-03 21:54:318467 EXPECT_EQ(AuthenticatorStatus::SUCCESS, status);
Adam Langleye6327a52020-01-03 22:36:108468 ASSERT_EQ(
8469 1u, virtual_device_factory_->mutable_state()->registrations.size());
Arthur Sonzognic686e8f2024-01-11 08:36:378470 const std::optional<device::CredProtect> result =
Adam Langleye6327a52020-01-03 22:36:108471 virtual_device_factory_->mutable_state()
8472 ->registrations.begin()
8473 ->second.protection;
8474 EXPECT_EQ(*result, config.force_cred_protect);
8475 } else {
Martin Kreichgauer55834402020-08-03 21:54:318476 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, status);
Adam Langleye6327a52020-01-03 22:36:108477 }
8478 }
8479 }
8480}
8481
Adam Langley6f8b030d2020-04-06 20:10:578482TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorDefaultCredProtect) {
8483 // Some authenticators may have a default credProtect level that isn't
8484 // kUVOptional. This has complex interactions that are tested here.
Adam Langley6f8b030d2020-04-06 20:10:578485 constexpr struct {
8486 blink::mojom::ProtectionPolicy requested_level;
8487 device::CredProtect authenticator_default;
8488 device::CredProtect result;
8489 } kExpectations[] = {
8490 // Standard case: normal authenticator and nothing specified. Chrome sets
8491 // a default of kUVOrCredIDRequired for discoverable credentials.
8492 {
8493 blink::mojom::ProtectionPolicy::UNSPECIFIED,
8494 device::CredProtect::kUVOptional,
8495 device::CredProtect::kUVOrCredIDRequired,
8496 },
8497 // Chrome's default of |kUVOrCredIDRequired| should not prevent a site
8498 // from requesting |kUVRequired| from a normal authenticator.
8499 {
8500 blink::mojom::ProtectionPolicy::UV_REQUIRED,
8501 device::CredProtect::kUVOptional,
8502 device::CredProtect::kUVRequired,
8503 },
8504 // Authenticator has a non-standard default, which should work fine.
8505 {
8506 blink::mojom::ProtectionPolicy::UNSPECIFIED,
8507 device::CredProtect::kUVOrCredIDRequired,
8508 device::CredProtect::kUVOrCredIDRequired,
8509 },
8510 // Authenticators can have a default of kUVRequired, but Chrome has a
8511 // default of kUVOrCredIDRequired for discoverable credentials. We should
8512 // not get a lesser protection level because of that.
8513 {
8514 blink::mojom::ProtectionPolicy::UNSPECIFIED,
8515 device::CredProtect::kUVRequired,
8516 device::CredProtect::kUVRequired,
8517 },
8518 // Site should be able to explicitly set credProtect kUVOptional despite
8519 // an authenticator default.
8520 {
8521 blink::mojom::ProtectionPolicy::NONE,
8522 device::CredProtect::kUVOrCredIDRequired,
8523 device::CredProtect::kUVOptional,
8524 },
8525 };
8526
8527 device::VirtualCtap2Device::Config config;
8528 config.pin_support = true;
8529 config.resident_key_support = true;
8530 config.cred_protect_support = true;
8531
8532 for (const auto& test : kExpectations) {
8533 config.default_cred_protect = test.authenticator_default;
8534 virtual_device_factory_->SetCtap2Config(config);
8535 virtual_device_factory_->mutable_state()->registrations.clear();
8536
8537 SCOPED_TRACE(::testing::Message()
8538 << "result=" << CredProtectDescription(test.result));
8539 SCOPED_TRACE(::testing::Message()
8540 << "default="
8541 << CredProtectDescription(test.authenticator_default));
8542 SCOPED_TRACE(::testing::Message()
8543 << "request="
8544 << ProtectionPolicyDescription(test.requested_level));
8545
8546 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
Martin Kreichgauerbece642a2021-12-07 21:02:468547 options->authenticator_selection->resident_key =
8548 device::ResidentKeyRequirement::kRequired;
Adam Langley6f8b030d2020-04-06 20:10:578549 options->protection_policy = test.requested_level;
Martin Kreichgauerbece642a2021-12-07 21:02:468550 options->authenticator_selection->user_verification_requirement =
8551 device::UserVerificationRequirement::kRequired;
Adam Langley6f8b030d2020-04-06 20:10:578552
Martin Kreichgauer55834402020-08-03 21:54:318553 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
8554 AuthenticatorStatus::SUCCESS);
Adam Langley6f8b030d2020-04-06 20:10:578555 ASSERT_EQ(1u,
8556 virtual_device_factory_->mutable_state()->registrations.size());
8557 const device::CredProtect result = virtual_device_factory_->mutable_state()
8558 ->registrations.begin()
8559 ->second.protection;
8560
8561 EXPECT_EQ(result, test.result) << CredProtectDescription(result);
8562 }
8563}
8564
Adam Langleyaa789952019-05-01 19:20:278565TEST_F(ResidentKeyAuthenticatorImplTest, ProtectedNonResidentCreds) {
8566 // Until we have UVToken, there's a danger that we'll preflight UV-required
8567 // credential IDs such that the authenticator denies knowledge of all of them
8568 // for silent requests and then we fail the whole request.
8569 device::VirtualCtap2Device::Config config;
8570 config.pin_support = true;
8571 config.resident_key_support = true;
8572 config.cred_protect_support = true;
Nina Satragnoacf403f92019-05-23 17:16:528573 virtual_device_factory_->SetCtap2Config(config);
8574 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Adam Langleyaa789952019-05-01 19:20:278575 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId));
Nina Satragnoacf403f92019-05-23 17:16:528576 ASSERT_EQ(1u, virtual_device_factory_->mutable_state()->registrations.size());
8577 virtual_device_factory_->mutable_state()
8578 ->registrations.begin()
8579 ->second.protection = device::CredProtect::kUVRequired;
Adam Langleyaa789952019-05-01 19:20:278580
Adam Langleyaa789952019-05-01 19:20:278581 // |SelectAccount| should not be called when there's only a single response.
Martin Kreichgauerb3689d062022-07-12 09:36:518582 test_client_.delegate_config.expected_accounts = "<invalid>";
Adam Langleyaa789952019-05-01 19:20:278583
8584 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
8585 options->allow_credentials = GetTestCredentials(5);
Martin Kreichgauerbece642a2021-12-07 21:02:468586 options->allow_credentials[0].id = {4, 3, 2, 1};
Adam Langleyaa789952019-05-01 19:20:278587
Martin Kreichgauer55834402020-08-03 21:54:318588 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8589
8590 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
8591 EXPECT_TRUE(HasUV(result.response));
Adam Langleyaa789952019-05-01 19:20:278592}
Adam Langleyb307ff62019-03-27 23:36:338593
Adam Langleyaad4c8392019-05-21 04:25:558594TEST_F(ResidentKeyAuthenticatorImplTest, WithAppIDExtension) {
8595 // Setting an AppID value for a resident-key request should be ignored.
8596 device::VirtualCtap2Device::Config config;
8597 config.u2f_support = true;
8598 config.pin_support = true;
8599 config.resident_key_support = true;
8600 config.cred_protect_support = true;
Nina Satragnoacf403f92019-05-23 17:16:528601 virtual_device_factory_->SetCtap2Config(config);
8602 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Adam Langleyaad4c8392019-05-21 04:25:558603 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:378604 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langleyaad4c8392019-05-21 04:25:558605
Adam Langley10a207e692019-08-22 01:38:238606 // |SelectAccount| should not be called when there's only a single response
8607 // without identifying information.
Martin Kreichgauerb3689d062022-07-12 09:36:518608 test_client_.delegate_config.expected_accounts = "<invalid>";
Adam Langleyaad4c8392019-05-21 04:25:558609
8610 PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
Slobodan Pejicc8ce88b2023-06-19 15:41:128611 options->extensions->appid = kTestOrigin1;
Adam Langleyaad4c8392019-05-21 04:25:558612
Martin Kreichgauer55834402020-08-03 21:54:318613 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8614
8615 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
8616 EXPECT_TRUE(HasUV(result.response));
Adam Langleyaad4c8392019-05-21 04:25:558617}
8618
Xiaohan Wang2ba85e32022-01-15 17:19:408619#if BUILDFLAG(IS_WIN)
Martin Kreichgauere5cdcf62019-05-07 22:55:008620// Requests with a credProtect extension that have |enforce_protection_policy|
8621// set should be rejected if the Windows WebAuthn API doesn't support
8622// credProtect.
8623TEST_F(ResidentKeyAuthenticatorImplTest, WinCredProtectApiVersion) {
8624 // The canned response returned by the Windows API fake is for acme.com.
Adam Langley30375442023-06-19 17:20:268625 virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true);
Martin Kreichgauer37ace492021-04-08 23:36:468626 fake_win_webauthn_api_.set_available(true);
Martin Kreichgauere5cdcf62019-05-07 22:55:008627 NavigateAndCommit(GURL("https://acme.com"));
Martin Kreichgauere5cdcf62019-05-07 22:55:008628 for (const bool supports_cred_protect : {false, true}) {
8629 SCOPED_TRACE(testing::Message()
8630 << "supports_cred_protect: " << supports_cred_protect);
8631
Martin Kreichgauer37ace492021-04-08 23:36:468632 fake_win_webauthn_api_.set_version(supports_cred_protect
8633 ? WEBAUTHN_API_VERSION_2
8634 : WEBAUTHN_API_VERSION_1);
Martin Kreichgauere5cdcf62019-05-07 22:55:008635
8636 PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
Ken Buchanan4a33ef0d2019-06-20 17:34:048637 options->relying_party = device::PublicKeyCredentialRpEntity();
8638 options->relying_party.id = device::test_data::kRelyingPartyId;
8639 options->relying_party.name = "";
Martin Kreichgauerbece642a2021-12-07 21:02:468640 options->authenticator_selection->user_verification_requirement =
8641 device::UserVerificationRequirement::kRequired;
8642 options->authenticator_selection->resident_key =
8643 device::ResidentKeyRequirement::kRequired;
Martin Kreichgauere5cdcf62019-05-07 22:55:008644 options->protection_policy =
8645 blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED;
8646 options->enforce_protection_policy = true;
Martin Kreichgauere5cdcf62019-05-07 22:55:008647
Martin Kreichgauer55834402020-08-03 21:54:318648 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
Martin Kreichgauere5cdcf62019-05-07 22:55:008649 supports_cred_protect ? AuthenticatorStatus::SUCCESS
8650 : AuthenticatorStatus::NOT_ALLOWED_ERROR);
8651 }
8652}
Nina Satragno8824f9a2023-02-02 15:54:468653
8654// Tests that the incognito flag is plumbed through conditional UI requests.
8655TEST_F(ResidentKeyAuthenticatorImplTest, ConditionalUI_Incognito) {
Adam Langley30375442023-06-19 17:20:268656 virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true);
Nina Satragno8824f9a2023-02-02 15:54:468657 fake_win_webauthn_api_.set_available(true);
8658 fake_win_webauthn_api_.set_version(WEBAUTHN_API_VERSION_4);
8659 fake_win_webauthn_api_.set_supports_silent_discovery(true);
8660 device::PublicKeyCredentialRpEntity rp(kTestRelyingPartyId);
8661 device::PublicKeyCredentialUserEntity user({1, 2, 3, 4});
8662 fake_win_webauthn_api_.InjectDiscoverableCredential(
Nina Satragnofdc85d822025-02-18 19:17:178663 /*credential_id=*/{{4, 3, 2, 1}}, std::move(rp), std::move(user),
8664 /*provider_name=*/std::nullopt);
Nina Satragno8824f9a2023-02-02 15:54:468665
8666 // |SelectAccount| should not be called for conditional UI requests.
8667 test_client_.delegate_config.expected_accounts = "<invalid>";
8668 test_client_.delegate_config.expect_conditional = true;
8669
8670 for (bool is_off_the_record : {true, false}) {
8671 SCOPED_TRACE(is_off_the_record ? "off the record" : "on the record");
8672 static_cast<TestBrowserContext*>(GetBrowserContext())
8673 ->set_is_off_the_record(is_off_the_record);
8674 PublicKeyCredentialRequestOptionsPtr options(get_credential_options());
Adem Derinel2154d4b12025-02-04 12:29:558675 options->mediation = blink::mojom::Mediation::CONDITIONAL;
Nina Satragno8824f9a2023-02-02 15:54:468676 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
8677 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
8678 ASSERT_TRUE(fake_win_webauthn_api_.last_get_credentials_options());
8679 EXPECT_EQ(fake_win_webauthn_api_.last_get_credentials_options()
8680 ->bBrowserInPrivateMode,
8681 is_off_the_record);
8682 }
8683}
Nina Satragno5b074792023-02-18 00:45:068684
8685// Tests that attempting to make a credential with large blob = required and
8686// attachment = platform on Windows fails and the request is not sent to the
8687// WebAuthn API.
8688// This is because largeBlob = required is ignored by the Windows platform
8689// authenticator at the time of writing (Feb 2023).
8690TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialLargeBlobWinPlatform) {
Nina Satragno0cace2392024-09-12 14:32:398691 virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true);
Nina Satragno5b074792023-02-18 00:45:068692 fake_win_webauthn_api_.set_available(true);
8693 fake_win_webauthn_api_.set_version(WEBAUTHN_API_VERSION_3);
8694 PublicKeyCredentialCreationOptionsPtr options =
8695 GetTestPublicKeyCredentialCreationOptions();
8696 options->large_blob_enable = device::LargeBlobSupport::kRequired;
8697 options->authenticator_selection->resident_key =
8698 device::ResidentKeyRequirement::kRequired;
8699 options->authenticator_selection->authenticator_attachment =
8700 device::AuthenticatorAttachment::kPlatform;
8701 MakeCredentialResult result = AuthenticatorMakeCredential(std::move(options));
8702 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
8703 EXPECT_FALSE(fake_win_webauthn_api_.last_make_credential_options());
8704}
Nina Satragno0cace2392024-09-12 14:32:398705
8706// Tests that attempting to make a credential with large blob = preferred does
8707// not fail the request on Windows.
8708// Regression test for crbug.com/325934997.
8709TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialLargeBlobWinPreferred) {
8710 virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true);
8711 fake_win_webauthn_api_.set_available(true);
8712 fake_win_webauthn_api_.set_version(WEBAUTHN_API_VERSION_3);
8713 for (bool large_blob_supported : {false, true}) {
8714 fake_win_webauthn_api_.set_large_blob_supported(large_blob_supported);
8715 SCOPED_TRACE(large_blob_supported);
8716 PublicKeyCredentialCreationOptionsPtr options =
8717 GetTestPublicKeyCredentialCreationOptions();
8718 options->large_blob_enable = device::LargeBlobSupport::kPreferred;
8719 options->authenticator_selection->resident_key =
8720 device::ResidentKeyRequirement::kRequired;
8721 options->authenticator_selection->authenticator_attachment =
8722 device::AuthenticatorAttachment::kCrossPlatform;
8723 MakeCredentialResult result =
8724 AuthenticatorMakeCredential(std::move(options));
8725 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
8726 EXPECT_TRUE(result.response->echo_large_blob);
8727 EXPECT_EQ(result.response->supports_large_blob, large_blob_supported);
8728 }
8729}
Xiaohan Wang2ba85e32022-01-15 17:19:408730#endif // BUILDFLAG(IS_WIN)
Martin Kreichgauere5cdcf62019-05-07 22:55:008731
Nina Satragno404d51242023-01-19 19:28:268732// Tests that chrome does not attempt setting the PRF extension during a
8733// PinUvAuthToken GetAssertion request if it is not supported by the
8734// authenticator.
8735// Regression test for crbug.com/1408786.
8736TEST_F(ResidentKeyAuthenticatorImplTest, PRFNotSupportedWithPinUvAuthToken) {
8737 NavigateAndCommit(GURL(kTestOrigin1));
8738
8739 device::VirtualCtap2Device::Config config;
8740 config.resident_key_support = true;
8741 config.u2f_support = true;
8742 config.pin_support = true;
8743 config.pin_uv_auth_token_support = true;
8744 config.hmac_secret_support = false;
8745 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
8746 virtual_device_factory_->mutable_state()->pin = kTestPIN;
8747 virtual_device_factory_->mutable_state()->pin_retries =
8748 device::kMaxPinRetries;
8749 virtual_device_factory_->SetCtap2Config(config);
8750
8751 PublicKeyCredentialRequestOptionsPtr options =
8752 GetTestPublicKeyCredentialRequestOptions();
8753 options->user_verification = device::UserVerificationRequirement::kRequired;
8754 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
8755 options->allow_credentials[0].id, options->relying_party_id,
Arthur Sonzognic686e8f2024-01-11 08:36:378756 std::vector<uint8_t>{1, 2, 3, 4}, std::nullopt, std::nullopt));
Nina Satragno404d51242023-01-19 19:28:268757
8758 auto prf_value = blink::mojom::PRFValues::New();
8759 prf_value->first = std::vector<uint8_t>(32, 1);
8760 std::vector<blink::mojom::PRFValuesPtr> inputs;
8761 inputs.emplace_back(std::move(prf_value));
Slobodan Pejicc8ce88b2023-06-19 15:41:128762 options->extensions->prf = true;
8763 options->extensions->prf_inputs = std::move(inputs);
Nina Satragno404d51242023-01-19 19:28:268764 options->allow_credentials.clear();
8765 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
8766 AuthenticatorStatus::SUCCESS);
8767}
8768
Adam Langleyc296f392020-07-16 03:55:248769TEST_F(ResidentKeyAuthenticatorImplTest, PRFExtension) {
8770 NavigateAndCommit(GURL(kTestOrigin1));
8771
Adam Langleyd27d7db2023-02-08 16:45:378772 for (bool use_prf_extension_instead : {false, true}) {
Adam Langley7d1df66672023-09-01 23:23:218773 for (const auto pin_protocol :
8774 {device::PINUVAuthProtocol::kV1, device::PINUVAuthProtocol::kV2}) {
8775 SCOPED_TRACE(use_prf_extension_instead);
8776 SCOPED_TRACE(static_cast<unsigned>(pin_protocol));
Adam Langleyd27d7db2023-02-08 16:45:378777
Arthur Sonzognic686e8f2024-01-11 08:36:378778 std::optional<device::PublicKeyCredentialDescriptor> credential;
Adam Langley7d1df66672023-09-01 23:23:218779 for (bool authenticator_support : {false, true}) {
8780 // Setting the PRF extension on an authenticator that doesn't support it
8781 // should cause the extension to be echoed, but with enabled=false.
8782 // Otherwise, enabled should be true.
8783 device::VirtualCtap2Device::Config config;
8784 if (authenticator_support) {
8785 config.prf_support = use_prf_extension_instead;
8786 config.hmac_secret_support = !use_prf_extension_instead;
Adam Langleyd27d7db2023-02-08 16:45:378787 }
Adam Langley7d1df66672023-09-01 23:23:218788 config.internal_account_chooser = config.prf_support;
8789 config.always_uv = config.prf_support;
8790 config.max_credential_count_in_list = 3;
8791 config.max_credential_id_length = 256;
8792 config.pin_support = true;
8793 config.pin_protocol = pin_protocol;
8794 config.resident_key_support = true;
8795 virtual_device_factory_->SetCtap2Config(config);
8796
8797 PublicKeyCredentialCreationOptionsPtr options =
8798 GetTestPublicKeyCredentialCreationOptions();
8799 options->prf_enable = true;
8800 options->authenticator_selection->resident_key =
8801 authenticator_support
8802 ? device::ResidentKeyRequirement::kRequired
8803 : device::ResidentKeyRequirement::kDiscouraged;
8804 options->user.id = {1, 2, 3, 4};
8805 options->user.name = "name";
8806 options->user.display_name = "displayName";
8807 MakeCredentialResult result =
8808 AuthenticatorMakeCredential(std::move(options));
8809 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
8810
8811 ASSERT_TRUE(result.response->echo_prf);
8812 ASSERT_EQ(result.response->prf, authenticator_support);
8813
8814 if (authenticator_support) {
8815 device::AuthenticatorData auth_data =
8816 AuthDataFromMakeCredentialResponse(result.response);
8817 credential.emplace(device::CredentialType::kPublicKey,
8818 auth_data.GetCredentialId());
8819 }
Adam Langleyd27d7db2023-02-08 16:45:378820 }
Adam Langleyc296f392020-07-16 03:55:248821
Adam Langley7d1df66672023-09-01 23:23:218822 auto assertion = [&](std::vector<blink::mojom::PRFValuesPtr> inputs,
8823 unsigned allow_list_size = 1,
8824 device::UserVerificationRequirement uv =
8825 device::UserVerificationRequirement::kPreferred)
8826 -> blink::mojom::PRFValuesPtr {
8827 PublicKeyCredentialRequestOptionsPtr options =
8828 GetTestPublicKeyCredentialRequestOptions();
8829 options->extensions->prf = true;
8830 options->extensions->prf_inputs = std::move(inputs);
8831 options->allow_credentials.clear();
8832 options->user_verification = uv;
8833 if (allow_list_size >= 1) {
8834 for (unsigned i = 0; i < allow_list_size - 1; i++) {
8835 std::vector<uint8_t> random_credential_id(32,
8836 static_cast<uint8_t>(i));
8837 options->allow_credentials.emplace_back(
8838 device::CredentialType::kPublicKey,
8839 std::move(random_credential_id));
8840 }
8841 options->allow_credentials.push_back(*credential);
8842 }
Adam Langleyc296f392020-07-16 03:55:248843
Adam Langley7d1df66672023-09-01 23:23:218844 GetAssertionResult result =
8845 AuthenticatorGetAssertion(std::move(options));
Adam Langleyc296f392020-07-16 03:55:248846
Adam Langley7d1df66672023-09-01 23:23:218847 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:258848 CHECK(result.response->extensions->prf_results);
8849 CHECK(!result.response->extensions->prf_results->id);
8850 return std::move(result.response->extensions->prf_results);
Adam Langley7d1df66672023-09-01 23:23:218851 };
Adam Langleyc296f392020-07-16 03:55:248852
Adam Langley7d1df66672023-09-01 23:23:218853 const std::vector<uint8_t> salt1(32, 1);
8854 const std::vector<uint8_t> salt2(32, 2);
8855 std::vector<uint8_t> salt1_eval;
8856 std::vector<uint8_t> salt2_eval;
Adam Langleyc296f392020-07-16 03:55:248857
Adam Langley7d1df66672023-09-01 23:23:218858 {
8859 auto prf_value = blink::mojom::PRFValues::New();
8860 prf_value->first = salt1;
8861 std::vector<blink::mojom::PRFValuesPtr> inputs;
8862 inputs.emplace_back(std::move(prf_value));
8863 auto result = assertion(std::move(inputs));
8864 salt1_eval = std::move(result->first);
8865 }
Adam Langleyd5aa72402023-02-01 00:43:308866
Adam Langley7d1df66672023-09-01 23:23:218867 // The result should be consistent
8868 {
8869 auto prf_value = blink::mojom::PRFValues::New();
8870 prf_value->first = salt1;
8871 std::vector<blink::mojom::PRFValuesPtr> inputs;
8872 inputs.emplace_back(std::move(prf_value));
8873 auto result = assertion(std::move(inputs));
8874 ASSERT_EQ(result->first, salt1_eval);
8875 }
Adam Langleyc296f392020-07-16 03:55:248876
Adam Langley7d1df66672023-09-01 23:23:218877 // Security keys will use a different PRF if UV isn't done. But the PRF
8878 // extension should always get the UV PRF so uv=discouraged shouldn't
8879 // change the output.
8880 {
8881 auto prf_value = blink::mojom::PRFValues::New();
8882 prf_value->first = salt1;
8883 std::vector<blink::mojom::PRFValuesPtr> inputs;
8884 inputs.emplace_back(std::move(prf_value));
8885 auto result =
8886 assertion(std::move(inputs), 1,
8887 device::UserVerificationRequirement::kDiscouraged);
8888 ASSERT_EQ(result->first, salt1_eval);
8889 }
Adam Langleyc296f392020-07-16 03:55:248890
Adam Langley7d1df66672023-09-01 23:23:218891 // Should be able to evaluate two points at once.
8892 {
8893 auto prf_value = blink::mojom::PRFValues::New();
8894 prf_value->first = salt1;
8895 prf_value->second = salt2;
8896 std::vector<blink::mojom::PRFValuesPtr> inputs;
8897 inputs.emplace_back(std::move(prf_value));
8898 auto result = assertion(std::move(inputs));
8899 ASSERT_EQ(result->first, salt1_eval);
8900 ASSERT_TRUE(result->second);
8901 salt2_eval = std::move(*result->second);
8902 ASSERT_NE(salt1_eval, salt2_eval);
8903 }
Adam Langleyc296f392020-07-16 03:55:248904
Adam Langley7d1df66672023-09-01 23:23:218905 // Should be consistent if swapped.
8906 {
8907 auto prf_value = blink::mojom::PRFValues::New();
8908 prf_value->first = salt2;
8909 prf_value->second = salt1;
8910 std::vector<blink::mojom::PRFValuesPtr> inputs;
8911 inputs.emplace_back(std::move(prf_value));
8912 auto result = assertion(std::move(inputs));
8913 ASSERT_EQ(result->first, salt2_eval);
8914 ASSERT_TRUE(result->second);
8915 ASSERT_EQ(*result->second, salt1_eval);
8916 }
Adam Langleyc296f392020-07-16 03:55:248917
Adam Langley7d1df66672023-09-01 23:23:218918 // Should still trigger if the credential ID is specified
8919 {
8920 auto prf_value = blink::mojom::PRFValues::New();
8921 prf_value->id.emplace(credential->id);
8922 prf_value->first = salt1;
8923 prf_value->second = salt2;
8924 std::vector<blink::mojom::PRFValuesPtr> inputs;
8925 inputs.emplace_back(std::move(prf_value));
8926 auto result = assertion(std::move(inputs));
8927 ASSERT_EQ(result->first, salt1_eval);
8928 ASSERT_TRUE(result->second);
8929 ASSERT_EQ(*result->second, salt2_eval);
8930 }
Adam Langleyc296f392020-07-16 03:55:248931
Adam Langley7d1df66672023-09-01 23:23:218932 // And the specified credential ID should override any default inputs.
8933 {
8934 auto prf_value1 = blink::mojom::PRFValues::New();
8935 prf_value1->first = std::vector<uint8_t>(32, 3);
8936 auto prf_value2 = blink::mojom::PRFValues::New();
8937 prf_value2->id.emplace(credential->id);
8938 prf_value2->first = salt1;
8939 prf_value2->second = salt2;
8940 std::vector<blink::mojom::PRFValuesPtr> inputs;
8941 inputs.emplace_back(std::move(prf_value1));
8942 inputs.emplace_back(std::move(prf_value2));
8943 auto result = assertion(std::move(inputs));
8944 ASSERT_EQ(result->first, salt1_eval);
8945 ASSERT_TRUE(result->second);
8946 ASSERT_EQ(*result->second, salt2_eval);
8947 }
Adam Langleyc296f392020-07-16 03:55:248948
Adam Langley7d1df66672023-09-01 23:23:218949 // ... and that should still be true if there there are lots of dummy
8950 // entries in the allowlist. Note that the virtual authenticator was
8951 // configured such that this will cause multiple batches.
8952 {
8953 auto prf_value = blink::mojom::PRFValues::New();
8954 prf_value->id.emplace(credential->id);
8955 prf_value->first = salt1;
8956 prf_value->second = salt2;
8957 std::vector<blink::mojom::PRFValuesPtr> inputs;
8958 inputs.emplace_back(std::move(prf_value));
8959 auto result = assertion(std::move(inputs), /*allowlist_size=*/20);
8960 ASSERT_EQ(result->first, salt1_eval);
8961 ASSERT_TRUE(result->second);
8962 ASSERT_EQ(*result->second, salt2_eval);
8963 }
Adam Langleyd27d7db2023-02-08 16:45:378964
Adam Langley7d1df66672023-09-01 23:23:218965 // Default PRF values should be passed down when the allowlist is empty.
8966 {
8967 auto prf_value = blink::mojom::PRFValues::New();
8968 prf_value->first = salt1;
8969 prf_value->second = salt2;
8970 test_client_.delegate_config.expected_accounts =
8971 "01020304:name:displayName";
8972 test_client_.delegate_config.selected_user_id = {1, 2, 3, 4};
8973 std::vector<blink::mojom::PRFValuesPtr> inputs;
8974 inputs.emplace_back(std::move(prf_value));
8975 auto result = assertion(std::move(inputs), /*allowlist_size=*/0);
8976 ASSERT_EQ(result->first, salt1_eval);
8977 ASSERT_TRUE(result->second);
8978 ASSERT_EQ(*result->second, salt2_eval);
8979 }
8980
8981 // And the default PRF values should be used if none of the specific
8982 // values match.
8983 {
8984 auto prf_value1 = blink::mojom::PRFValues::New();
8985 prf_value1->first = salt1;
8986 auto prf_value2 = blink::mojom::PRFValues::New();
8987 prf_value2->first = std::vector<uint8_t>(32, 3);
8988 prf_value2->id = std::vector<uint8_t>(32, 4);
8989 std::vector<blink::mojom::PRFValuesPtr> inputs;
8990 inputs.emplace_back(std::move(prf_value1));
8991 inputs.emplace_back(std::move(prf_value2));
8992 auto result = assertion(std::move(inputs), /*allowlist_size=*/20);
8993 ASSERT_EQ(result->first, salt1_eval);
8994 ASSERT_FALSE(result->second);
8995 }
Adam Langleyd27d7db2023-02-08 16:45:378996 }
Nina Satragno5ba78462020-10-02 17:25:158997 }
Adam Langleyc296f392020-07-16 03:55:248998}
8999
Nina Satragno8d73f1972024-02-02 19:13:349000// Tests that the PRF function is evaluated for all credentials in an empty
9001// allow-list request. Regression test for crbug.com/1520646.
9002TEST_F(ResidentKeyAuthenticatorImplTest, PRFEvaluationForMultipleCreds) {
9003 NavigateAndCommit(GURL(kTestOrigin1));
9004 device::PublicKeyCredentialDescriptor cred1;
9005 device::PublicKeyCredentialDescriptor cred2;
9006 device::VirtualCtap2Device::Config config;
9007 config.prf_support = false;
9008 config.hmac_secret_support = true;
9009 config.pin_support = true;
9010 config.resident_key_support = true;
9011 virtual_device_factory_->SetCtap2Config(config);
9012 {
9013 PublicKeyCredentialCreationOptionsPtr options =
9014 GetTestPublicKeyCredentialCreationOptions();
9015 options->prf_enable = true;
9016 options->authenticator_selection->resident_key =
9017 device::ResidentKeyRequirement::kRequired;
9018 options->user.id = {1};
9019 options->user.name = "noah";
9020 options->user.display_name = "Noah";
9021 MakeCredentialResult result =
9022 AuthenticatorMakeCredential(std::move(options));
9023 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9024 ASSERT_TRUE(result.response->echo_prf);
9025 ASSERT_EQ(result.response->prf, true);
9026 device::AuthenticatorData auth_data =
9027 AuthDataFromMakeCredentialResponse(result.response);
9028 cred1 = device::PublicKeyCredentialDescriptor(
9029 device::CredentialType::kPublicKey, auth_data.GetCredentialId());
9030 }
9031 {
9032 PublicKeyCredentialCreationOptionsPtr options =
9033 GetTestPublicKeyCredentialCreationOptions();
9034 options->prf_enable = true;
9035 options->authenticator_selection->resident_key =
9036 device::ResidentKeyRequirement::kRequired;
9037 options->user.id = {2};
9038 options->user.name = "mio";
9039 options->user.display_name = "Mio";
9040 MakeCredentialResult result =
9041 AuthenticatorMakeCredential(std::move(options));
9042 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9043 ASSERT_TRUE(result.response->echo_prf);
9044 ASSERT_EQ(result.response->prf, true);
9045 device::AuthenticatorData auth_data =
9046 AuthDataFromMakeCredentialResponse(result.response);
9047 cred2 = device::PublicKeyCredentialDescriptor(
9048 device::CredentialType::kPublicKey, auth_data.GetCredentialId());
9049 }
9050
9051 const std::vector<uint8_t> salt(32, 1);
9052 std::vector<uint8_t> salt1_eval;
9053 std::vector<uint8_t> salt2_eval;
9054 {
9055 PublicKeyCredentialRequestOptionsPtr options =
9056 GetTestPublicKeyCredentialRequestOptions();
9057 options->extensions->prf = true;
9058 auto prf_value = blink::mojom::PRFValues::New();
9059 prf_value->first = salt;
9060 std::vector<blink::mojom::PRFValuesPtr> inputs;
9061 inputs.emplace_back(std::move(prf_value));
9062 options->extensions->prf_inputs = std::move(inputs);
9063 options->allow_credentials.clear();
9064 test_client_.delegate_config.expected_accounts = "01:noah:Noah/02:mio:Mio";
9065 test_client_.delegate_config.selected_user_id = {1};
9066 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9067 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9068 ASSERT_TRUE(result.response->extensions->prf_results);
9069 ASSERT_FALSE(result.response->extensions->prf_results->id);
9070 salt1_eval = result.response->extensions->prf_results->first;
9071 }
9072 {
9073 PublicKeyCredentialRequestOptionsPtr options =
9074 GetTestPublicKeyCredentialRequestOptions();
9075 options->extensions->prf = true;
9076 auto prf_value = blink::mojom::PRFValues::New();
9077 prf_value->first = salt;
9078 std::vector<blink::mojom::PRFValuesPtr> inputs;
9079 inputs.emplace_back(std::move(prf_value));
9080 options->extensions->prf_inputs = std::move(inputs);
9081 options->allow_credentials.clear();
9082 test_client_.delegate_config.expected_accounts = "01:noah:Noah/02:mio:Mio";
9083 test_client_.delegate_config.selected_user_id = {2};
9084 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9085 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9086 ASSERT_TRUE(result.response->extensions->prf_results);
9087 ASSERT_FALSE(result.response->extensions->prf_results->id);
9088 salt2_eval = result.response->extensions->prf_results->first;
9089 }
9090 EXPECT_NE(salt1_eval, salt2_eval);
9091}
9092
Adam Langley5c0c8ab72023-10-10 23:06:129093TEST_F(ResidentKeyAuthenticatorImplTest, PRFEvaluationDuringMakeCredential) {
9094 // The WebAuthn "prf" extension supports evaluating the PRF when making a
9095 // credential. The hmac-secret extension does not support this, but hybrid
9096 // devices (and our virtual authenticator) can support it using the
9097 // CTAP2-level "prf" extension.
9098 NavigateAndCommit(GURL(kTestOrigin1));
9099
9100 device::VirtualCtap2Device::Config config;
9101 config.prf_support = true;
9102 config.internal_account_chooser = true;
9103 config.always_uv = true;
9104 config.pin_support = true;
9105 config.resident_key_support = true;
9106 virtual_device_factory_->SetCtap2Config(config);
9107
9108 PublicKeyCredentialCreationOptionsPtr options =
9109 GetTestPublicKeyCredentialCreationOptions();
9110 options->prf_enable = true;
9111 options->authenticator_selection->resident_key =
9112 device::ResidentKeyRequirement::kRequired;
9113 options->user.id = {1, 2, 3, 4};
9114 options->user.name = "name";
9115 options->user.display_name = "displayName";
9116 options->prf_input = blink::mojom::PRFValues::New();
9117 const std::vector<uint8_t> salt1(32, 1);
9118 const std::vector<uint8_t> salt2(32, 2);
9119 options->prf_input->first = salt1;
9120 options->prf_input->second = salt2;
9121
9122 MakeCredentialResult result = AuthenticatorMakeCredential(std::move(options));
9123 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9124
9125 EXPECT_TRUE(result.response->echo_prf);
9126 EXPECT_TRUE(result.response->prf);
9127 ASSERT_TRUE(result.response->prf_results);
9128 EXPECT_EQ(result.response->prf_results->first.size(), 32u);
9129 EXPECT_EQ(result.response->prf_results->second->size(), 32u);
9130}
9131
9132TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialPRFExtension) {
9133 NavigateAndCommit(GURL(kTestOrigin1));
9134}
9135
Adam Langley2bb75bd2023-03-06 01:39:559136TEST_F(ResidentKeyAuthenticatorImplTest,
9137 PRFExtensionOnUnconfiguredAuthenticator) {
9138 // If a credential is on a UV-capable, but not UV-configured authenticator and
9139 // then an assertion with `prf` is requested there shouldn't be a result
9140 // because it would be from the wrong PRF. (This state should only happen when
9141 // the credential was created without the `prf` extension, which is an RP
9142 // issue.)
9143 device::VirtualCtap2Device::Config config;
9144 config.hmac_secret_support = true;
9145 config.internal_uv_support = true;
9146 config.pin_uv_auth_token_support = true;
9147 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
9148 config.resident_key_support = true;
9149 virtual_device_factory_->SetCtap2Config(config);
9150
9151 PublicKeyCredentialRequestOptionsPtr options =
9152 GetTestPublicKeyCredentialRequestOptions();
9153 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
9154 options->allow_credentials[0].id, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:379155 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Adam Langley2bb75bd2023-03-06 01:39:559156 device::VirtualFidoDevice::RegistrationData& registration =
9157 virtual_device_factory_->mutable_state()->registrations.begin()->second;
9158 const std::array<uint8_t, 32> key1 = {1};
9159 const std::array<uint8_t, 32> key2 = {2};
9160 registration.hmac_key.emplace(key1, key2);
9161
9162 auto prf_value = blink::mojom::PRFValues::New();
9163 const std::vector<uint8_t> salt1(32, 1);
9164 prf_value->first = salt1;
9165 std::vector<blink::mojom::PRFValuesPtr> inputs;
9166 inputs.emplace_back(std::move(prf_value));
9167
Slobodan Pejicc8ce88b2023-06-19 15:41:129168 options->extensions->prf = true;
9169 options->extensions->prf_inputs = std::move(inputs);
Adam Langley2bb75bd2023-03-06 01:39:559170 options->user_verification =
9171 device::UserVerificationRequirement::kDiscouraged;
9172 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9173
9174 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:259175 EXPECT_FALSE(result.response->extensions->prf_results);
Adam Langley2bb75bd2023-03-06 01:39:559176}
9177
Nina Satragnob93b8422021-02-04 21:50:449178TEST_F(ResidentKeyAuthenticatorImplTest, ConditionalUI) {
9179 device::VirtualCtap2Device::Config config;
9180 config.resident_key_support = true;
9181 config.internal_uv_support = true;
9182 virtual_device_factory_->SetCtap2Config(config);
9183 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
9184
9185 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
9186 /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId,
Arthur Sonzognic686e8f2024-01-11 08:36:379187 /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt));
Nina Satragnob93b8422021-02-04 21:50:449188
Martin Kreichgauerb3689d062022-07-12 09:36:519189 // |SelectAccount| should not be called for conditional UI requests.
9190 test_client_.delegate_config.expected_accounts = "<invalid>";
9191 test_client_.delegate_config.expect_conditional = true;
Nina Satragnob93b8422021-02-04 21:50:449192 PublicKeyCredentialRequestOptionsPtr options(get_credential_options());
Adem Derinel2154d4b12025-02-04 12:29:559193 options->mediation = blink::mojom::Mediation::CONDITIONAL;
Nina Satragnob93b8422021-02-04 21:50:449194 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9195 EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
Ken Buchanan23dce912024-07-11 16:41:279196 VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kSuccess,
Ken Buchanan1ea549c12024-10-10 21:31:059197 AuthenticationRequestMode::kConditional);
Martin Kreichgauerb3689d062022-07-12 09:36:519198}
9199
9200// Tests that the AuthenticatorRequestDelegate can choose a known platform
9201// authentictor credential as "preselected", which causes the request to be
9202// specialized to the chosen credential ID and post-request account selection UI
9203// to be skipped.
9204TEST_F(ResidentKeyAuthenticatorImplTest, PreselectDiscoverableCredential) {
Martin Kreichgauerb3689d062022-07-12 09:36:519205 virtual_device_factory_->SetTransport(
9206 device::FidoTransportProtocol::kInternal);
9207 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
9208 constexpr char kAuthenticatorId[] = "internal-authenticator";
9209 virtual_device_factory_->mutable_state()->device_id_override =
9210 kAuthenticatorId;
9211 std::vector<uint8_t> kFirstCredentialId{{1, 2, 3, 4}};
9212 std::vector<uint8_t> kSecondCredentialId{{10, 20, 30, 40}};
9213 std::vector<uint8_t> kFirstUserId{{2, 3, 4, 5}};
9214 std::vector<uint8_t> kSecondUserId{{20, 30, 40, 50}};
9215
9216 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Arthur Sonzognic686e8f2024-01-11 08:36:379217 kFirstCredentialId, kTestRelyingPartyId, kFirstUserId, std::nullopt,
9218 std::nullopt));
Martin Kreichgauerb3689d062022-07-12 09:36:519219 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
Arthur Sonzognic686e8f2024-01-11 08:36:379220 kSecondCredentialId, kTestRelyingPartyId, kSecondUserId, std::nullopt,
9221 std::nullopt));
Nina Satragno9b54821c2024-11-22 19:21:569222 for (bool has_pin_uv_auth_token : {false, true}) {
9223 SCOPED_TRACE(has_pin_uv_auth_token);
9224 device::VirtualCtap2Device::Config config;
9225 config.pin_uv_auth_token_support = has_pin_uv_auth_token;
9226 config.ctap2_versions = {device::Ctap2Version::kCtap2_1};
9227 config.resident_key_support = true;
9228 config.internal_uv_support = true;
9229 virtual_device_factory_->SetCtap2Config(std::move(config));
Martin Kreichgauerb3689d062022-07-12 09:36:519230
Nina Satragno9b54821c2024-11-22 19:21:569231 // |SelectAccount| should not be called if an account was chosen from
9232 // pre-select UI.
9233 test_client_.delegate_config.expected_accounts = "<invalid>";
Martin Kreichgauerb3689d062022-07-12 09:36:519234
Nina Satragno9b54821c2024-11-22 19:21:569235 for (const auto& id : {kFirstCredentialId, kSecondCredentialId}) {
9236 test_client_.delegate_config.preselected_credential_id = id;
9237 test_client_.delegate_config.preselected_authenticator_id =
9238 kAuthenticatorId;
9239 PublicKeyCredentialRequestOptionsPtr options(get_credential_options());
9240 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9241 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9242 EXPECT_EQ(result.response->info->raw_id, id);
9243 }
Martin Kreichgauerb3689d062022-07-12 09:36:519244 }
Nina Satragnob93b8422021-02-04 21:50:449245}
9246
Nina Satragno17570452024-03-18 16:48:239247// Tests that preselecting a credential sets the response user entity to that of
9248// the credential metadata if it is not present in the response.
9249// Regression test for crbug.com/329412574.
9250TEST_F(ResidentKeyAuthenticatorImplTest, PreselectCredentialUserEntity) {
9251 device::VirtualCtap2Device::Config config;
9252 config.resident_key_support = true;
9253 config.internal_uv_support = true;
9254 config.omit_user_entity_on_allow_credentials_requests = true;
9255 virtual_device_factory_->SetCtap2Config(config);
9256 virtual_device_factory_->SetTransport(
9257 device::FidoTransportProtocol::kInternal);
9258 virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
9259 constexpr char kAuthenticatorId[] = "internal-authenticator";
9260 virtual_device_factory_->mutable_state()->device_id_override =
9261 kAuthenticatorId;
9262 std::vector<uint8_t> kCredId{{1, 2, 3, 4}};
9263 std::vector<uint8_t> kUserId{{5, 6, 7, 8}};
9264
9265 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey(
9266 kCredId, kTestRelyingPartyId, kUserId, std::nullopt, std::nullopt));
9267
9268 // |SelectAccount| should not be called if an account was chosen from
9269 // pre-select UI.
9270 test_client_.delegate_config.expected_accounts = "<invalid>";
9271
9272 test_client_.delegate_config.preselected_credential_id = kCredId;
9273 test_client_.delegate_config.preselected_authenticator_id = kAuthenticatorId;
9274 PublicKeyCredentialRequestOptionsPtr options(get_credential_options());
9275 GetAssertionResult result = AuthenticatorGetAssertion(std::move(options));
9276 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9277 EXPECT_EQ(result.response->info->raw_id, kCredId);
9278 EXPECT_EQ(result.response->user_handle, kUserId);
9279}
9280
Nina Satragnoacf403f92019-05-23 17:16:529281class InternalAuthenticatorImplTest : public AuthenticatorTestBase {
Martin Kreichgauer4faa9baf2019-07-17 17:57:399282 protected:
Manas Verma77aeb1782019-05-13 20:40:529283 InternalAuthenticatorImplTest() = default;
9284
Nina Satragno8d67dec32023-04-18 22:10:449285 void SetUp() override {
9286 AuthenticatorTestBase::SetUp();
9287 old_client_ = SetBrowserClientForTesting(&test_client_);
9288 }
9289
Manas Verma77aeb1782019-05-13 20:40:529290 void TearDown() override {
9291 // The |RenderFrameHost| must outlive |AuthenticatorImpl|.
9292 internal_authenticator_impl_.reset();
Nina Satragno8d67dec32023-04-18 22:10:449293 SetBrowserClientForTesting(old_client_);
Martin Kreichgauer70fc0cf2020-07-17 01:01:009294 AuthenticatorTestBase::TearDown();
Manas Verma77aeb1782019-05-13 20:40:529295 }
9296
9297 void NavigateAndCommit(const GURL& url) {
9298 // The |RenderFrameHost| must outlive |AuthenticatorImpl|.
9299 internal_authenticator_impl_.reset();
Martin Kreichgauerfefb3772021-04-12 23:02:489300 RenderViewHostTestHarness::NavigateAndCommit(url);
Manas Verma77aeb1782019-05-13 20:40:529301 }
9302
Manas Verma694e12f2020-03-30 17:19:369303 InternalAuthenticatorImpl* GetAuthenticator(
9304 const url::Origin& effective_origin_url) {
9305 internal_authenticator_impl_ =
9306 std::make_unique<InternalAuthenticatorImpl>(main_rfh());
9307 internal_authenticator_impl_->SetEffectiveOrigin(effective_origin_url);
9308 return internal_authenticator_impl_.get();
Manas Verma77aeb1782019-05-13 20:40:529309 }
9310
Manas Verma77aeb1782019-05-13 20:40:529311 protected:
9312 std::unique_ptr<InternalAuthenticatorImpl> internal_authenticator_impl_;
Nina Satragno8d67dec32023-04-18 22:10:449313 TestAuthenticatorContentBrowserClient test_client_;
9314 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Manas Verma77aeb1782019-05-13 20:40:529315};
9316
Nina Satragno8d67dec32023-04-18 22:10:449317// Regression test for crbug.com/1433416.
9318TEST_F(InternalAuthenticatorImplTest, MakeCredentialSkipTLSCheck) {
9319 NavigateAndCommit(GURL(kTestOrigin1));
9320 InternalAuthenticatorImpl* authenticator =
9321 GetAuthenticator(url::Origin::Create(GURL(kTestOrigin1)));
Ken Buchanan6e9929372023-10-31 14:05:439322 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno8d67dec32023-04-18 22:10:449323 PublicKeyCredentialCreationOptionsPtr options =
9324 GetTestPublicKeyCredentialCreationOptions();
Adem Derinele6378762024-06-27 06:05:079325 TestMakeCredentialFuture future;
9326 authenticator->MakeCredential(std::move(options), future.GetCallback());
9327 EXPECT_TRUE(future.Wait());
9328 EXPECT_EQ(std::get<0>(future.Get()),
9329 blink::mojom::AuthenticatorStatus::SUCCESS);
Nina Satragno8d67dec32023-04-18 22:10:449330}
9331
9332// Regression test for crbug.com/1433416.
9333TEST_F(InternalAuthenticatorImplTest, GetAssertionSkipTLSCheck) {
9334 NavigateAndCommit(GURL(kTestOrigin1));
9335 InternalAuthenticatorImpl* authenticator =
9336 GetAuthenticator(url::Origin::Create(GURL(kTestOrigin1)));
Ken Buchanan6e9929372023-10-31 14:05:439337 test_client_.is_webauthn_security_level_acceptable = false;
Nina Satragno8d67dec32023-04-18 22:10:449338 PublicKeyCredentialRequestOptionsPtr options =
9339 GetTestPublicKeyCredentialRequestOptions();
9340 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
9341 options->allow_credentials[0].id, options->relying_party_id));
Adem Derinele6378762024-06-27 06:05:079342 TestGetAssertionFuture future;
9343 authenticator->GetAssertion(std::move(options), future.GetCallback());
9344 EXPECT_TRUE(future.Wait());
9345 EXPECT_EQ(std::get<0>(future.Get()),
9346 blink::mojom::AuthenticatorStatus::SUCCESS);
Nina Satragno8d67dec32023-04-18 22:10:449347}
9348
Manas Verma77aeb1782019-05-13 20:40:529349// Verify behavior for various combinations of origins and RP IDs.
9350TEST_F(InternalAuthenticatorImplTest, MakeCredentialOriginAndRpIds) {
9351 // These instances should return security errors (for circumstances
9352 // that would normally crash the renderer).
Adem Derinel620520b42024-11-04 15:45:449353 for (auto test_case : kInvalidRpTestCases) {
Manas Verma77aeb1782019-05-13 20:40:529354 SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
9355 std::string(test_case.origin));
9356
9357 GURL origin = GURL(test_case.origin);
9358 if (url::Origin::Create(origin).opaque()) {
9359 // Opaque origins will cause DCHECK to fail.
9360 continue;
9361 }
9362
9363 NavigateAndCommit(origin);
Manas Verma694e12f2020-03-30 17:19:369364 InternalAuthenticatorImpl* authenticator =
9365 GetAuthenticator(url::Origin::Create(origin));
Manas Verma77aeb1782019-05-13 20:40:529366 PublicKeyCredentialCreationOptionsPtr options =
9367 GetTestPublicKeyCredentialCreationOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:049368 options->relying_party.id = test_case.claimed_authority;
Adem Derinele6378762024-06-27 06:05:079369 TestMakeCredentialFuture future;
9370 authenticator->MakeCredential(std::move(options), future.GetCallback());
9371 EXPECT_TRUE(future.Wait());
9372 EXPECT_EQ(test_case.expected_status, std::get<0>(future.Get()));
Manas Verma77aeb1782019-05-13 20:40:529373 }
9374
9375 // These instances should bypass security errors, by setting the effective
9376 // origin to a valid one.
Adem Derinel620520b42024-11-04 15:45:449377 for (auto test_case : kValidRpTestCases) {
Manas Verma77aeb1782019-05-13 20:40:529378 SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
9379 std::string(test_case.origin));
9380
9381 NavigateAndCommit(GURL("https://this.isthewrong.origin"));
Martin Kreichgauer70fc0cf2020-07-17 01:01:009382 auto* authenticator =
9383 GetAuthenticator(url::Origin::Create(GURL(test_case.origin)));
Manas Verma77aeb1782019-05-13 20:40:529384 PublicKeyCredentialCreationOptionsPtr options =
9385 GetTestPublicKeyCredentialCreationOptions();
Ken Buchanan4a33ef0d2019-06-20 17:34:049386 options->relying_party.id = test_case.claimed_authority;
Manas Verma77aeb1782019-05-13 20:40:529387
Nina Satragnoacf403f92019-05-23 17:16:529388 ResetVirtualDevice();
Adem Derinele6378762024-06-27 06:05:079389 TestMakeCredentialFuture future;
9390 authenticator->MakeCredential(std::move(options), future.GetCallback());
9391 EXPECT_TRUE(future.Wait());
9392 EXPECT_EQ(test_case.expected_status, std::get<0>(future.Get()));
Manas Verma77aeb1782019-05-13 20:40:529393 }
9394}
9395
9396// Verify behavior for various combinations of origins and RP IDs.
9397TEST_F(InternalAuthenticatorImplTest, GetAssertionOriginAndRpIds) {
9398 // These instances should return security errors (for circumstances
9399 // that would normally crash the renderer).
Adem Derinel620520b42024-11-04 15:45:449400 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
9401 SCOPED_TRACE(
9402 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Manas Verma77aeb1782019-05-13 20:40:529403
9404 GURL origin = GURL(test_case.origin);
9405 if (url::Origin::Create(origin).opaque()) {
9406 // Opaque origins will cause DCHECK to fail.
9407 continue;
9408 }
9409
9410 NavigateAndCommit(origin);
Manas Verma694e12f2020-03-30 17:19:369411 InternalAuthenticatorImpl* authenticator =
9412 GetAuthenticator(url::Origin::Create(origin));
Manas Verma77aeb1782019-05-13 20:40:529413 PublicKeyCredentialRequestOptionsPtr options =
9414 GetTestPublicKeyCredentialRequestOptions();
9415 options->relying_party_id = test_case.claimed_authority;
9416
Adem Derinele6378762024-06-27 06:05:079417 TestGetAssertionFuture future;
9418 authenticator->GetAssertion(std::move(options), future.GetCallback());
9419 EXPECT_TRUE(future.Wait());
9420 EXPECT_EQ(test_case.expected_status, std::get<0>(future.Get()));
Manas Verma77aeb1782019-05-13 20:40:529421 }
9422
9423 // These instances should bypass security errors, by setting the effective
9424 // origin to a valid one.
Adem Derinel620520b42024-11-04 15:45:449425 for (const OriginClaimedAuthorityPair& test_case : kValidRpTestCases) {
9426 SCOPED_TRACE(
9427 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Manas Verma77aeb1782019-05-13 20:40:529428
9429 NavigateAndCommit(GURL("https://this.isthewrong.origin"));
Martin Kreichgauer70fc0cf2020-07-17 01:01:009430 InternalAuthenticatorImpl* authenticator =
9431 GetAuthenticator(url::Origin::Create(GURL(test_case.origin)));
Manas Verma77aeb1782019-05-13 20:40:529432 PublicKeyCredentialRequestOptionsPtr options =
9433 GetTestPublicKeyCredentialRequestOptions();
9434 options->relying_party_id = test_case.claimed_authority;
9435
Nina Satragnoacf403f92019-05-23 17:16:529436 ResetVirtualDevice();
9437 ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
Adem Derinel620520b42024-11-04 15:45:449438 options->allow_credentials[0].id,
9439 std::string(test_case.claimed_authority)));
Adem Derinele6378762024-06-27 06:05:079440 TestGetAssertionFuture future;
9441 authenticator->GetAssertion(std::move(options), future.GetCallback());
9442 EXPECT_TRUE(future.Wait());
9443 EXPECT_EQ(test_case.expected_status, std::get<0>(future.Get()));
Manas Verma77aeb1782019-05-13 20:40:529444 }
9445}
9446
Xiaohan Wang2ba85e32022-01-15 17:19:409447#if BUILDFLAG(IS_MAC)
Martin Kreichgauer263f6d82020-03-10 05:46:459448class TouchIdAuthenticatorImplTest : public AuthenticatorImplTest {
Martin Kreichgauere11b8a992022-06-23 17:08:269449 protected:
Martin Kreichgauer108eb102022-06-29 23:08:419450 using Credential = device::fido::mac::Credential;
9451 using CredentialMetadata = device::fido::mac::CredentialMetadata;
9452
Martin Kreichgauer263f6d82020-03-10 05:46:459453 void SetUp() override {
9454 AuthenticatorImplTest::SetUp();
Martin Kreichgauer108eb102022-06-29 23:08:419455 test_client_.web_authentication_delegate.touch_id_authenticator_config =
9456 config_;
9457 test_client_.web_authentication_delegate.supports_resident_keys = true;
Martin Kreichgauer263f6d82020-03-10 05:46:459458 old_client_ = SetBrowserClientForTesting(&test_client_);
9459 }
9460
9461 void TearDown() override {
9462 SetBrowserClientForTesting(old_client_);
9463 AuthenticatorImplTest::TearDown();
9464 }
9465
Martin Kreichgauer108eb102022-06-29 23:08:419466 void ResetVirtualDevice() override {}
9467
Martin Kreichgauer00b687592022-07-29 19:23:269468 std::vector<Credential> GetCredentials(const std::string& rp_id) {
Martin Kreichgauer108eb102022-06-29 23:08:419469 return device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting(
9470 config_, rp_id);
9471 }
9472
Martin Kreichgauerfefb3772021-04-12 23:02:489473 TestAuthenticatorContentBrowserClient test_client_;
Keishi Hattori7c3c7182022-06-24 22:18:149474 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Martin Kreichgauere11b8a992022-06-23 17:08:269475 device::fido::mac::AuthenticatorConfig config_{
9476 .keychain_access_group = "test-keychain-access-group",
9477 .metadata_secret = "TestMetadataSecret"};
9478 device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment_{
9479 config_};
Martin Kreichgauer263f6d82020-03-10 05:46:459480};
9481
9482TEST_F(TouchIdAuthenticatorImplTest, IsUVPAA) {
Martin Kreichgauer55834402020-08-03 21:54:319483 NavigateAndCommit(GURL(kTestOrigin1));
Avi Drissmanad89a3902022-05-17 21:21:309484 for (const bool touch_id_available : {false, true}) {
9485 SCOPED_TRACE(::testing::Message()
9486 << "touch_id_available=" << touch_id_available);
Martin Kreichgauere11b8a992022-06-23 17:08:269487 touch_id_test_environment_.SetTouchIdAvailable(touch_id_available);
Martin Kreichgauerca18b432023-04-25 18:58:479488 EXPECT_EQ(AuthenticatorIsUvpaa(), touch_id_available);
Martin Kreichgauer263f6d82020-03-10 05:46:459489 }
9490}
Martin Kreichgauer108eb102022-06-29 23:08:419491
9492TEST_F(TouchIdAuthenticatorImplTest, MakeCredential) {
9493 NavigateAndCommit(GURL(kTestOrigin1));
9494 mojo::Remote<blink::mojom::Authenticator> authenticator =
9495 ConnectToAuthenticator();
9496 auto options = GetTestPublicKeyCredentialCreationOptions();
9497 options->authenticator_selection->authenticator_attachment =
9498 device::AuthenticatorAttachment::kPlatform;
9499 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9500 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
9501 AuthenticatorStatus::SUCCESS);
9502 auto credentials = GetCredentials(kTestRelyingPartyId);
9503 EXPECT_EQ(credentials.size(), 1u);
Martin Kreichgauer00b687592022-07-29 19:23:269504 const CredentialMetadata& metadata = credentials.at(0).metadata;
Ioana Pandeleb6e0b2b2022-10-13 16:45:509505 // New credentials are always created discoverable.
9506 EXPECT_TRUE(metadata.is_resident);
Martin Kreichgauer108eb102022-06-29 23:08:419507 auto expected_user = GetTestPublicKeyCredentialUserEntity();
Martin Kreichgauer108eb102022-06-29 23:08:419508 EXPECT_EQ(metadata.ToPublicKeyCredentialUserEntity(), expected_user);
9509}
9510
Adam Langley7640c142024-09-05 15:53:359511TEST_F(TouchIdAuthenticatorImplTest, MakeCredentialUnsupportedAlgorithm) {
9512 // crbug.com/362766319
9513 NavigateAndCommit(GURL(kTestOrigin1));
9514 mojo::Remote<blink::mojom::Authenticator> authenticator =
9515 ConnectToAuthenticator();
9516 auto options = GetTestPublicKeyCredentialCreationOptions();
9517 options->authenticator_selection->authenticator_attachment =
9518 device::AuthenticatorAttachment::kPlatform;
9519 options->public_key_parameters = GetTestPublicKeyCredentialParameters(
9520 static_cast<int32_t>(device::CoseAlgorithmIdentifier::kEdDSA));
9521 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9522 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
9523 AuthenticatorStatus::NOT_ALLOWED_ERROR);
9524}
9525
Martin Kreichgauer5f7a707b2023-03-02 22:22:259526TEST_F(TouchIdAuthenticatorImplTest, OptionalUv) {
9527 NavigateAndCommit(GURL(kTestOrigin1));
9528 mojo::Remote<blink::mojom::Authenticator> authenticator =
9529 ConnectToAuthenticator();
Nina Satragno1a81c852024-03-28 20:18:169530 // Disable biometrics to verify that requests without uv required do not
9531 // prompt the user for their macOS password.
9532 touch_id_test_environment_.keychain()->SetUVMethod(
Elly Fong-Jones38c29b62025-07-23 22:54:539533 crypto::apple::ScopedFakeKeychainV2::UVMethod::kPasswordOnly);
Martin Kreichgauer5f7a707b2023-03-02 22:22:259534 for (const auto uv : {device::UserVerificationRequirement::kDiscouraged,
9535 device::UserVerificationRequirement::kPreferred,
9536 device::UserVerificationRequirement::kRequired}) {
Nina Satragno1a81c852024-03-28 20:18:169537 SCOPED_TRACE(static_cast<int>(uv));
Martin Kreichgauer5f7a707b2023-03-02 22:22:259538 auto options = GetTestPublicKeyCredentialCreationOptions();
9539 options->authenticator_selection->authenticator_attachment =
9540 device::AuthenticatorAttachment::kPlatform;
Martin Kreichgauer3bdaa1b2023-07-12 01:20:509541 // Set rk to required. On platform authenticators Chrome should not
9542 // universally require UV to make make a resident/discoverable credential,
9543 // like it would on a security key.
9544 options->authenticator_selection->resident_key =
9545 device::ResidentKeyRequirement::kRequired;
Martin Kreichgauer5f7a707b2023-03-02 22:22:259546 options->authenticator_selection->user_verification_requirement = uv;
9547 bool requires_uv = uv == device::UserVerificationRequirement::kRequired;
9548 if (requires_uv) {
9549 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9550 } else {
9551 touch_id_test_environment_.DoNotResolveNextPrompt();
9552 }
9553 auto result = AuthenticatorMakeCredential(std::move(options));
9554 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
9555 EXPECT_EQ(HasUV(result.response), requires_uv);
9556 auto credentials = GetCredentials(kTestRelyingPartyId);
9557 EXPECT_EQ(credentials.size(), 1u);
9558
9559 auto assertion_options = GetTestPublicKeyCredentialRequestOptions();
9560 assertion_options->user_verification = uv;
9561 assertion_options->allow_credentials =
9562 std::vector<device::PublicKeyCredentialDescriptor>(
9563 {{device::CredentialType::kPublicKey,
9564 credentials[0].credential_id}});
9565 if (requires_uv) {
9566 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9567 } else {
9568 touch_id_test_environment_.DoNotResolveNextPrompt();
9569 }
9570 auto assertion = AuthenticatorGetAssertion(std::move(assertion_options));
9571 EXPECT_EQ(assertion.status, AuthenticatorStatus::SUCCESS);
9572 EXPECT_EQ(HasUV(assertion.response), requires_uv);
9573 }
9574}
9575
Martin Kreichgauer108eb102022-06-29 23:08:419576TEST_F(TouchIdAuthenticatorImplTest, MakeCredential_Resident) {
9577 NavigateAndCommit(GURL(kTestOrigin1));
9578 mojo::Remote<blink::mojom::Authenticator> authenticator =
9579 ConnectToAuthenticator();
9580 auto options = GetTestPublicKeyCredentialCreationOptions();
9581 options->authenticator_selection->authenticator_attachment =
9582 device::AuthenticatorAttachment::kPlatform;
9583 options->authenticator_selection->resident_key =
9584 device::ResidentKeyRequirement::kRequired;
9585 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9586 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
9587 AuthenticatorStatus::SUCCESS);
9588 auto credentials = GetCredentials(kTestRelyingPartyId);
9589 EXPECT_EQ(credentials.size(), 1u);
Martin Kreichgauer00b687592022-07-29 19:23:269590 EXPECT_TRUE(credentials.at(0).metadata.is_resident);
Martin Kreichgauer108eb102022-06-29 23:08:419591}
9592
9593TEST_F(TouchIdAuthenticatorImplTest, MakeCredential_Eviction) {
9594 NavigateAndCommit(GURL(kTestOrigin1));
9595 mojo::Remote<blink::mojom::Authenticator> authenticator =
9596 ConnectToAuthenticator();
9597
Martin Kreichgauer108eb102022-06-29 23:08:419598 // A resident credential will overwrite the non-resident one.
9599 auto options = GetTestPublicKeyCredentialCreationOptions();
9600 options->authenticator_selection->authenticator_attachment =
9601 device::AuthenticatorAttachment::kPlatform;
9602 options->authenticator_selection->resident_key =
9603 device::ResidentKeyRequirement::kRequired;
9604 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9605 EXPECT_EQ(AuthenticatorMakeCredential(options->Clone()).status,
9606 AuthenticatorStatus::SUCCESS);
9607 EXPECT_EQ(GetCredentials(kTestRelyingPartyId).size(), 1u);
9608
9609 // Another resident credential for the same user will evict the previous one.
9610 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9611 EXPECT_EQ(AuthenticatorMakeCredential(options->Clone()).status,
9612 AuthenticatorStatus::SUCCESS);
9613 EXPECT_EQ(GetCredentials(kTestRelyingPartyId).size(), 1u);
9614
9615 // But a resident credential for a different user shouldn't.
9616 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9617 options->user.id = std::vector<uint8_t>({99});
9618 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
9619 AuthenticatorStatus::SUCCESS);
9620 EXPECT_EQ(GetCredentials(kTestRelyingPartyId).size(), 2u);
9621
9622 // Neither should a credential for a different RP.
9623 touch_id_test_environment_.SimulateTouchIdPromptSuccess();
9624 options = GetTestPublicKeyCredentialCreationOptions();
9625 options->authenticator_selection->authenticator_attachment =
9626 device::AuthenticatorAttachment::kPlatform;
9627 options->relying_party.id = "a.google.com";
9628 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
9629 AuthenticatorStatus::SUCCESS);
9630 EXPECT_EQ(GetCredentials(kTestRelyingPartyId).size(), 2u);
9631}
9632
Adam Langley9134ffe2023-05-26 19:14:179633class ICloudKeychainAuthenticatorImplTest : public AuthenticatorImplTest {
9634 protected:
9635 class InspectTAIAuthenticatorRequestDelegate
9636 : public AuthenticatorRequestClientDelegate {
9637 public:
9638 using Callback = base::RepeatingCallback<void(
Adam Langley0d424242024-11-05 18:46:359639 const device::FidoRequestHandlerBase::TransportAvailabilityInfo&,
9640 const std::optional<std::string>& icloud_keychain_id,
9641 device::FidoRequestHandlerBase::RequestCallback request_callback)>;
Adam Langley9134ffe2023-05-26 19:14:179642 explicit InspectTAIAuthenticatorRequestDelegate(Callback callback)
9643 : callback_(std::move(callback)) {}
9644
Adam Langley0d424242024-11-05 18:46:359645 void RegisterActionCallbacks(
9646 base::OnceClosure cancel_callback,
Adem Derinel2154d4b12025-02-04 12:29:559647 base::OnceClosure immediate_not_found_callback,
Adam Langley0d424242024-11-05 18:46:359648 base::RepeatingClosure start_over_callback,
9649 AccountPreselectedCallback account_preselected_callback,
Adem Derinel72e11db2025-02-11 15:58:009650 PasswordSelectedCallback password_selected_callback,
Adam Langley0d424242024-11-05 18:46:359651 device::FidoRequestHandlerBase::RequestCallback request_callback,
Adem Derinel38626c22025-05-22 13:31:589652 base::OnceClosure cancel_ui_timeout_callback,
Adam Langley0d424242024-11-05 18:46:359653 base::RepeatingClosure bluetooth_adapter_power_on_callback,
9654 base::RepeatingCallback<
9655 void(device::FidoRequestHandlerBase::BlePermissionCallback)>
9656 request_ble_permission_callback) override {
9657 request_callback_ = std::move(request_callback);
9658 }
9659
Adam Langleyb5b72582023-09-13 19:41:469660 void ConfigureDiscoveries(
9661 const url::Origin& origin,
9662 const std::string& rp_id,
9663 RequestSource request_source,
9664 device::FidoRequestType request_type,
Arthur Sonzognic686e8f2024-01-11 08:36:379665 std::optional<device::ResidentKeyRequirement> resident_key_requirement,
Ken Buchananc4ae130ac2024-02-12 19:49:259666 device::UserVerificationRequirement user_verification_requirement,
Adam Langleyf2f838a62024-05-03 20:37:099667 std::optional<std::string_view> user_name,
Adam Langleyb5b72582023-09-13 19:41:469668 base::span<const device::CableDiscoveryData> pairings_from_extension,
Ken Buchananbc703ec02023-11-20 17:15:139669 bool is_enclave_authenticator_available,
Adam Langleyb5b72582023-09-13 19:41:469670 device::FidoDiscoveryFactory* fido_discovery_factory) override {
Avi Drissman1bade8232025-03-19 18:00:329671 fido_discovery_factory->set_allow_no_nswindow_for_testing(true);
Adam Langleyb5b72582023-09-13 19:41:469672 }
9673
Adam Langley9134ffe2023-05-26 19:14:179674 void OnTransportAvailabilityEnumerated(
9675 device::FidoRequestHandlerBase::TransportAvailabilityInfo tai)
9676 override {
Adam Langley0d424242024-11-05 18:46:359677 callback_.Run(tai, icloud_keychain_id_, request_callback_);
9678 }
9679
9680 void FidoAuthenticatorAdded(
9681 const device::FidoAuthenticator& authenticator) override {
9682 if (authenticator.GetType() ==
9683 device::AuthenticatorType::kICloudKeychain) {
9684 CHECK(!icloud_keychain_id_);
9685 icloud_keychain_id_ = authenticator.GetId();
9686 }
Adam Langley9134ffe2023-05-26 19:14:179687 }
9688
9689 private:
9690 Callback callback_;
Adam Langley0d424242024-11-05 18:46:359691 device::FidoRequestHandlerBase::RequestCallback request_callback_;
9692 std::optional<std::string> icloud_keychain_id_;
Adam Langley9134ffe2023-05-26 19:14:179693 };
9694
9695 class InspectTAIContentBrowserClient : public ContentBrowserClient {
9696 public:
9697 explicit InspectTAIContentBrowserClient(
9698 InspectTAIAuthenticatorRequestDelegate::Callback callback)
9699 : callback_(std::move(callback)) {}
9700
9701 std::unique_ptr<AuthenticatorRequestClientDelegate>
9702 GetWebAuthenticationRequestDelegate(
9703 RenderFrameHost* render_frame_host) override {
9704 return std::make_unique<InspectTAIAuthenticatorRequestDelegate>(
9705 callback_);
9706 }
9707
9708 private:
9709 InspectTAIAuthenticatorRequestDelegate::Callback callback_;
9710 };
9711
9712 void SetUp() override {
9713 AuthenticatorImplTest::SetUp();
9714 old_client_ = SetBrowserClientForTesting(&test_client_);
9715 // This test uses the real discoveries and sets the transports on an
9716 // allowlist entry to limit it to kInternal.
Jagadesh P8db17bf2023-11-28 04:14:159717 virtual_device_factory_ = nullptr;
Adam Langley9134ffe2023-05-26 19:14:179718 AuthenticatorEnvironment::GetInstance()->Reset();
9719 }
9720
9721 void TearDown() override {
9722 SetBrowserClientForTesting(old_client_);
9723 AuthenticatorImplTest::TearDown();
9724 }
9725
9726 void OnTransportAvailabilityEnumerated(
Adam Langley0d424242024-11-05 18:46:359727 const device::FidoRequestHandlerBase::TransportAvailabilityInfo& tai,
9728 const std::optional<std::string>& icloud_keychain_id,
9729 device::FidoRequestHandlerBase::RequestCallback request_callback) {
Adam Langley9134ffe2023-05-26 19:14:179730 if (tai_callback_) {
Adam Langley0d424242024-11-05 18:46:359731 std::move(tai_callback_).Run(tai, icloud_keychain_id, request_callback);
Adam Langley9134ffe2023-05-26 19:14:179732 }
9733 }
9734
9735 static std::vector<device::DiscoverableCredentialMetadata> GetCredentials() {
9736 device::DiscoverableCredentialMetadata metadata(
9737 device::AuthenticatorType::kICloudKeychain, kTestRelyingPartyId,
Nina Satragnodffa0e822025-02-13 15:27:379738 {1, 2, 3, 4}, {{5, 6, 7, 8}, "name", "displayName"},
9739 /*provider_name=*/std::nullopt);
Adam Langley9134ffe2023-05-26 19:14:179740 return {std::move(metadata)};
9741 }
9742
9743 InspectTAIContentBrowserClient test_client_{base::BindRepeating(
9744 &ICloudKeychainAuthenticatorImplTest::OnTransportAvailabilityEnumerated,
9745 base::Unretained(this))};
9746 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
9747 InspectTAIAuthenticatorRequestDelegate::Callback tai_callback_;
9748};
9749
Adam Langley7a4e5352024-11-01 21:06:229750TEST_F(ICloudKeychainAuthenticatorImplTest, Discovery) {
Adam Langley06a884e2023-08-21 15:22:159751 if (__builtin_available(macOS 13.5, *)) {
Adem Derinela80f77922024-04-25 17:19:239752 NavigateAndCommit(GURL(kTestOrigin1));
9753 device::fido::icloud_keychain::ScopedTestEnvironment test_environment(
9754 GetCredentials());
9755 bool tai_seen = false;
9756 tai_callback_ = base::BindLambdaForTesting(
9757 [&tai_seen](
9758 const device::FidoRequestHandlerBase::TransportAvailabilityInfo&
Adam Langley0d424242024-11-05 18:46:359759 tai,
9760 const std::optional<std::string>& icloud_keychain_id,
9761 device::FidoRequestHandlerBase::RequestCallback request_callback) {
Adem Derinela80f77922024-04-25 17:19:239762 tai_seen = true;
9763 CHECK_EQ(tai.has_icloud_keychain, true);
9764 CHECK_EQ(tai.recognized_credentials.size(), 1u);
9765 CHECK_EQ(tai.has_icloud_keychain_credential,
9766 device::FidoRequestHandlerBase::RecognizedCredential::
9767 kHasRecognizedCredential);
Adam Langley9134ffe2023-05-26 19:14:179768
Adem Derinela80f77922024-04-25 17:19:239769 CHECK_EQ(tai.recognized_credentials[0].user.name.value(), "name");
9770 });
Adam Langley9134ffe2023-05-26 19:14:179771
Adem Derinela80f77922024-04-25 17:19:239772 auto options = GetTestPublicKeyCredentialRequestOptions();
9773 options->allow_credentials.clear();
9774 options->allow_credentials.push_back(device::PublicKeyCredentialDescriptor(
9775 device::CredentialType::kPublicKey, {1, 2, 3, 4},
9776 {device::FidoTransportProtocol::kInternal}));
9777 const auto result = AuthenticatorGetAssertion(std::move(options));
9778 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
9779 EXPECT_TRUE(tai_seen);
Adam Langley9134ffe2023-05-26 19:14:179780 } else {
Adam Langley0d424242024-11-05 18:46:359781 GTEST_SKIP() << "Need macOS 13.5 for this test";
9782 }
9783}
9784
9785TEST_F(ICloudKeychainAuthenticatorImplTest, PRFOnCreate) {
9786 if (__builtin_available(macOS 15.0, *)) {
Adam Langley0d424242024-11-05 18:46:359787 NavigateAndCommit(GURL(kTestOrigin1));
9788 device::fido::icloud_keychain::ScopedTestEnvironment test_environment(
9789 GetCredentials());
9790
9791 auto prf_value = blink::mojom::PRFValues::New();
9792 const std::vector<uint8_t> input1(8, 1);
9793 const std::vector<uint8_t> input2(8, 2);
9794 prf_value->first = input1;
9795 prf_value->second = input2;
9796
9797 bool callback_was_called = false;
9798 test_environment.SetMakeCredentialCallback(base::BindLambdaForTesting(
9799 [&input1, &input2, &callback_was_called](
9800 const device::CtapMakeCredentialRequest& request) {
9801 CHECK(request.prf);
9802 CHECK(request.prf_input.has_value());
9803 CHECK(input1 == request.prf_input->input1);
9804 CHECK(input2 == request.prf_input->input2);
9805 callback_was_called = true;
9806 }));
9807
9808 auto options = GetTestPublicKeyCredentialCreationOptions();
9809 options->prf_enable = true;
9810 options->prf_input = std::move(prf_value);
9811
9812 const auto result = AuthenticatorMakeCredential(std::move(options));
9813 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
9814 EXPECT_TRUE(callback_was_called);
9815 } else {
9816 GTEST_SKIP() << "Need macOS 15.0 for this test";
9817 }
9818}
9819
9820TEST_F(ICloudKeychainAuthenticatorImplTest, PRFOnGet) {
9821 if (__builtin_available(macOS 15.0, *)) {
Adam Langley0d424242024-11-05 18:46:359822 NavigateAndCommit(GURL(kTestOrigin1));
9823 device::fido::icloud_keychain::ScopedTestEnvironment test_environment(
9824 GetCredentials());
9825
9826 auto prf_value = blink::mojom::PRFValues::New();
9827 const std::vector<uint8_t> input1(8, 1);
9828 const std::vector<uint8_t> input2(8, 2);
9829 prf_value->first = input1;
9830 prf_value->second = input2;
9831 std::vector<blink::mojom::PRFValuesPtr> prf_inputs;
9832 prf_inputs.emplace_back(std::move(prf_value));
9833
9834 bool callback_was_called = false;
9835 test_environment.SetGetAssertionCallback(base::BindLambdaForTesting(
9836 [&input1, &input2,
9837 &callback_was_called](const device::CtapGetAssertionRequest& request) {
9838 CHECK_EQ(request.prf_inputs.size(), 1u);
9839 CHECK(input1 == request.prf_inputs[0].input1);
9840 CHECK(input2 == request.prf_inputs[0].input2);
9841 callback_was_called = true;
9842 }));
9843
9844 tai_callback_ = base::BindLambdaForTesting(
9845 [](const device::FidoRequestHandlerBase::TransportAvailabilityInfo& tai,
9846 const std::optional<std::string>& icloud_keychain_id,
9847 device::FidoRequestHandlerBase::RequestCallback request_callback) {
9848 CHECK_EQ(tai.has_icloud_keychain, true);
9849 CHECK(icloud_keychain_id.has_value());
9850 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
9851 FROM_HERE, base::BindOnce(request_callback, *icloud_keychain_id));
9852 });
9853
9854 auto options = GetTestPublicKeyCredentialRequestOptions();
9855 options->extensions->prf = true;
9856 options->extensions->prf_inputs = std::move(prf_inputs);
9857 options->allow_credentials.clear();
9858 options->allow_credentials.push_back(device::PublicKeyCredentialDescriptor(
9859 device::CredentialType::kPublicKey, {1, 2, 3, 4},
9860 {device::FidoTransportProtocol::kInternal}));
9861
9862 const auto result = AuthenticatorGetAssertion(std::move(options));
9863 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
9864 EXPECT_TRUE(callback_was_called);
9865 } else {
9866 GTEST_SKIP() << "Need macOS 15.0 for this test";
Adam Langley9134ffe2023-05-26 19:14:179867 }
9868}
9869
Xiaohan Wang2ba85e32022-01-15 17:19:409870#endif // BUILDFLAG(IS_MAC)
Martin Kreichgauer263f6d82020-03-10 05:46:459871
Adem Derinel38626c22025-05-22 13:31:589872TEST_F(ResidentKeyAuthenticatorImplTest,
9873 GetAssertionImmediateMediationTimeout_NoUI) {
9874 base::HistogramTester histogram_tester;
9875 base::test::ScopedFeatureList feature_list;
9876 base::FieldTrialParams feature_params;
9877 constexpr base::TimeDelta kImmediateTimeout = base::Milliseconds(10);
9878 feature_params["timeout_ms"] =
9879 base::NumberToString(kImmediateTimeout.InMilliseconds());
9880 feature_list.InitAndEnableFeatureWithParameters(device::kWebAuthnImmediateGet,
9881 feature_params);
9882
9883 ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>());
9884
9885 PublicKeyCredentialRequestOptionsPtr options =
9886 GetTestPublicKeyCredentialRequestOptions();
9887 options->mediation = blink::mojom::Mediation::IMMEDIATE;
9888 options->allow_credentials.clear();
9889 options->timeout = kTestTimeout;
9890
9891 mojo::Remote<blink::mojom::Authenticator> authenticator =
9892 ConnectToAuthenticator();
9893 TestGetCredentialFuture future;
9894 authenticator->GetCredential(std::move(options), future.GetCallback());
9895
9896 task_environment()->FastForwardBy(kImmediateTimeout);
9897
9898 EXPECT_TRUE(future.Wait());
9899 ASSERT_TRUE(future.Get()->is_get_assertion_response());
9900 auto& get_assertion_response = future.Get()->get_get_assertion_response();
9901 EXPECT_EQ(get_assertion_response->status,
9902 AuthenticatorStatus::IMMEDIATE_NOT_FOUND);
9903 histogram_tester.ExpectUniqueSample(
9904 "WebAuthentication.GetAssertion.Immediate.TimeoutWhileWaitingForUi", true,
9905 1);
9906}
9907
9908TEST_F(ResidentKeyAuthenticatorImplTest,
9909 GetAssertionImmediateMediationTimeout_WithUiThenNoImmediateTimeout) {
9910 base::HistogramTester histogram_tester;
9911 test_client_.delegate_config.run_cancel_ui_timeout_callback = true;
9912 base::test::ScopedFeatureList feature_list;
9913 base::FieldTrialParams feature_params;
9914 constexpr base::TimeDelta kImmediateTimeout = base::Milliseconds(10);
9915 feature_params["timeout_ms"] =
9916 base::NumberToString(kImmediateTimeout.InMilliseconds());
9917 feature_list.InitAndEnableFeatureWithParameters(device::kWebAuthnImmediateGet,
9918 feature_params);
9919
9920 ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>());
9921
9922 PublicKeyCredentialRequestOptionsPtr options =
9923 GetTestPublicKeyCredentialRequestOptions();
9924 options->mediation = blink::mojom::Mediation::IMMEDIATE;
9925 options->allow_credentials.clear();
9926 options->timeout = kTestTimeout;
9927
9928 mojo::Remote<blink::mojom::Authenticator> authenticator =
9929 ConnectToAuthenticator();
9930 TestGetCredentialFuture future;
9931
9932 authenticator->GetCredential(std::move(options), future.GetCallback());
9933 // Fast forward by the immediate mediation timeout.
9934 task_environment()->FastForwardBy(kImmediateTimeout + base::Milliseconds(1));
9935
9936 // The request should NOT be complete yet because UI is displayed,
9937 // which bypasses the immediate timeout.
9938 EXPECT_FALSE(future.IsReady());
9939 histogram_tester.ExpectUniqueSample(
9940 "WebAuthentication.GetAssertion.Immediate.TimeoutWhileWaitingForUi",
9941 false, 1);
9942 test_client_.delegate_config.run_cancel_ui_timeout_callback = false;
9943}
9944
Adam Langley76e65092022-02-23 23:26:039945// AuthenticatorCableV2Test tests features of the caBLEv2 transport and
9946// protocol.
Adam Langley63b34812023-06-05 23:08:199947class AuthenticatorCableV2Test : public AuthenticatorImplRequestDelegateTest {
Adam Langley0e3a6422020-09-25 18:36:389948 public:
Adam Langley0e3a6422020-09-25 18:36:389949 void SetUp() override {
9950 AuthenticatorImplTest::SetUp();
9951
Adam Langley0e3a6422020-09-25 18:36:389952 NavigateAndCommit(GURL(kTestOrigin1));
Nina Satragnoe7188af2024-04-08 20:07:439953 ResetNetworkService();
Adam Langley0e3a6422020-09-25 18:36:389954
Adam Langley7c603212021-04-15 02:23:539955 old_client_ = SetBrowserClientForTesting(&browser_client_);
9956
Adam Langley0e3a6422020-09-25 18:36:389957 bssl::UniquePtr<EC_GROUP> p256(
9958 EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
9959 bssl::UniquePtr<EC_KEY> peer_identity(EC_KEY_derive_from_secret(
9960 p256.get(), zero_seed_.data(), zero_seed_.size()));
9961 CHECK_EQ(sizeof(peer_identity_x962_),
9962 EC_POINT_point2oct(
9963 p256.get(), EC_KEY_get0_public_key(peer_identity.get()),
9964 POINT_CONVERSION_UNCOMPRESSED, peer_identity_x962_,
9965 sizeof(peer_identity_x962_), /*ctx=*/nullptr));
Adam Langleybd604442021-03-31 00:39:179966
9967 std::tie(ble_advert_callback_, ble_advert_events_) =
9968 device::cablev2::Discovery::AdvertEventStream::New();
Adam Langley0e3a6422020-09-25 18:36:389969 }
9970
Adam Langley7c603212021-04-15 02:23:539971 void TearDown() override {
Adam Langleya13992e2022-03-08 00:18:519972 // Ensure that all pending caBLE connections have timed out and closed.
9973 task_environment()->FastForwardBy(base::Minutes(10));
9974
Adam Langley7c603212021-04-15 02:23:539975 SetBrowserClientForTesting(old_client_);
9976 AuthenticatorImplTest::TearDown();
Adam Langley8d249872022-03-07 23:16:069977
9978 // All `EstablishedConnection` instances should have been destroyed.
9979 CHECK_EQ(device::cablev2::FidoTunnelDevice::
9980 GetNumEstablishedConnectionInstancesForTesting(),
9981 0);
Adam Langley7c603212021-04-15 02:23:539982 }
9983
Adam Langleya08e1132022-03-07 23:28:289984 base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)>
Adam Langley0e3a6422020-09-25 18:36:389985 GetPairingCallback() {
Adam Langleya08e1132022-03-07 23:28:289986 return base::BindRepeating(&AuthenticatorCableV2Test::OnNewPairing,
9987 base::Unretained(this));
9988 }
9989
Nina Satragnoff3baed2023-08-03 20:24:409990 base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)>
9991 GetInvalidatedPairingCallback() {
Adam Langleya08e1132022-03-07 23:28:289992 return base::BindRepeating(&AuthenticatorCableV2Test::OnInvalidatedPairing,
Adam Langley0e3a6422020-09-25 18:36:389993 base::Unretained(this));
9994 }
9995
Adam Langley63b34812023-06-05 23:08:199996 base::RepeatingCallback<void(Event)> GetEventCallback() {
9997 return base::BindRepeating(&AuthenticatorCableV2Test::OnCableEvent,
9998 base::Unretained(this));
9999 }
10000
10001 void EnableConnectionSignalAtTunnelServer() {
10002 // Recreate the tunnel server so that it supports the connection signal.
10003 network_context_ = device::cablev2::NewMockTunnelServer(
10004 base::BindRepeating(&AuthenticatorCableV2Test::OnContact,
10005 base::Unretained(this)),
10006 /*supports_connect_signal=*/true);
10007 }
10008
Adam Langley0e3a6422020-09-25 18:36:3810009 protected:
10010 class DiscoveryFactory : public device::FidoDiscoveryFactory {
10011 public:
10012 explicit DiscoveryFactory(
10013 std::unique_ptr<device::cablev2::Discovery> discovery)
10014 : discovery_(std::move(discovery)) {}
10015
10016 std::vector<std::unique_ptr<device::FidoDiscoveryBase>> Create(
10017 device::FidoTransportProtocol transport) override {
Adam Langleycbafdd4f2022-08-15 02:48:2310018 if (transport != device::FidoTransportProtocol::kHybrid || !discovery_) {
Adam Langley0e3a6422020-09-25 18:36:3810019 return {};
10020 }
10021
10022 return SingleDiscovery(std::move(discovery_));
10023 }
10024
10025 private:
10026 std::unique_ptr<device::cablev2::Discovery> discovery_;
10027 };
10028
Adam Langley76e65092022-02-23 23:26:0310029 class TestAuthenticationDelegate : public WebAuthenticationDelegate {
10030 public:
10031 bool SupportsResidentKeys(RenderFrameHost*) override { return true; }
10032
10033 bool IsFocused(WebContents* web_contents) override { return true; }
10034 };
10035
Adam Langley7c603212021-04-15 02:23:5310036 class ContactWhenReadyAuthenticatorRequestDelegate
10037 : public AuthenticatorRequestClientDelegate {
10038 public:
10039 explicit ContactWhenReadyAuthenticatorRequestDelegate(
10040 base::RepeatingClosure callback)
10041 : callback_(callback) {}
10042 ~ContactWhenReadyAuthenticatorRequestDelegate() override = default;
10043
10044 void OnTransportAvailabilityEnumerated(
10045 device::FidoRequestHandlerBase::TransportAvailabilityInfo) override {
10046 callback_.Run();
10047 }
10048
10049 private:
10050 base::RepeatingClosure callback_;
10051 };
10052
10053 class ContactWhenReadyContentBrowserClient : public ContentBrowserClient {
10054 public:
10055 explicit ContactWhenReadyContentBrowserClient(
10056 base::RepeatingClosure callback)
10057 : callback_(callback) {}
10058
10059 std::unique_ptr<AuthenticatorRequestClientDelegate>
10060 GetWebAuthenticationRequestDelegate(
10061 RenderFrameHost* render_frame_host) override {
10062 return std::make_unique<ContactWhenReadyAuthenticatorRequestDelegate>(
10063 callback_);
10064 }
10065
Adam Langley76e65092022-02-23 23:26:0310066 WebAuthenticationDelegate* GetWebAuthenticationDelegate() override {
10067 return &authentication_delegate_;
10068 }
10069
Adam Langley7c603212021-04-15 02:23:5310070 private:
10071 base::RepeatingClosure callback_;
Adam Langley76e65092022-02-23 23:26:0310072 TestAuthenticationDelegate authentication_delegate_;
Adam Langley7c603212021-04-15 02:23:5310073 };
10074
10075 // MaybeContactPhones is called when OnTransportAvailabilityEnumerated is
10076 // called by the request handler.
10077 void MaybeContactPhones() {
10078 if (maybe_contact_phones_callback_) {
10079 std::move(maybe_contact_phones_callback_).Run();
10080 }
10081 }
10082
Adam Langley0e3a6422020-09-25 18:36:3810083 void OnContact(
10084 base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
Adam Langley670c6c1b2021-04-01 23:46:4510085 base::span<const uint8_t, device::cablev2::kPairingIDSize> pairing_id,
Adam Langley0a145aa32021-04-23 20:23:5810086 base::span<const uint8_t, device::cablev2::kClientNonceSize> client_nonce,
10087 const std::string& request_type_hint) {
10088 std::move(contact_callback_)
10089 .Run(tunnel_id, pairing_id, client_nonce, request_type_hint);
Adam Langley0e3a6422020-09-25 18:36:3810090 }
10091
Adam Langleya08e1132022-03-07 23:28:2810092 void OnNewPairing(std::unique_ptr<device::cablev2::Pairing> pairing) {
10093 pairings_.emplace_back(std::move(pairing));
10094 }
10095
Nina Satragnoff3baed2023-08-03 20:24:4010096 void OnInvalidatedPairing(
10097 std::unique_ptr<device::cablev2::Pairing> disabled_pairing) {
Peter Kasting1557e5f2025-01-28 01:14:0810098 pairings_.erase(std::ranges::find_if(
Nina Satragnoff3baed2023-08-03 20:24:4010099 pairings_, [&disabled_pairing](const auto& pairing) {
10100 return device::cablev2::Pairing::EqualPublicKeys(pairing,
10101 disabled_pairing);
10102 }));
Adam Langley0e3a6422020-09-25 18:36:3810103 }
10104
Adam Langley63b34812023-06-05 23:08:1910105 void OnCableEvent(Event event) { events_.push_back(event); }
10106
10107 void DoPairingConnection() {
10108 // First do unpaired exchange to get pairing data.
10109 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310110 device::FidoRequestType::kGetAssertion,
10111 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
Adam Langley63b34812023-06-05 23:08:1910112 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley63b34812023-06-05 23:08:1910113 /*contact_device_stream=*/nullptr,
10114 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
10115 GetPairingCallback(), GetInvalidatedPairingCallback(),
Adam Langleyeef90c22024-08-06 18:45:5710116 GetEventCallback(), /*must_support_ctap=*/true);
Adam Langley63b34812023-06-05 23:08:1910117
Jagadesh P8db17bf2023-11-28 04:14:1510118 ReplaceDiscoveryFactory(
10119 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley63b34812023-06-05 23:08:1910120
10121 const std::vector<uint8_t> contact_id(/*count=*/200, /*value=*/1);
10122 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
10123 device::cablev2::authenticator::TransactFromQRCode(
10124 device::cablev2::authenticator::NewMockPlatform(
10125 std::move(ble_advert_callback_), &virtual_device_,
10126 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310127 base::BindLambdaForTesting(
10128 [&]() { return network_context_.get(); }),
10129 root_secret_, "Test Authenticator", zero_qr_secret_,
10130 peer_identity_x962_, contact_id);
Adam Langley63b34812023-06-05 23:08:1910131
10132 EXPECT_EQ(AuthenticatorMakeCredential().status,
10133 AuthenticatorStatus::SUCCESS);
10134 EXPECT_EQ(pairings_.size(), 1u);
10135
10136 // Now do a pairing-based exchange. Generate a random request type hint to
10137 // ensure that all values work.
10138 device::FidoRequestType request_type =
10139 device::FidoRequestType::kMakeCredential;
10140 std::string expected_request_type_string = "mc";
10141 if (base::RandDouble() < 0.5) {
10142 request_type = device::FidoRequestType::kGetAssertion;
10143 expected_request_type_string = "ga";
10144 }
10145
10146 std::tie(ble_advert_callback_, ble_advert_events_) =
10147 device::cablev2::Discovery::EventStream<
10148 base::span<const uint8_t, device::cablev2::kAdvertSize>>::New();
Nina Satragnoff3baed2023-08-03 20:24:4010149 auto callback_and_event_stream = device::cablev2::Discovery::EventStream<
10150 std::unique_ptr<device::cablev2::Pairing>>::New();
Adam Langley63b34812023-06-05 23:08:1910151 discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310152 request_type,
10153 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10154 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley63b34812023-06-05 23:08:1910155 std::move(callback_and_event_stream.second),
10156 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
10157 GetPairingCallback(), GetInvalidatedPairingCallback(),
Adam Langleyeef90c22024-08-06 18:45:5710158 GetEventCallback(), /*must_support_ctap=*/true);
Adam Langley63b34812023-06-05 23:08:1910159
Nina Satragnoff3baed2023-08-03 20:24:4010160 maybe_contact_phones_callback_ = base::BindLambdaForTesting([&]() {
10161 callback_and_event_stream.first.Run(
10162 std::make_unique<device::cablev2::Pairing>(*pairings_[0]));
10163 });
Adam Langley63b34812023-06-05 23:08:1910164
10165 const std::array<uint8_t, device::cablev2::kRoutingIdSize> routing_id = {0};
Adem Derinele6378762024-06-27 06:05:0710166 bool contact_callback_IsReady = false;
Adam Langley63b34812023-06-05 23:08:1910167 // When the |cablev2::Discovery| starts it'll make a connection to the
10168 // tunnel service with the contact ID from the pairing data. This will be
10169 // handled by the |TestNetworkContext| and turned into a call to
10170 // |contact_callback_|. This simulates the tunnel server sending a cloud
10171 // message to a phone. Given the information from the connection, a
10172 // transaction can be created.
10173 contact_callback_ = base::BindLambdaForTesting(
Adem Derinele6378762024-06-27 06:05:0710174 [this, &transaction, routing_id, contact_id, &contact_callback_IsReady,
10175 &expected_request_type_string](
Adam Langley63b34812023-06-05 23:08:1910176 base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
10177 base::span<const uint8_t, device::cablev2::kPairingIDSize>
10178 pairing_id,
10179 base::span<const uint8_t, device::cablev2::kClientNonceSize>
10180 client_nonce,
10181 const std::string& request_type_hint) -> void {
Adem Derinele6378762024-06-27 06:05:0710182 contact_callback_IsReady = true;
Adam Langley63b34812023-06-05 23:08:1910183 CHECK_EQ(request_type_hint, expected_request_type_string);
10184 transaction = device::cablev2::authenticator::TransactFromFCM(
10185 device::cablev2::authenticator::NewMockPlatform(
10186 std::move(ble_advert_callback_), &virtual_device_,
10187 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310188 base::BindLambdaForTesting(
10189 [&]() { return network_context_.get(); }),
10190 root_secret_, routing_id, tunnel_id, pairing_id, client_nonce,
10191 contact_id);
Adam Langley63b34812023-06-05 23:08:1910192 });
10193
Jagadesh P8db17bf2023-11-28 04:14:1510194 ReplaceDiscoveryFactory(
10195 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley63b34812023-06-05 23:08:1910196
10197 EXPECT_EQ(AuthenticatorMakeCredential().status,
10198 AuthenticatorStatus::SUCCESS);
Adem Derinele6378762024-06-27 06:05:0710199 EXPECT_TRUE(contact_callback_IsReady);
Adam Langley63b34812023-06-05 23:08:1910200 }
10201
Nina Satragnoe7188af2024-04-08 20:07:4310202 void ResetNetworkService() {
10203 network_context_ = device::cablev2::NewMockTunnelServer(base::BindRepeating(
10204 &AuthenticatorCableV2Test::OnContact, base::Unretained(this)));
10205 }
10206
Adam Langley0e3a6422020-09-25 18:36:3810207 const std::array<uint8_t, device::cablev2::kRootSecretSize> root_secret_ = {
10208 0};
Adam Langley5c3cbb22020-09-29 00:35:1310209 const std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key_ = {
10210 0};
10211 const std::array<uint8_t, device::cablev2::kQRSecretSize> zero_qr_secret_ = {
10212 0};
10213 const std::array<uint8_t, device::cablev2::kQRSeedSize> zero_seed_ = {0};
Adam Langley0e3a6422020-09-25 18:36:3810214
Nina Satragnoe7188af2024-04-08 20:07:4310215 std::unique_ptr<network::mojom::NetworkContext> network_context_;
Arthur Sonzognife7eb632024-12-04 18:03:4710216 uint8_t peer_identity_x962_[device::kP256X962Length] = {};
Adam Langleyb8d77fc2023-02-15 23:27:4010217 device::VirtualCtap2Device virtual_device_{DeviceState(), DeviceConfig()};
Adam Langley0e3a6422020-09-25 18:36:3810218 std::vector<std::unique_ptr<device::cablev2::Pairing>> pairings_;
10219 base::OnceCallback<void(
10220 base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id,
Adam Langley670c6c1b2021-04-01 23:46:4510221 base::span<const uint8_t, device::cablev2::kPairingIDSize> pairing_id,
Adam Langley0a145aa32021-04-23 20:23:5810222 base::span<const uint8_t, device::cablev2::kClientNonceSize> client_nonce,
10223 const std::string& request_type_hint)>
Adam Langley0e3a6422020-09-25 18:36:3810224 contact_callback_;
Adam Langleybd604442021-03-31 00:39:1710225 std::unique_ptr<device::cablev2::Discovery::AdvertEventStream>
10226 ble_advert_events_;
10227 device::cablev2::Discovery::AdvertEventStream::Callback ble_advert_callback_;
Adam Langleyb8d77fc2023-02-15 23:27:4010228 ContactWhenReadyContentBrowserClient browser_client_{
10229 base::BindRepeating(&AuthenticatorCableV2Test::MaybeContactPhones,
10230 base::Unretained(this))};
Keishi Hattori0e45c022021-11-27 09:25:5210231 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Adam Langley7c603212021-04-15 02:23:5310232 base::OnceClosure maybe_contact_phones_callback_;
Adam Langley63b34812023-06-05 23:08:1910233 std::vector<Event> events_;
Ken Buchanan19fb9c52021-12-03 20:15:5110234
10235 private:
Adam Langleyb8d77fc2023-02-15 23:27:4010236 static VirtualCtap2Device::State* DeviceState() {
10237 VirtualCtap2Device::State* state = new VirtualCtap2Device::State;
10238 state->fingerprints_enrolled = true;
Nina Satragno96397ea2023-11-30 19:51:3810239 state->default_backup_eligibility = true;
Adam Langleyb8d77fc2023-02-15 23:27:4010240 return state;
10241 }
10242
Adam Langley47a4a63f2022-01-25 15:14:5810243 static VirtualCtap2Device::Config DeviceConfig() {
10244 // `MockPlatform` uses a virtual device to answer requests, but it can't
10245 // handle the credential ID being omitted in responses.
10246 VirtualCtap2Device::Config ret;
10247 ret.include_credential_in_assertion_response =
10248 VirtualCtap2Device::Config::IncludeCredential::ALWAYS;
Adam Langleyb8d77fc2023-02-15 23:27:4010249 ret.prf_support = true;
10250 ret.internal_account_chooser = true;
10251 ret.internal_uv_support = true;
10252 ret.always_uv = true;
Adam Langley47a4a63f2022-01-25 15:14:5810253 return ret;
10254 }
Adam Langley0e3a6422020-09-25 18:36:3810255};
10256
Adam Langley42a89db2023-04-03 21:55:2710257TEST_F(AuthenticatorCableV2Test, QRBasedWithNoPairing) {
Adam Langley0e3a6422020-09-25 18:36:3810258 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310259 device::FidoRequestType::kGetAssertion,
10260 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
Adam Langley0a145aa32021-04-23 20:23:5810261 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley7c603212021-04-15 02:23:5310262 /*contact_device_stream=*/nullptr,
Adam Langleydf4231b2020-11-21 00:35:4010263 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langleyeef90c22024-08-06 18:45:5710264 GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(),
10265 /*must_support_ctap=*/true);
Adam Langley0e3a6422020-09-25 18:36:3810266
Jagadesh P8db17bf2023-11-28 04:14:1510267 ReplaceDiscoveryFactory(
10268 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley0e3a6422020-09-25 18:36:3810269
10270 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
10271 device::cablev2::authenticator::TransactFromQRCode(
Adam Langleybd604442021-03-31 00:39:1710272 device::cablev2::authenticator::NewMockPlatform(
Adam Langley76e65092022-02-23 23:26:0310273 std::move(ble_advert_callback_), &virtual_device_,
10274 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310275 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10276 root_secret_, "Test Authenticator", zero_qr_secret_,
10277 peer_identity_x962_,
Arthur Sonzognic686e8f2024-01-11 08:36:3710278 /*contact_id=*/std::nullopt);
Adam Langleycdccc9c12022-01-31 23:24:3710279
10280 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
10281 EXPECT_EQ(pairings_.size(), 0u);
10282}
10283
Adam Langley63b34812023-06-05 23:08:1910284TEST_F(AuthenticatorCableV2Test, HandshakeError) {
Adam Langley63b34812023-06-05 23:08:1910285 // A handshake error should be fatal to the request with
10286 // `kHybridTransportError`.
Nina Satragnoe7188af2024-04-08 20:07:4310287 auto network_context_factory =
10288 base::BindLambdaForTesting([&]() { return network_context_.get(); });
Adam Langley0e3a6422020-09-25 18:36:3810289 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310290 device::FidoRequestType::kGetAssertion, network_context_factory,
Adam Langley0a145aa32021-04-23 20:23:5810291 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley7c603212021-04-15 02:23:5310292 /*contact_device_stream=*/nullptr,
Adam Langleydf4231b2020-11-21 00:35:4010293 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langleyeef90c22024-08-06 18:45:5710294 GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(),
10295 /*must_support_ctap=*/true);
Adam Langley0e3a6422020-09-25 18:36:3810296
Jagadesh P8db17bf2023-11-28 04:14:1510297 ReplaceDiscoveryFactory(
10298 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley0e3a6422020-09-25 18:36:3810299
10300 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
Adam Langley63b34812023-06-05 23:08:1910301 device::cablev2::authenticator::NewHandshakeErrorDevice(
Adam Langleybd604442021-03-31 00:39:1710302 device::cablev2::authenticator::NewMockPlatform(
Adam Langley76e65092022-02-23 23:26:0310303 std::move(ble_advert_callback_), &virtual_device_,
10304 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310305 network_context_factory, zero_qr_secret_);
Adam Langley0e3a6422020-09-25 18:36:3810306
Adem Derinele6378762024-06-27 06:05:0710307 FailureReasonFuture failure_reason_future;
Adam Langley63b34812023-06-05 23:08:1910308 auto mock_delegate = std::make_unique<
10309 ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>(
Adem Derinele6378762024-06-27 06:05:0710310 failure_reason_future.GetCallback());
Adam Langley63b34812023-06-05 23:08:1910311 auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate));
Adam Langley0e3a6422020-09-25 18:36:3810312
Adem Derinele6378762024-06-27 06:05:0710313 TestMakeCredentialFuture future;
Adam Langley63b34812023-06-05 23:08:1910314 authenticator->MakeCredential(GetTestPublicKeyCredentialCreationOptions(),
Adem Derinele6378762024-06-27 06:05:0710315 future.GetCallback());
Adam Langley0a145aa32021-04-23 20:23:5810316
Adem Derinele6378762024-06-27 06:05:0710317 EXPECT_TRUE(future.Wait());
10318 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, std::get<0>(future.Get()));
Adam Langley0e3a6422020-09-25 18:36:3810319
Adem Derinele6378762024-06-27 06:05:0710320 ASSERT_TRUE(failure_reason_future.IsReady());
Adam Langley63b34812023-06-05 23:08:1910321 EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason::
10322 kHybridTransportError,
Adem Derinele6378762024-06-27 06:05:0710323 failure_reason_future.Get());
Adam Langley63b34812023-06-05 23:08:1910324}
Adam Langley7c603212021-04-15 02:23:5310325
Nina Satragnoe7188af2024-04-08 20:07:4310326// Test having the network service crash between creating a discovery and
10327// performing a cable transaction. Regression test for crbug.com/332724843.
10328TEST_F(AuthenticatorCableV2Test, NetworkServiceCrash) {
10329 auto discovery = std::make_unique<device::cablev2::Discovery>(
10330 device::FidoRequestType::kGetAssertion,
10331 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10332 qr_generator_key_, std::move(ble_advert_events_),
10333 /*contact_device_stream=*/nullptr,
10334 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langleyeef90c22024-08-06 18:45:5710335 GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(),
10336 /*must_support_ctap=*/true);
Nina Satragnoe7188af2024-04-08 20:07:4310337
10338 ReplaceDiscoveryFactory(
10339 std::make_unique<DiscoveryFactory>(std::move(discovery)));
10340
10341 // Simulate the network service restarting.
10342 ResetNetworkService();
10343
10344 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
10345 device::cablev2::authenticator::TransactFromQRCode(
10346 device::cablev2::authenticator::NewMockPlatform(
10347 std::move(ble_advert_callback_), &virtual_device_,
10348 /*observer=*/nullptr),
10349 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10350 root_secret_, "Test Authenticator", zero_qr_secret_,
10351 peer_identity_x962_,
10352 /*contact_id=*/std::nullopt);
10353
10354 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
10355 EXPECT_EQ(pairings_.size(), 0u);
10356}
10357
Adam Langley63b34812023-06-05 23:08:1910358TEST_F(AuthenticatorCableV2Test, PairingBased) {
10359 DoPairingConnection();
Adam Langley0e3a6422020-09-25 18:36:3810360
Adam Langley63b34812023-06-05 23:08:1910361 const std::vector<Event> kExpectedEvents = {
10362 // From the QR connection
10363 Event::kBLEAdvertReceived,
10364 Event::kReady,
10365 // From the paired connection
10366 Event::kBLEAdvertReceived,
10367 Event::kReady,
10368 };
10369 EXPECT_EQ(events_, kExpectedEvents);
10370}
Adam Langley0e3a6422020-09-25 18:36:3810371
Adam Langley63b34812023-06-05 23:08:1910372TEST_F(AuthenticatorCableV2Test, PairingBasedWithConnectionSignal) {
10373 EnableConnectionSignalAtTunnelServer();
10374 DoPairingConnection();
10375
10376 const std::vector<Event> kExpectedEvents = {
10377 // From the QR connection
10378 Event::kBLEAdvertReceived,
10379 Event::kReady,
10380 // From the paired connection
10381 Event::kPhoneConnected,
10382 Event::kBLEAdvertReceived,
10383 Event::kReady,
10384 };
10385 EXPECT_EQ(events_, kExpectedEvents);
Adam Langley0e3a6422020-09-25 18:36:3810386}
10387
Adam Langleyf05091c2020-11-17 02:54:4710388static std::unique_ptr<device::cablev2::Pairing> DummyPairing() {
10389 auto ret = std::make_unique<device::cablev2::Pairing>();
Nina Satragno60af84df2023-08-14 19:25:2810390 ret->tunnel_server_domain = device::cablev2::kTunnelServer;
Adam Langleyf05091c2020-11-17 02:54:4710391 ret->contact_id = {1, 2, 3, 4, 5};
10392 ret->id = {6, 7, 8, 9};
10393 ret->secret = {10, 11, 12, 13};
10394 std::fill(ret->peer_public_key_x962.begin(), ret->peer_public_key_x962.end(),
10395 22);
10396 ret->name = __func__;
10397
10398 return ret;
10399}
10400
Adam Langley42a89db2023-04-03 21:55:2710401TEST_F(AuthenticatorCableV2Test, ContactIDDisabled) {
Adam Langleyf05091c2020-11-17 02:54:4710402 // Passing |nullopt| as the callback here causes all contact IDs to be
10403 // rejected.
Nina Satragnoe7188af2024-04-08 20:07:4310404 network_context_ = device::cablev2::NewMockTunnelServer(std::nullopt);
Nina Satragnoff3baed2023-08-03 20:24:4010405 auto callback_and_event_stream = device::cablev2::Discovery::EventStream<
10406 std::unique_ptr<device::cablev2::Pairing>>::New();
Adam Langleyf05091c2020-11-17 02:54:4710407 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310408 device::FidoRequestType::kGetAssertion,
10409 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
Nina Satragnoff3baed2023-08-03 20:24:4010410 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley0a145aa32021-04-23 20:23:5810411 std::move(callback_and_event_stream.second),
Adam Langleydf4231b2020-11-21 00:35:4010412 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langleyeef90c22024-08-06 18:45:5710413 GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(),
10414 /*must_support_ctap=*/true);
Adam Langleyf05091c2020-11-17 02:54:4710415
Jagadesh P8db17bf2023-11-28 04:14:1510416 ReplaceDiscoveryFactory(
10417 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langleyf05091c2020-11-17 02:54:4710418
Adam Langley7c603212021-04-15 02:23:5310419 maybe_contact_phones_callback_ =
10420 base::BindLambdaForTesting([&callback_and_event_stream]() {
Nina Satragnoff3baed2023-08-03 20:24:4010421 callback_and_event_stream.first.Run(DummyPairing());
Adam Langley7c603212021-04-15 02:23:5310422 });
10423
Adam Langleyf05091c2020-11-17 02:54:4710424 pairings_.emplace_back(DummyPairing());
10425 ASSERT_EQ(pairings_.size(), 1u);
10426 EXPECT_EQ(AuthenticatorMakeCredentialAndWaitForTimeout(
10427 GetTestPublicKeyCredentialCreationOptions())
10428 .status,
10429 AuthenticatorStatus::NOT_ALLOWED_ERROR);
10430 // The pairing should be been erased because of the signal from the tunnel
10431 // server.
10432 ASSERT_EQ(pairings_.size(), 0u);
10433}
10434
Adam Langley9cc71482022-02-24 21:26:3710435// ServerLinkValues contains keys that mimic those created by a site doing
10436// caBLEv2 server-link.
10437struct ServerLinkValues {
10438 // This value would be provided by the site to the desktop, in a caBLE
10439 // extension in the get() call.
10440 device::CableDiscoveryData desktop_side;
10441
10442 // These values would be provided to the phone via a custom mechanism.
10443 std::array<uint8_t, device::cablev2::kQRSecretSize> secret;
10444 std::array<uint8_t, device::kP256X962Length> peer_identity;
10445};
10446
10447// CreateServerLink simulates a site doing caBLEv2 server-link and calculates
10448// server-link values that could be sent to the desktop and phone sides of a
10449// transaction.
10450static ServerLinkValues CreateServerLink() {
10451 std::vector<uint8_t> seed(device::cablev2::kQRSeedSize);
danakj95305d272024-05-09 20:38:4410452 base::RandBytes(seed);
Adam Langley9cc71482022-02-24 21:26:3710453
10454 bssl::UniquePtr<EC_GROUP> p256(
10455 EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
10456 bssl::UniquePtr<EC_KEY> ec_key(
10457 EC_KEY_derive_from_secret(p256.get(), seed.data(), seed.size()));
10458
10459 ServerLinkValues ret;
danakj95305d272024-05-09 20:38:4410460 base::RandBytes(ret.secret);
Adam Langley9cc71482022-02-24 21:26:3710461 CHECK_EQ(ret.peer_identity.size(),
10462 EC_POINT_point2oct(p256.get(), EC_KEY_get0_public_key(ec_key.get()),
10463 POINT_CONVERSION_UNCOMPRESSED,
10464 ret.peer_identity.data(),
10465 ret.peer_identity.size(), /*ctx=*/nullptr));
10466
10467 ret.desktop_side.version = device::CableDiscoveryData::Version::V2;
Adam Langleyae765532022-04-19 21:18:3610468 ret.desktop_side.v2.emplace(seed, std::vector<uint8_t>());
10469 ret.desktop_side.v2->server_link_data.insert(
10470 ret.desktop_side.v2->server_link_data.end(), ret.secret.begin(),
10471 ret.secret.end());
Adam Langley9cc71482022-02-24 21:26:3710472
10473 return ret;
10474}
10475
Adam Langley42a89db2023-04-03 21:55:2710476TEST_F(AuthenticatorCableV2Test, ServerLink) {
Adam Langley9cc71482022-02-24 21:26:3710477 const ServerLinkValues server_link_1 = CreateServerLink();
10478 const ServerLinkValues server_link_2 = CreateServerLink();
10479 const std::vector<device::CableDiscoveryData> extension_values = {
10480 server_link_1.desktop_side, server_link_2.desktop_side};
10481
10482 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310483 device::FidoRequestType::kGetAssertion,
10484 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
Adam Langley9cc71482022-02-24 21:26:3710485 qr_generator_key_, std::move(ble_advert_events_),
Adam Langleya08e1132022-03-07 23:28:2810486 /*contact_device_stream=*/nullptr, extension_values, GetPairingCallback(),
Adam Langleyeef90c22024-08-06 18:45:5710487 GetInvalidatedPairingCallback(), GetEventCallback(),
10488 /*must_support_ctap=*/true);
Adam Langley9cc71482022-02-24 21:26:3710489
Jagadesh P8db17bf2023-11-28 04:14:1510490 ReplaceDiscoveryFactory(
10491 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley9cc71482022-02-24 21:26:3710492
10493 // Both extension values should work, but we can only do a single transaction
10494 // per test because a lot of state is setup for a test. Therefore pick one of
10495 // the two to check, at random.
10496 const auto& server_link =
10497 (base::RandUint64() & 1) ? server_link_1 : server_link_2;
10498
10499 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
10500 device::cablev2::authenticator::TransactFromQRCode(
10501 device::cablev2::authenticator::NewMockPlatform(
10502 std::move(ble_advert_callback_), &virtual_device_,
10503 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310504 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10505 root_secret_, "Test Authenticator", server_link.secret,
10506 server_link.peer_identity,
Arthur Sonzognic686e8f2024-01-11 08:36:3710507 /*contact_id=*/std::nullopt);
Adam Langley9cc71482022-02-24 21:26:3710508
10509 EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS);
10510 EXPECT_EQ(pairings_.size(), 0u);
10511}
10512
Adam Langley42a89db2023-04-03 21:55:2710513TEST_F(AuthenticatorCableV2Test, LateLinking) {
Nina Satragnoe7188af2024-04-08 20:07:4310514 auto network_context_factory =
10515 base::BindLambdaForTesting([&]() { return network_context_.get(); });
Adam Langleya13992e2022-03-08 00:18:5110516 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310517 device::FidoRequestType::kGetAssertion, network_context_factory,
Adam Langleya13992e2022-03-08 00:18:5110518 qr_generator_key_, std::move(ble_advert_events_),
Adam Langleya13992e2022-03-08 00:18:5110519 /*contact_device_stream=*/nullptr,
10520 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langleyeef90c22024-08-06 18:45:5710521 GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(),
10522 /*must_support_ctap=*/true);
Adam Langleya13992e2022-03-08 00:18:5110523
Jagadesh P8db17bf2023-11-28 04:14:1510524 ReplaceDiscoveryFactory(
10525 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langleya13992e2022-03-08 00:18:5110526
10527 const std::vector<uint8_t> contact_id(/*count=*/200, /*value=*/1);
10528 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction =
10529 device::cablev2::authenticator::NewLateLinkingDevice(
10530 device::CtapDeviceResponseCode::kCtap2ErrOperationDenied,
10531 device::cablev2::authenticator::NewMockPlatform(
10532 std::move(ble_advert_callback_), &virtual_device_,
10533 /*observer=*/nullptr),
Nina Satragnoe7188af2024-04-08 20:07:4310534 network_context_factory, zero_qr_secret_, peer_identity_x962_);
Adam Langleya13992e2022-03-08 00:18:5110535
10536 EXPECT_EQ(AuthenticatorMakeCredential().status,
10537 AuthenticatorStatus::NOT_ALLOWED_ERROR);
10538
10539 // There should not be any pairing at this point because the device shouldn't
10540 // have sent the information yet.
10541 EXPECT_EQ(pairings_.size(), 0u);
10542
10543 // After 30 seconds, a pairing should have been recorded even though the
10544 // WebAuthn request has completed.
10545 task_environment()->FastForwardBy(base::Seconds(30));
10546 EXPECT_EQ(pairings_.size(), 1u);
10547}
10548
Adam Langley76e65092022-02-23 23:26:0310549// AuthenticatorCableV2AuthenticatorTest tests aspects of the authenticator
10550// implementation, rather than of the underlying caBLEv2 transport.
10551class AuthenticatorCableV2AuthenticatorTest
10552 : public AuthenticatorCableV2Test,
10553 public device::cablev2::authenticator::Observer {
10554 public:
10555 void SetUp() override {
10556 AuthenticatorCableV2Test::SetUp();
10557
10558 auto discovery = std::make_unique<device::cablev2::Discovery>(
Nina Satragnoe7188af2024-04-08 20:07:4310559 device::FidoRequestType::kGetAssertion,
10560 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
Adam Langley76e65092022-02-23 23:26:0310561 qr_generator_key_, std::move(ble_advert_events_),
Adam Langley76e65092022-02-23 23:26:0310562 /*contact_device_stream=*/nullptr,
10563 /*extension_contents=*/std::vector<device::CableDiscoveryData>(),
Adam Langley63b34812023-06-05 23:08:1910564 GetPairingCallback(), GetInvalidatedPairingCallback(),
Adam Langleyeef90c22024-08-06 18:45:5710565 GetEventCallback(), /*must_support_ctap=*/true);
Adam Langley76e65092022-02-23 23:26:0310566
Jagadesh P8db17bf2023-11-28 04:14:1510567 ReplaceDiscoveryFactory(
10568 std::make_unique<DiscoveryFactory>(std::move(discovery)));
Adam Langley76e65092022-02-23 23:26:0310569
10570 transaction_ = device::cablev2::authenticator::TransactFromQRCode(
10571 device::cablev2::authenticator::NewMockPlatform(
10572 std::move(ble_advert_callback_), &virtual_device_, this),
Nina Satragnoe7188af2024-04-08 20:07:4310573 base::BindLambdaForTesting([&]() { return network_context_.get(); }),
10574 root_secret_, "Test Authenticator", zero_qr_secret_,
10575 peer_identity_x962_,
Arthur Sonzognic686e8f2024-01-11 08:36:3710576 /*contact_id=*/std::nullopt);
Adam Langley76e65092022-02-23 23:26:0310577 }
10578
10579 protected:
10580 // device::cablev2::authenticator::Observer
10581 void OnStatus(device::cablev2::authenticator::Platform::Status) override {}
10582 void OnCompleted(
Arthur Sonzognic686e8f2024-01-11 08:36:3710583 std::optional<device::cablev2::authenticator::Platform::Error> error)
Adam Langley76e65092022-02-23 23:26:0310584 override {
Adam Langley3fc8a522022-03-07 23:51:1710585 CHECK(!did_complete_);
Adam Langley76e65092022-02-23 23:26:0310586 did_complete_ = true;
10587 error_ = error;
10588 }
10589
10590 std::unique_ptr<device::cablev2::authenticator::Transaction> transaction_;
10591 bool did_complete_ = false;
Arthur Sonzognic686e8f2024-01-11 08:36:3710592 std::optional<device::cablev2::authenticator::Platform::Error> error_;
Adam Langley76e65092022-02-23 23:26:0310593};
10594
10595TEST_F(AuthenticatorCableV2AuthenticatorTest, GetAssertion) {
10596 PublicKeyCredentialRequestOptionsPtr options =
10597 GetTestPublicKeyCredentialRequestOptions();
10598 options->allow_credentials[0].transports.insert(
Adam Langleycbafdd4f2022-08-15 02:48:2310599 device::FidoTransportProtocol::kHybrid);
Adam Langley76e65092022-02-23 23:26:0310600 ASSERT_TRUE(virtual_device_.mutable_state()->InjectRegistration(
10601 options->allow_credentials[0].id, options->relying_party_id));
10602
10603 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
10604 AuthenticatorStatus::SUCCESS);
10605}
10606
10607TEST_F(AuthenticatorCableV2AuthenticatorTest, MakeDiscoverableCredential) {
10608 auto options = GetTestPublicKeyCredentialCreationOptions();
10609 options->authenticator_selection->resident_key =
10610 device::ResidentKeyRequirement::kRequired;
10611 EXPECT_EQ(
10612 AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status,
10613 AuthenticatorStatus::NOT_ALLOWED_ERROR);
10614
10615 ASSERT_TRUE(did_complete_);
10616 ASSERT_TRUE(error_.has_value());
10617 EXPECT_EQ(*error_, device::cablev2::authenticator::Platform::Error::
Adam Langleyfb32dbf2022-08-16 01:08:5810618 DISCOVERABLE_CREDENTIALS_REQUEST);
Adam Langley76e65092022-02-23 23:26:0310619}
10620
10621TEST_F(AuthenticatorCableV2AuthenticatorTest, EmptyAllowList) {
10622 auto options = GetTestPublicKeyCredentialRequestOptions();
10623 options->allow_credentials.clear();
10624 EXPECT_EQ(
10625 AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status,
10626 AuthenticatorStatus::NOT_ALLOWED_ERROR);
10627
10628 ASSERT_TRUE(did_complete_);
10629 ASSERT_TRUE(error_.has_value());
10630 EXPECT_EQ(*error_, device::cablev2::authenticator::Platform::Error::
10631 DISCOVERABLE_CREDENTIALS_REQUEST);
10632}
10633
Adam Langleyb8d77fc2023-02-15 23:27:4010634TEST_F(AuthenticatorCableV2AuthenticatorTest, PRFMakeCredential) {
10635 auto options = GetTestPublicKeyCredentialCreationOptions();
10636 options->prf_enable = true;
10637
10638 const auto result = AuthenticatorMakeCredential(std::move(options));
10639
10640 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
10641 EXPECT_TRUE(result.response->echo_prf);
10642 EXPECT_TRUE(result.response->prf);
10643}
10644
Adam Langley1d1adaf2023-07-12 22:55:2610645static std::vector<uint8_t> HashPRFInput(base::span<const uint8_t> input) {
Elly2f1928d62025-07-14 15:31:2510646 crypto::hash::Hasher hasher(crypto::hash::kSha256);
10647 // clang-format off
10648 constexpr auto kPrefix = std::to_array<uint8_t>({
10649 'W', 'e', 'b', 'A', 'u', 't', 'h', 'n',
10650 ' ', 'P', 'R', 'F',
10651 0x00,
10652 });
10653 // clang-format on
10654 hasher.Update(kPrefix);
10655 hasher.Update(input);
10656 std::array<uint8_t, crypto::hash::kSha256Size> result;
10657 hasher.Finish(result);
10658 return base::ToVector(result);
Adam Langley1d1adaf2023-07-12 22:55:2610659}
10660
Adam Langleyb8d77fc2023-02-15 23:27:4010661static std::tuple<PublicKeyCredentialRequestOptionsPtr,
10662 std::vector<uint8_t>,
10663 std::vector<uint8_t>>
10664BuildPRFGetAssertion(device::VirtualCtap2Device& virtual_device,
10665 bool use_eval_by_credential) {
Adam Langley1d1adaf2023-07-12 22:55:2610666 const std::vector<uint8_t> input1(32, 1);
10667 const std::vector<uint8_t> input2(32, 2);
10668 const std::vector<uint8_t> salt1 = HashPRFInput(input1);
10669 const std::vector<uint8_t> salt2 = HashPRFInput(input2);
Adam Langleyb8d77fc2023-02-15 23:27:4010670 const std::array<uint8_t, 32> key1 = {1};
10671 const std::array<uint8_t, 32> key2 = {2};
Elly2f1928d62025-07-14 15:31:2510672 const std::array<uint8_t, 32> output1 = crypto::hmac::SignSha256(key2, salt1);
10673 const std::array<uint8_t, 32> output2 = crypto::hmac::SignSha256(key2, salt2);
Adam Langleyb8d77fc2023-02-15 23:27:4010674 auto options = GetTestPublicKeyCredentialRequestOptions();
10675
10676 CHECK(virtual_device.mutable_state()->InjectRegistration(
10677 options->allow_credentials[0].id, options->relying_party_id));
10678 virtual_device.mutable_state()
10679 ->registrations.begin()
10680 ->second.hmac_key.emplace(key1, key2);
10681
10682 std::vector<blink::mojom::PRFValuesPtr> prf_inputs;
10683 auto prf_value = blink::mojom::PRFValues::New();
Adam Langley1d1adaf2023-07-12 22:55:2610684 prf_value->first = input1;
10685 prf_value->second = input2;
Adam Langleyb8d77fc2023-02-15 23:27:4010686 if (use_eval_by_credential) {
10687 prf_value->id = options->allow_credentials[0].id;
10688 }
10689 prf_inputs.emplace_back(std::move(prf_value));
10690
10691 options->allow_credentials[0].transports.insert(
10692 device::FidoTransportProtocol::kHybrid);
Slobodan Pejicc8ce88b2023-06-19 15:41:1210693 options->extensions->prf = true;
10694 options->extensions->prf_inputs = std::move(prf_inputs);
Adam Langleyb8d77fc2023-02-15 23:27:4010695 options->user_verification = device::UserVerificationRequirement::kRequired;
10696
Elly450ad9fe2025-07-09 17:13:2210697 return std::make_tuple(std::move(options), base::ToVector(output1),
10698 base::ToVector(output2));
Adam Langleyb8d77fc2023-02-15 23:27:4010699}
10700
10701TEST_F(AuthenticatorCableV2AuthenticatorTest, PRFGetAssertion) {
10702 PublicKeyCredentialRequestOptionsPtr options;
10703 std::vector<uint8_t> output1, output2;
10704 std::tie(options, output1, output2) = BuildPRFGetAssertion(
10705 virtual_device_, /* use_eval_by_credential= */ false);
10706
10707 const auto result = AuthenticatorGetAssertion(std::move(options));
10708
10709 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:2510710 EXPECT_TRUE(result.response->extensions->echo_prf);
10711 EXPECT_TRUE(result.response->extensions->prf_results);
10712 EXPECT_EQ(result.response->extensions->prf_results->first, output1);
10713 ASSERT_TRUE(result.response->extensions->prf_results->second.has_value());
10714 EXPECT_EQ(*result.response->extensions->prf_results->second, output2);
Adam Langleyb8d77fc2023-02-15 23:27:4010715}
10716
10717TEST_F(AuthenticatorCableV2AuthenticatorTest, PRFGetAssertionByCredential) {
10718 PublicKeyCredentialRequestOptionsPtr options;
10719 std::vector<uint8_t> output1, output2;
10720 std::tie(options, output1, output2) =
10721 BuildPRFGetAssertion(virtual_device_, /* use_eval_by_credential= */ true);
10722
10723 const auto result = AuthenticatorGetAssertion(std::move(options));
10724
10725 ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Slobodan Pejica0f2b9d2023-09-28 01:56:2510726 EXPECT_TRUE(result.response->extensions->echo_prf);
10727 EXPECT_TRUE(result.response->extensions->prf_results);
10728 EXPECT_EQ(result.response->extensions->prf_results->first, output1);
10729 ASSERT_TRUE(result.response->extensions->prf_results->second.has_value());
10730 EXPECT_EQ(*result.response->extensions->prf_results->second, output2);
Adam Langleyb8d77fc2023-02-15 23:27:4010731}
10732
Martin Kreichgauer165ff722021-08-26 01:33:5210733// AuthenticatorImplWithRequestProxyTest tests behavior with an installed
10734// TestWebAuthenticationRequestProxy that takes over WebAuthn request handling.
10735class AuthenticatorImplWithRequestProxyTest : public AuthenticatorImplTest {
Martin Kreichgauerbf776d72022-04-18 23:38:1610736 protected:
Martin Kreichgauer165ff722021-08-26 01:33:5210737 void SetUp() override {
10738 AuthenticatorImplTest::SetUp();
10739 old_client_ = SetBrowserClientForTesting(&test_client_);
10740 test_client_.GetTestWebAuthenticationDelegate()->request_proxy =
10741 std::make_unique<TestWebAuthenticationRequestProxy>();
10742 }
10743
10744 void TearDown() override {
10745 SetBrowserClientForTesting(old_client_);
10746 AuthenticatorImplTest::TearDown();
10747 }
10748
10749 TestWebAuthenticationRequestProxy& request_proxy() {
10750 return static_cast<TestWebAuthenticationRequestProxy&>(
10751 *test_client_.GetTestWebAuthenticationDelegate()->request_proxy);
10752 }
10753
Keishi Hattori0e45c022021-11-27 09:25:5210754 raw_ptr<ContentBrowserClient> old_client_ = nullptr;
Martin Kreichgauer165ff722021-08-26 01:33:5210755 TestAuthenticatorContentBrowserClient test_client_;
10756};
10757
Martin Kreichgauer8c97189a2022-01-10 20:31:4310758TEST_F(AuthenticatorImplWithRequestProxyTest, Inactive) {
10759 request_proxy().config().is_active = false;
10760 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerca18b432023-04-25 18:58:4710761 AuthenticatorIsUvpaa();
Martin Kreichgauerbf776d72022-04-18 23:38:1610762 EXPECT_EQ(request_proxy().observations().num_isuvpaa, 0u);
Martin Kreichgauer8c97189a2022-01-10 20:31:4310763}
10764
Martin Kreichgauer165ff722021-08-26 01:33:5210765TEST_F(AuthenticatorImplWithRequestProxyTest, IsUVPAA) {
10766 size_t i = 0;
10767 for (const bool is_uvpaa : {false, true}) {
10768 SCOPED_TRACE(testing::Message() << "is_uvpaa=" << is_uvpaa);
10769 request_proxy().config().is_uvpaa = is_uvpaa;
10770 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerca18b432023-04-25 18:58:4710771 EXPECT_EQ(AuthenticatorIsUvpaa(), is_uvpaa);
Martin Kreichgauerbf776d72022-04-18 23:38:1610772 EXPECT_EQ(request_proxy().observations().num_isuvpaa, ++i);
Martin Kreichgauer165ff722021-08-26 01:33:5210773 }
10774}
10775
Nina Satragno867bca52022-10-13 15:02:0210776TEST_F(AuthenticatorImplWithRequestProxyTest, IsConditionalMediationAvailable) {
Martin Kreichgauerca18b432023-04-25 18:58:4710777 // We can't autofill credentials over the request proxy. Hence, conditional
10778 // mediation is unavailable, even if IsUVPAA returns true.
Nina Satragno867bca52022-10-13 15:02:0210779 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerca18b432023-04-25 18:58:4710780
10781 // Ensure there is no test override set and we're testing the real
10782 // implementation.
10783 ASSERT_EQ(test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override,
Arthur Sonzognic686e8f2024-01-11 08:36:3710784 std::nullopt);
Martin Kreichgauerca18b432023-04-25 18:58:4710785
10786 // Proxy says `IsUVPAA()` is true.
10787 request_proxy().config().is_uvpaa = true;
10788 EXPECT_TRUE(AuthenticatorIsUvpaa());
10789 EXPECT_EQ(request_proxy().observations().num_isuvpaa, 1u);
10790
10791 // But `IsConditionalMediationAvailable()` still returns false, bypassing the
10792 // proxy.
10793 EXPECT_FALSE(AuthenticatorIsConditionalMediationAvailable());
10794 EXPECT_EQ(request_proxy().observations().num_isuvpaa, 1u);
Nina Satragno867bca52022-10-13 15:02:0210795}
10796
Andrii Natiahlyi6b2f4b12024-09-03 14:58:4210797TEST_F(AuthenticatorImplWithRequestProxyTest,
10798 GetClientCapabilities_ConditionalGet_ReturnsFalse) {
10799 // We can't autofill credentials over the request proxy. Hence, conditional
10800 // mediation is unavailable, even if IsUVPAA returns true.
10801 NavigateAndCommit(GURL(kTestOrigin1));
10802 ASSERT_EQ(test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override,
10803 std::nullopt);
10804 request_proxy().config().is_uvpaa = true;
10805
10806 // Internally, `IsConditionalMediationAvailable()` should returns `false`,
10807 // bypassing the proxy.
10808 ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities();
10809 ExpectCapability(capabilities, client_capabilities::kConditionalGet, false);
10810}
10811
Martin Kreichgauer8c97189a2022-01-10 20:31:4310812TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredential) {
Martin Kreichgauer1beaff02022-02-02 18:58:4210813 request_proxy().config().request_success = true;
Martin Kreichgauer8c97189a2022-01-10 20:31:4310814 request_proxy().config().make_credential_response =
10815 MakeCredentialAuthenticatorResponse::New();
10816 request_proxy().config().make_credential_response->info =
10817 CommonCredentialInfo::New();
10818
Martin Kreichgauer165ff722021-08-26 01:33:5210819 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerbf776d72022-04-18 23:38:1610820 auto request = GetTestPublicKeyCredentialCreationOptions();
10821 MakeCredentialResult result = AuthenticatorMakeCredential(request->Clone());
Martin Kreichgauer8c97189a2022-01-10 20:31:4310822
10823 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Martin Kreichgauerbf776d72022-04-18 23:38:1610824 EXPECT_EQ(request_proxy().observations().num_cancel, 0u);
10825 EXPECT_EQ(request_proxy().observations().create_requests.size(), 1u);
10826
10827 auto expected = request->Clone();
10828 expected->remote_desktop_client_override = RemoteDesktopClientOverride::New();
10829 expected->remote_desktop_client_override->origin =
10830 url::Origin::Create(GURL(kTestOrigin1));
10831 expected->remote_desktop_client_override->same_origin_with_ancestors = true;
10832 EXPECT_EQ(request_proxy().observations().create_requests.at(0), expected);
Martin Kreichgauer165ff722021-08-26 01:33:5210833}
10834
Martin Kreichgauer26c2cec2022-01-26 01:00:5010835// Verify requests with an attached proxy run RP ID checks.
10836TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredentialOriginAndRpIds) {
Martin Kreichgauer1beaff02022-02-02 18:58:4210837 request_proxy().config().request_success = true;
Martin Kreichgauer26c2cec2022-01-26 01:00:5010838 request_proxy().config().make_credential_response =
10839 MakeCredentialAuthenticatorResponse::New();
10840 request_proxy().config().make_credential_response->info =
10841 CommonCredentialInfo::New();
10842
Adem Derinel620520b42024-11-04 15:45:4410843 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
10844 SCOPED_TRACE(
10845 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Martin Kreichgauer26c2cec2022-01-26 01:00:5010846
10847 NavigateAndCommit(GURL(test_case.origin));
Martin Kreichgauer328ef9f12022-12-22 10:45:5510848 BrowserContext* context = main_rfh()->GetBrowserContext();
Martin Kreichgauer1f4aa592023-01-06 18:39:3710849 ASSERT_TRUE(
10850 test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
10851 context, url::Origin::Create(GURL(test_case.origin))));
Martin Kreichgauer26c2cec2022-01-26 01:00:5010852
10853 PublicKeyCredentialCreationOptionsPtr options =
10854 GetTestPublicKeyCredentialCreationOptions();
10855 options->relying_party.id = test_case.claimed_authority;
10856
10857 EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
10858 test_case.expected_status);
Martin Kreichgauerbf776d72022-04-18 23:38:1610859 EXPECT_EQ(request_proxy().observations().create_requests.size(), 0u);
Martin Kreichgauer26c2cec2022-01-26 01:00:5010860 }
10861}
10862
Nina Satragno867bca52022-10-13 15:02:0210863// Tests that attempting to make a credential when a request is already proxied
10864// fails with NotAllowedError.
10865TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredentialAlreadyProxied) {
10866 GURL origin(kCorpCrdOrigin);
10867 test_client_.GetTestWebAuthenticationDelegate()
10868 ->remote_desktop_client_override_origin = url::Origin::Create(origin);
10869 NavigateAndCommit(origin);
10870 auto request = GetTestPublicKeyCredentialCreationOptions();
10871 request->remote_desktop_client_override =
10872 RemoteDesktopClientOverride::New(url::Origin::Create(origin), true);
10873 MakeCredentialResult result = AuthenticatorMakeCredential(std::move(request));
10874
10875 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
10876 EXPECT_EQ(request_proxy().observations().create_requests.size(), 0u);
10877}
10878
Martin Kreichgauer2d878b072022-05-02 18:33:1110879TEST_F(AuthenticatorImplWithRequestProxyTest, AppId) {
10880 request_proxy().config().request_success = true;
10881 request_proxy().config().make_credential_response =
10882 MakeCredentialAuthenticatorResponse::New();
10883 request_proxy().config().make_credential_response->info =
10884 CommonCredentialInfo::New();
10885
10886 for (const auto& test_case : kValidAppIdCases) {
Adem Derinel620520b42024-11-04 15:45:4410887 SCOPED_TRACE(
10888 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Martin Kreichgauer2d878b072022-05-02 18:33:1110889
Martin Kreichgauer328ef9f12022-12-22 10:45:5510890 BrowserContext* context = main_rfh()->GetBrowserContext();
Martin Kreichgauer1f4aa592023-01-06 18:39:3710891 ASSERT_TRUE(
10892 test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
10893 context, url::Origin::Create(GURL(test_case.origin))));
Martin Kreichgauer2d878b072022-05-02 18:33:1110894
10895 EXPECT_EQ(TryAuthenticationWithAppId(test_case.origin,
10896 test_case.claimed_authority),
10897 AuthenticatorStatus::SUCCESS);
10898 EXPECT_EQ(request_proxy().observations().get_requests.size(), 1u);
10899 request_proxy().observations().get_requests.clear();
10900
10901 EXPECT_EQ(TryRegistrationWithAppIdExclude(test_case.origin,
10902 test_case.claimed_authority),
10903 AuthenticatorStatus::SUCCESS);
10904 EXPECT_EQ(request_proxy().observations().create_requests.size(), 1u);
10905 request_proxy().observations().create_requests.clear();
10906 }
10907
Adem Derinel620520b42024-11-04 15:45:4410908 // Test invalid cases that should be rejected. `kInvalidRpTestCases`
Martin Kreichgauer2d878b072022-05-02 18:33:1110909 // contains a mix of RP ID an App ID cases, but they should all be rejected.
Adem Derinel620520b42024-11-04 15:45:4410910 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
10911 SCOPED_TRACE(
10912 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Martin Kreichgauer2d878b072022-05-02 18:33:1110913
Adem Derinel620520b42024-11-04 15:45:4410914 if (test_case.claimed_authority.empty()) {
Martin Kreichgauer2d878b072022-05-02 18:33:1110915 // In this case, no AppID is actually being tested.
10916 continue;
10917 }
10918
Martin Kreichgauer328ef9f12022-12-22 10:45:5510919 BrowserContext* context = main_rfh()->GetBrowserContext();
Martin Kreichgauer1f4aa592023-01-06 18:39:3710920 ASSERT_TRUE(
10921 test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
10922 context, url::Origin::Create(GURL(test_case.origin))));
Martin Kreichgauer2d878b072022-05-02 18:33:1110923
10924 AuthenticatorStatus test_status = TryAuthenticationWithAppId(
10925 test_case.origin, test_case.claimed_authority);
10926 EXPECT_TRUE(test_status == AuthenticatorStatus::INVALID_DOMAIN ||
10927 test_status == test_case.expected_status);
10928 EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u);
10929
10930 test_status = TryRegistrationWithAppIdExclude(test_case.origin,
10931 test_case.claimed_authority);
10932 EXPECT_TRUE(test_status == AuthenticatorStatus::INVALID_DOMAIN ||
10933 test_status == test_case.expected_status);
10934 EXPECT_EQ(request_proxy().observations().create_requests.size(), 0u);
10935 }
10936}
10937
Martin Kreichgauer8c97189a2022-01-10 20:31:4310938TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredential_Timeout) {
10939 request_proxy().config().resolve_callbacks = false;
Martin Kreichgauer1beaff02022-02-02 18:58:4210940 request_proxy().config().request_success = true;
Martin Kreichgauer8c97189a2022-01-10 20:31:4310941 request_proxy().config().make_credential_response =
10942 MakeCredentialAuthenticatorResponse::New();
10943 request_proxy().config().make_credential_response->info =
10944 CommonCredentialInfo::New();
10945
10946 NavigateAndCommit(GURL(kTestOrigin1));
10947 MakeCredentialResult result = AuthenticatorMakeCredentialAndWaitForTimeout(
10948 GetTestPublicKeyCredentialCreationOptions());
10949
10950 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauerbf776d72022-04-18 23:38:1610951 EXPECT_EQ(request_proxy().observations().create_requests.size(), 1u);
10952 EXPECT_EQ(request_proxy().observations().num_cancel, 1u);
Martin Kreichgauer8c97189a2022-01-10 20:31:4310953
Martin Kreichgauerb27f6312022-01-25 00:03:3210954 // Proxy should not hold a pending request after cancellation.
10955 EXPECT_FALSE(request_proxy().HasPendingRequest());
Martin Kreichgauer8c97189a2022-01-10 20:31:4310956}
Martin Kreichgauerb27f6312022-01-25 00:03:3210957
Martin Kreichgauer1beaff02022-02-02 18:58:4210958TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertion) {
10959 request_proxy().config().request_success = true;
10960 request_proxy().config().get_assertion_response =
10961 GetAssertionAuthenticatorResponse::New();
10962 request_proxy().config().get_assertion_response->info =
10963 CommonCredentialInfo::New();
Slobodan Pejica0f2b9d2023-09-28 01:56:2510964 request_proxy().config().get_assertion_response->extensions =
10965 AuthenticationExtensionsClientOutputs::New();
Martin Kreichgauer1beaff02022-02-02 18:58:4210966
10967 NavigateAndCommit(GURL(kTestOrigin1));
Martin Kreichgauerbf776d72022-04-18 23:38:1610968 auto request = GetTestPublicKeyCredentialRequestOptions();
10969 GetAssertionResult result = AuthenticatorGetAssertion(request->Clone());
Martin Kreichgauer1beaff02022-02-02 18:58:4210970
10971 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
Martin Kreichgauerbf776d72022-04-18 23:38:1610972 EXPECT_EQ(request_proxy().observations().num_cancel, 0u);
10973 EXPECT_EQ(request_proxy().observations().get_requests.size(), 1u);
10974
10975 auto expected = request->Clone();
Slobodan Pejicc8ce88b2023-06-19 15:41:1210976 expected->extensions->remote_desktop_client_override =
10977 RemoteDesktopClientOverride::New();
10978 expected->extensions->remote_desktop_client_override->origin =
Martin Kreichgauerbf776d72022-04-18 23:38:1610979 url::Origin::Create(GURL(kTestOrigin1));
Slobodan Pejicc8ce88b2023-06-19 15:41:1210980 expected->extensions->remote_desktop_client_override
10981 ->same_origin_with_ancestors = true;
Martin Kreichgauerbf776d72022-04-18 23:38:1610982 EXPECT_EQ(request_proxy().observations().get_requests.at(0), expected);
Martin Kreichgauer1beaff02022-02-02 18:58:4210983}
10984
Nina Satragno867bca52022-10-13 15:02:0210985// Tests that attempting to get an assertion when a request is already proxied
10986// fails with NotAllowedError.
10987TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertionAlreadyProxied) {
10988 GURL origin(kCorpCrdOrigin);
10989 test_client_.GetTestWebAuthenticationDelegate()
10990 ->remote_desktop_client_override_origin = url::Origin::Create(origin);
10991 NavigateAndCommit(origin);
10992 auto request = GetTestPublicKeyCredentialRequestOptions();
Slobodan Pejicc8ce88b2023-06-19 15:41:1210993 request->extensions->remote_desktop_client_override =
Nina Satragno867bca52022-10-13 15:02:0210994 RemoteDesktopClientOverride::New(url::Origin::Create(origin), true);
10995 GetAssertionResult result = AuthenticatorGetAssertion(std::move(request));
10996
10997 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
10998 EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u);
10999}
11000
11001// Verify that Conditional UI requests are not proxied.
11002TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertionConditionalUI) {
11003 NavigateAndCommit(GURL(kTestOrigin1));
11004 auto request = GetTestPublicKeyCredentialRequestOptions();
Adem Derinel2154d4b12025-02-04 12:29:5511005 request->mediation = blink::mojom::Mediation::CONDITIONAL;
Nina Satragno867bca52022-10-13 15:02:0211006 GetAssertionResult result = AuthenticatorGetAssertion(std::move(request));
11007
11008 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
11009 EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u);
11010}
11011
Martin Kreichgauer1beaff02022-02-02 18:58:4211012// Verify requests with an attached proxy run RP ID checks.
11013TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertionOriginAndRpIds) {
11014 request_proxy().config().request_success = true;
11015 request_proxy().config().get_assertion_response =
11016 GetAssertionAuthenticatorResponse::New();
11017 request_proxy().config().get_assertion_response->info =
11018 CommonCredentialInfo::New();
11019
Adem Derinel620520b42024-11-04 15:45:4411020 for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) {
11021 SCOPED_TRACE(
11022 base::StrCat({test_case.claimed_authority, " ", test_case.origin}));
Martin Kreichgauer1beaff02022-02-02 18:58:4211023
11024 NavigateAndCommit(GURL(test_case.origin));
Martin Kreichgauer328ef9f12022-12-22 10:45:5511025 BrowserContext* context = main_rfh()->GetBrowserContext();
Martin Kreichgauer1f4aa592023-01-06 18:39:3711026 ASSERT_TRUE(
11027 test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy(
11028 context, url::Origin::Create(GURL(test_case.origin))));
Martin Kreichgauer1beaff02022-02-02 18:58:4211029
11030 PublicKeyCredentialRequestOptionsPtr options =
11031 GetTestPublicKeyCredentialRequestOptions();
11032 options->relying_party_id = test_case.claimed_authority;
11033
11034 EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status,
11035 test_case.expected_status);
Martin Kreichgauerbf776d72022-04-18 23:38:1611036 EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u);
Martin Kreichgauer1beaff02022-02-02 18:58:4211037 }
11038}
11039
11040TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertion_Timeout) {
11041 request_proxy().config().resolve_callbacks = false;
11042 request_proxy().config().request_success = true;
11043 request_proxy().config().get_assertion_response =
11044 GetAssertionAuthenticatorResponse::New();
11045 request_proxy().config().get_assertion_response->info =
11046 CommonCredentialInfo::New();
11047
11048 NavigateAndCommit(GURL(kTestOrigin1));
11049 GetAssertionResult result = AuthenticatorGetAssertionAndWaitForTimeout(
11050 GetTestPublicKeyCredentialRequestOptions());
11051
11052 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
Martin Kreichgauerbf776d72022-04-18 23:38:1611053 EXPECT_EQ(request_proxy().observations().get_requests.size(), 1u);
11054 EXPECT_EQ(request_proxy().observations().num_cancel, 1u);
Martin Kreichgauer1beaff02022-02-02 18:58:4211055
11056 // Proxy should not hold a pending request after cancellation.
11057 EXPECT_FALSE(request_proxy().HasPendingRequest());
11058}
11059
Martin Kreichgauerca18b432023-04-25 18:58:4711060TEST_F(AuthenticatorImplWithRequestProxyTest,
11061 VirtualAuthenticatorTakesPrecedence) {
11062 // With the virtual authenticator enabled, no requests should hit the proxy.
11063 content::AuthenticatorEnvironment::GetInstance()
11064 ->EnableVirtualAuthenticatorFor(
11065 static_cast<content::RenderFrameHostImpl*>(main_rfh())
11066 ->frame_tree_node(),
11067 /*enable_ui=*/false);
11068 test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override = true;
11069
11070 NavigateAndCommit(GURL(kTestOrigin1));
11071 ASSERT_TRUE(
11072 request_proxy().IsActive(url::Origin::Create(GURL(kTestOrigin1))));
11073
11074 {
11075 MakeCredentialResult result = AuthenticatorMakeCredential(
11076 GetTestPublicKeyCredentialCreationOptions());
11077 EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS);
11078 EXPECT_EQ(request_proxy().observations().create_requests.size(), 0u);
11079 }
11080
11081 {
11082 GetAssertionResult result =
11083 AuthenticatorGetAssertion(GetTestPublicKeyCredentialRequestOptions());
11084 EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR);
11085 EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u);
11086 }
11087
11088 EXPECT_TRUE(AuthenticatorIsUvpaa());
11089 EXPECT_EQ(request_proxy().observations().num_isuvpaa, 0u);
11090 EXPECT_TRUE(AuthenticatorIsConditionalMediationAvailable());
11091 EXPECT_EQ(request_proxy().observations().num_isuvpaa, 0u);
11092}
11093
kpaulhamus7c9f00942017-06-30 11:08:4511094} // namespace content