Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2017 The Chromium Authors |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 2 | // 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 Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 7 | #include <algorithm> |
| 8 | #include <array> |
| 9 | #include <cstddef> |
| 10 | #include <cstdint> |
| 11 | #include <cstring> |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 12 | #include <iterator> |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 13 | #include <list> |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 14 | #include <map> |
Jinho Bang | ddead4e | 2018-01-01 10:32:43 | [diff] [blame] | 15 | #include <memory> |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 16 | #include <optional> |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 17 | #include <string> |
Md Hasibul Hasan | a963a934 | 2024-04-03 10:15:14 | [diff] [blame] | 18 | #include <string_view> |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 19 | #include <tuple> |
Kim Paulhamus | e549f84 | 2018-01-09 16:12:44 | [diff] [blame] | 20 | #include <utility> |
| 21 | #include <vector> |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 22 | |
Avi Drissman | 1bade823 | 2025-03-19 18:00:32 | [diff] [blame] | 23 | #include "base/apple/owned_objc.h" |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 24 | #include "base/base64url.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 25 | #include "base/check.h" |
| 26 | #include "base/check_op.h" |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 27 | #include "base/compiler_specific.h" |
Adam Langley | 9095b418 | 2022-07-20 16:14:50 | [diff] [blame] | 28 | #include "base/containers/flat_set.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 29 | #include "base/containers/span.h" |
Elly | 450ad9fe | 2025-07-09 17:13:22 | [diff] [blame] | 30 | #include "base/containers/to_vector.h" |
Avi Drissman | adac2199 | 2023-01-11 23:46:39 | [diff] [blame] | 31 | #include "base/functional/bind.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 32 | #include "base/functional/callback.h" |
| 33 | #include "base/functional/callback_forward.h" |
Avi Drissman | adac2199 | 2023-01-11 23:46:39 | [diff] [blame] | 34 | #include "base/functional/callback_helpers.h" |
Nigel Tao | 410788e | 2020-06-24 07:12:27 | [diff] [blame] | 35 | #include "base/json/json_reader.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 36 | #include "base/location.h" |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 37 | #include "base/memory/raw_ptr.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 38 | #include "base/memory/scoped_refptr.h" |
Kevin McNee | ab4af651 | 2024-06-19 20:55:57 | [diff] [blame] | 39 | #include "base/memory/stack_allocated.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 40 | #include "base/notreached.h" |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 41 | #include "base/rand_util.h" |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 42 | #include "base/run_loop.h" |
Lei Zhang | 9cc9aad7 | 2022-08-25 19:19:18 | [diff] [blame] | 43 | #include "base/strings/strcat.h" |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 44 | #include "base/strings/string_number_conversions.h" |
| 45 | #include "base/strings/string_util.h" |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 46 | #include "base/task/sequenced_task_runner.h" |
Sean Maher | e672a66 | 2023-01-09 21:42:28 | [diff] [blame] | 47 | #include "base/task/single_thread_task_runner.h" |
Guido Urdaneta | ef4e9194 | 2020-11-09 15:06:24 | [diff] [blame] | 48 | #include "base/test/bind.h" |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 49 | #include "base/test/metrics/histogram_tester.h" |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 50 | #include "base/test/scoped_command_line.h" |
Jun Choi | 17bbafc | 2018-04-11 08:37:20 | [diff] [blame] | 51 | #include "base/test/scoped_feature_list.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 52 | #include "base/test/task_environment.h" |
| 53 | #include "base/test/test_future.h" |
Kim Paulhamus | 40de29e4 | 2017-12-07 04:14:08 | [diff] [blame] | 54 | #include "base/time/time.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 55 | #include "base/values.h" |
Martin Kreichgauer | bcfda9cf | 2018-08-02 00:47:07 | [diff] [blame] | 56 | #include "build/build_config.h" |
Adam Langley | e0e46cdf | 2018-10-29 19:23:16 | [diff] [blame] | 57 | #include "components/cbor/reader.h" |
| 58 | #include "components/cbor/values.h" |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 59 | #include "components/ukm/test_ukm_recorder.h" |
Liquan (Max) Gu | 5710af13 | 2021-06-28 07:22:23 | [diff] [blame] | 60 | #include "components/webauthn/content/browser/internal_authenticator_impl.h" |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 61 | #include "content/browser/renderer_host/frame_tree_node.h" |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 62 | #include "content/browser/webauth/authenticator_common_impl.h" |
Adam Langley | 1e03fb0 | 2023-03-16 23:02:03 | [diff] [blame] | 63 | #include "content/browser/webauth/authenticator_environment.h" |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 64 | #include "content/browser/webauth/authenticator_request_outcome_enums.h" |
Liquan (Max) Gu | 292fc3d | 2021-07-19 19:03:03 | [diff] [blame] | 65 | #include "content/browser/webauth/client_data_json.h" |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 66 | #include "content/browser/webauth/virtual_authenticator.h" |
| 67 | #include "content/browser/webauth/virtual_authenticator_manager_impl.h" |
Pavel Beloborodov | d2dfbaa8 | 2025-02-27 20:05:36 | [diff] [blame] | 68 | #include "content/browser/webauth/webauth_request_security_checker.h" |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 69 | #include "content/public/browser/authenticator_request_client_delegate.h" |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 70 | #include "content/public/browser/content_browser_client.h" |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 71 | #include "content/public/browser/render_frame_host.h" |
Adem Derinel | dae9eff | 2025-01-23 12:16:00 | [diff] [blame] | 72 | #include "content/public/browser/web_authentication_delegate.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 73 | #include "content/public/browser/web_authentication_request_proxy.h" |
Hans Wennborg | 5ffd139 | 2019-10-16 11:00:02 | [diff] [blame] | 74 | #include "content/public/common/content_client.h" |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 75 | #include "content/public/common/content_switches.h" |
Casey Piper | d785ef0 | 2018-11-16 20:15:41 | [diff] [blame] | 76 | #include "content/public/test/browser_test_utils.h" |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 77 | #include "content/public/test/navigation_simulator.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 78 | #include "content/public/test/test_renderer_host.h" |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 79 | #include "crypto/evp.h" |
| 80 | #include "crypto/hash.h" |
| 81 | #include "crypto/hmac.h" |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 82 | #include "device/bluetooth/bluetooth_adapter_factory.h" |
| 83 | #include "device/bluetooth/test/mock_bluetooth_adapter.h" |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 84 | #include "device/fido/attested_credential_data.h" |
| 85 | #include "device/fido/authenticator_data.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 86 | #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 Langley | 8d24987 | 2022-03-07 23:16:06 | [diff] [blame] | 89 | #include "device/fido/cable/fido_tunnel_device.h" |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 90 | #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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 95 | #include "device/fido/discoverable_credential_metadata.h" |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 96 | #include "device/fido/fake_fido_discovery.h" |
Casey Piper | d785ef0 | 2018-11-16 20:15:41 | [diff] [blame] | 97 | #include "device/fido/features.h" |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 98 | #include "device/fido/fido_authenticator.h" |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 99 | #include "device/fido/fido_constants.h" |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 100 | #include "device/fido/fido_device_authenticator.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 101 | #include "device/fido/fido_discovery_base.h" |
Nina Satragno | 6e0f1ab | 2024-06-13 22:28:11 | [diff] [blame] | 102 | #include "device/fido/fido_request_handler_base.h" |
Martin Kreichgauer | 930f341a | 2022-01-07 20:20:46 | [diff] [blame] | 103 | #include "device/fido/fido_transport_protocol.h" |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 104 | #include "device/fido/fido_types.h" |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 105 | #include "device/fido/filter.h" |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 106 | #include "device/fido/large_blob.h" |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 107 | #include "device/fido/mock_fido_device.h" |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 108 | #include "device/fido/multiple_virtual_fido_device_factory.h" |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 109 | #include "device/fido/pin.h" |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 110 | #include "device/fido/public_key_credential_descriptor.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 111 | #include "device/fido/public_key_credential_params.h" |
| 112 | #include "device/fido/public_key_credential_rp_entity.h" |
Nina Satragno | 8824f9a | 2023-02-02 15:54:46 | [diff] [blame] | 113 | #include "device/fido/public_key_credential_user_entity.h" |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 114 | #include "device/fido/virtual_ctap2_device.h" |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 115 | #include "device/fido/virtual_fido_device.h" |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 116 | #include "device/fido/virtual_fido_device_factory.h" |
Nina Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 117 | #include "mojo/public/cpp/base/big_buffer.h" |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 118 | #include "mojo/public/cpp/bindings/remote.h" |
Lei Zhang | 32475bc | 2022-08-20 18:24:08 | [diff] [blame] | 119 | #include "mojo/public/cpp/system/functions.h" |
Nina Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 120 | #include "services/data_decoder/gzipper.h" |
Nina Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 121 | #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 122 | #include "services/metrics/public/cpp/ukm_builders.h" |
| 123 | #include "services/metrics/public/cpp/ukm_source.h" |
Sandor Major | 878f835 | 2025-02-18 20:16:02 | [diff] [blame] | 124 | #include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h" |
Hans Wennborg | 78b5218 | 2021-06-15 13:42:15 | [diff] [blame] | 125 | #include "services/network/public/mojom/network_context.mojom.h" |
Sandor Major | 878f835 | 2025-02-18 20:16:02 | [diff] [blame] | 126 | #include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h" |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 127 | #include "testing/gmock/include/gmock/gmock.h" |
| 128 | #include "testing/gtest/include/gtest/gtest.h" |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 129 | #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 130 | #include "third_party/boringssl/src/include/openssl/base.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 131 | #include "third_party/boringssl/src/include/openssl/ec.h" |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 132 | #include "third_party/boringssl/src/include/openssl/ec_key.h" |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 133 | #include "third_party/boringssl/src/include/openssl/evp.h" |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 134 | #include "third_party/boringssl/src/include/openssl/obj.h" |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 135 | #include "url/origin.h" |
Hans Wennborg | 5ffd139 | 2019-10-16 11:00:02 | [diff] [blame] | 136 | #include "url/url_util.h" |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 137 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 138 | #if BUILDFLAG(IS_MAC) |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 139 | #include "base/files/file_path.h" |
| 140 | #include "base/path_service.h" |
Elly Fong-Jones | 38c29b6 | 2025-07-23 22:54:53 | [diff] [blame] | 141 | #include "crypto/apple/scoped_fake_keychain_v2.h" |
Martin Kreichgauer | eaa8eb9d | 2019-05-30 19:03:45 | [diff] [blame] | 142 | #include "device/fido/mac/authenticator_config.h" |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 143 | #include "device/fido/mac/credential_store.h" |
Adam Langley | b5b7258 | 2023-09-13 19:41:46 | [diff] [blame] | 144 | #include "device/fido/mac/icloud_keychain.h" |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 145 | #include "device/fido/mac/scoped_icloud_keychain_test_environment.h" |
Martin Kreichgauer | a3c0f931 | 2018-08-10 19:04:39 | [diff] [blame] | 146 | #include "device/fido/mac/scoped_touch_id_test_environment.h" |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 147 | #include "ui/base/resource/resource_bundle.h" |
| 148 | #include "ui/base/resource/resource_scale_factor.h" |
Martin Kreichgauer | a3c0f931 | 2018-08-10 19:04:39 | [diff] [blame] | 149 | #endif |
| 150 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 151 | #if BUILDFLAG(IS_WIN) |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 152 | #include "content/public/test/test_browser_context.h" |
| 153 | #include "device/fido/fido_test_data.h" |
Martin Kreichgauer | 977c0047 | 2018-11-29 00:09:04 | [diff] [blame] | 154 | #include "device/fido/win/fake_webauthn_api.h" |
Ken Buchanan | 1ffd391 | 2024-07-08 01:16:09 | [diff] [blame] | 155 | #include "device/fido/win/util.h" |
Nina Satragno | 0cace239 | 2024-09-12 14:32:39 | [diff] [blame] | 156 | #include "third_party/microsoft_webauthn/webauthn.h" // nogncheck |
Martin Kreichgauer | 977c0047 | 2018-11-29 00:09:04 | [diff] [blame] | 157 | #endif |
| 158 | |
Howard Yang | 72a1412b | 2022-04-13 00:16:02 | [diff] [blame] | 159 | #if BUILDFLAG(IS_CHROMEOS) |
Yi Chou | f674584 | 2021-07-30 05:38:19 | [diff] [blame] | 160 | #include "chromeos/dbus/tpm_manager/tpm_manager_client.h" |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 161 | #include "chromeos/dbus/u2f/u2f_client.h" |
| 162 | #endif |
| 163 | |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 164 | namespace content { |
| 165 | |
| 166 | using ::testing::_; |
| 167 | |
Amos Lim | dddb699 | 2018-07-19 22:14:32 | [diff] [blame] | 168 | using blink::mojom::AttestationConveyancePreference; |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 169 | using blink::mojom::AuthenticationExtensionsClientInputs; |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 170 | using blink::mojom::AuthenticationExtensionsClientOutputs; |
Amos Lim | dddb699 | 2018-07-19 22:14:32 | [diff] [blame] | 171 | using blink::mojom::AuthenticatorSelectionCriteria; |
| 172 | using blink::mojom::AuthenticatorSelectionCriteriaPtr; |
| 173 | using blink::mojom::AuthenticatorStatus; |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 174 | using blink::mojom::AuthenticatorTransport; |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 175 | using blink::mojom::CableAuthentication; |
| 176 | using blink::mojom::CableAuthenticationPtr; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 177 | using blink::mojom::CommonCredentialInfo; |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 178 | using blink::mojom::GetAssertionAuthenticatorResponse; |
Amos Lim | dddb699 | 2018-07-19 22:14:32 | [diff] [blame] | 179 | using blink::mojom::GetAssertionAuthenticatorResponsePtr; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 180 | using blink::mojom::MakeCredentialAuthenticatorResponse; |
Amos Lim | dddb699 | 2018-07-19 22:14:32 | [diff] [blame] | 181 | using blink::mojom::MakeCredentialAuthenticatorResponsePtr; |
| 182 | using blink::mojom::PublicKeyCredentialCreationOptions; |
| 183 | using blink::mojom::PublicKeyCredentialCreationOptionsPtr; |
| 184 | using blink::mojom::PublicKeyCredentialDescriptor; |
| 185 | using blink::mojom::PublicKeyCredentialDescriptorPtr; |
| 186 | using blink::mojom::PublicKeyCredentialParameters; |
| 187 | using blink::mojom::PublicKeyCredentialParametersPtr; |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 188 | using blink::mojom::PublicKeyCredentialReportOptions; |
| 189 | using blink::mojom::PublicKeyCredentialReportOptionsPtr; |
Amos Lim | dddb699 | 2018-07-19 22:14:32 | [diff] [blame] | 190 | using blink::mojom::PublicKeyCredentialRequestOptions; |
| 191 | using blink::mojom::PublicKeyCredentialRequestOptionsPtr; |
| 192 | using blink::mojom::PublicKeyCredentialRpEntity; |
| 193 | using blink::mojom::PublicKeyCredentialRpEntityPtr; |
| 194 | using blink::mojom::PublicKeyCredentialType; |
| 195 | using blink::mojom::PublicKeyCredentialUserEntity; |
| 196 | using blink::mojom::PublicKeyCredentialUserEntityPtr; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 197 | using blink::mojom::RemoteDesktopClientOverride; |
| 198 | using blink::mojom::RemoteDesktopClientOverridePtr; |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 199 | using blink::mojom::WebAuthnDOMExceptionDetails; |
| 200 | using blink::mojom::WebAuthnDOMExceptionDetailsPtr; |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 201 | using cbor::Reader; |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 202 | using cbor::Value; |
Martin Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 203 | using device::VirtualCtap2Device; |
| 204 | using device::VirtualFidoDevice; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 205 | using device::cablev2::Event; |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 206 | |
Kim Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 207 | namespace { |
| 208 | |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 209 | using InterestingFailureReason = |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 210 | AuthenticatorRequestClientDelegate::InterestingFailureReason; |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 211 | using FailureReasonFuture = base::test::TestFuture<InterestingFailureReason>; |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 212 | |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 213 | constexpr base::TimeDelta kTestTimeout = base::Minutes(1); |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 214 | |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 215 | // The size of credential IDs returned by GetTestCredentials(). |
| 216 | constexpr size_t kTestCredentialIdLength = 32u; |
| 217 | |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 218 | constexpr char kTestOrigin1[] = "https://a.google.com"; |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 219 | constexpr char kTestOrigin2[] = "https://acme.org"; |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 220 | constexpr char kTestRelyingPartyId[] = "google.com"; |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 221 | constexpr char kDifferentTestRelyingPartyId[] = "different-rp.com"; |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 222 | constexpr char kExtensionScheme[] = "chrome-extension"; |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 223 | static constexpr char kCorpCrdOrigin[] = |
| 224 | "https://remotedesktop.corp.google.com"; |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 225 | |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 226 | constexpr 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 Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 231 | constexpr char kTestRegisterClientDataJsonString[] = |
Kim Paulhamus | 83a16a8 | 2018-01-19 11:27:37 | [diff] [blame] | 232 | R"({"challenge":"aHE0loIi7BcgLkJQX47SsWriLxa7BbiMJdueYCZF8UE","origin":)" |
Adam Langley | 96986fc8 | 2018-05-24 20:08:05 | [diff] [blame] | 233 | R"("https://a.google.com", "type":"webauthn.create"})"; |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 234 | |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 235 | constexpr char kTestSignClientDataJsonString[] = |
Kim Paulhamus | 83a16a8 | 2018-01-19 11:27:37 | [diff] [blame] | 236 | R"({"challenge":"aHE0loIi7BcgLkJQX47SsWriLxa7BbiMJdueYCZF8UE","origin":)" |
Adam Langley | 96986fc8 | 2018-05-24 20:08:05 | [diff] [blame] | 237 | R"("https://a.google.com", "type":"webauthn.get"})"; |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 238 | |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 239 | typedef struct { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 240 | std::string_view origin; |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 241 | // Either a relying party ID or a U2F AppID. |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 242 | std::string_view claimed_authority; |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 243 | AuthenticatorStatus expected_status; |
| 244 | } OriginClaimedAuthorityPair; |
| 245 | |
Kalvin Lee | 4c50a3fd | 2025-02-27 15:00:16 | [diff] [blame] | 246 | constexpr auto kValidRpTestCases = std::to_array<OriginClaimedAuthorityPair>({ |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 247 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 258 | |
| 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 Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 262 | {"https://google.com.", "google.com", AuthenticatorStatus::SUCCESS}, |
| 263 | {"https://google.com.", "google.com.", AuthenticatorStatus::SUCCESS}, |
| 264 | {"https://google.com..", "google.com..", AuthenticatorStatus::SUCCESS}, |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 265 | |
| 266 | // Leading dots are ignored in canonicalized hosts. |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 267 | {"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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 273 | }); |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 274 | |
Kalvin Lee | 4c50a3fd | 2025-02-27 15:00:16 | [diff] [blame] | 275 | constexpr auto kInvalidRpTestCases = std::to_array<OriginClaimedAuthorityPair>({ |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 276 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 293 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 294 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 304 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 305 | {"https://1.2.3", "1.2.3", AuthenticatorStatus::INVALID_DOMAIN}, |
| 306 | {"https://1.2.3", "2.3", AuthenticatorStatus::INVALID_DOMAIN}, |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 307 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 308 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 312 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 313 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 316 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 317 | {"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 Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 324 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 325 | {"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 Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 330 | {"ws://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL}, |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 331 | {"gopher://google.com", "google.com", AuthenticatorStatus::OPAQUE_DOMAIN}, |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 332 | {"ftp://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL}, |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 333 | {"file:///google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL}, |
Adam Langley | d41f6518 | 2018-03-21 20:17:27 | [diff] [blame] | 334 | // 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 Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 336 | {"wss://google.com", "google.com", AuthenticatorStatus::INVALID_PROTOCOL}, |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 337 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 338 | {"data:,", "", AuthenticatorStatus::OPAQUE_DOMAIN}, |
| 339 | {"https://google.com", "", AuthenticatorStatus::BAD_RELYING_PARTY_ID}, |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 340 | {"ws:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL}, |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 341 | {"wss:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL}, |
| 342 | {"gopher://google.com", "", AuthenticatorStatus::OPAQUE_DOMAIN}, |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 343 | {"ftp://google.com", "", AuthenticatorStatus::INVALID_PROTOCOL}, |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 344 | {"file:///google.com", "", AuthenticatorStatus::INVALID_PROTOCOL}, |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 345 | |
| 346 | // This case is acceptable according to spec, but both renderer |
| 347 | // and browser handling currently do not permit it. |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 348 | {"https://login.awesomecompany", "awesomecompany", |
| 349 | AuthenticatorStatus::BAD_RELYING_PARTY_ID}, |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 350 | |
| 351 | // These are AppID test cases, but should also be invalid relying party |
| 352 | // examples too. |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 353 | {"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 Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 365 | {"https://www.notgoogle.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 366 | "https://www.gstatic.com/securitykey/origins.json", |
| 367 | AuthenticatorStatus::BAD_RELYING_PARTY_ID}, |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 368 | {"https://www.google.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 369 | "https://www.gstatic.com/securitykey/origins.json#x", |
| 370 | AuthenticatorStatus::BAD_RELYING_PARTY_ID}, |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 371 | {"https://www.google.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 372 | "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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 380 | }); |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 381 | |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 382 | using TestGetClientCapabilityFuture = base::test::TestFuture< |
| 383 | std::vector<blink::mojom::WebAuthnClientCapabilityPtr>>; |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 384 | using TestIsUvpaaFuture = base::test::TestFuture<bool>; |
| 385 | using TestMakeCredentialFuture = |
| 386 | base::test::TestFuture<AuthenticatorStatus, |
| 387 | MakeCredentialAuthenticatorResponsePtr, |
| 388 | WebAuthnDOMExceptionDetailsPtr>; |
| 389 | using TestGetAssertionFuture = |
| 390 | base::test::TestFuture<AuthenticatorStatus, |
| 391 | GetAssertionAuthenticatorResponsePtr, |
| 392 | WebAuthnDOMExceptionDetailsPtr>; |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 393 | using TestGetCredentialFuture = |
| 394 | base::test::TestFuture<blink::mojom::GetCredentialResponsePtr>; |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 395 | using TestRequestStartedFuture = base::test::TestFuture<void>; |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 396 | using TestReportFuture = |
| 397 | base::test::TestFuture<AuthenticatorStatus, WebAuthnDOMExceptionDetailsPtr>; |
Adam Langley | 15a1543 | 2018-03-27 08:07:37 | [diff] [blame] | 398 | |
Kim Paulhamus | 35995bb6 | 2018-01-05 11:47:04 | [diff] [blame] | 399 | std::vector<uint8_t> GetTestChallengeBytes() { |
| 400 | return std::vector<uint8_t>(std::begin(kTestChallengeBytes), |
| 401 | std::end(kTestChallengeBytes)); |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 402 | } |
| 403 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 404 | device::PublicKeyCredentialRpEntity GetTestPublicKeyCredentialRPEntity() { |
| 405 | device::PublicKeyCredentialRpEntity entity; |
| 406 | entity.id = std::string(kTestRelyingPartyId); |
| 407 | entity.name = "[email protected]"; |
Kim Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 408 | return entity; |
| 409 | } |
| 410 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 411 | device::PublicKeyCredentialUserEntity GetTestPublicKeyCredentialUserEntity() { |
| 412 | device::PublicKeyCredentialUserEntity entity; |
| 413 | entity.display_name = "User A. Name"; |
Kim Paulhamus | 6549c81 | 2017-11-28 22:36:17 | [diff] [blame] | 414 | std::vector<uint8_t> id(32, 0x0A); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 415 | entity.id = id; |
| 416 | entity.name = "[email protected]"; |
Kim Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 417 | return entity; |
| 418 | } |
| 419 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 420 | std::vector<device::PublicKeyCredentialParams::CredentialInfo> |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 421 | GetTestPublicKeyCredentialParameters(int32_t algorithm_identifier) { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 422 | 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 Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 426 | parameters.push_back(std::move(fake_parameter)); |
| 427 | return parameters; |
| 428 | } |
| 429 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 430 | device::AuthenticatorSelectionCriteria GetTestAuthenticatorSelectionCriteria() { |
| 431 | return device::AuthenticatorSelectionCriteria( |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 432 | device::AuthenticatorAttachment::kAny, |
| 433 | device::ResidentKeyRequirement::kDiscouraged, |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 434 | device::UserVerificationRequirement::kPreferred); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 435 | } |
| 436 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 437 | std::vector<device::PublicKeyCredentialDescriptor> GetTestCredentials( |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 438 | size_t num_credentials = 1) { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 439 | std::vector<device::PublicKeyCredentialDescriptor> descriptors; |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 440 | for (size_t i = 0; i < num_credentials; i++) { |
| 441 | DCHECK(i <= std::numeric_limits<uint8_t>::max()); |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 442 | std::vector<uint8_t> id(kTestCredentialIdLength, static_cast<uint8_t>(i)); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 443 | base::flat_set<device::FidoTransportProtocol> transports{ |
| 444 | device::FidoTransportProtocol::kUsbHumanInterfaceDevice, |
| 445 | device::FidoTransportProtocol::kBluetoothLowEnergy}; |
Adam Langley | 5b8bda4 | 2019-08-29 21:22:59 | [diff] [blame] | 446 | descriptors.emplace_back(device::CredentialType::kPublicKey, std::move(id), |
| 447 | std::move(transports)); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 448 | } |
Kim Paulhamus | 6f81d68 | 2018-04-11 05:37:03 | [diff] [blame] | 449 | return descriptors; |
| 450 | } |
| 451 | |
Kim Paulhamus | 31998d2 | 2018-02-10 22:28:48 | [diff] [blame] | 452 | PublicKeyCredentialCreationOptionsPtr |
| 453 | GetTestPublicKeyCredentialCreationOptions() { |
| 454 | auto options = PublicKeyCredentialCreationOptions::New(); |
Kim Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 455 | options->relying_party = GetTestPublicKeyCredentialRPEntity(); |
| 456 | options->user = GetTestPublicKeyCredentialUserEntity(); |
Adam Langley | d1eb57f | 2020-06-12 02:07:00 | [diff] [blame] | 457 | options->public_key_parameters = GetTestPublicKeyCredentialParameters( |
| 458 | static_cast<int32_t>(device::CoseAlgorithmIdentifier::kEs256)); |
Kim Paulhamus | 9d87fb0d | 2018-01-18 18:36:22 | [diff] [blame] | 459 | options->challenge.assign(32, 0x0A); |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 460 | options->timeout = base::Minutes(1); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 461 | options->authenticator_selection = GetTestAuthenticatorSelectionCriteria(); |
Kim Paulhamus | 9d87fb0d | 2018-01-18 18:36:22 | [diff] [blame] | 462 | return options; |
| 463 | } |
| 464 | |
| 465 | PublicKeyCredentialRequestOptionsPtr |
| 466 | GetTestPublicKeyCredentialRequestOptions() { |
| 467 | auto options = PublicKeyCredentialRequestOptions::New(); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 468 | options->extensions = AuthenticationExtensionsClientInputs::New(); |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 469 | options->relying_party_id = std::string(kTestRelyingPartyId); |
Ken Buchanan | bba66b4 | 2024-11-29 16:43:15 | [diff] [blame] | 470 | options->challenge = std::vector<uint8_t>(32, 0x0A); |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 471 | options->timeout = base::Minutes(1); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 472 | options->user_verification = device::UserVerificationRequirement::kPreferred; |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 473 | options->allow_credentials = GetTestCredentials(); |
Kim Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 474 | return options; |
| 475 | } |
| 476 | |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 477 | PublicKeyCredentialReportOptionsPtr GetTestPublicKeyCredentialReportOptions() { |
| 478 | auto options = PublicKeyCredentialReportOptions::New(); |
| 479 | options->relying_party_id = std::string(kTestRelyingPartyId); |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 480 | return options; |
| 481 | } |
| 482 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 483 | std::vector<device::CableDiscoveryData> GetTestCableExtension() { |
| 484 | device::CableDiscoveryData cable; |
Adam Langley | 4ce0c31 | 2019-09-10 15:19:43 | [diff] [blame] | 485 | 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 Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 490 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 491 | std::vector<device::CableDiscoveryData> ret; |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 492 | ret.emplace_back(std::move(cable)); |
| 493 | return ret; |
| 494 | } |
| 495 | |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 496 | device::AuthenticatorData AuthDataFromMakeCredentialResponse( |
| 497 | const MakeCredentialAuthenticatorResponsePtr& response) { |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 498 | std::optional<Value> attestation_value = |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 499 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 506 | std::optional<device::AuthenticatorData> parsed_auth_data = |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 507 | device::AuthenticatorData::DecodeAuthenticatorData(auth_data); |
| 508 | return std::move(parsed_auth_data.value()); |
| 509 | } |
| 510 | |
Martin Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 511 | bool HasUV(const MakeCredentialAuthenticatorResponsePtr& response) { |
| 512 | return AuthDataFromMakeCredentialResponse(response) |
| 513 | .obtained_user_verification(); |
| 514 | } |
| 515 | |
| 516 | bool HasUV(const GetAssertionAuthenticatorResponsePtr& response) { |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 517 | std::optional<device::AuthenticatorData> auth_data = |
Martin Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 518 | device::AuthenticatorData::DecodeAuthenticatorData( |
| 519 | response->info->authenticator_data); |
| 520 | return auth_data->obtained_user_verification(); |
| 521 | } |
| 522 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 523 | url::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 Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 529 | std::string GetTestClientDataJSON(ClientDataRequestType type) { |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 530 | return BuildClientDataJson({std::move(type), GetTestOrigin(), GetTestOrigin(), |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 531 | GetTestChallengeBytes(), |
| 532 | /*is_cross_origin_iframe=*/false}); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 533 | } |
| 534 | |
Nina Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 535 | device::LargeBlob CompressLargeBlob(base::span<const uint8_t> blob) { |
| 536 | data_decoder::Gzipper gzipper; |
| 537 | std::vector<uint8_t> compressed; |
| 538 | base::RunLoop run_loop; |
Elly | 450ad9fe | 2025-07-09 17:13:22 | [diff] [blame] | 539 | gzipper.Deflate(blob, base::BindLambdaForTesting( |
| 540 | [&](std::optional<mojo_base::BigBuffer> result) { |
| 541 | compressed = base::ToVector(*result); |
| 542 | run_loop.Quit(); |
| 543 | })); |
Nina Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 544 | run_loop.Run(); |
| 545 | return device::LargeBlob(std::move(compressed), blob.size()); |
Nina Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 546 | } |
| 547 | |
Nina Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 548 | std::vector<uint8_t> UncompressLargeBlob(device::LargeBlob blob) { |
| 549 | data_decoder::Gzipper gzipper; |
| 550 | std::vector<uint8_t> uncompressed; |
| 551 | base::RunLoop run_loop; |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 552 | gzipper.Inflate( |
Peter Kasting | 2b138f1 | 2024-10-31 17:10:27 | [diff] [blame] | 553 | {blob.compressed_data}, blob.original_size, |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 554 | base::BindLambdaForTesting( |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 555 | [&](std::optional<mojo_base::BigBuffer> result) { |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 556 | if (result) { |
Elly | 450ad9fe | 2025-07-09 17:13:22 | [diff] [blame] | 557 | uncompressed = base::ToVector(*result); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 558 | } 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 Satragno | 9ca1c64 | 2022-04-21 16:26:47 | [diff] [blame] | 567 | run_loop.Run(); |
| 568 | return uncompressed; |
Nina Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 569 | } |
| 570 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 571 | // Convert a blink::mojom::AttestationConveyancePreference to a |
| 572 | // device::AtttestationConveyancePreference. |
| 573 | device::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 Paulhamus | fbe554b | 2017-08-22 19:46:34 | [diff] [blame] | 588 | } // namespace |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 589 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 590 | class AuthenticatorTestBase : public RenderViewHostTestHarness { |
Martin Kreichgauer | 4faa9baf | 2019-07-17 17:57:39 | [diff] [blame] | 591 | protected: |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 592 | AuthenticatorTestBase() |
| 593 | : RenderViewHostTestHarness( |
| 594 | base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 595 | ~AuthenticatorTestBase() override = default; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 596 | |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 597 | 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 Kreichgauer | 17bc163b | 2019-09-12 00:38:42 | [diff] [blame] | 610 | void SetUp() override { |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 611 | RenderViewHostTestHarness::SetUp(); |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 612 | |
Pavel Beloborodov | d2dfbaa8 | 2025-02-27 20:05:36 | [diff] [blame] | 613 | WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() = |
| 614 | true; |
| 615 | |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 616 | mojo::SetDefaultProcessErrorHandler(base::BindRepeating( |
| 617 | &AuthenticatorTestBase::OnMojoError, base::Unretained(this))); |
| 618 | |
Howard Yang | 72a1412b | 2022-04-13 00:16:02 | [diff] [blame] | 619 | #if BUILDFLAG(IS_CHROMEOS) |
Yi Chou | f674584 | 2021-07-30 05:38:19 | [diff] [blame] | 620 | chromeos::TpmManagerClient::InitializeFake(); |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 621 | chromeos::U2FClient::InitializeFake(); |
| 622 | #endif |
| 623 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 624 | #if BUILDFLAG(IS_WIN) |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 625 | // Disable the Windows WebAuthn API integration by default. Individual tests |
| 626 | // can modify this. |
| 627 | fake_win_webauthn_api_.set_available(false); |
Ken Buchanan | 1ffd391 | 2024-07-08 01:16:09 | [diff] [blame] | 628 | |
| 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 Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 634 | #endif |
| 635 | |
Martin Kreichgauer | 17bc163b | 2019-09-12 00:38:42 | [diff] [blame] | 636 | ResetVirtualDevice(); |
| 637 | } |
| 638 | |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 639 | void TearDown() override { |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 640 | RenderViewHostTestHarness::TearDown(); |
Pavel Beloborodov | d2dfbaa8 | 2025-02-27 20:05:36 | [diff] [blame] | 641 | WebAuthRequestSecurityChecker::UseSystemSharedURLLoaderFactoryForTesting() = |
| 642 | false; |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 643 | |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 644 | mojo::SetDefaultProcessErrorHandler(base::NullCallback()); |
| 645 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 646 | virtual_device_factory_ = nullptr; |
Adam Langley | 1e03fb0 | 2023-03-16 23:02:03 | [diff] [blame] | 647 | AuthenticatorEnvironment::GetInstance()->Reset(); |
Howard Yang | 72a1412b | 2022-04-13 00:16:02 | [diff] [blame] | 648 | #if BUILDFLAG(IS_CHROMEOS) |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 649 | chromeos::U2FClient::Shutdown(); |
Yi Chou | f674584 | 2021-07-30 05:38:19 | [diff] [blame] | 650 | chromeos::TpmManagerClient::Shutdown(); |
Martin Kreichgauer | dc37b75 | 2021-02-23 23:12:31 | [diff] [blame] | 651 | #endif |
| 652 | } |
| 653 | |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 654 | virtual void ResetVirtualDevice() { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 655 | auto virtual_device_factory = |
| 656 | std::make_unique<device::test::VirtualFidoDeviceFactory>(); |
| 657 | virtual_device_factory_ = virtual_device_factory.get(); |
Adam Langley | 1e03fb0 | 2023-03-16 23:02:03 | [diff] [blame] | 658 | AuthenticatorEnvironment::GetInstance() |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 659 | ->ReplaceDefaultDiscoveryFactoryForTesting( |
| 660 | std::move(virtual_device_factory)); |
| 661 | } |
| 662 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 663 | 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 Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 670 | void SetMojoErrorHandler( |
| 671 | base::RepeatingCallback<void(const std::string&)> callback) { |
| 672 | mojo_error_handler_ = callback; |
| 673 | } |
| 674 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 675 | raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_ = |
| 676 | nullptr; |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 677 | #if BUILDFLAG(IS_WIN) |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 678 | device::FakeWinWebAuthnApi fake_win_webauthn_api_; |
Adam Langley | 3037544 | 2023-06-19 17:20:26 | [diff] [blame] | 679 | device::WinWebAuthnApi::ScopedOverride win_webauthn_api_override_{ |
| 680 | &fake_win_webauthn_api_}; |
Ken Buchanan | 1ffd391 | 2024-07-08 01:16:09 | [diff] [blame] | 681 | std::unique_ptr<device::fido::win::ScopedBiometricsOverride> |
| 682 | biometrics_override_; |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 683 | #endif |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 684 | |
| 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 695 | }; |
| 696 | |
| 697 | class AuthenticatorImplTest : public AuthenticatorTestBase { |
Martin Kreichgauer | 4faa9baf | 2019-07-17 17:57:39 | [diff] [blame] | 698 | protected: |
Michael Thiessen | 2add7d44 | 2020-02-05 13:49:38 | [diff] [blame] | 699 | AuthenticatorImplTest() { |
| 700 | url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST); |
| 701 | } |
| 702 | ~AuthenticatorImplTest() override = default; |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 703 | |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 704 | void SetUp() override { |
| 705 | AuthenticatorTestBase::SetUp(); |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 706 | SetBluetoothLESupported(true); |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 707 | device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); |
| 708 | } |
| 709 | |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 710 | void SetBluetoothLESupported(bool supported) { |
| 711 | bluetooth_global_values_->SetLESupported(supported); |
| 712 | } |
| 713 | |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 714 | void NavigateAndCommit(const GURL& url) { |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 715 | RenderViewHostTestHarness::NavigateAndCommit(url); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 716 | } |
| 717 | |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 718 | mojo::Remote<blink::mojom::Authenticator> ConnectToAuthenticator() { |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 719 | mojo::Remote<blink::mojom::Authenticator> authenticator; |
Martin Kreichgauer | 7d2b8dbb | 2021-04-01 16:03:45 | [diff] [blame] | 720 | static_cast<RenderFrameHostImpl*>(main_rfh()) |
| 721 | ->GetWebAuthenticationService( |
| 722 | authenticator.BindNewPipeAndPassReceiver()); |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 723 | return authenticator; |
| 724 | } |
| 725 | |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 726 | bool AuthenticatorIsUvpaa() { |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 727 | TestIsUvpaaFuture future; |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 728 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 729 | ConnectToAuthenticator(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 730 | authenticator->IsUserVerifyingPlatformAuthenticatorAvailable( |
| 731 | future.GetCallback()); |
| 732 | EXPECT_TRUE(future.Wait()); |
| 733 | return future.Get(); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 734 | } |
| 735 | |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 736 | 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 Derinel | d564032 | 2025-04-24 09:14:06 | [diff] [blame] | 752 | std::optional<bool> supported) { |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 753 | 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 Derinel | d564032 | 2025-04-24 09:14:06 | [diff] [blame] | 759 | 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 Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 765 | } |
| 766 | |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 767 | bool AuthenticatorIsConditionalMediationAvailable() { |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 768 | TestIsUvpaaFuture future; |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 769 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 770 | ConnectToAuthenticator(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 771 | authenticator->IsConditionalMediationAvailable(future.GetCallback()); |
| 772 | EXPECT_TRUE(future.Wait()); |
| 773 | return future.Get(); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 774 | } |
| 775 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 776 | struct MakeCredentialResult { |
| 777 | AuthenticatorStatus status; |
| 778 | MakeCredentialAuthenticatorResponsePtr response; |
| 779 | }; |
| 780 | |
| 781 | MakeCredentialResult AuthenticatorMakeCredential() { |
| 782 | return AuthenticatorMakeCredential( |
| 783 | GetTestPublicKeyCredentialCreationOptions()); |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 784 | } |
| 785 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 786 | MakeCredentialResult AuthenticatorMakeCredential( |
| 787 | PublicKeyCredentialCreationOptionsPtr options) { |
| 788 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 789 | ConnectToAuthenticator(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 790 | TestMakeCredentialFuture future; |
| 791 | authenticator->MakeCredential(std::move(options), future.GetCallback()); |
| 792 | EXPECT_TRUE(future.Wait()); |
| 793 | auto [status, response, dom_exception] = future.Take(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 794 | return {status, std::move(response)}; |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 795 | } |
| 796 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 797 | MakeCredentialResult AuthenticatorMakeCredentialAndWaitForTimeout( |
| 798 | PublicKeyCredentialCreationOptionsPtr options) { |
| 799 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 800 | ConnectToAuthenticator(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 801 | TestMakeCredentialFuture future; |
| 802 | authenticator->MakeCredential(std::move(options), future.GetCallback()); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 803 | task_environment()->FastForwardBy(kTestTimeout); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 804 | EXPECT_TRUE(future.Wait()); |
| 805 | auto [status, response, dom_exception] = future.Take(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 806 | 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 Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 823 | TestGetCredentialFuture future; |
| 824 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 825 | EXPECT_TRUE(future.Wait()); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 826 | 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 830 | } |
| 831 | |
| 832 | GetAssertionResult AuthenticatorGetAssertionAndWaitForTimeout( |
| 833 | PublicKeyCredentialRequestOptionsPtr options) { |
| 834 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 835 | ConnectToAuthenticator(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 836 | TestGetCredentialFuture future; |
| 837 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 838 | task_environment()->FastForwardBy(kTestTimeout); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 839 | 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 Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 843 | } |
| 844 | |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 845 | 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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 856 | AuthenticatorStatus TryAuthenticationWithAppId(std::string_view origin, |
| 857 | std::string_view appid) { |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 858 | const GURL origin_url(origin); |
| 859 | NavigateAndCommit(origin_url); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 860 | |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 861 | PublicKeyCredentialRequestOptionsPtr options = |
| 862 | GetTestPublicKeyCredentialRequestOptions(); |
| 863 | options->relying_party_id = origin_url.host(); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 864 | options->extensions->appid = appid; |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 865 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 866 | return AuthenticatorGetAssertion(std::move(options)).status; |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 867 | } |
| 868 | |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 869 | AuthenticatorStatus TryRegistrationWithAppIdExclude( |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 870 | std::string_view origin, |
| 871 | std::string_view appid_exclude) { |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 872 | const GURL origin_url(origin); |
| 873 | NavigateAndCommit(origin_url); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 874 | |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 875 | PublicKeyCredentialCreationOptionsPtr options = |
| 876 | GetTestPublicKeyCredentialCreationOptions(); |
| 877 | options->relying_party.id = origin_url.host(); |
| 878 | options->appid_exclude = appid_exclude; |
| 879 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 880 | return AuthenticatorMakeCredential(std::move(options)).status; |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 881 | } |
| 882 | |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 883 | ukm::TestUkmRecorder* GetTestUkmRecorder() { return &test_ukm_recorder_; } |
| 884 | |
| 885 | void VerifyGetAssertionOutcomeUkm(uint32_t index, |
| 886 | GetAssertionOutcome outcome, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 887 | AuthenticationRequestMode mode) { |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 888 | 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 Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 899 | AuthenticationRequestMode mode) { |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 900 | 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 Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 910 | // 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 Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 935 | scoped_refptr<::testing::NiceMock<device::MockBluetoothAdapter>> |
| 936 | mock_adapter_ = base::MakeRefCounted< |
| 937 | ::testing::NiceMock<device::MockBluetoothAdapter>>(); |
| 938 | |
| 939 | private: |
Alex N. Jose | f3258bb | 2024-07-02 20:13:46 | [diff] [blame] | 940 | std::unique_ptr<device::BluetoothAdapterFactory::GlobalOverrideValues> |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 941 | bluetooth_global_values_ = |
Alex N. Jose | f3258bb | 2024-07-02 20:13:46 | [diff] [blame] | 942 | device::BluetoothAdapterFactory::Get()->InitGlobalOverrideValues(); |
Nina Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 943 | data_decoder::test::InProcessDataDecoder data_decoder_service_; |
Michael Thiessen | 2add7d44 | 2020-02-05 13:49:38 | [diff] [blame] | 944 | url::ScopedSchemeRegistryForTests scoped_registry_; |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 945 | ukm::TestAutoSetUkmRecorder test_ukm_recorder_; |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 946 | }; |
| 947 | |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 948 | TEST_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 Kreichgauer | 7d2b8dbb | 2021-04-01 16:03:45 | [diff] [blame] | 954 | EXPECT_EQ( |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 955 | BuildClientDataJson({ClientDataRequestType::kWebAuthnCreate, |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 956 | GetTestOrigin(), GetTestOrigin(), challenge_bytes, |
| 957 | false}) |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 958 | .find( |
| 959 | "{\"type\":\"webauthn.create\",\"challenge\":\"AQID\",\"origin\":" |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 960 | "\"https://a.google.com\",\"crossOrigin\":false"), |
Martin Kreichgauer | 7d2b8dbb | 2021-04-01 16:03:45 | [diff] [blame] | 961 | 0u); |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 962 | |
| 963 | // Second, check that a generic JSON parser correctly parses the result. |
| 964 | static const struct { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 965 | const ClientDataRequestType type; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 966 | url::Origin origin; |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 967 | url::Origin top_origin; |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 968 | std::vector<uint8_t> challenge; |
| 969 | bool is_cross_origin; |
| 970 | } kTestCases[] = { |
| 971 | { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 972 | ClientDataRequestType::kWebAuthnGet, |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 973 | GetTestOrigin(), |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 974 | GetTestOrigin(), |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 975 | {1, 2, 3}, |
| 976 | false, |
| 977 | }, |
| 978 | { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 979 | ClientDataRequestType::kPaymentGet, |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 980 | GetTestOrigin(), |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 981 | GetTestOrigin(), |
| 982 | {1, 2, 3}, |
| 983 | false, |
| 984 | }, |
| 985 | { |
| 986 | ClientDataRequestType::kWebAuthnCreate, |
| 987 | GetTestOrigin(), |
| 988 | url::Origin::Create(GURL("https://toplevel.example")), |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 989 | {1, 2, 3}, |
| 990 | false, |
| 991 | }, |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 992 | }; |
| 993 | |
| 994 | size_t num = 0; |
| 995 | for (const auto& test : kTestCases) { |
| 996 | SCOPED_TRACE(num++); |
| 997 | |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 998 | const std::string json = |
| 999 | BuildClientDataJson({test.type, test.origin, test.top_origin, |
| 1000 | test.challenge, test.is_cross_origin}); |
Adam Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 1001 | |
| 1002 | const auto parsed = base::JSONReader::Read(json); |
| 1003 | ASSERT_TRUE(parsed.has_value()); |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 1004 | std::string type_key; |
| 1005 | std::string expected_type; |
| 1006 | switch (test.type) { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 1007 | 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 Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 1015 | case ClientDataRequestType::kPaymentGet: |
| 1016 | type_key = "type"; |
| 1017 | expected_type = "payment.get"; |
| 1018 | break; |
| 1019 | } |
[email protected] | 147bc83a | 2023-02-14 13:11:07 | [diff] [blame] | 1020 | 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 Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 1023 | std::string expected_challenge; |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1024 | base::Base64UrlEncode(test.challenge, |
| 1025 | base::Base64UrlEncodePolicy::OMIT_PADDING, |
| 1026 | &expected_challenge); |
[email protected] | 147bc83a | 2023-02-14 13:11:07 | [diff] [blame] | 1027 | EXPECT_EQ(*parsed->GetDict().FindString("challenge"), expected_challenge); |
| 1028 | EXPECT_EQ(*parsed->GetDict().FindBool("crossOrigin"), test.is_cross_origin); |
Ken Buchanan | 4fb3ef1 | 2024-08-19 20:19:32 | [diff] [blame] | 1029 | 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 Langley | 2e71e7a | 2020-03-02 21:19:04 | [diff] [blame] | 1035 | } |
| 1036 | } |
| 1037 | |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1038 | // Verify behavior for various combinations of origins and RP IDs. |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 1039 | TEST_F(AuthenticatorImplTest, MakeCredentialOriginAndRpIds) { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1040 | std::vector<OriginClaimedAuthorityPair> tests; |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 1041 | std::ranges::copy(kValidRpTestCases, std::back_inserter(tests)); |
| 1042 | std::ranges::copy(kInvalidRpTestCases, std::back_inserter(tests)); |
Adam Langley | 4f054828 | 2020-06-12 18:54:30 | [diff] [blame] | 1043 | |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1044 | int test_case_count = 0; |
Adam Langley | 4f054828 | 2020-06-12 18:54:30 | [diff] [blame] | 1045 | for (const auto& test_case : tests) { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1046 | SCOPED_TRACE( |
| 1047 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1048 | |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 1049 | NavigateAndCommit(GURL(test_case.origin)); |
Kim Paulhamus | 31998d2 | 2018-02-10 22:28:48 | [diff] [blame] | 1050 | PublicKeyCredentialCreationOptionsPtr options = |
| 1051 | GetTestPublicKeyCredentialCreationOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 1052 | options->relying_party.id = test_case.claimed_authority; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1053 | |
| 1054 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 1055 | test_case.expected_status); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1056 | VerifyMakeCredentialOutcomeUkm( |
| 1057 | test_case_count++, |
| 1058 | (test_case.expected_status == AuthenticatorStatus::SUCCESS) |
| 1059 | ? MakeCredentialOutcome::kSuccess |
| 1060 | : MakeCredentialOutcome::kSecurityError, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1061 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | cd76a26e | 2018-01-18 16:11:16 | [diff] [blame] | 1062 | } |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 1063 | } |
Adam Langley | 31c85e7 | 2017-10-17 06:10:35 | [diff] [blame] | 1064 | |
Jun Choi | f45741d | 2018-06-08 20:58:31 | [diff] [blame] | 1065 | // Test that MakeCredential request times out with NOT_ALLOWED_ERROR if user |
| 1066 | // verification is required for U2F devices. |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1067 | TEST_F(AuthenticatorImplTest, MakeCredentialUserVerification) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1068 | NavigateAndCommit(GURL(kTestOrigin1)); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1069 | |
| 1070 | PublicKeyCredentialCreationOptionsPtr options = |
| 1071 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1072 | options->authenticator_selection->user_verification_requirement = |
| 1073 | device::UserVerificationRequirement::kRequired; |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1074 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1075 | EXPECT_EQ( |
| 1076 | AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status, |
| 1077 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1078 | } |
| 1079 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1080 | TEST_F(AuthenticatorImplTest, MakeCredentialResidentKeyUnsupported) { |
| 1081 | NavigateAndCommit(GURL(kTestOrigin1)); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1082 | |
| 1083 | PublicKeyCredentialCreationOptionsPtr options = |
| 1084 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1085 | options->authenticator_selection->resident_key = |
| 1086 | device::ResidentKeyRequirement::kRequired; |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1087 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1088 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 1089 | AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1090 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kRkNotSupported, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1091 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | 3eef95f | 2018-04-03 10:37:45 | [diff] [blame] | 1092 | } |
| 1093 | |
Jun Choi | f45741d | 2018-06-08 20:58:31 | [diff] [blame] | 1094 | // Test that MakeCredential request times out with NOT_ALLOWED_ERROR if a |
| 1095 | // platform authenticator is requested for U2F devices. |
Kim Paulhamus | 3eef95f | 2018-04-03 10:37:45 | [diff] [blame] | 1096 | TEST_F(AuthenticatorImplTest, MakeCredentialPlatformAuthenticator) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1097 | NavigateAndCommit(GURL(kTestOrigin1)); |
Kim Paulhamus | 3eef95f | 2018-04-03 10:37:45 | [diff] [blame] | 1098 | |
| 1099 | PublicKeyCredentialCreationOptionsPtr options = |
| 1100 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1101 | options->authenticator_selection->authenticator_attachment = |
| 1102 | device::AuthenticatorAttachment::kPlatform; |
Kim Paulhamus | 3eef95f | 2018-04-03 10:37:45 | [diff] [blame] | 1103 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1104 | EXPECT_EQ( |
| 1105 | AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status, |
| 1106 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1107 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUiTimeout, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1108 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | e457399 | 2018-03-13 18:46:25 | [diff] [blame] | 1109 | } |
| 1110 | |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1111 | TEST_F(AuthenticatorImplTest, GetClientCapabilities) { |
Adem Derinel | 8601e78 | 2025-05-19 08:04:44 | [diff] [blame] | 1112 | base::test::ScopedFeatureList feature_list; |
| 1113 | feature_list.InitWithFeatureState(device::kWebAuthnImmediateGet, false); |
| 1114 | |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1115 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 1116 | |
| 1117 | ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities(); |
| 1118 | |
| 1119 | std::vector<std::string> capability_names; |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 1120 | std::ranges::transform( |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1121 | capabilities, std::back_inserter(capability_names), |
| 1122 | [](const auto& capability) { return capability->name; }); |
| 1123 | |
| 1124 | const std::vector<std::string_view> kRequiredCapabilities = { |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1125 | client_capabilities::kConditionalGet, |
| 1126 | client_capabilities::kHybridTransport, |
| 1127 | client_capabilities::kPasskeyPlatformAuthenticator, |
| 1128 | client_capabilities::kUserVerifyingPlatformAuthenticator, |
| 1129 | client_capabilities::kRelatedOrigins, |
Martin Kreichgauer | a57d2f1 | 2025-03-12 16:47:45 | [diff] [blame] | 1130 | client_capabilities::kConditionalCreate, |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1131 | }; |
| 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 Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 1139 | std::ranges::count(capability_names, capability))); |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1140 | } |
| 1141 | } |
| 1142 | |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 1143 | TEST_F(AuthenticatorImplTest, GetClientCapabilities_HybridTransportSupported) { |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1144 | NavigateAndCommit(GURL(kTestOrigin1)); |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 1145 | EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); |
| 1146 | ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities(); |
| 1147 | ExpectCapability(capabilities, client_capabilities::kHybridTransport, true); |
| 1148 | } |
| 1149 | |
| 1150 | TEST_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 | |
| 1158 | TEST_F(AuthenticatorImplTest, |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 1159 | GetClientCapabilities_HybridTransport_LowEnergyNotSupported) { |
| 1160 | SetBluetoothLESupported(false); |
| 1161 | |
| 1162 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 1163 | EXPECT_CALL(*mock_adapter_, IsPresent).Times(0); |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 1164 | ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities(); |
| 1165 | ExpectCapability(capabilities, client_capabilities::kHybridTransport, false); |
| 1166 | } |
| 1167 | |
| 1168 | TEST_F(AuthenticatorImplTest, GetClientCapabilities_RelatedOrigins) { |
| 1169 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 1170 | ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities(); |
| 1171 | ExpectCapability(capabilities, client_capabilities::kRelatedOrigins, true); |
| 1172 | } |
| 1173 | |
Martin Kreichgauer | a57d2f1 | 2025-03-12 16:47:45 | [diff] [blame] | 1174 | TEST_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 Derinel | d564032 | 2025-04-24 09:14:06 | [diff] [blame] | 1185 | TEST_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 Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1196 | // 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 Hasan | a963a934 | 2024-04-03 10:15:14 | [diff] [blame] | 1198 | static void CheckJSONIsSubsetOfJSON(std::string_view subset_str, |
| 1199 | std::string_view test_str) { |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 1200 | std::optional<base::Value> subset = base::JSONReader::Read(subset_str); |
Adam Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1201 | ASSERT_TRUE(subset); |
| 1202 | ASSERT_TRUE(subset->is_dict()); |
Daniel Cheng | 249aa4f | 2022-07-13 18:26:31 | [diff] [blame] | 1203 | const base::Value::Dict& subset_dict = subset->GetDict(); |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 1204 | std::optional<base::Value> test = base::JSONReader::Read(test_str); |
Adam Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1205 | ASSERT_TRUE(test); |
| 1206 | ASSERT_TRUE(test->is_dict()); |
Daniel Cheng | 249aa4f | 2022-07-13 18:26:31 | [diff] [blame] | 1207 | const base::Value::Dict& test_dict = test->GetDict(); |
Adam Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1208 | |
Daniel Cheng | 249aa4f | 2022-07-13 18:26:31 | [diff] [blame] | 1209 | for (auto item : subset_dict) { |
| 1210 | const base::Value* test_value = test_dict.Find(item.first); |
Adam Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1211 | if (test_value == nullptr) { |
| 1212 | ADD_FAILURE() << item.first << " does not exist in the test dictionary"; |
| 1213 | continue; |
| 1214 | } |
| 1215 | |
Daniel Cheng | 249aa4f | 2022-07-13 18:26:31 | [diff] [blame] | 1216 | EXPECT_EQ(item.second, *test_value); |
Adam Langley | 9f4fb5a | 2018-02-06 17:17:18 | [diff] [blame] | 1217 | } |
| 1218 | } |
| 1219 | |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 1220 | // Test that client data serializes to JSON properly. |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1221 | TEST(ClientDataSerializationTest, Register) { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 1222 | CheckJSONIsSubsetOfJSON( |
| 1223 | kTestRegisterClientDataJsonString, |
| 1224 | GetTestClientDataJSON(ClientDataRequestType::kWebAuthnCreate)); |
Kim Paulhamus | fbe092bf | 2017-11-21 16:46:08 | [diff] [blame] | 1225 | } |
| 1226 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1227 | TEST(ClientDataSerializationTest, Sign) { |
Martin Kreichgauer | 0b1f559 | 2021-07-02 22:28:25 | [diff] [blame] | 1228 | CheckJSONIsSubsetOfJSON( |
| 1229 | kTestSignClientDataJsonString, |
| 1230 | GetTestClientDataJSON(ClientDataRequestType::kWebAuthnGet)); |
Adam Langley | 72d721e | 2018-02-23 19:37:08 | [diff] [blame] | 1231 | } |
| 1232 | |
Kim Paulhamus | 9d87fb0d | 2018-01-18 18:36:22 | [diff] [blame] | 1233 | TEST_F(AuthenticatorImplTest, TestMakeCredentialTimeout) { |
Ken Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 1234 | base::HistogramTester histogram_tester; |
| 1235 | |
Martin Kreichgauer | c5e427a | 2021-02-24 02:35:59 | [diff] [blame] | 1236 | // 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1240 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 1241 | |
Kim Paulhamus | 31998d2 | 2018-02-10 22:28:48 | [diff] [blame] | 1242 | PublicKeyCredentialCreationOptionsPtr options = |
| 1243 | GetTestPublicKeyCredentialCreationOptions(); |
Kim Paulhamus | 40de29e4 | 2017-12-07 04:14:08 | [diff] [blame] | 1244 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1245 | EXPECT_EQ( |
| 1246 | AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status, |
| 1247 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Ken Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 1248 | histogram_tester.ExpectUniqueSample( |
| 1249 | "WebAuthentication.MakeCredential.Result", |
| 1250 | AuthenticatorCommonImpl::CredentialRequestResult::kTimeout, 1); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1251 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUiTimeout, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1252 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | 40de29e4 | 2017-12-07 04:14:08 | [diff] [blame] | 1253 | } |
| 1254 | |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1255 | // Verify behavior for various combinations of origins and RP IDs. |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1256 | TEST_F(AuthenticatorImplTest, GetAssertionOriginAndRpIds) { |
| 1257 | // These instances should return security errors (for circumstances |
| 1258 | // that would normally crash the renderer). |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1259 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 1260 | SCOPED_TRACE( |
| 1261 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1262 | |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1263 | NavigateAndCommit(GURL(test_case.origin)); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1264 | |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1265 | PublicKeyCredentialRequestOptionsPtr options = |
| 1266 | GetTestPublicKeyCredentialRequestOptions(); |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1267 | options->relying_party_id = test_case.claimed_authority; |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1268 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1269 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 1270 | test_case.expected_status); |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1271 | } |
| 1272 | } |
| 1273 | |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 1274 | // Verify behavior for various combinations of origins and RP IDs. |
| 1275 | TEST_F(AuthenticatorImplTest, ReportOriginAndRpIds) { |
| 1276 | // These instances should return security errors (for circumstances |
| 1277 | // that would normally crash the renderer). |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1278 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 1279 | SCOPED_TRACE( |
| 1280 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 1281 | |
| 1282 | NavigateAndCommit(GURL(test_case.origin)); |
| 1283 | PublicKeyCredentialReportOptionsPtr options = |
| 1284 | GetTestPublicKeyCredentialReportOptions(); |
| 1285 | options->relying_party_id = test_case.claimed_authority; |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 1286 | options->unknown_credential_id = std::vector<uint8_t>(32, 0x0A); |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 1287 | |
| 1288 | EXPECT_EQ(AuthenticatorReport(std::move(options)), |
| 1289 | test_case.expected_status); |
| 1290 | } |
| 1291 | } |
| 1292 | |
Kalvin Lee | 4c50a3fd | 2025-02-27 15:00:16 | [diff] [blame] | 1293 | constexpr auto kValidAppIdCases = std::to_array<OriginClaimedAuthorityPair>({ |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1294 | {"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 Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1306 | {"https://www.google.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1307 | "https://www.gstatic.com/securitykey/origins.json", |
| 1308 | AuthenticatorStatus::SUCCESS}, |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1309 | {"https://www.google.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1310 | "https://www.gstatic.com/securitykey/a/google.com/origins.json", |
| 1311 | AuthenticatorStatus::SUCCESS}, |
Adam Langley | 42f5dd5 | 2018-03-19 21:23:22 | [diff] [blame] | 1312 | {"https://accounts.google.com", |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1313 | "https://www.gstatic.com/securitykey/origins.json", |
| 1314 | AuthenticatorStatus::SUCCESS}, |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1315 | }); |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1316 | |
| 1317 | // Verify behavior for various combinations of origins and RP IDs. |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1318 | TEST_F(AuthenticatorImplTest, AppIdExtensionValues) { |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1319 | for (const auto& test_case : kValidAppIdCases) { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1320 | SCOPED_TRACE( |
| 1321 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1322 | |
Martin Kreichgauer | 4138eab | 2019-05-21 07:05:55 | [diff] [blame] | 1323 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1324 | TryAuthenticationWithAppId(test_case.origin, |
| 1325 | test_case.claimed_authority)); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1326 | |
| 1327 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, |
| 1328 | TryRegistrationWithAppIdExclude(test_case.origin, |
| 1329 | test_case.claimed_authority)); |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1330 | } |
| 1331 | |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1332 | // All the invalid relying party test cases should also be invalid as AppIDs. |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1333 | for (const auto& test_case : kInvalidRpTestCases) { |
| 1334 | SCOPED_TRACE( |
| 1335 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1336 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 1337 | if (test_case.claimed_authority.empty()) { |
Adam Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1338 | // In this case, no AppID is actually being tested. |
| 1339 | continue; |
| 1340 | } |
| 1341 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1342 | 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 Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1346 | |
Ken Buchanan | a36345d | 2019-11-01 17:48:30 | [diff] [blame] | 1347 | 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 Langley | a4ace03 | 2018-04-04 20:14:27 | [diff] [blame] | 1351 | } |
Adam Langley | dc639a9 | 2018-02-28 17:18:42 | [diff] [blame] | 1352 | } |
| 1353 | |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1354 | // Verify that a credential registered with U2F can be used via webauthn. |
| 1355 | TEST_F(AuthenticatorImplTest, AppIdExtension) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1356 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1357 | |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1358 | { |
| 1359 | // First, test that the appid extension isn't echoed at all when not |
| 1360 | // requested. |
| 1361 | PublicKeyCredentialRequestOptionsPtr options = |
| 1362 | GetTestPublicKeyCredentialRequestOptions(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1363 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1364 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1365 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1366 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 1367 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 1368 | EXPECT_EQ(result.response->extensions->echo_appid_extension, false); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1369 | } |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1370 | |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1371 | { |
| 1372 | // Second, test that the appid extension is echoed, but is false, when appid |
| 1373 | // is requested but not used. |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1374 | ResetVirtualDevice(); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1375 | PublicKeyCredentialRequestOptionsPtr options = |
| 1376 | GetTestPublicKeyCredentialRequestOptions(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1377 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1378 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1379 | |
| 1380 | // This AppID won't be used because the RP ID will be tried (successfully) |
| 1381 | // first. |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 1382 | options->extensions->appid = kTestOrigin1; |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1383 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1384 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 1385 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 1386 | EXPECT_EQ(result.response->extensions->echo_appid_extension, true); |
| 1387 | EXPECT_EQ(result.response->extensions->appid_extension, false); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1388 | } |
| 1389 | |
| 1390 | { |
| 1391 | // Lastly, when used, the appid extension result should be "true". |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1392 | ResetVirtualDevice(); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1393 | PublicKeyCredentialRequestOptionsPtr options = |
| 1394 | GetTestPublicKeyCredentialRequestOptions(); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1395 | // Inject a registration for the URL (which is a U2F AppID). |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1396 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1397 | options->allow_credentials[0].id, kTestOrigin1)); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1398 | |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 1399 | options->extensions->appid = kTestOrigin1; |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1400 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1401 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 1402 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 1403 | EXPECT_EQ(result.response->extensions->echo_appid_extension, true); |
| 1404 | EXPECT_EQ(result.response->extensions->appid_extension, true); |
Adam Langley | ce35226 | 2018-08-10 17:38:22 | [diff] [blame] | 1405 | } |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1406 | |
| 1407 | { |
| 1408 | // AppID should still work when the authenticator supports credProtect. |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1409 | ResetVirtualDevice(); |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1410 | 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1416 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1417 | |
| 1418 | // Inject a registration for the URL (which is a U2F AppID). |
| 1419 | PublicKeyCredentialRequestOptionsPtr options = |
| 1420 | GetTestPublicKeyCredentialRequestOptions(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1421 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1422 | options->allow_credentials[0].id, kTestOrigin1)); |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1423 | |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 1424 | options->extensions->appid = kTestOrigin1; |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1425 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1426 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 1427 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 1428 | EXPECT_EQ(result.response->extensions->echo_appid_extension, true); |
| 1429 | EXPECT_EQ(result.response->extensions->appid_extension, true); |
Adam Langley | ab18201 | 2019-05-17 17:22:02 | [diff] [blame] | 1430 | } |
Adam Langley | 5a5d5af | 2018-08-02 15:28:51 | [diff] [blame] | 1431 | } |
| 1432 | |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1433 | TEST_F(AuthenticatorImplTest, AppIdExcludeExtension) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1434 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1435 | |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1457 | options->exclude_credentials[0].id, kTestOrigin1)); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1458 | } |
| 1459 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1460 | MakeCredentialResult result = |
| 1461 | AuthenticatorMakeCredential(std::move(options)); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1462 | |
| 1463 | if (credential_already_exists) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1464 | ASSERT_EQ(result.status, AuthenticatorStatus::CREDENTIAL_EXCLUDED); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1465 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1466 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1467 | } |
| 1468 | } |
| 1469 | } |
Adam Langley | 407d81d0 | 2020-02-21 02:18:16 | [diff] [blame] | 1470 | |
| 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1480 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 1481 | AuthenticatorStatus::SUCCESS); |
Adam Langley | 407d81d0 | 2020-02-21 02:18:16 | [diff] [blame] | 1482 | } |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1498 | ASSERT_GT(cred.id.size(), config.max_credential_id_length); |
Adam Langley | 407d81d0 | 2020-02-21 02:18:16 | [diff] [blame] | 1499 | } |
| 1500 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1501 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 1502 | AuthenticatorStatus::SUCCESS); |
Adam Langley | 407d81d0 | 2020-02-21 02:18:16 | [diff] [blame] | 1503 | } |
Adam Langley | 0616cd4 | 2019-08-08 22:31:10 | [diff] [blame] | 1504 | } |
| 1505 | |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1506 | TEST_F(AuthenticatorImplTest, TestGetAssertionTimeout) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1507 | // The VirtualFidoAuthenticator simulates a tap immediately after it gets the |
| 1508 | // request. Replace by the real discovery that will wait until timeout. |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 1509 | ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>()); |
| 1510 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1511 | NavigateAndCommit(GURL(kTestOrigin1)); |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 1512 | base::HistogramTester histogram_tester; |
Kim Paulhamus | 9d87fb0d | 2018-01-18 18:36:22 | [diff] [blame] | 1513 | PublicKeyCredentialRequestOptionsPtr options = |
| 1514 | GetTestPublicKeyCredentialRequestOptions(); |
Kim Paulhamus | 9433181 | 2018-01-18 21:17:32 | [diff] [blame] | 1515 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1516 | EXPECT_EQ( |
| 1517 | AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status, |
| 1518 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 1519 | histogram_tester.ExpectUniqueSample( |
| 1520 | "WebAuthentication.GetAssertion.Result", |
Ken Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 1521 | AuthenticatorCommonImpl::CredentialRequestResult::kTimeout, 1); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1522 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUiTimeout, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1523 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | 9d87fb0d | 2018-01-18 18:36:22 | [diff] [blame] | 1524 | } |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 1525 | |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1526 | TEST_F(AuthenticatorImplTest, OversizedCredentialId) { |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1527 | // 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 Langley | b4a2270a | 2018-04-05 21:47:15 | [diff] [blame] | 1531 | for (const size_t size : kSizes) { |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1532 | SCOPED_TRACE(size); |
| 1533 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1534 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1535 | PublicKeyCredentialRequestOptionsPtr options = |
| 1536 | GetTestPublicKeyCredentialRequestOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 1537 | device::PublicKeyCredentialDescriptor credential; |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1538 | credential.credential_type = device::CredentialType::kPublicKey; |
| 1539 | credential.id.resize(size); |
| 1540 | credential.transports.emplace( |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 1541 | device::FidoTransportProtocol::kUsbHumanInterfaceDevice); |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1542 | |
| 1543 | const bool should_be_valid = size < 256; |
| 1544 | if (should_be_valid) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1545 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1546 | credential.id, kTestRelyingPartyId)); |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1547 | } |
| 1548 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 1549 | options->allow_credentials.emplace_back(credential); |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1550 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1551 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 1552 | should_be_valid ? AuthenticatorStatus::SUCCESS |
| 1553 | : AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | ce16059 | 2018-04-05 18:38:12 | [diff] [blame] | 1554 | } |
| 1555 | } |
| 1556 | |
Adam Langley | ebfd987 | 2022-07-12 20:26:08 | [diff] [blame] | 1557 | TEST_F(AuthenticatorImplTest, NoSilentAuthenticationForCable) { |
Ken Buchanan | 19fb9c5 | 2021-12-03 20:15:51 | [diff] [blame] | 1558 | // https://crbug.com/954355 |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1559 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1560 | |
| 1561 | for (bool is_cable_device : {false, true}) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1562 | ResetVirtualDevice(); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1563 | device::VirtualCtap2Device::Config config; |
| 1564 | config.reject_silent_authentication_requests = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1565 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1566 | |
| 1567 | PublicKeyCredentialRequestOptionsPtr options = |
| 1568 | GetTestPublicKeyCredentialRequestOptions(); |
| 1569 | options->allow_credentials = GetTestCredentials(/*num_credentials=*/2); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 1570 | options->extensions->cable_authentication_data = GetTestCableExtension(); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1571 | |
| 1572 | if (is_cable_device) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1573 | virtual_device_factory_->SetTransport( |
Adam Langley | cbafdd4f | 2022-08-15 02:48:23 | [diff] [blame] | 1574 | device::FidoTransportProtocol::kHybrid); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1575 | for (auto& cred : options->allow_credentials) { |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1576 | cred.transports.clear(); |
Adam Langley | cbafdd4f | 2022-08-15 02:48:23 | [diff] [blame] | 1577 | cred.transports.emplace(device::FidoTransportProtocol::kHybrid); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1578 | } |
| 1579 | } |
| 1580 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1581 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1582 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1583 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1584 | // 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 Langley | 2a9355a | 2019-04-23 00:11:02 | [diff] [blame] | 1591 | } |
| 1592 | } |
| 1593 | |
Adam Langley | 0abb556a | 2023-06-08 22:39:54 | [diff] [blame] | 1594 | TEST_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 Choi | 0e56e5dd | 2018-06-08 21:27:34 | [diff] [blame] | 1614 | TEST_F(AuthenticatorImplTest, TestGetAssertionU2fDeviceBackwardsCompatibility) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1615 | NavigateAndCommit(GURL(kTestOrigin1)); |
Jun Choi | 17bbafc | 2018-04-11 08:37:20 | [diff] [blame] | 1616 | PublicKeyCredentialRequestOptionsPtr options = |
| 1617 | GetTestPublicKeyCredentialRequestOptions(); |
Jun Choi | 0e56e5dd | 2018-06-08 21:27:34 | [diff] [blame] | 1618 | // Inject credential ID to the virtual device so that successful sign in is |
| 1619 | // possible. |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1620 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1621 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Jun Choi | 0e56e5dd | 2018-06-08 21:27:34 | [diff] [blame] | 1622 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1623 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 1624 | AuthenticatorStatus::SUCCESS); |
Kim Paulhamus | 19b1cd5 | 2018-05-14 20:50:05 | [diff] [blame] | 1625 | } |
| 1626 | |
Kim Paulhamus | 595abc65 | 2018-04-12 20:42:51 | [diff] [blame] | 1627 | TEST_F(AuthenticatorImplTest, GetAssertionWithEmptyAllowCredentials) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1628 | NavigateAndCommit(GURL(kTestOrigin1)); |
Kim Paulhamus | 595abc65 | 2018-04-12 20:42:51 | [diff] [blame] | 1629 | PublicKeyCredentialRequestOptionsPtr options = |
| 1630 | GetTestPublicKeyCredentialRequestOptions(); |
| 1631 | options->allow_credentials.clear(); |
Kim Paulhamus | 595abc65 | 2018-04-12 20:42:51 | [diff] [blame] | 1632 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1633 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 1634 | AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1635 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kRkNotSupported, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1636 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1637 | } |
| 1638 | |
| 1639 | TEST_F(AuthenticatorImplTest, MakeCredentialAlreadyRegistered) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1640 | NavigateAndCommit(GURL(kTestOrigin1)); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1641 | PublicKeyCredentialCreationOptionsPtr options = |
| 1642 | GetTestPublicKeyCredentialCreationOptions(); |
| 1643 | |
| 1644 | // Exclude the one already registered credential. |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 1645 | options->exclude_credentials = GetTestCredentials(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1646 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1647 | options->exclude_credentials[0].id, kTestRelyingPartyId)); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1648 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1649 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 1650 | AuthenticatorStatus::CREDENTIAL_EXCLUDED); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1651 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kCredentialExcluded, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1652 | AuthenticationRequestMode::kModalWebAuthn); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1653 | } |
| 1654 | |
| 1655 | TEST_F(AuthenticatorImplTest, MakeCredentialPendingRequest) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1656 | NavigateAndCommit(GURL(kTestOrigin1)); |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 1657 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 1658 | ConnectToAuthenticator(); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1659 | |
| 1660 | // Make first request. |
| 1661 | PublicKeyCredentialCreationOptionsPtr options = |
| 1662 | GetTestPublicKeyCredentialCreationOptions(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1663 | TestMakeCredentialFuture future; |
| 1664 | authenticator->MakeCredential(std::move(options), future.GetCallback()); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1665 | |
| 1666 | // Make second request. |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 1667 | // TODO(crbug.com/41355992): Rework to ensure there are potential race |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1668 | // conditions once we have VirtualAuthenticatorEnvironment. |
| 1669 | PublicKeyCredentialCreationOptionsPtr options2 = |
| 1670 | GetTestPublicKeyCredentialCreationOptions(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1671 | TestMakeCredentialFuture future2; |
| 1672 | authenticator->MakeCredential(std::move(options2), future2.GetCallback()); |
| 1673 | EXPECT_TRUE(future2.Wait()); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1674 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1675 | EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, std::get<0>(future2.Get())); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1676 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1677 | EXPECT_TRUE(future.Wait()); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1678 | } |
| 1679 | |
| 1680 | TEST_F(AuthenticatorImplTest, GetAssertionPendingRequest) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1681 | NavigateAndCommit(GURL(kTestOrigin1)); |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 1682 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 1683 | ConnectToAuthenticator(); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1684 | |
| 1685 | // Make first request. |
| 1686 | PublicKeyCredentialRequestOptionsPtr options = |
| 1687 | GetTestPublicKeyCredentialRequestOptions(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 1688 | TestGetCredentialFuture future; |
| 1689 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1690 | |
| 1691 | // Make second request. |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 1692 | // TODO(crbug.com/41355992): Rework to ensure there are potential race |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1693 | // conditions once we have VirtualAuthenticatorEnvironment. |
| 1694 | PublicKeyCredentialRequestOptionsPtr options2 = |
| 1695 | GetTestPublicKeyCredentialRequestOptions(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 1696 | TestGetCredentialFuture future2; |
| 1697 | authenticator->GetCredential(std::move(options2), future2.GetCallback()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1698 | EXPECT_TRUE(future2.Wait()); |
Kim Paulhamus | 183d192 | 2018-04-20 23:27:09 | [diff] [blame] | 1699 | |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 1700 | EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, |
| 1701 | future2.Get()->get_get_assertion_response()->status); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1702 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 1703 | EXPECT_TRUE(future.Wait()); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1704 | } |
| 1705 | |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 1706 | TEST_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 Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 1714 | TestGetCredentialFuture future; |
| 1715 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Gabriel Viera | 7bc08f21 | 2024-07-10 15:42:33 | [diff] [blame] | 1716 | |
| 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 Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1729 | TEST_F(AuthenticatorImplTest, NavigationDuringOperation) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1730 | NavigateAndCommit(GURL(kTestOrigin1)); |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 1731 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 1732 | ConnectToAuthenticator(); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1733 | |
| 1734 | base::RunLoop run_loop; |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 1735 | authenticator.set_disconnect_handler(run_loop.QuitClosure()); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1736 | |
| 1737 | // Make first request. |
| 1738 | PublicKeyCredentialRequestOptionsPtr options = |
| 1739 | GetTestPublicKeyCredentialRequestOptions(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 1740 | TestGetCredentialFuture future; |
| 1741 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1742 | |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 1743 | // Simulate a navigation while waiting for the user to press the token. |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1744 | virtual_device_factory_->mutable_state()->simulate_press_callback = |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 1745 | base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) { |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 1746 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 1747 | FROM_HERE, base::BindLambdaForTesting( |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1748 | [&]() { NavigateAndCommit(GURL(kTestOrigin2)); })); |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 1749 | return false; |
| 1750 | }); |
Adam Langley | 2cffe38 | 2018-06-12 01:52:54 | [diff] [blame] | 1751 | |
| 1752 | run_loop.Run(); |
Kim Paulhamus | 595abc65 | 2018-04-12 20:42:51 | [diff] [blame] | 1753 | } |
| 1754 | |
Adam Langley | dd978e31 | 2018-05-24 18:50:48 | [diff] [blame] | 1755 | TEST_F(AuthenticatorImplTest, InvalidResponse) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1756 | virtual_device_factory_->mutable_state()->simulate_invalid_response = true; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1757 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | dd978e31 | 2018-05-24 18:50:48 | [diff] [blame] | 1758 | |
| 1759 | { |
| 1760 | PublicKeyCredentialRequestOptionsPtr options = |
| 1761 | GetTestPublicKeyCredentialRequestOptions(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1762 | EXPECT_EQ( |
| 1763 | AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status, |
| 1764 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | dd978e31 | 2018-05-24 18:50:48 | [diff] [blame] | 1765 | } |
| 1766 | |
| 1767 | { |
| 1768 | PublicKeyCredentialCreationOptionsPtr options = |
| 1769 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1770 | EXPECT_EQ( |
| 1771 | AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status, |
| 1772 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | dd978e31 | 2018-05-24 18:50:48 | [diff] [blame] | 1773 | } |
| 1774 | } |
| 1775 | |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1776 | TEST_F(AuthenticatorImplTest, Ctap2AssertionWithUnknownCredential) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1777 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 322908a8 | 2019-03-25 21:03:10 | [diff] [blame] | 1778 | |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1779 | 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 Langley | 322908a8 | 2019-03-25 21:03:10 | [diff] [blame] | 1783 | |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1784 | device::VirtualCtap2Device::Config config; |
| 1785 | config.return_immediate_invalid_credential_error = |
| 1786 | return_immediate_invalid_credential_error; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1787 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1788 | |
| 1789 | bool pressed = false; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1790 | virtual_device_factory_->mutable_state()->simulate_press_callback = |
Nina Satragno | 9d6389e | 2019-06-14 21:21:35 | [diff] [blame] | 1791 | base::BindRepeating( |
| 1792 | [](bool* flag, device::VirtualFidoDevice* device) { |
| 1793 | *flag = true; |
| 1794 | return true; |
| 1795 | }, |
| 1796 | &pressed); |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1797 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1798 | EXPECT_EQ( |
| 1799 | AuthenticatorGetAssertion(GetTestPublicKeyCredentialRequestOptions()) |
| 1800 | .status, |
| 1801 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 1802 | VerifyGetAssertionOutcomeUkm(0, |
| 1803 | GetAssertionOutcome::kCredentialNotRecognized, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 1804 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | db01719 | 2019-03-26 04:47:39 | [diff] [blame] | 1805 | // The user must have pressed the authenticator for the operation to |
| 1806 | // resolve. |
| 1807 | EXPECT_TRUE(pressed); |
| 1808 | } |
Adam Langley | 322908a8 | 2019-03-25 21:03:10 | [diff] [blame] | 1809 | } |
| 1810 | |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1811 | TEST_F(AuthenticatorImplTest, GetAssertionResponseWithAttestedCredentialData) { |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1812 | device::VirtualCtap2Device::Config config; |
| 1813 | config.return_attested_cred_data_in_get_assertion_response = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1814 | virtual_device_factory_->SetCtap2Config(config); |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1815 | PublicKeyCredentialRequestOptionsPtr options = |
| 1816 | GetTestPublicKeyCredentialRequestOptions(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 1817 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 1818 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1819 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1820 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1821 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1822 | EXPECT_EQ( |
| 1823 | AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status, |
| 1824 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Martin Kreichgauer | 3b57e2a0 | 2019-03-30 01:13:17 | [diff] [blame] | 1825 | } |
| 1826 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 1827 | #if BUILDFLAG(IS_WIN) |
Martin Kreichgauer | a4c7fcf4 | 2024-08-19 22:59:45 | [diff] [blame] | 1828 | TEST_F(AuthenticatorImplTest, Win_IsUVPAA) { |
Adam Langley | 3037544 | 2023-06-19 17:20:26 | [diff] [blame] | 1829 | virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1830 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 1831 | 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 Kreichgauer | a4c7fcf4 | 2024-08-19 22:59:45 | [diff] [blame] | 1839 | 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 Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 1847 | } |
| 1848 | } |
| 1849 | } |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 1850 | #endif // BUILDFLAG(IS_WIN) |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 1851 | |
Howard Yang | 72a1412b | 2022-04-13 00:16:02 | [diff] [blame] | 1852 | #if BUILDFLAG(IS_CHROMEOS) |
Martin Kreichgauer | de9c5dee | 2020-03-11 21:20:41 | [diff] [blame] | 1853 | TEST_F(AuthenticatorImplTest, IsUVPAA) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 1854 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 1855 | EXPECT_FALSE(AuthenticatorIsUvpaa()); |
Martin Kreichgauer | de9c5dee | 2020-03-11 21:20:41 | [diff] [blame] | 1856 | } |
Howard Yang | 72a1412b | 2022-04-13 00:16:02 | [diff] [blame] | 1857 | #endif // BUILDFLAG(IS_CHROMEOS) |
Martin Kreichgauer | de9c5dee | 2020-03-11 21:20:41 | [diff] [blame] | 1858 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1859 | // TestWebAuthenticationRequestProxy is a test fake implementation of the |
| 1860 | // WebAuthenticationRequestProxy embedder interface. |
| 1861 | class TestWebAuthenticationRequestProxy : public WebAuthenticationRequestProxy { |
| 1862 | public: |
| 1863 | struct Config { |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1864 | // If true, resolves all request event callbacks instantly. |
| 1865 | bool resolve_callbacks = true; |
| 1866 | |
| 1867 | // The return value of IsActive(). |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1868 | bool is_active = true; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1869 | |
| 1870 | // The fake response to SignalIsUVPAARequest(). |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1871 | bool is_uvpaa = true; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1872 | |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1873 | // Whether the request to SignalCreateRequest() should succeed. |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1874 | bool request_success = true; |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1875 | |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1876 | // If `request_success` is false, the name of the DOMError to be |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1877 | // returned. |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1878 | std::string request_error_name = "NotAllowedError"; |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1879 | |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1880 | // If `request_success` is true, the fake response to be returned for an |
| 1881 | // onCreateRequest event. |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1882 | blink::mojom::MakeCredentialAuthenticatorResponsePtr |
| 1883 | make_credential_response = nullptr; |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1884 | |
| 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 Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1889 | }; |
| 1890 | |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 1891 | 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 Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1896 | }; |
| 1897 | |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1898 | ~TestWebAuthenticationRequestProxy() override { |
| 1899 | DCHECK(!HasPendingRequest()); |
| 1900 | } |
| 1901 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1902 | Config& config() { return config_; } |
| 1903 | |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 1904 | Observations& observations() { return observations_; } |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1905 | |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 1906 | bool IsActive(const url::Origin& caller_origin) override { |
| 1907 | return config_.is_active; |
| 1908 | } |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1909 | |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1910 | RequestId SignalCreateRequest( |
| 1911 | const PublicKeyCredentialCreationOptionsPtr& options, |
| 1912 | CreateCallback callback) override { |
| 1913 | DCHECK(!HasPendingRequest()); |
| 1914 | |
| 1915 | current_request_id_++; |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 1916 | observations_.create_requests.push_back(options->Clone()); |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1917 | pending_create_callback_ = std::move(callback); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1918 | if (config_.resolve_callbacks) { |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1919 | RunPendingCreateCallback(); |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1920 | return current_request_id_; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1921 | } |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1922 | return current_request_id_; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1923 | } |
| 1924 | |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1925 | RequestId SignalGetRequest( |
| 1926 | const PublicKeyCredentialRequestOptionsPtr& options, |
| 1927 | GetCallback callback) override { |
| 1928 | current_request_id_++; |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 1929 | observations_.get_requests.push_back(options->Clone()); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1930 | 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 Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1938 | RequestId SignalIsUvpaaRequest(IsUvpaaCallback callback) override { |
| 1939 | DCHECK(!HasPendingRequest()); |
| 1940 | |
| 1941 | current_request_id_++; |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 1942 | observations_.num_isuvpaa++; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1943 | if (config_.resolve_callbacks) { |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 1944 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1945 | FROM_HERE, base::BindOnce(std::move(callback), config_.is_uvpaa)); |
| 1946 | return current_request_id_; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1947 | } |
| 1948 | DCHECK(!pending_is_uvpaa_callback_); |
| 1949 | pending_is_uvpaa_callback_ = std::move(callback); |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1950 | return current_request_id_; |
| 1951 | } |
| 1952 | |
| 1953 | void CancelRequest(RequestId request_id) override { |
| 1954 | DCHECK_EQ(request_id, current_request_id_); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 1955 | observations_.num_cancel++; |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 1956 | if (pending_create_callback_) { |
| 1957 | pending_create_callback_.Reset(); |
| 1958 | } |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1959 | if (pending_get_callback_) { |
| 1960 | pending_get_callback_.Reset(); |
| 1961 | } |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1962 | } |
| 1963 | |
| 1964 | void RunPendingCreateCallback() { |
| 1965 | DCHECK(pending_create_callback_); |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1966 | auto callback = |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1967 | config_.request_success |
Martin Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1968 | ? 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 Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1974 | config_.request_error_name, "message"), |
| 1975 | nullptr); |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 1976 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| 1977 | FROM_HERE, std::move(callback)); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 1978 | } |
| 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 Kreichgauer | 6119e84 | 2022-01-28 01:52:41 | [diff] [blame] | 1991 | nullptr); |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 1992 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| 1993 | FROM_HERE, std::move(callback)); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 1994 | } |
| 1995 | |
| 1996 | void RunPendingIsUvpaaCallback() { |
| 1997 | DCHECK(pending_is_uvpaa_callback_); |
| 1998 | std::move(pending_is_uvpaa_callback_).Run(config_.is_uvpaa); |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 1999 | } |
| 2000 | |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 2001 | bool HasPendingRequest() { |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 2002 | return pending_create_callback_ || pending_get_callback_ || |
| 2003 | pending_is_uvpaa_callback_; |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 2004 | } |
| 2005 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 2006 | private: |
| 2007 | Config config_; |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 2008 | Observations observations_; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 2009 | |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 2010 | RequestId current_request_id_ = 0; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 2011 | CreateCallback pending_create_callback_; |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 2012 | GetCallback pending_get_callback_; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 2013 | IsUvpaaCallback pending_is_uvpaa_callback_; |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 2014 | }; |
| 2015 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2016 | // TestWebAuthenticationDelegate is a test fake implementation of the |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2017 | // WebAuthenticationDelegate embedder interface. |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2018 | class TestWebAuthenticationDelegate : public WebAuthenticationDelegate { |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 2019 | public: |
Ken Buchanan | 90fe2955 | 2024-04-26 21:15:48 | [diff] [blame] | 2020 | void IsUserVerifyingPlatformAuthenticatorAvailableOverride( |
| 2021 | RenderFrameHost*, |
| 2022 | base::OnceCallback<void(std::optional<bool>)> callback) override { |
| 2023 | std::move(callback).Run(is_uvpaa_override); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2024 | } |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 2025 | |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2026 | bool OverrideCallerOriginAndRelyingPartyIdValidation( |
Martin Kreichgauer | d7aa4f9 | 2022-02-22 20:46:21 | [diff] [blame] | 2027 | content::BrowserContext* browser_context, |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2028 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2034 | std::optional<std::string> MaybeGetRelyingPartyIdOverride( |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 2035 | const std::string& claimed_rp_id, |
| 2036 | const url::Origin& caller_origin) override { |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2037 | if (permit_extensions && caller_origin.scheme() == kExtensionScheme) { |
| 2038 | return caller_origin.Serialize(); |
| 2039 | } |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2040 | return std::nullopt; |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 2041 | } |
| 2042 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2043 | bool ShouldPermitIndividualAttestation( |
| 2044 | content::BrowserContext* browser_context, |
Martin Kreichgauer | 4c607076 | 2022-03-29 16:27:22 | [diff] [blame] | 2045 | const url::Origin& caller_origin, |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2046 | const std::string& relying_party_id) override { |
Adam Langley | f21a60b5 | 2021-08-19 01:39:45 | [diff] [blame] | 2047 | return permit_individual_attestation || |
| 2048 | (permit_individual_attestation_for_rp_id.has_value() && |
| 2049 | relying_party_id == *permit_individual_attestation_for_rp_id); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2050 | } |
Nina Satragno | 9d64c5b | 2020-08-21 23:22:08 | [diff] [blame] | 2051 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2052 | bool SupportsResidentKeys(RenderFrameHost*) override { |
| 2053 | return supports_resident_keys; |
| 2054 | } |
| 2055 | |
| 2056 | bool IsFocused(WebContents* web_contents) override { return is_focused; } |
| 2057 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 2058 | #if BUILDFLAG(IS_MAC) |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2059 | std::optional<TouchIdAuthenticatorConfig> GetTouchIdAuthenticatorConfig( |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2060 | BrowserContext* browser_context) override { |
| 2061 | return touch_id_authenticator_config; |
| 2062 | } |
| 2063 | #endif |
| 2064 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 2065 | WebAuthenticationRequestProxy* MaybeGetRequestProxy( |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 2066 | 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 Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 2071 | } |
| 2072 | |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 2073 | 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 Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2079 | 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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2085 | // If set, the return value of IsUVPAA() will be overridden with this value. |
| 2086 | // Platform-specific implementations will not be invoked. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2087 | std::optional<bool> is_uvpaa_override; |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2088 | |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2089 | // If set, the delegate will permit WebAuthn requests from chrome-extension |
| 2090 | // origins. |
| 2091 | bool permit_extensions = false; |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2092 | |
| 2093 | // Indicates whether individual attestation should be permitted by the |
| 2094 | // delegate. |
| 2095 | bool permit_individual_attestation = false; |
| 2096 | |
Adam Langley | f21a60b5 | 2021-08-19 01:39:45 | [diff] [blame] | 2097 | // A specific RP ID for which individual attestation will be permitted. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2098 | std::optional<std::string> permit_individual_attestation_for_rp_id; |
Adam Langley | f21a60b5 | 2021-08-19 01:39:45 | [diff] [blame] | 2099 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2100 | // 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 Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 2107 | #if BUILDFLAG(IS_MAC) |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2108 | // Configuration data for the macOS platform authenticator. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2109 | std::optional<TouchIdAuthenticatorConfig> touch_id_authenticator_config; |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2110 | #endif |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 2111 | |
| 2112 | // The WebAuthenticationRequestProxy returned by |MaybeGetRequestProxy|. |
| 2113 | std::unique_ptr<TestWebAuthenticationRequestProxy> request_proxy = nullptr; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 2114 | |
| 2115 | // The origin permitted to use the RemoteDesktopClientOverride extension. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2116 | std::optional<url::Origin> remote_desktop_client_override_origin; |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2117 | |
| 2118 | // Return value of `BrowserProvidedPasskeysAvailable()`. |
| 2119 | bool browser_provided_passkeys_available = false; |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 2120 | }; |
| 2121 | |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2122 | enum class EnterprisePolicy { |
| 2123 | LISTED, |
| 2124 | NOT_LISTED, |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2125 | }; |
| 2126 | |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2127 | enum class AttestationType { |
| 2128 | ANY, |
| 2129 | NONE, |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2130 | NONE_WITH_NONZERO_AAGUID, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2131 | U2F, |
| 2132 | SELF, |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2133 | SELF_WITH_NONZERO_AAGUID, |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2134 | PACKED, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2135 | }; |
| 2136 | |
Adam Langley | 70a2415 | 2022-08-30 02:01:04 | [diff] [blame] | 2137 | const 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öm | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 2149 | NOTREACHED(); |
Adam Langley | 70a2415 | 2022-08-30 02:01:04 | [diff] [blame] | 2150 | } |
| 2151 | } |
| 2152 | |
| 2153 | const 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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2170 | // TestAuthenticatorRequestDelegate is a test fake implementation of the |
| 2171 | // AuthenticatorRequestClientDelegate embedder interface. |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2172 | class TestAuthenticatorRequestDelegate |
| 2173 | : public AuthenticatorRequestClientDelegate { |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2174 | public: |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2175 | TestAuthenticatorRequestDelegate( |
| 2176 | RenderFrameHost* render_frame_host, |
| 2177 | base::OnceClosure action_callbacks_registered_callback, |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2178 | base::OnceClosure started_over_callback, |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2179 | bool simulate_user_cancelled, |
| 2180 | std::optional<bool>* enclave_authenticator_should_be_discovered, |
| 2181 | base::flat_set<device::FidoTransportProtocol>* discovered_transports) |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2182 | : action_callbacks_registered_callback_( |
| 2183 | std::move(action_callbacks_registered_callback)), |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 2184 | started_over_callback_(std::move(started_over_callback)), |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2185 | does_block_request_on_failure_(!started_over_callback_.is_null()), |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2186 | simulate_user_cancelled_(simulate_user_cancelled), |
| 2187 | enclave_authenticator_should_be_discovered_( |
| 2188 | enclave_authenticator_should_be_discovered), |
| 2189 | discovered_transports_(discovered_transports) {} |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2190 | |
Peter Boström | 828b902 | 2021-09-21 02:28:43 | [diff] [blame] | 2191 | TestAuthenticatorRequestDelegate(const TestAuthenticatorRequestDelegate&) = |
| 2192 | delete; |
| 2193 | TestAuthenticatorRequestDelegate& operator=( |
| 2194 | const TestAuthenticatorRequestDelegate&) = delete; |
| 2195 | |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 2196 | void RegisterActionCallbacks( |
| 2197 | base::OnceClosure cancel_callback, |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 2198 | base::OnceClosure immediate_not_found_callback, |
danakj | d46bdf4 | 2019-11-27 22:07:27 | [diff] [blame] | 2199 | base::RepeatingClosure start_over_callback, |
Nina Satragno | fe6e52ad7 | 2022-06-01 14:04:14 | [diff] [blame] | 2200 | AccountPreselectedCallback account_preselected_callback, |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 2201 | PasswordSelectedCallback password_selected_callback, |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 2202 | device::FidoRequestHandlerBase::RequestCallback request_callback, |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 2203 | base::OnceClosure cancel_ui_timeout_callback, |
Nina Satragno | 6e0f1ab | 2024-06-13 22:28:11 | [diff] [blame] | 2204 | base::RepeatingClosure bluetooth_adapter_power_on_callback, |
| 2205 | base::RepeatingCallback< |
| 2206 | void(device::FidoRequestHandlerBase::BlePermissionCallback)> |
| 2207 | ble_status_callback) override { |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2208 | ASSERT_TRUE(action_callbacks_registered_callback_) |
| 2209 | << "RegisterActionCallbacks called twice."; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 2210 | cancel_callback_ = std::move(cancel_callback); |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2211 | std::move(action_callbacks_registered_callback_).Run(); |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 2212 | if (started_over_callback_) { |
| 2213 | action_callbacks_registered_callback_ = std::move(started_over_callback_); |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 2214 | start_over_callback_ = start_over_callback; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 2215 | } |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 2216 | } |
| 2217 | |
Adam Langley | 8bf3e4f | 2019-08-29 21:08:11 | [diff] [blame] | 2218 | void OnTransportAvailabilityEnumerated( |
| 2219 | device::FidoRequestHandlerBase::TransportAvailabilityInfo transport_info) |
| 2220 | override { |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2221 | if (discovered_transports_) { |
| 2222 | *discovered_transports_ = transport_info.available_transports; |
| 2223 | } |
Adam Langley | 8bf3e4f | 2019-08-29 21:08:11 | [diff] [blame] | 2224 | // 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 Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2227 | if (transport_info.available_transports.empty() || |
| 2228 | simulate_user_cancelled_) { |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 2229 | std::move(cancel_callback_).Run(); |
Adam Langley | 8bf3e4f | 2019-08-29 21:08:11 | [diff] [blame] | 2230 | } |
| 2231 | } |
| 2232 | |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 2233 | 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 Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2243 | 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 Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2260 | base::OnceClosure action_callbacks_registered_callback_; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 2261 | base::OnceClosure cancel_callback_; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 2262 | base::OnceClosure started_over_callback_; |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 2263 | base::OnceClosure start_over_callback_; |
| 2264 | bool does_block_request_on_failure_ = false; |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2265 | bool simulate_user_cancelled_ = false; |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2266 | 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 Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2269 | }; |
| 2270 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2271 | // TestAuthenticatorContentBrowserClient is a test fake implementation of the |
| 2272 | // ContentBrowserClient interface that injects |TestWebAuthenticationDelegate| |
| 2273 | // and |TestAuthenticatorRequestDelegate| instances into |AuthenticatorImpl|. |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2274 | class TestAuthenticatorContentBrowserClient : public ContentBrowserClient { |
| 2275 | public: |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 2276 | TestWebAuthenticationDelegate* GetTestWebAuthenticationDelegate() { |
| 2277 | return &web_authentication_delegate; |
| 2278 | } |
| 2279 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2280 | // ContentBrowserClient: |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 2281 | WebAuthenticationDelegate* GetWebAuthenticationDelegate() override { |
| 2282 | return &web_authentication_delegate; |
| 2283 | } |
| 2284 | |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2285 | bool IsSecurityLevelAcceptableForWebAuthn( |
| 2286 | content::RenderFrameHost* rfh, |
| 2287 | const url::Origin& origin) override { |
| 2288 | return is_webauthn_security_level_acceptable; |
| 2289 | } |
| 2290 | |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2291 | std::unique_ptr<AuthenticatorRequestClientDelegate> |
| 2292 | GetWebAuthenticationRequestDelegate( |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 2293 | RenderFrameHost* render_frame_host) override { |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 2294 | if (return_null_delegate) { |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 2295 | return nullptr; |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 2296 | } |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2297 | return std::make_unique<TestAuthenticatorRequestDelegate>( |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 2298 | render_frame_host, |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2299 | action_callbacks_registered_callback |
| 2300 | ? std::move(action_callbacks_registered_callback) |
| 2301 | : base::DoNothing(), |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2302 | std::move(started_over_callback_), simulate_user_cancelled_, |
| 2303 | &enclave_authenticator_should_be_discovered_, &discovered_transports_); |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2304 | } |
| 2305 | |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 2306 | TestWebAuthenticationDelegate web_authentication_delegate; |
| 2307 | |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 2308 | // If set, this closure will be called when the subsequently constructed |
| 2309 | // delegate is informed that the request has started. |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 2310 | base::OnceClosure action_callbacks_registered_callback; |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 2311 | |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 2312 | // This emulates scenarios where a nullptr RequestClientDelegate is returned |
| 2313 | // because a request is already in progress. |
| 2314 | bool return_null_delegate = false; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 2315 | |
| 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 Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 2320 | // second time action callbacks are registered. It also causes |
| 2321 | // DoesBlockRequestOnFailure to return true, once. |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 2322 | base::OnceClosure started_over_callback_; |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2323 | |
| 2324 | // This simulates the user immediately cancelling the request after transport |
| 2325 | // availability info is enumerated. |
| 2326 | bool simulate_user_cancelled_ = false; |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2327 | |
| 2328 | // The return value of IsSecurityLevelAcceptableForWebAuthn. |
| 2329 | bool is_webauthn_security_level_acceptable = true; |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2330 | |
| 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 Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2336 | }; |
| 2337 | |
| 2338 | // A test class that installs and removes an |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2339 | // |TestAuthenticatorContentBrowserClient| automatically and can run tests |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2340 | // against simulated attestation results. |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2341 | class AuthenticatorContentBrowserClientTest : public AuthenticatorImplTest { |
| 2342 | public: |
| 2343 | AuthenticatorContentBrowserClientTest() = default; |
| 2344 | |
Peter Boström | 9b03653 | 2021-10-28 23:37:28 | [diff] [blame] | 2345 | AuthenticatorContentBrowserClientTest( |
| 2346 | const AuthenticatorContentBrowserClientTest&) = delete; |
| 2347 | AuthenticatorContentBrowserClientTest& operator=( |
| 2348 | const AuthenticatorContentBrowserClientTest&) = delete; |
| 2349 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2350 | struct TestCase { |
| 2351 | AttestationConveyancePreference attestation_requested; |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2352 | EnterprisePolicy enterprise_policy; |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2353 | AuthenticatorStatus expected_status; |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2354 | AttestationType expected_attestation; |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2355 | std::string_view expected_certificate_substring; |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2356 | }; |
| 2357 | |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2358 | 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 Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2368 | void RunTestCases(const std::vector<TestCase>& tests) { |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2369 | for (size_t i = 0; i < tests.size(); i++) { |
| 2370 | const auto& test = tests[i]; |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2371 | SCOPED_TRACE(test.enterprise_policy == EnterprisePolicy::LISTED |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2372 | ? "individual attestation" |
| 2373 | : "no individual attestation"); |
| 2374 | SCOPED_TRACE( |
| 2375 | AttestationConveyancePreferenceToString(test.attestation_requested)); |
| 2376 | SCOPED_TRACE(i); |
| 2377 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2378 | test_client_.GetTestWebAuthenticationDelegate() |
| 2379 | ->permit_individual_attestation = |
| 2380 | test.enterprise_policy == EnterprisePolicy::LISTED; |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2381 | |
| 2382 | PublicKeyCredentialCreationOptionsPtr options = |
| 2383 | GetTestPublicKeyCredentialCreationOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 2384 | options->relying_party.id = "example.com"; |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 2385 | options->timeout = base::Seconds(1); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 2386 | options->attestation = |
| 2387 | ConvertAttestationConveyancePreference(test.attestation_requested); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 2388 | |
| 2389 | MakeCredentialResult result = |
| 2390 | AuthenticatorMakeCredential(std::move(options)); |
| 2391 | EXPECT_EQ(result.status, test.expected_status); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2392 | |
| 2393 | if (test.expected_status != AuthenticatorStatus::SUCCESS) { |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2394 | ASSERT_EQ(AttestationType::ANY, test.expected_attestation); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2395 | continue; |
| 2396 | } |
| 2397 | |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2398 | const device::AuthenticatorData auth_data = |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 2399 | AuthDataFromMakeCredentialResponse(result.response); |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2400 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 2401 | std::optional<Value> attestation_value = |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 2402 | Reader::Read(result.response->attestation_object); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2403 | ASSERT_TRUE(attestation_value); |
| 2404 | ASSERT_TRUE(attestation_value->is_map()); |
| 2405 | const auto& attestation = attestation_value->GetMap(); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2406 | |
| 2407 | switch (test.expected_attestation) { |
| 2408 | case AttestationType::ANY: |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2409 | ASSERT_TRUE(test.expected_certificate_substring.empty()); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2410 | break; |
| 2411 | |
| 2412 | case AttestationType::NONE: |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2413 | ASSERT_TRUE(test.expected_certificate_substring.empty()); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2414 | ExpectMapHasKeyWithStringValue(attestation, "fmt", "none"); |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2415 | EXPECT_TRUE(auth_data.attested_data()->IsAaguidZero()); |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2416 | break; |
| 2417 | |
| 2418 | case AttestationType::NONE_WITH_NONZERO_AAGUID: |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2419 | ASSERT_TRUE(test.expected_certificate_substring.empty()); |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2420 | ExpectMapHasKeyWithStringValue(attestation, "fmt", "none"); |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2421 | EXPECT_FALSE(auth_data.attested_data()->IsAaguidZero()); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2422 | break; |
| 2423 | |
| 2424 | case AttestationType::U2F: |
| 2425 | ExpectMapHasKeyWithStringValue(attestation, "fmt", "fido-u2f"); |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2426 | if (!test.expected_certificate_substring.empty()) { |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2427 | ExpectCertificateContainingSubstring( |
| 2428 | attestation, test.expected_certificate_substring); |
| 2429 | } |
| 2430 | break; |
| 2431 | |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2432 | case AttestationType::PACKED: |
| 2433 | ExpectMapHasKeyWithStringValue(attestation, "fmt", "packed"); |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2434 | if (!test.expected_certificate_substring.empty()) { |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2435 | ExpectCertificateContainingSubstring( |
| 2436 | attestation, test.expected_certificate_substring); |
| 2437 | } |
| 2438 | break; |
| 2439 | |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2440 | case AttestationType::SELF: { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2441 | ASSERT_TRUE(test.expected_certificate_substring.empty()); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2442 | 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 Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2446 | attestation.find(Value("attStmt")); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2447 | 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 Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2452 | ASSERT_TRUE(attestation_statement.find(Value("x5c")) == |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2453 | attestation_statement.end()); |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2454 | ASSERT_TRUE(attestation_statement.find(Value("ecdaaKeyId")) == |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2455 | attestation_statement.end()); |
Adam Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2456 | EXPECT_TRUE(auth_data.attested_data()->IsAaguidZero()); |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2457 | break; |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2458 | } |
| 2459 | case AttestationType::SELF_WITH_NONZERO_AAGUID: { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2460 | ASSERT_TRUE(test.expected_certificate_substring.empty()); |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2461 | 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 Langley | b7d451e02 | 2020-06-23 20:18:57 | [diff] [blame] | 2475 | EXPECT_FALSE(auth_data.attested_data()->IsAaguidZero()); |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2476 | break; |
| 2477 | } |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2478 | } |
| 2479 | } |
| 2480 | } |
| 2481 | |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2482 | protected: |
Balazs Engedy | a7ff7098 | 2018-06-04 18:14:47 | [diff] [blame] | 2483 | TestAuthenticatorContentBrowserClient test_client_; |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2484 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2485 | // Expects that |map| contains the given key with a string-value equal to |
| 2486 | // |expected|. |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2487 | static void ExpectMapHasKeyWithStringValue(const Value::MapValue& map, |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2488 | const char* key, |
| 2489 | const char* expected) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2490 | const auto it = map.find(Value(key)); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2491 | 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 Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 2501 | // Asserts that the webauthn attestation CBOR map in |attestation| contains a |
| 2502 | // single X.509 certificate containing |substring|. |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2503 | static void ExpectCertificateContainingSubstring( |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2504 | const Value::MapValue& attestation, |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 2505 | std::string_view substring) { |
Adam Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2506 | const auto& attestation_statement_it = attestation.find(Value("attStmt")); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2507 | 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 Langley | b4f12f9 | 2018-10-26 21:00:02 | [diff] [blame] | 2511 | const auto& x5c_it = attestation_statement.find(Value("x5c")); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2512 | 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 Hasan | a963a934 | 2024-04-03 10:15:14 | [diff] [blame] | 2517 | std::string_view cert = x5c[0].GetBytestringAsString(); |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2518 | EXPECT_TRUE(cert.find(substring) != cert.npos); |
| 2519 | } |
| 2520 | |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 2521 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2522 | }; |
| 2523 | |
Nina Satragno | 8d8e5cc | 2022-11-08 18:29:06 | [diff] [blame] | 2524 | TEST_F(AuthenticatorContentBrowserClientTest, MakeCredentialTLSError) { |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2525 | NavigateAndCommit(GURL(kTestOrigin1)); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2526 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 8d8e5cc | 2022-11-08 18:29:06 | [diff] [blame] | 2527 | PublicKeyCredentialCreationOptionsPtr options = |
| 2528 | GetTestPublicKeyCredentialCreationOptions(); |
| 2529 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 2530 | AuthenticatorStatus::CERTIFICATE_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 2531 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kOtherFailure, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 2532 | AuthenticationRequestMode::kModalWebAuthn); |
Nina Satragno | 8d8e5cc | 2022-11-08 18:29:06 | [diff] [blame] | 2533 | } |
| 2534 | |
| 2535 | TEST_F(AuthenticatorContentBrowserClientTest, GetAssertionTLSError) { |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2536 | NavigateAndCommit(GURL(kTestOrigin1)); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2537 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 8d8e5cc | 2022-11-08 18:29:06 | [diff] [blame] | 2538 | PublicKeyCredentialRequestOptionsPtr options = |
| 2539 | GetTestPublicKeyCredentialRequestOptions(); |
| 2540 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 2541 | AuthenticatorStatus::CERTIFICATE_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 2542 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kOtherFailure, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 2543 | AuthenticationRequestMode::kModalWebAuthn); |
Nina Satragno | 8d8e5cc | 2022-11-08 18:29:06 | [diff] [blame] | 2544 | } |
| 2545 | |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2546 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 2547 | MakeCredentialSkipTLSCheckWithVirtualEnvironment) { |
| 2548 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 1e03fb0 | 2023-03-16 23:02:03 | [diff] [blame] | 2549 | content::AuthenticatorEnvironment::GetInstance() |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2550 | ->EnableVirtualAuthenticatorFor( |
| 2551 | static_cast<content::RenderFrameHostImpl*>(main_rfh()) |
| 2552 | ->frame_tree_node(), |
| 2553 | /*enable_ui=*/false); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2554 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2555 | PublicKeyCredentialCreationOptionsPtr options = |
| 2556 | GetTestPublicKeyCredentialCreationOptions(); |
| 2557 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 2558 | AuthenticatorStatus::SUCCESS); |
| 2559 | } |
| 2560 | |
| 2561 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 2562 | GetAssertionSkipTLSCheckWithVirtualEnvironment) { |
| 2563 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 1e03fb0 | 2023-03-16 23:02:03 | [diff] [blame] | 2564 | content::AuthenticatorEnvironment::GetInstance() |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2565 | ->EnableVirtualAuthenticatorFor( |
| 2566 | static_cast<content::RenderFrameHostImpl*>(main_rfh()) |
| 2567 | ->frame_tree_node(), |
| 2568 | /*enable_ui=*/false); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 2569 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 52163de | 2022-11-23 23:03:10 | [diff] [blame] | 2570 | 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 Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2578 | TEST_F(AuthenticatorContentBrowserClientTest, TestGetAssertionCancel) { |
| 2579 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 2580 | test_client_.simulate_user_cancelled_ = true; |
| 2581 | base::HistogramTester histogram_tester; |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2582 | |
| 2583 | EXPECT_EQ(AuthenticatorGetAssertion().status, |
| 2584 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
| 2585 | histogram_tester.ExpectUniqueSample( |
| 2586 | "WebAuthentication.GetAssertion.Result", |
Ken Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 2587 | AuthenticatorCommonImpl::CredentialRequestResult::kUserCancelled, 1); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 2588 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUserCancellation, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 2589 | AuthenticationRequestMode::kModalWebAuthn); |
Ken Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 2590 | } |
| 2591 | |
| 2592 | TEST_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 Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 2602 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kUserCancellation, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 2603 | AuthenticationRequestMode::kModalWebAuthn); |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 2604 | } |
| 2605 | |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 2606 | // Tests that the enclave authenticator should only be discovered for make |
| 2607 | // credential requests it can fulfill. |
| 2608 | TEST_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. |
| 2641 | TEST_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 | |
| 2688 | TEST_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 | |
| 2707 | TEST_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 | |
| 2726 | TEST_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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2744 | // Test that credentials can be created and used from an extension origin when |
| 2745 | // permitted by the delegate. |
| 2746 | TEST_F(AuthenticatorContentBrowserClientTest, ChromeExtensions) { |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2747 | constexpr char kExtensionId[] = "abcdefg"; |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2748 | static const std::string kExtensionOrigin = |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2749 | std::string(kExtensionScheme) + "://" + kExtensionId; |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2750 | |
| 2751 | NavigateAndCommit(GURL(kExtensionOrigin + "/test.html")); |
| 2752 | |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2753 | 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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2758 | |
| 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 Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2767 | if (permit_webauthn_in_extensions) { |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2768 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
| 2769 | credential_id = result.response->info->raw_id; |
| 2770 | } else { |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2771 | EXPECT_EQ(result.status, AuthenticatorStatus::INVALID_PROTOCOL); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2772 | } |
| 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 Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2783 | permit_webauthn_in_extensions |
| 2784 | ? AuthenticatorStatus::SUCCESS |
| 2785 | : AuthenticatorStatus::INVALID_PROTOCOL); |
| 2786 | } |
| 2787 | } |
| 2788 | } |
| 2789 | |
| 2790 | TEST_F(AuthenticatorContentBrowserClientTest, ChromeExtensionBadRpIds) { |
| 2791 | // Permit WebAuthn in extensions. |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2792 | static const std::string kExtensionOrigin = |
Lei Zhang | 9cc9aad7 | 2022-08-25 19:19:18 | [diff] [blame] | 2793 | base::StrCat({kExtensionScheme, "://abcdefg"}); |
Martin Kreichgauer | 05a33fb | 2022-02-18 23:31:29 | [diff] [blame] | 2794 | 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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 2817 | } |
| 2818 | } |
| 2819 | } |
| 2820 | |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2821 | TEST_F(AuthenticatorContentBrowserClientTest, AttestationBehaviour) { |
| 2822 | const char kStandardCommonName[] = "U2F Attestation"; |
| 2823 | const char kIndividualCommonName[] = "Individual Cert"; |
| 2824 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2825 | const std::vector<TestCase> kTests = { |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2826 | { |
| 2827 | AttestationConveyancePreference::NONE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2828 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2829 | AuthenticatorStatus::SUCCESS, |
| 2830 | AttestationType::NONE, |
| 2831 | "", |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2832 | }, |
| 2833 | { |
| 2834 | AttestationConveyancePreference::NONE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2835 | EnterprisePolicy::LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2836 | AuthenticatorStatus::SUCCESS, |
| 2837 | AttestationType::NONE, |
| 2838 | "", |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2839 | }, |
| 2840 | { |
| 2841 | AttestationConveyancePreference::INDIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2842 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2843 | AuthenticatorStatus::SUCCESS, |
| 2844 | AttestationType::U2F, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2845 | kStandardCommonName, |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2846 | }, |
| 2847 | { |
| 2848 | AttestationConveyancePreference::INDIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2849 | EnterprisePolicy::LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2850 | AuthenticatorStatus::SUCCESS, |
| 2851 | AttestationType::U2F, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2852 | kStandardCommonName, |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2853 | }, |
| 2854 | { |
| 2855 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2856 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2857 | AuthenticatorStatus::SUCCESS, |
| 2858 | AttestationType::U2F, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2859 | kStandardCommonName, |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2860 | }, |
| 2861 | { |
| 2862 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2863 | EnterprisePolicy::LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2864 | AuthenticatorStatus::SUCCESS, |
| 2865 | AttestationType::U2F, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2866 | kStandardCommonName, |
Adam Langley | 02b10ce | 2018-06-09 03:32:00 | [diff] [blame] | 2867 | }, |
| 2868 | { |
| 2869 | AttestationConveyancePreference::ENTERPRISE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2870 | EnterprisePolicy::NOT_LISTED, |
Suzy Li | 4870264 | 2019-04-08 20:01:46 | [diff] [blame] | 2871 | AuthenticatorStatus::SUCCESS, |
Adam Langley | 193cb37 | 2024-09-13 20:09:05 | [diff] [blame] | 2872 | AttestationType::U2F, |
| 2873 | kStandardCommonName, |
Adam Langley | 02b10ce | 2018-06-09 03:32:00 | [diff] [blame] | 2874 | }, |
| 2875 | { |
| 2876 | AttestationConveyancePreference::ENTERPRISE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2877 | EnterprisePolicy::LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2878 | AuthenticatorStatus::SUCCESS, |
| 2879 | AttestationType::U2F, |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 2880 | kIndividualCommonName, |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2881 | }, |
| 2882 | }; |
| 2883 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 2884 | virtual_device_factory_->mutable_state()->attestation_cert_common_name = |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2885 | kStandardCommonName; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 2886 | virtual_device_factory_->mutable_state() |
| 2887 | ->individual_attestation_cert_common_name = kIndividualCommonName; |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2888 | NavigateAndCommit(GURL("https://example.com")); |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2889 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2890 | RunTestCases(kTests); |
| 2891 | } |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2892 | |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2893 | TEST_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 Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2913 | AuthenticatorStatus::SUCCESS, |
| 2914 | AttestationType::PACKED, |
| 2915 | kIndividualCommonName, |
| 2916 | }, |
| 2917 | { |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2918 | AttestationConveyancePreference::ENTERPRISE, |
| 2919 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2920 | AuthenticatorStatus::SUCCESS, |
Adam Langley | 193cb37 | 2024-09-13 20:09:05 | [diff] [blame] | 2921 | AttestationType::PACKED, |
| 2922 | kStandardCommonName, |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2923 | }, |
| 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 Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2944 | AuthenticatorStatus::SUCCESS, |
| 2945 | AttestationType::PACKED, |
| 2946 | kIndividualCommonName, |
| 2947 | }, |
| 2948 | { |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2949 | AttestationConveyancePreference::ENTERPRISE, |
Adam Langley | 193cb37 | 2024-09-13 20:09:05 | [diff] [blame] | 2950 | EnterprisePolicy::LISTED, |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2951 | AuthenticatorStatus::SUCCESS, |
Adam Langley | 193cb37 | 2024-09-13 20:09:05 | [diff] [blame] | 2952 | AttestationType::PACKED, |
| 2953 | kIndividualCommonName, |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2954 | }, |
| 2955 | }; |
| 2956 | |
| 2957 | RunTestCases(kTests); |
| 2958 | } |
| 2959 | } |
| 2960 | |
| 2961 | TEST_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 Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2969 | { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 2970 | EXPECT_EQ( |
| 2971 | AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions()) |
| 2972 | .status, |
| 2973 | AuthenticatorStatus::SUCCESS); |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2974 | } |
| 2975 | |
| 2976 | config.always_return_enterprise_attestation = true; |
| 2977 | virtual_device_factory_->SetCtap2Config(config); |
| 2978 | |
| 2979 | { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 2980 | EXPECT_EQ( |
| 2981 | AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions()) |
| 2982 | .status, |
| 2983 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | f1c8b74 | 2020-07-22 19:36:06 | [diff] [blame] | 2984 | } |
| 2985 | } |
| 2986 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2987 | TEST_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 Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 2992 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 2993 | const std::vector<TestCase> kTests = { |
| 2994 | { |
Adam Langley | 02b10ce | 2018-06-09 03:32:00 | [diff] [blame] | 2995 | AttestationConveyancePreference::ENTERPRISE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 2996 | EnterprisePolicy::NOT_LISTED, |
Suzy Li | 4870264 | 2019-04-08 20:01:46 | [diff] [blame] | 2997 | AuthenticatorStatus::SUCCESS, |
| 2998 | AttestationType::NONE, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 2999 | "", |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 3000 | }, |
| 3001 | { |
Adam Langley | 02b10ce | 2018-06-09 03:32:00 | [diff] [blame] | 3002 | AttestationConveyancePreference::ENTERPRISE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3003 | EnterprisePolicy::LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3004 | AuthenticatorStatus::SUCCESS, |
| 3005 | AttestationType::U2F, |
| 3006 | kCommonName, |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 3007 | }, |
| 3008 | }; |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 3009 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3010 | virtual_device_factory_->mutable_state()->attestation_cert_common_name = |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 3011 | kCommonName; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3012 | virtual_device_factory_->mutable_state() |
| 3013 | ->individual_attestation_cert_common_name = kCommonName; |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 3014 | NavigateAndCommit(GURL("https://example.com")); |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 3015 | |
Adam Langley | 88415c81 | 2018-03-22 20:20:22 | [diff] [blame] | 3016 | RunTestCases(kTests); |
Adam Langley | 31caeef4 | 2018-03-22 03:44:10 | [diff] [blame] | 3017 | } |
| 3018 | |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3019 | // 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. |
| 3022 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3023 | PlatformAuthenticatorAttestation) { |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 3024 | test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3025 | virtual_device_factory_->SetSupportedProtocol( |
| 3026 | device::ProtocolVersion::kCtap2); |
Nina Satragno | f585eca | 2019-07-29 20:05:32 | [diff] [blame] | 3027 | virtual_device_factory_->SetTransport( |
| 3028 | device::FidoTransportProtocol::kInternal); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3029 | virtual_device_factory_->mutable_state()->self_attestation = true; |
| 3030 | virtual_device_factory_->mutable_state() |
| 3031 | ->non_zero_aaguid_with_self_attestation = true; |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3032 | 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 Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3041 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3042 | AuthenticatorStatus::SUCCESS, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3043 | AttestationType::NONE_WITH_NONZERO_AAGUID, |
| 3044 | "", |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3045 | }, |
| 3046 | { |
Martin Kreichgauer | f3c5ed5b | 2021-01-25 21:19:00 | [diff] [blame] | 3047 | // Attestation is always returned if requested because it is privacy |
| 3048 | // preserving. The AttestationConsent value is irrelevant. |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3049 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3050 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3051 | AuthenticatorStatus::SUCCESS, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3052 | AttestationType::SELF_WITH_NONZERO_AAGUID, |
| 3053 | "", |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3054 | }, |
| 3055 | }; |
| 3056 | |
| 3057 | RunTestCases(kTests); |
| 3058 | } |
| 3059 | |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3060 | TEST_F(AuthenticatorContentBrowserClientTest, Ctap2SelfAttestation) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3061 | virtual_device_factory_->SetSupportedProtocol( |
| 3062 | device::ProtocolVersion::kCtap2); |
| 3063 | virtual_device_factory_->mutable_state()->self_attestation = true; |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3064 | 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 Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3071 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3072 | AuthenticatorStatus::SUCCESS, |
| 3073 | AttestationType::SELF, |
| 3074 | "", |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3075 | }, |
| 3076 | { |
Adam Langley | 193cb37 | 2024-09-13 20:09:05 | [diff] [blame] | 3077 | // And if direct attestation was requested. |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3078 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3079 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3080 | AuthenticatorStatus::SUCCESS, |
| 3081 | AttestationType::SELF, |
| 3082 | "", |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3083 | }, |
| 3084 | }; |
| 3085 | |
| 3086 | RunTestCases(kTests); |
| 3087 | } |
| 3088 | |
| 3089 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3090 | Ctap2SelfAttestationNonZeroAaguid) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3091 | 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 Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3096 | NavigateAndCommit(GURL("https://example.com")); |
| 3097 | |
| 3098 | const std::vector<TestCase> kTests = { |
| 3099 | { |
Martin Kreichgauer | 2c9e535 | 2018-10-30 23:28:53 | [diff] [blame] | 3100 | // Since the virtual device is configured to set a non-zero AAGUID the |
| 3101 | // self-attestation should still be replaced with a "none" |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3102 | // attestation. |
| 3103 | AttestationConveyancePreference::NONE, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3104 | EnterprisePolicy::NOT_LISTED, |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 3105 | AuthenticatorStatus::SUCCESS, |
| 3106 | AttestationType::NONE, |
| 3107 | "", |
Adam Langley | 3015ad4 | 2018-08-15 02:04:36 | [diff] [blame] | 3108 | }, |
| 3109 | }; |
| 3110 | |
| 3111 | RunTestCases(kTests); |
| 3112 | } |
| 3113 | |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3114 | TEST_F(AuthenticatorContentBrowserClientTest, BlockedAttestation) { |
| 3115 | NavigateAndCommit(GURL("https://foo.example.com")); |
| 3116 | |
| 3117 | static constexpr struct { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3118 | const char* filter_json; |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3119 | AttestationConveyancePreference attestation; |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3120 | EnterprisePolicy enterprise_policy; |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3121 | AttestationType result; |
| 3122 | } kTests[] = { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3123 | // Empty or nonsense filter doesn't block anything. |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3124 | { |
| 3125 | "", |
| 3126 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3127 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3128 | AttestationType::U2F, |
| 3129 | }, |
| 3130 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3131 | R"({"filters": []})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3132 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3133 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3134 | AttestationType::U2F, |
| 3135 | }, |
| 3136 | // Direct listing of domain blocks... |
| 3137 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3138 | R"({"filters": [{ |
| 3139 | "operation": "mc", |
| 3140 | "rp_id": "example.com", |
| 3141 | "action": "no-attestation" |
| 3142 | }]})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3143 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3144 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3145 | AttestationType::NONE, |
| 3146 | }, |
| 3147 | // ... unless attestation is permitted by policy. |
| 3148 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3149 | R"({"filters": [{ |
| 3150 | "operation": "mc", |
| 3151 | "rp_id": "example.com", |
| 3152 | "action": "no-attestation" |
| 3153 | }]})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3154 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3155 | EnterprisePolicy::LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3156 | AttestationType::U2F, |
| 3157 | }, |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3158 | // 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 Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3161 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3162 | R"({"filters": [{ |
| 3163 | "operation": "mc", |
| 3164 | "rp_id": "*example.com", |
| 3165 | "action": "no-attestation" |
| 3166 | }]})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3167 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3168 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3169 | AttestationType::NONE, |
| 3170 | }, |
| 3171 | // Policy again overrides |
| 3172 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3173 | R"({"filters": [{ |
| 3174 | "operation": "mc", |
| 3175 | "rp_id": "*example.com", |
| 3176 | "action": "no-attestation" |
| 3177 | }]})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3178 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3179 | EnterprisePolicy::LISTED, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3180 | AttestationType::U2F, |
| 3181 | }, |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3182 | // An explicit wildcard will match everything, be careful. (Omitting |
| 3183 | // both RP ID and device is a parse error, however.) |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3184 | { |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3185 | R"({"filters": [{ |
| 3186 | "operation": "mc", |
| 3187 | "rp_id": "*", |
| 3188 | "action": "no-attestation" |
| 3189 | }]})", |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3190 | AttestationConveyancePreference::DIRECT, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3191 | EnterprisePolicy::NOT_LISTED, |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3192 | AttestationType::NONE, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3193 | }, |
| 3194 | }; |
| 3195 | |
| 3196 | int test_num = 0; |
| 3197 | for (const auto& test : kTests) { |
| 3198 | SCOPED_TRACE(test_num++); |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3199 | SCOPED_TRACE(test.filter_json); |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3200 | |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3201 | device::fido_filter::ScopedFilterForTesting filter(test.filter_json); |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3202 | |
| 3203 | const std::vector<TestCase> kTestCase = { |
| 3204 | { |
| 3205 | test.attestation, |
Adam Langley | 1d65a2a | 2020-06-08 22:13:24 | [diff] [blame] | 3206 | test.enterprise_policy, |
Adam Langley | f6fbd03 | 2020-03-16 23:44:23 | [diff] [blame] | 3207 | AuthenticatorStatus::SUCCESS, |
| 3208 | test.result, |
| 3209 | "", |
| 3210 | }, |
| 3211 | }; |
| 3212 | |
| 3213 | RunTestCases(kTestCase); |
| 3214 | } |
| 3215 | } |
| 3216 | |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3217 | TEST_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 | |
| 3372 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 3478 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | 051b9793 | 2021-01-11 19:11:23 | [diff] [blame] | 3479 | 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 | |
| 3489 | TEST_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 Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3504 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3505 | MakeCredentialRequestStartedCallback) { |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3506 | NavigateAndCommit(GURL(kTestOrigin1)); |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 3507 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 3508 | ConnectToAuthenticator(); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3509 | |
| 3510 | PublicKeyCredentialCreationOptionsPtr options = |
| 3511 | GetTestPublicKeyCredentialCreationOptions(); |
| 3512 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3513 | TestRequestStartedFuture request_started_future; |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 3514 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3515 | request_started_future.GetCallback(); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3516 | authenticator->MakeCredential(std::move(options), base::DoNothing()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3517 | EXPECT_TRUE(request_started_future.Wait()); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3518 | } |
| 3519 | |
| 3520 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3521 | GetAssertionRequestStartedCallback) { |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3522 | NavigateAndCommit(GURL(kTestOrigin1)); |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 3523 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 3524 | ConnectToAuthenticator(); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3525 | |
| 3526 | PublicKeyCredentialRequestOptionsPtr options = |
| 3527 | GetTestPublicKeyCredentialRequestOptions(); |
| 3528 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3529 | TestRequestStartedFuture request_started_future; |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 3530 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3531 | request_started_future.GetCallback(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 3532 | authenticator->GetCredential(std::move(options), base::DoNothing()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3533 | EXPECT_TRUE(request_started_future.Wait()); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3534 | } |
| 3535 | |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3536 | TEST_F(AuthenticatorContentBrowserClientTest, MakeCredentialStartOver) { |
| 3537 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 3538 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 3539 | ConnectToAuthenticator(); |
| 3540 | |
| 3541 | PublicKeyCredentialCreationOptionsPtr options = |
| 3542 | GetTestPublicKeyCredentialCreationOptions(); |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 3543 | // Make the request fail so that it's started over. |
| 3544 | options->authenticator_selection->user_verification_requirement = |
| 3545 | device::UserVerificationRequirement::kRequired; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3546 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3547 | TestRequestStartedFuture request_started_future; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3548 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3549 | request_started_future.GetCallback(); |
| 3550 | TestRequestStartedFuture request_restarted_future; |
| 3551 | test_client_.started_over_callback_ = request_restarted_future.GetCallback(); |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3552 | |
| 3553 | authenticator->MakeCredential(std::move(options), base::DoNothing()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3554 | EXPECT_TRUE(request_started_future.Wait()); |
| 3555 | EXPECT_TRUE(request_restarted_future.Wait()); |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 3556 | |
| 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 Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3563 | } |
| 3564 | |
| 3565 | TEST_F(AuthenticatorContentBrowserClientTest, GetAssertionStartOver) { |
| 3566 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 3567 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 3568 | ConnectToAuthenticator(); |
| 3569 | |
| 3570 | PublicKeyCredentialRequestOptionsPtr options = |
| 3571 | GetTestPublicKeyCredentialRequestOptions(); |
| 3572 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3573 | TestRequestStartedFuture request_started_future; |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3574 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3575 | request_started_future.GetCallback(); |
| 3576 | TestRequestStartedFuture request_restarted_future; |
| 3577 | test_client_.started_over_callback_ = request_restarted_future.GetCallback(); |
Nina Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3578 | |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 3579 | authenticator->GetCredential(std::move(options), base::DoNothing()); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3580 | EXPECT_TRUE(request_started_future.Wait()); |
| 3581 | EXPECT_TRUE(request_restarted_future.Wait()); |
Adam Langley | 39ca22f | 2022-01-19 01:15:32 | [diff] [blame] | 3582 | |
| 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 Satragno | 7782b103 | 2020-11-05 17:59:26 | [diff] [blame] | 3589 | } |
| 3590 | |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3591 | TEST_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 Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 3595 | test_client_.GetTestWebAuthenticationDelegate()->is_focused = false; |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3596 | |
| 3597 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3598 | |
| 3599 | { |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3600 | TestRequestStartedFuture request_started_future; |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 3601 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3602 | request_started_future.GetCallback(); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3603 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 3604 | EXPECT_EQ( |
| 3605 | AuthenticatorMakeCredential(GetTestPublicKeyCredentialCreationOptions()) |
| 3606 | .status, |
| 3607 | AuthenticatorStatus::NOT_FOCUSED); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3608 | EXPECT_FALSE(request_started_future.IsReady()); |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3609 | } |
| 3610 | |
| 3611 | { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 3612 | device::PublicKeyCredentialDescriptor credential; |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 3613 | credential.credential_type = device::CredentialType::kPublicKey; |
| 3614 | credential.id.resize(16); |
| 3615 | credential.transports = { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 3616 | device::FidoTransportProtocol::kUsbHumanInterfaceDevice}; |
Jun Choi | e4aee4ff3 | 2018-08-13 19:35:07 | [diff] [blame] | 3617 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 3618 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 3619 | credential.id, kTestRelyingPartyId)); |
Adam Langley | 4f054828 | 2020-06-12 18:54:30 | [diff] [blame] | 3620 | PublicKeyCredentialRequestOptionsPtr options = |
| 3621 | GetTestPublicKeyCredentialRequestOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 3622 | options->allow_credentials.emplace_back(credential); |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3623 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3624 | TestRequestStartedFuture request_started_future; |
Jun Choi | d53a039c | 2018-08-16 23:38:15 | [diff] [blame] | 3625 | test_client_.action_callbacks_registered_callback = |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3626 | request_started_future.GetCallback(); |
Balazs Engedy | a275018 | 2018-06-13 07:45:26 | [diff] [blame] | 3627 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 3628 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 3629 | AuthenticatorStatus::SUCCESS); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 3630 | EXPECT_TRUE(request_started_future.IsReady()); |
Adam Langley | 573d3ac | 2018-04-28 00:32:13 | [diff] [blame] | 3631 | } |
| 3632 | } |
| 3633 | |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 3634 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3635 | NullDelegate_RejectsWithPendingRequest) { |
| 3636 | test_client_.return_null_delegate = true; |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 3637 | NavigateAndCommit(GURL(kTestOrigin1)); |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 3638 | |
| 3639 | { |
| 3640 | PublicKeyCredentialCreationOptionsPtr options = |
| 3641 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 3642 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 3643 | AuthenticatorStatus::PENDING_REQUEST); |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 3644 | } |
| 3645 | |
| 3646 | { |
| 3647 | PublicKeyCredentialRequestOptionsPtr options = |
| 3648 | GetTestPublicKeyCredentialRequestOptions(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 3649 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 3650 | AuthenticatorStatus::PENDING_REQUEST); |
Balazs Engedy | 5b108876 | 2018-06-05 09:30:46 | [diff] [blame] | 3651 | } |
| 3652 | } |
| 3653 | |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 3654 | TEST_F(AuthenticatorContentBrowserClientTest, IsUVPAAOverride) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 3655 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 3656 | |
Nina Satragno | f585eca | 2019-07-29 20:05:32 | [diff] [blame] | 3657 | for (const bool is_uvpaa : {false, true}) { |
| 3658 | SCOPED_TRACE(::testing::Message() << "is_uvpaa=" << is_uvpaa); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 3659 | test_client_.GetTestWebAuthenticationDelegate()->is_uvpaa_override = |
| 3660 | is_uvpaa; |
Martin Kreichgauer | 977c0047 | 2018-11-29 00:09:04 | [diff] [blame] | 3661 | |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 3662 | EXPECT_EQ(AuthenticatorIsUvpaa(), is_uvpaa); |
Martin Kreichgauer | 977c0047 | 2018-11-29 00:09:04 | [diff] [blame] | 3663 | } |
| 3664 | } |
Martin Kreichgauer | bcfda9cf | 2018-08-02 00:47:07 | [diff] [blame] | 3665 | |
Nina Satragno | 365759f | 2023-10-11 20:01:29 | [diff] [blame] | 3666 | TEST_F(AuthenticatorContentBrowserClientTest, |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3667 | 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 Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 3679 | } |
| 3680 | } |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3681 | |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 3682 | TEST_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 Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3695 | ExpectCapability(capabilities, |
| 3696 | client_capabilities::kPasskeyPlatformAuthenticator, |
| 3697 | is_uvpaa); |
| 3698 | } |
Andrii Natiahlyi | e480a49 | 2024-09-18 15:20:37 | [diff] [blame] | 3699 | |
| 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 Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3715 | } |
| 3716 | |
| 3717 | TEST_F(AuthenticatorContentBrowserClientTest, |
| 3718 | GetClientCapabilities_ConditionalGet_ReturnsFalse) { |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3719 | NavigateAndCommit(GURL(kTestOrigin1)); |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 3720 | ClientCapabilitiesList capabilities = AuthenticatorGetClientCapabilities(); |
| 3721 | ExpectCapability(capabilities, client_capabilities::kConditionalGet, true); |
| 3722 | } |
| 3723 | |
| 3724 | TEST_F(AuthenticatorContentBrowserClientTest, |
Nina Satragno | 365759f | 2023-10-11 20:01:29 | [diff] [blame] | 3725 | GPMPasskeys_IsConditionalMediationAvailable) { |
Nina Satragno | 365759f | 2023-10-11 20:01:29 | [diff] [blame] | 3726 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 3727 | ASSERT_TRUE(AuthenticatorIsConditionalMediationAvailable()); |
| 3728 | } |
| 3729 | |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3730 | // AuthenticatorImplRemoteDesktopClientOverrideTest exercises the |
| 3731 | // RemoteDesktopClientOverride extension, which is used by remote desktop |
| 3732 | // applications exercising requests on behalf of other origins. |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3733 | class AuthenticatorImplRemoteDesktopClientOverrideTest |
| 3734 | : public AuthenticatorContentBrowserClientTest { |
| 3735 | protected: |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3736 | static constexpr char kOtherRdpOrigin[] = "https://myrdp.test"; |
| 3737 | static constexpr char kExampleOrigin[] = "https://example.test"; |
| 3738 | static constexpr char kExampleRpId[] = "example.test"; |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3739 | static constexpr char kExampleAppid[] = "https://example.test/appid.json"; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3740 | static constexpr char kOtherRpId[] = "other.test"; |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3741 | static constexpr char kOtherAppid[] = "https://other.test/appid.json"; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3742 | |
| 3743 | void SetUp() override { |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 3744 | AuthenticatorContentBrowserClientTest::SetUp(); |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3745 | // Authorize `kCorpCrdOrigin` to exercise the extension. In //chrome, this |
| 3746 | // is controlled by the `WebAuthenticationRemoteProxiedRequestsAllowed` |
| 3747 | // enterprise policy. |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3748 | test_client_.GetTestWebAuthenticationDelegate() |
| 3749 | ->remote_desktop_client_override_origin = |
| 3750 | url::Origin::Create(GURL(kCorpCrdOrigin)); |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3751 | // Controls the Blink feature gating the extension. |
| 3752 | scoped_command_line_.GetProcessCommandLine()->AppendSwitch( |
| 3753 | switches::kWebAuthRemoteDesktopSupport); |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3754 | } |
| 3755 | |
| 3756 | base::test::ScopedCommandLine scoped_command_line_; |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3757 | }; |
| 3758 | |
| 3759 | TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, MakeCredential) { |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3760 | // 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 Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3763 | 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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3789 | : AuthenticatorStatus:: |
| 3790 | REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED); |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3791 | } |
| 3792 | } |
| 3793 | |
| 3794 | TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest, GetAssertion) { |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3795 | // 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 Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3798 | 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 3820 | options->extensions->remote_desktop_client_override = |
| 3821 | RemoteDesktopClientOverride::New( |
| 3822 | url::Origin::Create(GURL(test.remote_origin)), true); |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3823 | |
| 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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3829 | : AuthenticatorStatus:: |
| 3830 | REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED); |
Martin Kreichgauer | e255af06 | 2022-04-18 19:40:56 | [diff] [blame] | 3831 | } |
| 3832 | } |
| 3833 | |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3834 | TEST_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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3843 | AuthenticatorStatus expected; |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3844 | } test_cases[] = { |
Martin Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3845 | {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 Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3857 | }; |
| 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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3874 | EXPECT_EQ(result.status, test.expected); |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3875 | } |
| 3876 | } |
| 3877 | |
| 3878 | TEST_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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3887 | AuthenticatorStatus expected; |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3888 | } test_cases[] = { |
Martin Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3889 | {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 Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3901 | }; |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 3914 | 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 Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3918 | |
| 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 Kreichgauer | 00b04dd | 2022-05-04 14:04:22 | [diff] [blame] | 3923 | EXPECT_EQ(result.status, test.expected); |
Martin Kreichgauer | f43496d7 | 2022-04-26 03:21:55 | [diff] [blame] | 3924 | } |
| 3925 | } |
| 3926 | |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3927 | class MockAuthenticatorRequestDelegateObserver |
| 3928 | : public TestAuthenticatorRequestDelegate { |
| 3929 | public: |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3930 | using InterestingFailureReasonCallback = |
| 3931 | base::OnceCallback<void(InterestingFailureReason)>; |
| 3932 | |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 3933 | explicit MockAuthenticatorRequestDelegateObserver( |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3934 | InterestingFailureReasonCallback failure_reasons_callback = |
| 3935 | base::DoNothing()) |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3936 | : TestAuthenticatorRequestDelegate( |
| 3937 | nullptr /* render_frame_host */, |
| 3938 | base::DoNothing() /* did_start_request_callback */, |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 3939 | /*started_over_callback=*/base::OnceClosure(), |
Nina Satragno | a11a1ff | 2025-07-07 21:05:30 | [diff] [blame] | 3940 | /*simulate_user_cancelled=*/false, |
| 3941 | /*enclave_authenticator_should_be_discovered=*/nullptr, |
| 3942 | /*discovered_transports=*/nullptr), |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3943 | failure_reasons_callback_(std::move(failure_reasons_callback)) {} |
Peter Boström | 828b902 | 2021-09-21 02:28:43 | [diff] [blame] | 3944 | |
| 3945 | MockAuthenticatorRequestDelegateObserver( |
| 3946 | const MockAuthenticatorRequestDelegateObserver&) = delete; |
| 3947 | MockAuthenticatorRequestDelegateObserver& operator=( |
| 3948 | const MockAuthenticatorRequestDelegateObserver&) = delete; |
| 3949 | |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3950 | ~MockAuthenticatorRequestDelegateObserver() override = default; |
| 3951 | |
Martin Kreichgauer | f45542a | 2019-08-30 16:45:50 | [diff] [blame] | 3952 | bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override { |
Kim Paulhamus | 0aa02f8 | 2019-02-06 23:43:55 | [diff] [blame] | 3953 | CHECK(failure_reasons_callback_); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3954 | std::move(failure_reasons_callback_).Run(reason); |
Kim Paulhamus | 0aa02f8 | 2019-02-06 23:43:55 | [diff] [blame] | 3955 | return false; |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3956 | } |
| 3957 | |
Jun Choi | 84abf8e | 2018-08-14 00:03:00 | [diff] [blame] | 3958 | MOCK_METHOD1( |
| 3959 | OnTransportAvailabilityEnumerated, |
| 3960 | void(device::FidoRequestHandlerBase::TransportAvailabilityInfo data)); |
Martin Kreichgauer | 23355d59 | 2018-08-23 22:40:31 | [diff] [blame] | 3961 | MOCK_METHOD1(EmbedderControlsAuthenticatorDispatch, |
| 3962 | bool(const device::FidoAuthenticator&)); |
| 3963 | MOCK_METHOD1(FidoAuthenticatorAdded, void(const device::FidoAuthenticator&)); |
Md Hasibul Hasan | a963a934 | 2024-04-03 10:15:14 | [diff] [blame] | 3964 | MOCK_METHOD1(FidoAuthenticatorRemoved, void(std::string_view)); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3965 | |
| 3966 | private: |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 3967 | InterestingFailureReasonCallback failure_reasons_callback_; |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3968 | }; |
| 3969 | |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 3970 | // Fake test construct that shares all other behavior with |
| 3971 | // AuthenticatorCommonImpl except that: |
| 3972 | // - FakeAuthenticatorCommonImpl does not trigger UI activity. |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3973 | // - MockAuthenticatorRequestDelegateObserver is injected to |
| 3974 | // |request_delegate_| |
| 3975 | // instead of ChromeAuthenticatorRequestDelegate. |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 3976 | class FakeAuthenticatorCommonImpl : public AuthenticatorCommonImpl { |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3977 | public: |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 3978 | explicit FakeAuthenticatorCommonImpl( |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3979 | RenderFrameHost* render_frame_host, |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3980 | std::unique_ptr<MockAuthenticatorRequestDelegateObserver> mock_delegate) |
Adam Langley | 3ec44c2 | 2023-08-10 01:04:01 | [diff] [blame] | 3981 | : AuthenticatorCommonImpl(render_frame_host, |
| 3982 | ServingRequestsFor::kWebContents), |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3983 | mock_delegate_(std::move(mock_delegate)) {} |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 3984 | ~FakeAuthenticatorCommonImpl() override = default; |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3985 | |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 3986 | std::unique_ptr<AuthenticatorRequestClientDelegate> |
| 3987 | MaybeCreateRequestDelegate() override { |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3988 | DCHECK(mock_delegate_); |
Nina Satragno | f3b63e7 | 2019-08-20 16:44:38 | [diff] [blame] | 3989 | return std::move(mock_delegate_); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 3990 | } |
| 3991 | |
| 3992 | private: |
| 3993 | friend class AuthenticatorImplRequestDelegateTest; |
| 3994 | |
| 3995 | std::unique_ptr<MockAuthenticatorRequestDelegateObserver> mock_delegate_; |
| 3996 | }; |
| 3997 | |
| 3998 | class AuthenticatorImplRequestDelegateTest : public AuthenticatorImplTest { |
| 3999 | public: |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 4000 | AuthenticatorImplRequestDelegateTest() = default; |
| 4001 | ~AuthenticatorImplRequestDelegateTest() override = default; |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4002 | |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 4003 | mojo::Remote<blink::mojom::Authenticator> ConnectToFakeAuthenticator( |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4004 | std::unique_ptr<MockAuthenticatorRequestDelegateObserver> delegate) { |
Mario Sanchez Prada | c791f54e | 2019-07-09 23:38:37 | [diff] [blame] | 4005 | mojo::Remote<blink::mojom::Authenticator> authenticator; |
Martin Kreichgauer | 7d2b8dbb | 2021-04-01 16:03:45 | [diff] [blame] | 4006 | // AuthenticatorImpl owns itself. It self-destructs when the RenderFrameHost |
| 4007 | // navigates or is deleted. |
danakj | c70aec1f | 2022-07-07 15:48:19 | [diff] [blame] | 4008 | AuthenticatorImpl::CreateForTesting( |
| 4009 | *main_rfh(), authenticator.BindNewPipeAndPassReceiver(), |
Amos Lim | 12696e5e3 | 2022-09-16 07:37:58 | [diff] [blame] | 4010 | std::make_unique<FakeAuthenticatorCommonImpl>(main_rfh(), |
| 4011 | std::move(delegate))); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4012 | return authenticator; |
| 4013 | } |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4014 | }; |
| 4015 | |
| 4016 | TEST_F(AuthenticatorImplRequestDelegateTest, |
| 4017 | TestRequestDelegateObservesFidoRequestHandler) { |
Martin Kreichgauer | db03268 | 2019-02-20 05:05:17 | [diff] [blame] | 4018 | EXPECT_CALL(*mock_adapter_, IsPresent()) |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 4019 | .WillRepeatedly(::testing::Return(true)); |
Balazs Engedy | 5b4891f | 2018-08-29 23:08:00 | [diff] [blame] | 4020 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4021 | auto discovery_factory = |
| 4022 | std::make_unique<device::test::FakeFidoDiscoveryFactory>(); |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4023 | auto* fake_hid_discovery = discovery_factory->ForgeNextHidDiscovery(); |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 4024 | ReplaceDiscoveryFactory(std::move(discovery_factory)); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4025 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4026 | NavigateAndCommit(GURL(kTestOrigin1)); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4027 | PublicKeyCredentialRequestOptionsPtr options = |
| 4028 | GetTestPublicKeyCredentialRequestOptions(); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4029 | TestGetCredentialFuture future; |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4030 | |
| 4031 | auto mock_delegate = |
| 4032 | std::make_unique<MockAuthenticatorRequestDelegateObserver>(); |
| 4033 | auto* const mock_delegate_ptr = mock_delegate.get(); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4034 | auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate)); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4035 | |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4036 | 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 Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4041 | |
Jun Choi | 84abf8e | 2018-08-14 00:03:00 | [diff] [blame] | 4042 | EXPECT_CALL(*mock_delegate_ptr, OnTransportAvailabilityEnumerated(_)); |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 4043 | EXPECT_CALL(*mock_delegate_ptr, EmbedderControlsAuthenticatorDispatch(_)) |
| 4044 | .WillOnce(testing::Return(true)); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4045 | |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4046 | base::RunLoop usb_device_found_done; |
Martin Kreichgauer | 23355d59 | 2018-08-23 22:40:31 | [diff] [blame] | 4047 | EXPECT_CALL(*mock_delegate_ptr, FidoAuthenticatorAdded(_)) |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4048 | .WillOnce(testing::InvokeWithoutArgs( |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4049 | [&usb_device_found_done]() { usb_device_found_done.Quit(); })); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4050 | |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4051 | base::RunLoop usb_device_lost_done; |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4052 | EXPECT_CALL(*mock_delegate_ptr, FidoAuthenticatorRemoved(_)) |
| 4053 | .WillOnce(testing::InvokeWithoutArgs( |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4054 | [&usb_device_lost_done]() { usb_device_lost_done.Quit(); })); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4055 | |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4056 | authenticator->GetCredential(std::move(options), future.GetCallback()); |
Martin Kreichgauer | 2ef7869 | 2021-06-24 17:41:49 | [diff] [blame] | 4057 | fake_hid_discovery->WaitForCallToStartAndSimulateSuccess(); |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4058 | fake_hid_discovery->AddDevice(std::move(mock_usb_device)); |
| 4059 | usb_device_found_done.Run(); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4060 | |
Ken Buchanan | 0228d32 | 2020-05-04 21:20:58 | [diff] [blame] | 4061 | fake_hid_discovery->RemoveDevice(device_id); |
| 4062 | usb_device_lost_done.Run(); |
Jun Choi | fe17668 | 2018-08-30 07:49:14 | [diff] [blame] | 4063 | base::RunLoop().RunUntilIdle(); |
Jun Choi | 7d7139a | 2018-07-27 21:02:04 | [diff] [blame] | 4064 | } |
| 4065 | |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4066 | TEST_F(AuthenticatorImplRequestDelegateTest, FailureReasonForTimeout) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4067 | // The VirtualFidoAuthenticator simulates a tap immediately after it gets the |
| 4068 | // request. Replace by the real discovery that will wait until timeout. |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 4069 | ReplaceDiscoveryFactory(std::make_unique<device::FidoDiscoveryFactory>()); |
| 4070 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4071 | NavigateAndCommit(GURL(kTestOrigin1)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4072 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4073 | FailureReasonFuture failure_reason_future; |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4074 | auto mock_delegate = std::make_unique< |
| 4075 | ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>( |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4076 | failure_reason_future.GetCallback()); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4077 | auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4078 | |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4079 | TestGetCredentialFuture future; |
| 4080 | authenticator->GetCredential(GetTestPublicKeyCredentialRequestOptions(), |
| 4081 | future.GetCallback()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4082 | |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4083 | task_environment()->FastForwardBy(kTestTimeout); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4084 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4085 | EXPECT_TRUE(future.Wait()); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4086 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, |
| 4087 | future.Get()->get_get_assertion_response()->status); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4088 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4089 | ASSERT_TRUE(failure_reason_future.IsReady()); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 4090 | EXPECT_EQ( |
| 4091 | AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout, |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4092 | failure_reason_future.Get()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4093 | } |
| 4094 | |
| 4095 | TEST_F(AuthenticatorImplRequestDelegateTest, |
| 4096 | FailureReasonForDuplicateRegistration) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4097 | NavigateAndCommit(GURL(kTestOrigin1)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4098 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4099 | FailureReasonFuture failure_reason_future; |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4100 | auto mock_delegate = std::make_unique< |
| 4101 | ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>( |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4102 | failure_reason_future.GetCallback()); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4103 | auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4104 | |
| 4105 | PublicKeyCredentialCreationOptionsPtr options = |
| 4106 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4107 | options->exclude_credentials = GetTestCredentials(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4108 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4109 | options->exclude_credentials[0].id, kTestRelyingPartyId)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4110 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4111 | TestMakeCredentialFuture future; |
| 4112 | authenticator->MakeCredential(std::move(options), future.GetCallback()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4113 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4114 | EXPECT_TRUE(future.Wait()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4115 | EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_EXCLUDED, |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4116 | std::get<0>(future.Get())); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4117 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4118 | ASSERT_TRUE(failure_reason_future.IsReady()); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 4119 | EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason:: |
| 4120 | kKeyAlreadyRegistered, |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4121 | failure_reason_future.Get()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4122 | } |
| 4123 | |
| 4124 | TEST_F(AuthenticatorImplRequestDelegateTest, |
| 4125 | FailureReasonForMissingRegistration) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4126 | NavigateAndCommit(GURL(kTestOrigin1)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4127 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4128 | FailureReasonFuture failure_reason_future; |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4129 | auto mock_delegate = std::make_unique< |
| 4130 | ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>( |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4131 | failure_reason_future.GetCallback()); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 4132 | auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate)); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4133 | |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4134 | TestGetCredentialFuture future; |
| 4135 | authenticator->GetCredential(GetTestPublicKeyCredentialRequestOptions(), |
| 4136 | future.GetCallback()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4137 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4138 | EXPECT_TRUE(future.Wait()); |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 4139 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, |
| 4140 | future.Get()->get_get_assertion_response()->status); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4141 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4142 | ASSERT_TRUE(failure_reason_future.IsReady()); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 4143 | EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason:: |
| 4144 | kKeyNotRegistered, |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 4145 | failure_reason_future.Get()); |
Balazs Engedy | ed4966f7 | 2018-08-29 22:07:45 | [diff] [blame] | 4146 | } |
| 4147 | |
Adam Langley | 9095b418 | 2022-07-20 16:14:50 | [diff] [blame] | 4148 | TEST_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 | |
| 4165 | TEST_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 | |
| 4190 | TEST_F(AuthenticatorImplTest, TransportsInAttestationCertificate) { |
Adam Langley | 28254a4 | 2018-09-07 18:33:27 | [diff] [blame] | 4191 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 4192 | |
| 4193 | for (auto protocol : |
Matthew Webb | 62661b6 | 2019-05-21 19:07:57 | [diff] [blame] | 4194 | {device::ProtocolVersion::kU2f, device::ProtocolVersion::kCtap2}) { |
Adam Langley | 28254a4 | 2018-09-07 18:33:27 | [diff] [blame] | 4195 | SCOPED_TRACE(static_cast<int>(protocol)); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4196 | virtual_device_factory_->SetSupportedProtocol(protocol); |
Adam Langley | 28254a4 | 2018-09-07 18:33:27 | [diff] [blame] | 4197 | |
Nina Satragno | f8ed79a | 2019-06-05 01:09:32 | [diff] [blame] | 4198 | 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 Langley | 28254a4 | 2018-09-07 18:33:27 | [diff] [blame] | 4207 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4208 | MakeCredentialResult result = AuthenticatorMakeCredential(); |
| 4209 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Nina Satragno | f8ed79a | 2019-06-05 01:09:32 | [diff] [blame] | 4210 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 4211 | const std::vector<device::FidoTransportProtocol>& transports( |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4212 | result.response->transports); |
Nina Satragno | f8ed79a | 2019-06-05 01:09:32 | [diff] [blame] | 4213 | ASSERT_EQ(1u, transports.size()); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 4214 | EXPECT_EQ(transport.first, transports[0]); |
Nina Satragno | f8ed79a | 2019-06-05 01:09:32 | [diff] [blame] | 4215 | } |
Adam Langley | 28254a4 | 2018-09-07 18:33:27 | [diff] [blame] | 4216 | } |
| 4217 | } |
| 4218 | |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4219 | TEST_F(AuthenticatorImplTest, ExtensionHMACSecret) { |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4220 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 4221 | |
| 4222 | for (const bool include_extension : {false, true}) { |
Adam Langley | f04665c | 2020-06-23 19:57:51 | [diff] [blame] | 4223 | for (const bool authenticator_support : {false, true}) { |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4224 | for (const bool pin_support : {false, true}) { |
| 4225 | SCOPED_TRACE(include_extension); |
| 4226 | SCOPED_TRACE(authenticator_support); |
| 4227 | SCOPED_TRACE(pin_support); |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4228 | |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4229 | device::VirtualCtap2Device::Config config; |
| 4230 | config.hmac_secret_support = authenticator_support; |
| 4231 | config.pin_support = pin_support; |
| 4232 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4233 | |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4234 | 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 Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4240 | |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4241 | device::AuthenticatorData parsed_auth_data = |
| 4242 | AuthDataFromMakeCredentialResponse(result.response); |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4243 | |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4244 | // 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 Langley | f04665c | 2020-06-23 19:57:51 | [diff] [blame] | 4260 | } |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4261 | |
Adam Langley | 2d92e61 | 2022-12-29 14:43:01 | [diff] [blame] | 4262 | EXPECT_EQ(include_extension && authenticator_support && pin_support, |
| 4263 | has_hmac_secret); |
| 4264 | } |
Adam Langley | f04665c | 2020-06-23 19:57:51 | [diff] [blame] | 4265 | } |
Adam Langley | d072d4f | 2018-10-18 16:46:06 | [diff] [blame] | 4266 | } |
| 4267 | } |
| 4268 | |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4269 | // 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 Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4272 | TEST_F(AuthenticatorImplTest, MakeCredentialWithLargeExcludeList) { |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4273 | 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4279 | ResetVirtualDevice(); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4280 | device::VirtualCtap2Device::Config config; |
| 4281 | config.reject_large_allow_and_exclude_lists = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4282 | virtual_device_factory_->SetCtap2Config(config); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4283 | |
| 4284 | PublicKeyCredentialCreationOptionsPtr options = |
| 4285 | GetTestPublicKeyCredentialCreationOptions(); |
| 4286 | options->exclude_credentials = GetTestCredentials(/*num_credentials=*/10); |
| 4287 | if (has_excluded_credential) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4288 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4289 | options->exclude_credentials.back().id, kTestRelyingPartyId)); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4290 | } |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4291 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4292 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4293 | has_excluded_credential ? AuthenticatorStatus::CREDENTIAL_EXCLUDED |
| 4294 | : AuthenticatorStatus::SUCCESS); |
| 4295 | } |
| 4296 | } |
| 4297 | |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 4298 | TEST_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 Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 4308 | AuthenticatorCommonImpl::CredentialRequestResult::kOtherError, 1); |
Nina Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 4309 | } |
| 4310 | |
| 4311 | TEST_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 Buchanan | d5edc078 | 2024-06-10 22:01:22 | [diff] [blame] | 4323 | AuthenticatorCommonImpl::CredentialRequestResult::kOtherSuccess, 1); |
| 4324 | } |
| 4325 | |
| 4326 | TEST_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 | |
| 4342 | TEST_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 Satragno | 129251c | 2023-10-23 21:50:40 | [diff] [blame] | 4350 | } |
| 4351 | |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4352 | // 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 Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4355 | TEST_F(AuthenticatorImplTest, GetAssertionWithLargeAllowList) { |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4356 | 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4362 | ResetVirtualDevice(); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4363 | device::VirtualCtap2Device::Config config; |
| 4364 | config.reject_large_allow_and_exclude_lists = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4365 | virtual_device_factory_->SetCtap2Config(config); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4366 | |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4367 | PublicKeyCredentialRequestOptionsPtr options = |
| 4368 | GetTestPublicKeyCredentialRequestOptions(); |
| 4369 | options->allow_credentials = GetTestCredentials(/*num_credentials=*/10); |
| 4370 | if (has_allowed_credential) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 4371 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4372 | options->allow_credentials.back().id, kTestRelyingPartyId)); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4373 | } |
| 4374 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4375 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
Martin Kreichgauer | 4138eab | 2019-05-21 07:05:55 | [diff] [blame] | 4376 | has_allowed_credential ? AuthenticatorStatus::SUCCESS |
| 4377 | : AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Martin Kreichgauer | 43558c7 | 2019-04-06 22:15:37 | [diff] [blame] | 4378 | } |
| 4379 | } |
| 4380 | |
Adam Langley | f530c07 | 2021-05-17 20:49:15 | [diff] [blame] | 4381 | // Tests that, regardless of batching support, GetAssertion requests with a |
| 4382 | // single allowed credential ID don't result in a silent probing request. |
| 4383 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4401 | test_credentials.front().id, kTestRelyingPartyId)); |
Adam Langley | f530c07 | 2021-05-17 20:49:15 | [diff] [blame] | 4402 | |
| 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. |
| 4414 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4433 | test_credentials.back().id, kTestRelyingPartyId)); |
Adam Langley | f530c07 | 2021-05-17 20:49:15 | [diff] [blame] | 4434 | |
| 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 Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4446 | TEST_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 Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4451 | |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4490 | cred.id, kTestRelyingPartyId)); |
Adam Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4491 | } |
| 4492 | |
| 4493 | PublicKeyCredentialRequestOptionsPtr options = |
| 4494 | GetTestPublicKeyCredentialRequestOptions(); |
| 4495 | options->allow_credentials = std::move(test_credentials); |
| 4496 | |
Adam Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4497 | if (should_timeout) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4498 | 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 Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4504 | } |
Adam Langley | afba11f6 | 2020-07-10 23:49:49 | [diff] [blame] | 4505 | } |
| 4506 | } |
| 4507 | |
Adam Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4508 | // 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. |
| 4511 | TEST_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 Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4526 | for (const bool has_app_id : {false, true}) { |
| 4527 | SCOPED_TRACE(has_app_id); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4528 | virtual_device_factory_->mutable_state()->allow_list_history.clear(); |
Adam Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4529 | |
| 4530 | PublicKeyCredentialRequestOptionsPtr options = |
| 4531 | GetTestPublicKeyCredentialRequestOptions(); |
| 4532 | if (has_app_id) { |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 4533 | options->extensions->appid = kTestOrigin1; |
Adam Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4534 | } |
| 4535 | options->allow_credentials = {device::PublicKeyCredentialDescriptor( |
| 4536 | device::CredentialType::kPublicKey, cred_id)}; |
| 4537 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4538 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 4539 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4540 | const auto& allow_list_history = |
| 4541 | virtual_device_factory_->mutable_state()->allow_list_history; |
Adam Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4542 | // No empty allow-list requests should have been made. |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 4543 | EXPECT_TRUE(std::ranges::none_of( |
Peter Kasting | d568594 | 2022-09-02 17:52:17 | [diff] [blame] | 4544 | allow_list_history, |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4545 | [](const std::vector<device::PublicKeyCredentialDescriptor>& |
| 4546 | allow_list) { return allow_list.empty(); })); |
Adam Langley | 2723c8e | 2020-07-11 00:11:40 | [diff] [blame] | 4547 | } |
| 4548 | } |
| 4549 | |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4550 | // Tests that duplicate credential IDs are filtered from an assertion allow_list |
| 4551 | // parameter. |
| 4552 | TEST_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 Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4568 | // Same ID as `cred_a` and `cred_b` but with different transports. Transport |
| 4569 | // hints from descriptors with equal IDs should be merged. |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4570 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4580 | cred_b.id, kTestRelyingPartyId)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4581 | |
| 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 Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4597 | device::PublicKeyCredentialDescriptor cred_a_and_c( |
| 4598 | device::CredentialType::kPublicKey, |
Martin Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4599 | std::vector<uint8_t>(kTestCredentialIdLength, 1)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4600 | device::PublicKeyCredentialDescriptor cred_b_and_d( |
| 4601 | device::CredentialType::kPublicKey, |
Martin Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4602 | std::vector<uint8_t>(kTestCredentialIdLength, 2)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4603 | 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. |
| 4610 | TEST_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 Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4626 | // Same ID as `cred_a` and `cred_b` but with different transports. Transport |
| 4627 | // hints from descriptors with equal IDs should be merged. |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4628 | 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 Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4653 | device::PublicKeyCredentialDescriptor cred_a_and_c( |
| 4654 | device::CredentialType::kPublicKey, |
Martin Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4655 | std::vector<uint8_t>(kTestCredentialIdLength, 1)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4656 | device::PublicKeyCredentialDescriptor cred_b_and_d( |
| 4657 | device::CredentialType::kPublicKey, |
Martin Kreichgauer | 73e1efb | 2021-11-17 01:34:48 | [diff] [blame] | 4658 | std::vector<uint8_t>(kTestCredentialIdLength, 2)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4659 | 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. |
| 4665 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4679 | test_credentials.at(0).id, kTestRelyingPartyId)); |
Martin Kreichgauer | 8d55b45 | 2021-10-04 20:51:11 | [diff] [blame] | 4680 | |
| 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. |
| 4695 | TEST_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 Langley | 98b6a29 | 2019-09-06 17:23:26 | [diff] [blame] | 4722 | TEST_F(AuthenticatorImplTest, NoUnexpectedAuthenticatorExtensions) { |
Adam Langley | 98b6a29 | 2019-09-06 17:23:26 | [diff] [blame] | 4723 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 4724 | |
| 4725 | device::VirtualCtap2Device::Config config; |
| 4726 | config.add_extra_extension = true; |
| 4727 | virtual_device_factory_->SetCtap2Config(config); |
| 4728 | |
Adam Langley | 98b6a29 | 2019-09-06 17:23:26 | [diff] [blame] | 4729 | // Check that extra authenticator extensions are rejected when creating a |
| 4730 | // credential. |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4731 | EXPECT_EQ(AuthenticatorMakeCredential().status, |
| 4732 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | 98b6a29 | 2019-09-06 17:23:26 | [diff] [blame] | 4733 | |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4738 | assertion_options->allow_credentials.back().id, kTestRelyingPartyId)); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4739 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(assertion_options)).status, |
Adam Langley | 98b6a29 | 2019-09-06 17:23:26 | [diff] [blame] | 4740 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
| 4741 | } |
| 4742 | |
Martin Kreichgauer | c2f2cfc | 2020-04-25 15:23:07 | [diff] [blame] | 4743 | TEST_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 Kreichgauer | c2f2cfc | 2020-04-25 15:23:07 | [diff] [blame] | 4750 | // Check that no unexpected client extensions are sent to the authenticator. |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4751 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | c2f2cfc | 2020-04-25 15:23:07 | [diff] [blame] | 4752 | |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4757 | assertion_options->allow_credentials.back().id, kTestRelyingPartyId)); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4758 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(assertion_options)).status, |
| 4759 | AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | c2f2cfc | 2020-04-25 15:23:07 | [diff] [blame] | 4760 | } |
| 4761 | |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4762 | // Tests that on an authenticator that supports batching, exclude lists that fit |
| 4763 | // into a single batch are sent without probing. |
| 4764 | TEST_F(AuthenticatorImplTest, ExcludeListBatching) { |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4765 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 4790 | test_credentials.back().id, kTestRelyingPartyId)); |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4791 | } |
| 4792 | |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4793 | PublicKeyCredentialCreationOptionsPtr options = |
| 4794 | GetTestPublicKeyCredentialCreationOptions(); |
| 4795 | options->exclude_credentials = std::move(test_credentials); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4796 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 4797 | authenticator_has_excluded_credential |
| 4798 | ? AuthenticatorStatus::CREDENTIAL_EXCLUDED |
| 4799 | : AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | acef6fd | 2019-10-21 23:33:04 | [diff] [blame] | 4800 | } |
| 4801 | } |
| 4802 | |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4803 | TEST_F(AuthenticatorImplTest, GetPublicKey) { |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4804 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 4805 | |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4806 | static constexpr struct { |
| 4807 | device::CoseAlgorithmIdentifier algo; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 4808 | std::optional<int> evp_id; |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4809 | } kTests[] = { |
Adam Langley | d1eb57f | 2020-06-12 02:07:00 | [diff] [blame] | 4810 | {device::CoseAlgorithmIdentifier::kEs256, EVP_PKEY_EC}, |
| 4811 | {device::CoseAlgorithmIdentifier::kRs256, EVP_PKEY_RSA}, |
| 4812 | {device::CoseAlgorithmIdentifier::kEdDSA, EVP_PKEY_ED25519}, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 4813 | {device::CoseAlgorithmIdentifier::kInvalidForTesting, std::nullopt}, |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4814 | }; |
| 4815 | |
Adam Langley | cbe07b7 | 2021-07-16 19:32:22 | [diff] [blame] | 4816 | 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 Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4825 | for (const auto& test : kTests) { |
| 4826 | PublicKeyCredentialCreationOptionsPtr options = |
| 4827 | GetTestPublicKeyCredentialCreationOptions(); |
| 4828 | options->public_key_parameters = |
| 4829 | GetTestPublicKeyCredentialParameters(static_cast<int32_t>(test.algo)); |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4830 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 4831 | MakeCredentialResult result = |
| 4832 | AuthenticatorMakeCredential(std::move(options)); |
| 4833 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
| 4834 | const auto& response = result.response; |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4835 | EXPECT_EQ(response->public_key_algo, static_cast<int32_t>(test.algo)); |
Adam Langley | 1ab5746 | 2022-08-23 03:29:00 | [diff] [blame] | 4836 | |
| 4837 | // The value of the parsed authenticator data should match what's in |
| 4838 | // the attestation object. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 4839 | std::optional<Value> attestation_value = |
Adam Langley | 1ab5746 | 2022-08-23 03:29:00 | [diff] [blame] | 4840 | 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 Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4848 | |
| 4849 | ASSERT_EQ(test.evp_id.has_value(), response->public_key_der.has_value()); |
| 4850 | if (!test.evp_id) { |
| 4851 | continue; |
| 4852 | } |
| 4853 | |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 4854 | bssl::UniquePtr<EVP_PKEY> pkey = |
| 4855 | crypto::evp::PublicKeyFromBytes(response->public_key_der.value()); |
Adam Langley | a09284eb | 2020-06-11 18:58:29 | [diff] [blame] | 4856 | ASSERT_TRUE(pkey.get()); |
| 4857 | |
| 4858 | EXPECT_EQ(test.evp_id.value(), EVP_PKEY_id(pkey.get())); |
| 4859 | } |
| 4860 | } |
| 4861 | |
Adam Langley | e3777f7d | 2021-07-14 23:13:06 | [diff] [blame] | 4862 | TEST_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 Langley | cbe07b7 | 2021-07-16 19:32:22 | [diff] [blame] | 4869 | config.advertised_algorithms.clear(); |
Adam Langley | e3777f7d | 2021-07-14 23:13:06 | [diff] [blame] | 4870 | 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 Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 4905 | VerifyMakeCredentialOutcomeUkm( |
| 4906 | 1, MakeCredentialOutcome::kAlgorithmNotSupported, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 4907 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | e3777f7d | 2021-07-14 23:13:06 | [diff] [blame] | 4908 | EXPECT_TRUE(touched); |
| 4909 | } |
| 4910 | } |
| 4911 | |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4912 | TEST_F(AuthenticatorImplTest, VirtualAuthenticatorPublicKeyAlgos) { |
| 4913 | // Exercise all the public key types in the virtual authenticator for create() |
| 4914 | // and get(). |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4915 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 4916 | |
| 4917 | static const struct { |
Kevin McNee | ab4af651 | 2024-06-19 20:55:57 | [diff] [blame] | 4918 | STACK_ALLOCATED(); |
| 4919 | |
| 4920 | public: |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4921 | device::CoseAlgorithmIdentifier algo; |
Kevin McNee | ab4af651 | 2024-06-19 20:55:57 | [diff] [blame] | 4922 | const EVP_MD* digest; |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4923 | } kTests[] = { |
| 4924 | {device::CoseAlgorithmIdentifier::kEs256, EVP_sha256()}, |
| 4925 | {device::CoseAlgorithmIdentifier::kRs256, EVP_sha256()}, |
| 4926 | {device::CoseAlgorithmIdentifier::kEdDSA, nullptr}, |
| 4927 | }; |
| 4928 | |
Adam Langley | cbe07b7 | 2021-07-16 19:32:22 | [diff] [blame] | 4929 | 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 Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4938 | 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 | |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 4952 | bssl::UniquePtr<EVP_PKEY> pkey = crypto::evp::PublicKeyFromBytes( |
| 4953 | create_result.response->public_key_der.value()); |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4954 | 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); |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 4969 | std::array<uint8_t, crypto::hash::kSha256Size> client_data_json_hash = |
| 4970 | crypto::hash::Sha256(get_result.response->info->client_data_json); |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4971 | 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 Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 4976 | /*e=*/nullptr, pkey.get()), |
Adam Langley | 0ab76e5af | 2020-09-01 16:47:06 | [diff] [blame] | 4977 | 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 Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 4984 | TEST_F(AuthenticatorImplTest, TestAuthenticationTransport) { |
Alison Gale | 81f4f2c7 | 2024-04-22 19:33:31 | [diff] [blame] | 4985 | // TODO(crbug.com/40197472): handle case where the transport is unknown. |
Zakaria Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 4986 | 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 ridouh | 15ce79e1 | 2021-09-24 20:20:14 | [diff] [blame] | 4992 | device::FidoTransportProtocol::kNearFieldCommunication, |
| 4993 | device::FidoTransportProtocol::kInternal}) { |
Martin Kreichgauer | 930f341a | 2022-01-07 20:20:46 | [diff] [blame] | 4994 | device::AuthenticatorAttachment attachment = |
| 4995 | (transport == device::FidoTransportProtocol::kInternal |
| 4996 | ? device::AuthenticatorAttachment::kPlatform |
| 4997 | : device::AuthenticatorAttachment::kCrossPlatform); |
Zakaria Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 4998 | ResetVirtualDevice(); |
| 4999 | virtual_device_factory_->SetSupportedProtocol( |
zakaria ridouh | 15ce79e1 | 2021-09-24 20:20:14 | [diff] [blame] | 5000 | device::ProtocolVersion::kCtap2); |
Zakaria Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 5001 | 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 Kreichgauer | 930f341a | 2022-01-07 20:20:46 | [diff] [blame] | 5009 | EXPECT_EQ(create_result.response->authenticator_attachment, attachment); |
Zakaria Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 5010 | |
| 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 Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 5017 | GetAssertionResult get_result = |
| 5018 | AuthenticatorGetAssertion(std::move(get_options)); |
| 5019 | ASSERT_EQ(get_result.status, AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | 930f341a | 2022-01-07 20:20:46 | [diff] [blame] | 5020 | EXPECT_EQ(get_result.response->authenticator_attachment, attachment); |
Zakaria Ridouh | ae30c601 | 2021-09-15 17:30:13 | [diff] [blame] | 5021 | } |
| 5022 | } |
| 5023 | |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5024 | TEST_F(AuthenticatorImplTest, ResetDiscoveryFactoryOverride) { |
| 5025 | // This is a regression test for crbug.com/1087158. |
| 5026 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 5027 | |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 5028 | // Make the entire discovery factory disappear mid-request. |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 5029 | bool IsReady = false; |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5030 | virtual_device_factory_->SetSupportedProtocol( |
| 5031 | device::ProtocolVersion::kCtap2); |
| 5032 | virtual_device_factory_->mutable_state()->simulate_press_callback = |
| 5033 | base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) { |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 5034 | IsReady = true; |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 5035 | ResetVirtualDevice(); |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5036 | return false; |
| 5037 | }); |
| 5038 | |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5039 | PublicKeyCredentialCreationOptionsPtr options = |
| 5040 | GetTestPublicKeyCredentialCreationOptions(); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 5041 | EXPECT_EQ( |
| 5042 | AuthenticatorMakeCredentialAndWaitForTimeout(std::move(options)).status, |
| 5043 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5044 | } |
| 5045 | |
Adam Langley | 07ef8d73 | 2021-03-02 00:37:17 | [diff] [blame] | 5046 | TEST_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 | |
| 5057 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 5065 | options->allow_credentials[0].id, kTestOrigin1)); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 5066 | options->extensions->appid = kTestOrigin1; |
Adam Langley | 07ef8d73 | 2021-03-02 00:37:17 | [diff] [blame] | 5067 | |
| 5068 | EXPECT_EQ( |
| 5069 | AuthenticatorGetAssertionAndWaitForTimeout(std::move(options)).status, |
| 5070 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
| 5071 | } |
| 5072 | |
Adam Langley | 77bbb1f | 2021-04-16 16:44:10 | [diff] [blame] | 5073 | TEST_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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 5103 | options->extensions->get_cred_blob = true; |
Adam Langley | 77bbb1f | 2021-04-16 16:44:10 | [diff] [blame] | 5104 | |
| 5105 | auto result = AuthenticatorGetAssertion(std::move(options)); |
| 5106 | ASSERT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 5107 | EXPECT_EQ(result.response->extensions->get_cred_blob, cred_blob); |
Adam Langley | 77bbb1f | 2021-04-16 16:44:10 | [diff] [blame] | 5108 | } |
| 5109 | } |
| 5110 | |
Adam Langley | 4b0ca3e2 | 2021-11-29 20:51:18 | [diff] [blame] | 5111 | TEST_F(AuthenticatorImplTest, MinPINLength) { |
| 5112 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 5113 | |
Adam Langley | 84220e9 | 2021-12-23 18:33:38 | [diff] [blame] | 5114 | 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 Langley | 4b0ca3e2 | 2021-11-29 20:51:18 | [diff] [blame] | 5118 | |
Adam Langley | 84220e9 | 2021-12-23 18:33:38 | [diff] [blame] | 5119 | 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 Langley | 4b0ca3e2 | 2021-11-29 20:51:18 | [diff] [blame] | 5125 | |
Adam Langley | 84220e9 | 2021-12-23 18:33:38 | [diff] [blame] | 5126 | 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 Langley | 4b0ca3e2 | 2021-11-29 20:51:18 | [diff] [blame] | 5138 | } |
Adam Langley | 4b0ca3e2 | 2021-11-29 20:51:18 | [diff] [blame] | 5139 | } |
| 5140 | } |
| 5141 | |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 5142 | // 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. |
| 5146 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 5167 | dummy_options->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 5168 | 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 P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 5179 | ReplaceDiscoveryFactory(std::move(discovery)); |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 5180 | |
| 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 Langley | 9da727ca | 2022-12-22 18:48:00 | [diff] [blame] | 5191 | TEST_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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 5204 | options->extensions->prf = true; |
| 5205 | options->extensions->prf_inputs = std::move(prf_inputs); |
Adam Langley | 9da727ca | 2022-12-22 18:48:00 | [diff] [blame] | 5206 | |
| 5207 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 5208 | |
| 5209 | EXPECT_EQ(result.status, AuthenticatorStatus::NOT_ALLOWED_ERROR); |
| 5210 | } |
| 5211 | |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 5212 | // These test verify that the virtual authenticator supports the Signal API. |
| 5213 | class 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 Satragno | 0d0e1ba | 2024-10-15 19:10:07 | [diff] [blame] | 5237 | 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 Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 5241 | authenticator_ = |
| 5242 | virtual_authenticator_manager |
Nina Satragno | 0d0e1ba | 2024-10-15 19:10:07 | [diff] [blame] | 5243 | ->AddAuthenticatorAndReturnNonOwningPointer(virt_auth_options); |
Nina Satragno | cb0406e | 2024-09-09 19:47:48 | [diff] [blame] | 5244 | |
| 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 | |
| 5271 | TEST_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 | |
| 5304 | TEST_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 | |
| 5355 | TEST_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 Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5402 | static constexpr char kTestPIN[] = "1234"; |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5403 | static constexpr char16_t kTestPIN16[] = u"1234"; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5404 | |
| 5405 | class UVTestAuthenticatorClientDelegate |
| 5406 | : public AuthenticatorRequestClientDelegate { |
| 5407 | public: |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5408 | explicit UVTestAuthenticatorClientDelegate(bool* collected_pin, |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5409 | uint32_t* min_pin_length, |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5410 | bool* did_bio_enrollment, |
| 5411 | bool cancel_bio_enrollment) |
| 5412 | : collected_pin_(collected_pin), |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5413 | min_pin_length_(min_pin_length), |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5414 | did_bio_enrollment_(did_bio_enrollment), |
| 5415 | cancel_bio_enrollment_(cancel_bio_enrollment) { |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5416 | *collected_pin_ = false; |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5417 | *did_bio_enrollment_ = false; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5418 | } |
| 5419 | |
| 5420 | bool SupportsPIN() const override { return true; } |
| 5421 | |
| 5422 | void CollectPIN( |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5423 | CollectPINOptions options, |
Jan Wilken Dörrie | aace0cfef | 2021-03-11 22:01:58 | [diff] [blame] | 5424 | base::OnceCallback<void(std::u16string)> provide_pin_cb) override { |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5425 | *collected_pin_ = true; |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5426 | *min_pin_length_ = options.min_pin_length; |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 5427 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5428 | FROM_HERE, base::BindOnce(std::move(provide_pin_cb), kTestPIN16)); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5429 | } |
| 5430 | |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5431 | void StartBioEnrollment(base::OnceClosure next_callback) override { |
| 5432 | *did_bio_enrollment_ = true; |
| 5433 | if (cancel_bio_enrollment_) { |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 5434 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5435 | 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 Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 5443 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5444 | FROM_HERE, std::move(bio_callback_)); |
| 5445 | } |
| 5446 | } |
| 5447 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5448 | void FinishCollectToken() override {} |
| 5449 | |
| 5450 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 5451 | raw_ptr<bool> collected_pin_; |
| 5452 | raw_ptr<uint32_t> min_pin_length_; |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5453 | base::OnceClosure bio_callback_; |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 5454 | raw_ptr<bool> did_bio_enrollment_; |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5455 | bool cancel_bio_enrollment_; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5456 | }; |
| 5457 | |
| 5458 | class UVTestAuthenticatorContentBrowserClient : public ContentBrowserClient { |
| 5459 | public: |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 5460 | // ContentBrowserClient: |
| 5461 | WebAuthenticationDelegate* GetWebAuthenticationDelegate() override { |
| 5462 | return &web_authentication_delegate; |
| 5463 | } |
| 5464 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5465 | std::unique_ptr<AuthenticatorRequestClientDelegate> |
| 5466 | GetWebAuthenticationRequestDelegate( |
| 5467 | RenderFrameHost* render_frame_host) override { |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 5468 | return std::make_unique<UVTestAuthenticatorClientDelegate>( |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 5469 | &collected_pin, &min_pin_length, &did_bio_enrollment, |
| 5470 | cancel_bio_enrollment); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5471 | } |
| 5472 | |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 5473 | TestWebAuthenticationDelegate web_authentication_delegate; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5474 | |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 5475 | bool collected_pin; |
| 5476 | uint32_t min_pin_length = 0; |
| 5477 | bool did_bio_enrollment; |
| 5478 | bool cancel_bio_enrollment = false; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5479 | }; |
| 5480 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5481 | class UVAuthenticatorImplTest : public AuthenticatorImplTest { |
| 5482 | public: |
| 5483 | UVAuthenticatorImplTest() = default; |
| 5484 | |
Peter Boström | 9b03653 | 2021-10-28 23:37:28 | [diff] [blame] | 5485 | UVAuthenticatorImplTest(const UVAuthenticatorImplTest&) = delete; |
| 5486 | UVAuthenticatorImplTest& operator=(const UVAuthenticatorImplTest&) = delete; |
| 5487 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5488 | 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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5498 | protected: |
| 5499 | static PublicKeyCredentialCreationOptionsPtr make_credential_options( |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5500 | device::UserVerificationRequirement uv = |
Adam Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5501 | device::UserVerificationRequirement::kRequired, |
| 5502 | bool exclude_credentials = false, |
| 5503 | bool appid_exclude = false) { |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5504 | PublicKeyCredentialCreationOptionsPtr options = |
| 5505 | GetTestPublicKeyCredentialCreationOptions(); |
Adam Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5506 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 5513 | options->authenticator_selection->user_verification_requirement = uv; |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5514 | return options; |
| 5515 | } |
| 5516 | |
| 5517 | static PublicKeyCredentialRequestOptionsPtr get_credential_options( |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5518 | device::UserVerificationRequirement uv = |
| 5519 | device::UserVerificationRequirement::kRequired) { |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5520 | PublicKeyCredentialRequestOptionsPtr options = |
| 5521 | GetTestPublicKeyCredentialRequestOptions(); |
| 5522 | options->user_verification = uv; |
| 5523 | return options; |
| 5524 | } |
| 5525 | |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5526 | static const char* UVToString(device::UserVerificationRequirement uv) { |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5527 | switch (uv) { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5528 | case device::UserVerificationRequirement::kDiscouraged: |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5529 | return "discouraged"; |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5530 | case device::UserVerificationRequirement::kPreferred: |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5531 | return "preferred"; |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5532 | case device::UserVerificationRequirement::kRequired: |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5533 | return "required"; |
| 5534 | } |
| 5535 | } |
| 5536 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 5537 | UVTestAuthenticatorContentBrowserClient test_client_; |
| 5538 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5539 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 5540 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5541 | }; |
| 5542 | |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5543 | using PINReason = device::pin::PINEntryReason; |
| 5544 | using PINError = device::pin::PINEntryError; |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5545 | |
| 5546 | // PINExpectation represent expected |mode|, |attempts|, |min_pin_length| and |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5547 | // the PIN to answer with. |
| 5548 | struct PINExpectation { |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5549 | PINReason reason; |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5550 | std::u16string pin; |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5551 | int attempts; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5552 | uint32_t min_pin_length = device::kMinPinLength; |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5553 | PINError error = PINError::kNoError; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5554 | }; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5555 | |
| 5556 | class PINTestAuthenticatorRequestDelegate |
| 5557 | : public AuthenticatorRequestClientDelegate { |
| 5558 | public: |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5559 | PINTestAuthenticatorRequestDelegate( |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5560 | bool supports_pin, |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5561 | const std::list<PINExpectation>& pins, |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5562 | std::optional<InterestingFailureReason>* failure_reason, |
| 5563 | base::RepeatingCallback<bool()> collect_pin_cb) |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5564 | : supports_pin_(supports_pin), |
| 5565 | expected_(pins), |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5566 | failure_reason_(failure_reason), |
| 5567 | collect_pin_cb_(collect_pin_cb) {} |
Peter Boström | 828b902 | 2021-09-21 02:28:43 | [diff] [blame] | 5568 | |
| 5569 | PINTestAuthenticatorRequestDelegate( |
| 5570 | const PINTestAuthenticatorRequestDelegate&) = delete; |
| 5571 | PINTestAuthenticatorRequestDelegate& operator=( |
| 5572 | const PINTestAuthenticatorRequestDelegate&) = delete; |
| 5573 | |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 5574 | ~PINTestAuthenticatorRequestDelegate() override { |
| 5575 | DCHECK(expected_.empty()) |
| 5576 | << expected_.size() << " unsatisifed PIN expectations"; |
| 5577 | } |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5578 | |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5579 | bool SupportsPIN() const override { return supports_pin_; } |
| 5580 | |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5581 | void CollectPIN( |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5582 | CollectPINOptions options, |
Jan Wilken Dörrie | aace0cfef | 2021-03-11 22:01:58 | [diff] [blame] | 5583 | base::OnceCallback<void(std::u16string)> provide_pin_cb) override { |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5584 | if (collect_pin_cb_ && !collect_pin_cb_.Run()) { |
| 5585 | return; |
| 5586 | } |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5587 | DCHECK(supports_pin_); |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 5588 | DCHECK(!expected_.empty()) << "unexpected PIN request"; |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5589 | if (expected_.front().reason == PINReason::kChallenge) { |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5590 | 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 Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5595 | DCHECK_EQ(expected_.front().reason, options.reason); |
| 5596 | DCHECK_EQ(expected_.front().error, options.error); |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5597 | std::u16string pin = std::move(expected_.front().pin); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5598 | expected_.pop_front(); |
| 5599 | |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 5600 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5601 | FROM_HERE, base::BindOnce(std::move(provide_pin_cb), std::move(pin))); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5602 | } |
| 5603 | |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5604 | void FinishCollectToken() override {} |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5605 | |
Martin Kreichgauer | f45542a | 2019-08-30 16:45:50 | [diff] [blame] | 5606 | bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override { |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5607 | *failure_reason_ = reason; |
| 5608 | return AuthenticatorRequestClientDelegate::DoesBlockRequestOnFailure( |
Martin Kreichgauer | f45542a | 2019-08-30 16:45:50 | [diff] [blame] | 5609 | reason); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5610 | } |
| 5611 | |
| 5612 | private: |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5613 | const bool supports_pin_; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5614 | std::list<PINExpectation> expected_; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 5615 | const raw_ptr<std::optional<InterestingFailureReason>> failure_reason_; |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5616 | // 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 Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5619 | }; |
| 5620 | |
| 5621 | class PINTestAuthenticatorContentBrowserClient : public ContentBrowserClient { |
| 5622 | public: |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 5623 | // ContentBrowserClient: |
| 5624 | WebAuthenticationDelegate* GetWebAuthenticationDelegate() override { |
| 5625 | return &web_authentication_delegate; |
| 5626 | } |
| 5627 | |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5628 | std::unique_ptr<AuthenticatorRequestClientDelegate> |
| 5629 | GetWebAuthenticationRequestDelegate( |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 5630 | RenderFrameHost* render_frame_host) override { |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5631 | return std::make_unique<PINTestAuthenticatorRequestDelegate>( |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5632 | supports_pin, expected, &failure_reason, collect_pin_cb); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5633 | } |
| 5634 | |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 5635 | TestWebAuthenticationDelegate web_authentication_delegate; |
| 5636 | |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5637 | bool supports_pin = true; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5638 | std::list<PINExpectation> expected; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 5639 | std::optional<InterestingFailureReason> failure_reason; |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 5640 | base::RepeatingCallback<bool()> collect_pin_cb; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5641 | }; |
| 5642 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5643 | class PINAuthenticatorImplTest : public UVAuthenticatorImplTest { |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5644 | public: |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5645 | PINAuthenticatorImplTest() = default; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5646 | |
Peter Boström | 9b03653 | 2021-10-28 23:37:28 | [diff] [blame] | 5647 | PINAuthenticatorImplTest(const PINAuthenticatorImplTest&) = delete; |
| 5648 | PINAuthenticatorImplTest& operator=(const PINAuthenticatorImplTest&) = delete; |
| 5649 | |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5650 | void SetUp() override { |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5651 | UVAuthenticatorImplTest::SetUp(); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5652 | old_client_ = SetBrowserClientForTesting(&test_client_); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5653 | device::VirtualCtap2Device::Config config; |
| 5654 | config.pin_support = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5655 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5656 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 5657 | } |
| 5658 | |
| 5659 | void TearDown() override { |
| 5660 | SetBrowserClientForTesting(old_client_); |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 5661 | UVAuthenticatorImplTest::TearDown(); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5662 | } |
| 5663 | |
| 5664 | protected: |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5665 | PINTestAuthenticatorContentBrowserClient test_client_; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5666 | |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5667 | // An enumerate of outcomes for PIN tests. |
| 5668 | enum { |
| 5669 | kFailure, |
| 5670 | kNoPIN, |
| 5671 | kSetPIN, |
| 5672 | kUsePIN, |
| 5673 | }; |
| 5674 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 5675 | void ConfigureVirtualDevice(device::PINUVAuthProtocol pin_protocol, |
| 5676 | bool pin_uv_auth_token, |
| 5677 | int support_level) { |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5678 | device::VirtualCtap2Device::Config config; |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 5679 | config.pin_protocol = pin_protocol; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 5680 | config.pin_uv_auth_token_support = pin_uv_auth_token; |
| 5681 | config.ctap2_versions = {device::Ctap2Version::kCtap2_0, |
| 5682 | device::Ctap2Version::kCtap2_1}; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5683 | switch (support_level) { |
| 5684 | case 0: |
| 5685 | // No support. |
| 5686 | config.pin_support = false; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5687 | virtual_device_factory_->mutable_state()->pin = ""; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5688 | virtual_device_factory_->mutable_state()->pin_retries = 0; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5689 | break; |
| 5690 | |
| 5691 | case 1: |
| 5692 | // PIN supported, but no PIN set. |
| 5693 | config.pin_support = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5694 | virtual_device_factory_->mutable_state()->pin = ""; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5695 | virtual_device_factory_->mutable_state()->pin_retries = 0; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5696 | break; |
| 5697 | |
| 5698 | case 2: |
| 5699 | // PIN set. |
| 5700 | config.pin_support = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5701 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5702 | virtual_device_factory_->mutable_state()->pin_retries = |
| 5703 | device::kMaxPinRetries; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5704 | break; |
| 5705 | |
| 5706 | default: |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 5707 | NOTREACHED(); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5708 | } |
| 5709 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5710 | virtual_device_factory_->SetCtap2Config(config); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5711 | } |
| 5712 | |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5713 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 5714 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5715 | }; |
| 5716 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5717 | static constexpr std::array<device::UserVerificationRequirement, 3> kUVLevel = { |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 5718 | device::UserVerificationRequirement::kDiscouraged, |
| 5719 | device::UserVerificationRequirement::kPreferred, |
| 5720 | device::UserVerificationRequirement::kRequired, |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5721 | }; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5722 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5723 | static const std::array<std::string_view, 3> kUVDescription = { |
| 5724 | "discouraged", "preferred", "required"}; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5725 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5726 | static const std::array<std::string_view, 3> kPINSupportDescription = { |
| 5727 | "no PIN support", "PIN not set", "PIN set"}; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5728 | |
| 5729 | TEST_F(PINAuthenticatorImplTest, MakeCredential) { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5730 | typedef std::array<int, 3> UvRequirement; |
| 5731 | typedef std::array<UvRequirement, 3> Expectations; |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5732 | // kExpectedWithUISupport enumerates the expected behaviour when the embedder |
| 5733 | // supports prompting the user for a PIN. |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5734 | // clang-format off |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5735 | const Expectations kExpectedWithUISupport = std::to_array<UvRequirement>({ |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5736 | // 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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5743 | }); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5744 | // clang-format on |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5745 | |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5746 | // kExpectedWithoutUISupport enumerates the expected behaviour when the |
| 5747 | // embedder cannot prompt the user for a PIN. |
| 5748 | // clang-format off |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5749 | const Expectations kExpectedWithoutUISupport = std::to_array<UvRequirement>({ |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5750 | // 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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 5759 | }); |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5760 | // clang-format on |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5761 | |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 5762 | 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 Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 5770 | |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 5771 | for (int support_level = 0; support_level <= 2; support_level++) { |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 5772 | 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 Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5778 | 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 Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 5787 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5788 | 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 Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5793 | } |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5794 | SCOPED_TRACE(::testing::Message() << "always_uv=" << always_uv); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5795 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5796 | ConfigureVirtualDevice(pin_protocol, pin_uv_auth_token, |
| 5797 | support_level); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5798 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5799 | for (int uv_level = 0; uv_level <= 2; uv_level++) { |
| 5800 | SCOPED_TRACE(kUVDescription[uv_level]); |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5801 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5802 | 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 Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5808 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5809 | case kSetPIN: |
| 5810 | // A single PIN prompt to set a PIN is expected. |
| 5811 | test_client_.expected = {{PINReason::kSet, kTestPIN16}}; |
| 5812 | break; |
Adam Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5813 | |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5814 | 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öm | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 5821 | NOTREACHED(); |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5822 | } |
| 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öm | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 5851 | NOTREACHED(); |
Adam Langley | 3e9ecfd | 2021-06-15 17:10:13 | [diff] [blame] | 5852 | } |
Adam Langley | 8348a0bc | 2021-06-07 23:45:15 | [diff] [blame] | 5853 | } |
| 5854 | } |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 5855 | } |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 5856 | } |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 5857 | } |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 5858 | } |
| 5859 | } |
Adam Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 5860 | } |
| 5861 | } |
| 5862 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5863 | TEST_F(PINAuthenticatorImplTest, MakeCredentialSoftLock) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5864 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5865 | virtual_device_factory_->mutable_state()->pin_retries = |
| 5866 | device::kMaxPinRetries; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5867 | |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5868 | test_client_.expected = {{PINReason::kChallenge, u"wrong", 8}, |
| 5869 | {PINReason::kChallenge, u"wrong", 7, |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5870 | device::kMinPinLength, PINError::kWrongPIN}, |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5871 | {PINReason::kChallenge, u"wrong", 6, |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5872 | device::kMinPinLength, PINError::kWrongPIN}}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 5873 | EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status, |
| 5874 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5875 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->pin_retries); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5876 | EXPECT_TRUE(virtual_device_factory_->mutable_state()->soft_locked); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5877 | ASSERT_TRUE(test_client_.failure_reason.has_value()); |
| 5878 | EXPECT_EQ(InterestingFailureReason::kSoftPINBlock, |
| 5879 | *test_client_.failure_reason); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 5880 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kSoftPinBlock, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 5881 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5882 | } |
| 5883 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 5884 | TEST_F(PINAuthenticatorImplTest, MakeCredentialHardLock) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 5885 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5886 | virtual_device_factory_->mutable_state()->pin_retries = 1; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5887 | |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5888 | test_client_.expected = {{PINReason::kChallenge, u"wrong", 1}}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 5889 | EXPECT_EQ(AuthenticatorMakeCredential().status, |
| 5890 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 5891 | EXPECT_EQ(0, virtual_device_factory_->mutable_state()->pin_retries); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5892 | ASSERT_TRUE(test_client_.failure_reason.has_value()); |
| 5893 | EXPECT_EQ(InterestingFailureReason::kHardPINBlock, |
| 5894 | *test_client_.failure_reason); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 5895 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kHardPinBlock, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 5896 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 5897 | } |
| 5898 | |
Adam Langley | 790bea2 | 2020-09-24 22:52:47 | [diff] [blame] | 5899 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5905 | test_client_.expected = {{PINReason::kChallenge, u"wrong", 8}, |
| 5906 | {PINReason::kChallenge, kTestPIN16, 7, |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 5907 | device::kMinPinLength, PINError::kWrongPIN}}; |
Adam Langley | 790bea2 | 2020-09-24 22:52:47 | [diff] [blame] | 5908 | 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 Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5913 | TEST_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 Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5923 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5924 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5925 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
| 5926 | EXPECT_EQ(taps, 1); |
| 5927 | } |
| 5928 | |
| 5929 | TEST_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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 5937 | base::BindRepeating([](VirtualFidoDevice* ignore) { return false; }); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5938 | 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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 5945 | base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) { |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5946 | ++taps; |
| 5947 | return true; |
| 5948 | }); |
| 5949 | discovery->AddDevice(std::move(device_2)); |
| 5950 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 5951 | ReplaceDiscoveryFactory(std::move(discovery)); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5952 | |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5953 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5954 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 5955 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
| 5956 | EXPECT_EQ(taps, 2); |
| 5957 | } |
| 5958 | |
Nina Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 5959 | TEST_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 Satragno | 75cff10 | 2020-12-01 00:16:25 | [diff] [blame] | 5965 | |
| 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 Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 5970 | virtual_device_factory_->SetCtap2Config(config); |
| 5971 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 5972 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5973 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 5974 | |
| 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 Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5982 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 5992 | test_client_.expected = {{PINReason::kSet, u"123456", 0, 6}}; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 5993 | |
| 5994 | MakeCredentialResult result = |
| 5995 | AuthenticatorMakeCredential(make_credential_options()); |
| 5996 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
| 5997 | } |
| 5998 | |
| 5999 | TEST_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 Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6011 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6012 | {PINReason::kChallenge, u"123456", device::kMaxPinRetries, 6}}; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 6013 | |
| 6014 | MakeCredentialResult result = |
| 6015 | AuthenticatorMakeCredential(make_credential_options()); |
| 6016 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
| 6017 | } |
| 6018 | |
| 6019 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6035 | test_client_.expected = {{PINReason::kChallenge, kTestPIN16, |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6036 | device::kMaxPinRetries, device::kMinPinLength}, |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6037 | {PINReason::kChange, u"567890", 0, 6}}; |
Nina Satragno | 62fe3e0 | 2020-11-16 18:13:26 | [diff] [blame] | 6038 | |
| 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 Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6045 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6067 | test_client_.expected = {{PINReason::kChallenge, kTestPIN16, |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6068 | device::kMaxPinRetries, |
| 6069 | device::kMinPinLength}}; |
| 6070 | } else { |
| 6071 | test_client_.expected = {}; |
| 6072 | } |
| 6073 | |
| 6074 | PublicKeyCredentialCreationOptionsPtr request = make_credential_options(); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6075 | request->authenticator_selection->user_verification_requirement = |
| 6076 | request_uv ? device::UserVerificationRequirement::kPreferred |
| 6077 | : device::UserVerificationRequirement::kDiscouraged; |
| 6078 | request->authenticator_selection->resident_key = |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6079 | discoverable ? device::ResidentKeyRequirement::kPreferred |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6080 | : device::ResidentKeyRequirement::kDiscouraged; |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6081 | |
| 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 | |
| 6093 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6111 | test_client_.expected = {{PINReason::kChallenge, kTestPIN16, |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6112 | device::kMaxPinRetries, device::kMinPinLength}}; |
| 6113 | } else { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6114 | test_client_.expected = {{PINReason::kSet, kTestPIN16, |
Martin Kreichgauer | a2503a7 | 2021-04-29 20:53:48 | [diff] [blame] | 6115 | 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 Langley | f1ec964 | 2023-01-10 00:01:57 | [diff] [blame] | 6124 | TEST_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 Langley | 2bb75bd | 2023-03-06 01:39:55 | [diff] [blame] | 6138 | {device::UserVerificationRequirement::kDiscouraged, true, true}, |
Adam Langley | f1ec964 | 2023-01-10 00:01:57 | [diff] [blame] | 6139 | {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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6172 | TEST_F(PINAuthenticatorImplTest, GetAssertion) { |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 6173 | typedef std::array<int, 3> UvRequirement; |
| 6174 | typedef std::array<UvRequirement, 3> Expectations; |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6175 | // kExpectedWithUISupport enumerates the expected behaviour when the embedder |
| 6176 | // supports prompting the user for a PIN. |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6177 | // clang-format off |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 6178 | const Expectations kExpectedWithUISupport = std::to_array<UvRequirement>({ |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6179 | // discouraged | preferred | required |
| 6180 | /* No support */ { kNoPIN, kNoPIN, kFailure }, |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6181 | /* PIN not set */ { kNoPIN, kNoPIN, kFailure }, |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6182 | /* PIN set */ { kNoPIN, kUsePIN, kUsePIN }, |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 6183 | }); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6184 | // clang-format on |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6185 | |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6186 | // kExpectedWithoutUISupport enumerates the expected behaviour when the |
| 6187 | // embedder cannot prompt the user for a PIN. |
| 6188 | // clang-format off |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 6189 | const Expectations kExpectedWithoutUISupport = std::to_array<UvRequirement>({ |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6190 | // discouraged | preferred | required |
| 6191 | /* No support */ { kNoPIN, kNoPIN, kFailure }, |
| 6192 | /* PIN not set */ { kNoPIN, kNoPIN, kFailure }, |
| 6193 | /* PIN set */ { kNoPIN, kNoPIN, kFailure }, |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 6194 | }); |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6195 | // clang-format on |
| 6196 | |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6197 | PublicKeyCredentialRequestOptionsPtr dummy_options = get_credential_options(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6198 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6199 | dummy_options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6200 | |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 6201 | 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 Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6207 | |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 6208 | for (int support_level = 0; support_level <= 2; support_level++) { |
| 6209 | SCOPED_TRACE(kPINSupportDescription[support_level]); |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6210 | 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 Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 6218 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6219 | for (int uv_level = 0; uv_level <= 2; uv_level++) { |
| 6220 | SCOPED_TRACE(kUVDescription[uv_level]); |
Adam Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 6221 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6222 | 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 Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 6228 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6229 | case kUsePIN: |
| 6230 | // A single prompt to get the PIN is expected. |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6231 | test_client_.expected = { |
| 6232 | {PINReason::kChallenge, kTestPIN16, 8}}; |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6233 | break; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6234 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6235 | default: |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 6236 | NOTREACHED(); |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6237 | } |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6238 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6239 | GetAssertionResult result = AuthenticatorGetAssertion( |
| 6240 | get_credential_options(kUVLevel[uv_level])); |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6241 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6242 | switch (expected[support_level][uv_level]) { |
| 6243 | case kFailure: |
| 6244 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, |
| 6245 | result.status); |
| 6246 | break; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6247 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6248 | case kNoPIN: |
| 6249 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6250 | EXPECT_FALSE(HasUV(result.response)); |
| 6251 | break; |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6252 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6253 | 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 Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6259 | |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6260 | default: |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 6261 | NOTREACHED(); |
Martin Kreichgauer | 0787dc3 | 2020-10-05 17:44:11 | [diff] [blame] | 6262 | } |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 6263 | } |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 6264 | } |
Adam Langley | bf92fdc | 2019-03-27 21:13:54 | [diff] [blame] | 6265 | } |
| 6266 | } |
Adam Langley | 6b06710 | 2019-03-16 21:18:05 | [diff] [blame] | 6267 | } |
| 6268 | } |
| 6269 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6270 | TEST_F(PINAuthenticatorImplTest, GetAssertionSoftLock) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6271 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6272 | virtual_device_factory_->mutable_state()->pin_retries = |
| 6273 | device::kMaxPinRetries; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6274 | |
| 6275 | PublicKeyCredentialRequestOptionsPtr options = get_credential_options(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6276 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6277 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6278 | |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6279 | test_client_.expected = {{PINReason::kChallenge, u"wrong", 8}, |
| 6280 | {PINReason::kChallenge, u"wrong", 7, |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 6281 | device::kMinPinLength, PINError::kWrongPIN}, |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6282 | {PINReason::kChallenge, u"wrong", 6, |
Nina Satragno | 21e40a7 | 2020-12-17 17:00:14 | [diff] [blame] | 6283 | device::kMinPinLength, PINError::kWrongPIN}}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6284 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 6285 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6286 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->pin_retries); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6287 | EXPECT_TRUE(virtual_device_factory_->mutable_state()->soft_locked); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6288 | ASSERT_TRUE(test_client_.failure_reason.has_value()); |
| 6289 | EXPECT_EQ(InterestingFailureReason::kSoftPINBlock, |
| 6290 | *test_client_.failure_reason); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 6291 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kSoftPinBlock, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 6292 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6293 | } |
| 6294 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6295 | TEST_F(PINAuthenticatorImplTest, GetAssertionHardLock) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6296 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6297 | virtual_device_factory_->mutable_state()->pin_retries = 1; |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6298 | |
| 6299 | PublicKeyCredentialRequestOptionsPtr options = get_credential_options(); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6300 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6301 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6302 | |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6303 | test_client_.expected = {{PINReason::kChallenge, u"wrong", 1}}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6304 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 6305 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6306 | EXPECT_EQ(0, virtual_device_factory_->mutable_state()->pin_retries); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6307 | ASSERT_TRUE(test_client_.failure_reason.has_value()); |
| 6308 | EXPECT_EQ(InterestingFailureReason::kHardPINBlock, |
| 6309 | *test_client_.failure_reason); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 6310 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kHardPinBlock, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 6311 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | b159c31 | 2019-03-05 00:33:19 | [diff] [blame] | 6312 | } |
| 6313 | |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6314 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6324 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6325 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6326 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6327 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 6328 | AuthenticatorStatus::SUCCESS); |
| 6329 | EXPECT_EQ(taps, 1); |
| 6330 | } |
| 6331 | |
| 6332 | TEST_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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 6340 | base::BindRepeating([](VirtualFidoDevice* ignore) { return false; }); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6341 | 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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 6348 | base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) { |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6349 | ++taps; |
| 6350 | return true; |
| 6351 | }); |
| 6352 | PublicKeyCredentialRequestOptionsPtr options = get_credential_options(); |
| 6353 | ASSERT_TRUE(device_2.state->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6354 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6355 | discovery->AddDevice(std::move(device_2)); |
| 6356 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 6357 | ReplaceDiscoveryFactory(std::move(discovery)); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6358 | |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6359 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6360 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 6361 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 6362 | AuthenticatorStatus::SUCCESS); |
| 6363 | EXPECT_EQ(taps, 2); |
| 6364 | } |
| 6365 | |
Nina Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 6366 | TEST_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 Satragno | 75cff10 | 2020-12-01 00:16:25 | [diff] [blame] | 6372 | config.u2f_support = true; |
Nina Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 6373 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6378 | options->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6379 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6380 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Nina Satragno | 0410d09 | 2020-10-29 22:39:35 | [diff] [blame] | 6381 | |
| 6382 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 6383 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
| 6384 | EXPECT_TRUE(HasUV(result.response)); |
| 6385 | } |
| 6386 | |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6387 | TEST_F(PINAuthenticatorImplTest, MakeCredentialNoSupportedAlgorithm) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6388 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6389 | |
| 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 Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6400 | 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 Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6407 | config.u2f_support = true; |
| 6408 | config.pin_support = true; |
Adam Langley | cbe07b7 | 2021-07-16 19:32:22 | [diff] [blame] | 6409 | config.advertised_algorithms = { |
| 6410 | device::CoseAlgorithmIdentifier::kInvalidForTesting}; |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6411 | 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 Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6415 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6416 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6417 | // 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 Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6427 | PublicKeyCredentialCreationOptionsPtr options = |
| 6428 | GetTestPublicKeyCredentialCreationOptions(); |
| 6429 | // Set uv=discouraged so that U2F fallback is possible. |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6430 | options->authenticator_selection->user_verification_requirement = |
| 6431 | device::UserVerificationRequirement::kDiscouraged; |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6432 | options->public_key_parameters = |
| 6433 | GetTestPublicKeyCredentialParameters(static_cast<int32_t>( |
| 6434 | device::CoseAlgorithmIdentifier::kInvalidForTesting)); |
| 6435 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6436 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 6437 | expected_to_succeed ? AuthenticatorStatus::SUCCESS |
| 6438 | : AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Adam Langley | b8fdd11 | 2020-06-15 21:44:32 | [diff] [blame] | 6439 | } |
| 6440 | } |
| 6441 | |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6442 | TEST_F(PINAuthenticatorImplTest, PRFCreatedOnCTAP2) { |
| 6443 | // Check that credential creation requests that include the PRF extension use |
| 6444 | // CTAP2 if possible. |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6445 | NavigateAndCommit(GURL(kTestOrigin1)); |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6446 | |
| 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6457 | |
| 6458 | PublicKeyCredentialCreationOptionsPtr options = |
| 6459 | GetTestPublicKeyCredentialCreationOptions(); |
| 6460 | // Set uv=discouraged so that U2F fallback is possible. |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6461 | options->authenticator_selection->user_verification_requirement = |
| 6462 | device::UserVerificationRequirement::kDiscouraged; |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6463 | |
| 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 Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 6474 | test_client_.expected = { |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6475 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6476 | } 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6486 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 6487 | AuthenticatorStatus::SUCCESS); |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 6488 | } |
| 6489 | } |
| 6490 | |
Martin Kreichgauer | 5c1d09af | 2021-03-19 22:27:45 | [diff] [blame] | 6491 | // 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. |
| 6494 | TEST_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 Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 6513 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
Martin Kreichgauer | 5c1d09af | 2021-03-19 22:27:45 | [diff] [blame] | 6514 | |
| 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6523 | test_credentials.back().id, std::move(cred_protect_credential))); |
Martin Kreichgauer | 5c1d09af | 2021-03-19 22:27:45 | [diff] [blame] | 6524 | |
| 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 Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6533 | TEST_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 Buchanan | e7a9a809 | 2023-12-27 17:08:30 | [diff] [blame] | 6542 | device::FidoDiscoveryBase::EventStream<bool>::New(); |
Adam Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6543 | |
| 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 Buchanan | e7a9a809 | 2023-12-27 17:08:30 | [diff] [blame] | 6548 | device::FidoDiscoveryBase::EventStream<bool>::New(); |
Adam Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6549 | |
| 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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 6573 | [&](VirtualFidoDevice* ignore) -> bool { return touch_callback(1); }); |
Adam Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6574 | device_2.state->simulate_press_callback = base::BindLambdaForTesting( |
Peter Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 6575 | [&](VirtualFidoDevice* ignore) -> bool { return touch_callback(2); }); |
Adam Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6576 | |
| 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 P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 6581 | ReplaceDiscoveryFactory(std::move(discovery)); |
Adam Langley | 8e22550 | 2021-07-13 18:19:18 | [diff] [blame] | 6582 | |
| 6583 | test_client_.expected = { |
| 6584 | {PINReason::kChallenge, kTestPIN16, device::kMaxPinRetries}}; |
| 6585 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
| 6586 | } |
| 6587 | |
Adam Langley | 2cdc597 | 2024-10-03 12:33:13 | [diff] [blame] | 6588 | TEST_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 | |
| 6614 | TEST_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 Langley | dda3ea8 | 2023-05-10 21:14:36 | [diff] [blame] | 6643 | TEST_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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6670 | class InternalUVAuthenticatorImplTest : public UVAuthenticatorImplTest { |
| 6671 | public: |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6672 | struct TestCase { |
| 6673 | const bool fingerprints_enrolled; |
| 6674 | const bool supports_pin; |
| 6675 | const device::UserVerificationRequirement uv; |
| 6676 | }; |
| 6677 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6678 | InternalUVAuthenticatorImplTest() = default; |
| 6679 | |
Peter Boström | 9b03653 | 2021-10-28 23:37:28 | [diff] [blame] | 6680 | InternalUVAuthenticatorImplTest(const InternalUVAuthenticatorImplTest&) = |
| 6681 | delete; |
| 6682 | InternalUVAuthenticatorImplTest& operator=( |
| 6683 | const InternalUVAuthenticatorImplTest&) = delete; |
| 6684 | |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6685 | void SetUp() override { |
| 6686 | UVAuthenticatorImplTest::SetUp(); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6687 | 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 Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 6695 | if (!fingerprints_enrolled && supports_pin) { |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6696 | continue; |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 6697 | } |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6698 | 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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6709 | device::VirtualCtap2Device::Config config; |
| 6710 | config.internal_uv_support = true; |
Adam Langley | 7dab11d0 | 2019-03-29 00:37:16 | [diff] [blame] | 6711 | config.u2f_support = true; |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6712 | 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6718 | virtual_device_factory_->SetCtap2Config(config); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6719 | 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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6724 | } |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6725 | }; |
| 6726 | |
| 6727 | TEST_F(InternalUVAuthenticatorImplTest, MakeCredential) { |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6728 | for (const auto test_case : GetTestCases()) { |
| 6729 | ConfigureDevice(test_case); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6730 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6731 | 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 Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 6737 | options->timeout = base::Milliseconds(100); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6738 | } |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6739 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6740 | MakeCredentialResult result = |
| 6741 | AuthenticatorMakeCredential(std::move(options)); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6742 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6743 | if (should_timeout) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6744 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6745 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6746 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6747 | EXPECT_EQ(test_case.fingerprints_enrolled, HasUV(result.response)); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6748 | } |
| 6749 | } |
| 6750 | } |
| 6751 | |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6752 | // Test falling back to PIN for devices that support internal user verification |
| 6753 | // but not uv token. |
| 6754 | TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialFallBackToPin) { |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6755 | 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6768 | MakeCredentialResult result = AuthenticatorMakeCredential(std::move(options)); |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6769 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6770 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6771 | EXPECT_TRUE(HasUV(result.response)); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6772 | EXPECT_TRUE(test_client_.collected_pin); |
| 6773 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6774 | } |
| 6775 | |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6776 | // Test making a credential on an authenticator that supports biometric |
| 6777 | // enrollment but has no fingerprints enrolled. |
| 6778 | TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialInlineBioEnrollment) { |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6779 | 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6790 | MakeCredentialResult result = AuthenticatorMakeCredential( |
| 6791 | make_credential_options(device::UserVerificationRequirement::kRequired)); |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6792 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6793 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6794 | EXPECT_TRUE(HasUV(result.response)); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6795 | 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 Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6798 | EXPECT_TRUE(virtual_device_factory_->mutable_state()->fingerprints_enrolled); |
| 6799 | } |
| 6800 | |
Nina Satragno | 3ca1c4b | 2025-06-16 20:19:24 | [diff] [blame] | 6801 | // Test having an authenticator report an error during inline biometrics |
| 6802 | // enrollment. Regression test for crbug.com/410985009. |
| 6803 | TEST_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 Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6829 | // Test making a credential skipping biometric enrollment during credential |
| 6830 | // creation. |
| 6831 | TEST_F(InternalUVAuthenticatorImplTest, MakeCredentialSkipInlineBioEnrollment) { |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6832 | test_client_.cancel_bio_enrollment = true; |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6833 | |
| 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6845 | MakeCredentialResult result = AuthenticatorMakeCredential( |
| 6846 | make_credential_options(device::UserVerificationRequirement::kRequired)); |
Nina Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6847 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6848 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6849 | EXPECT_TRUE(HasUV(result.response)); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6850 | 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 Satragno | 11971dd | 2020-04-22 21:01:46 | [diff] [blame] | 6853 | EXPECT_FALSE(virtual_device_factory_->mutable_state()->fingerprints_enrolled); |
| 6854 | } |
| 6855 | |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6856 | TEST_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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6880 | request->authenticator_selection->user_verification_requirement = |
| 6881 | request_uv ? device::UserVerificationRequirement::kPreferred |
| 6882 | : device::UserVerificationRequirement::kDiscouraged; |
| 6883 | request->authenticator_selection->resident_key = |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6884 | discoverable ? device::ResidentKeyRequirement::kPreferred |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6885 | : device::ResidentKeyRequirement::kDiscouraged; |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6886 | |
| 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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6900 | TEST_F(InternalUVAuthenticatorImplTest, GetAssertion) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 6901 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6902 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6903 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6904 | for (const auto test_case : GetTestCases()) { |
| 6905 | ConfigureDevice(test_case); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6906 | // 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 Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6914 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6915 | GetAssertionResult result = |
| 6916 | AuthenticatorGetAssertion(get_credential_options(test_case.uv)); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6917 | |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6918 | if (should_be_unrecognized) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6919 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6920 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6921 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Nina Satragno | 356ffd78 | 2020-03-10 03:48:26 | [diff] [blame] | 6922 | EXPECT_EQ( |
| 6923 | test_case.fingerprints_enrolled && |
| 6924 | test_case.uv != device::UserVerificationRequirement::kDiscouraged, |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6925 | HasUV(result.response)); |
Adam Langley | c5ae37d | 2019-03-08 20:32:31 | [diff] [blame] | 6926 | } |
| 6927 | } |
| 6928 | } |
| 6929 | |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6930 | // Test falling back to PIN for devices that support internal user verification |
| 6931 | // but not uv token. |
| 6932 | TEST_F(InternalUVAuthenticatorImplTest, GetAssertionFallbackToPIN) { |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6933 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6944 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6945 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6946 | GetAssertionResult result = AuthenticatorGetAssertion( |
| 6947 | get_credential_options(device::UserVerificationRequirement::kRequired)); |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6948 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6949 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 6950 | EXPECT_TRUE(HasUV(result.response)); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 6951 | EXPECT_TRUE(test_client_.collected_pin); |
| 6952 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | 0775ff1 | 2020-03-13 17:27:00 | [diff] [blame] | 6953 | } |
| 6954 | |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6955 | class UVTokenAuthenticatorImplTest : public UVAuthenticatorImplTest { |
| 6956 | public: |
| 6957 | UVTokenAuthenticatorImplTest() = default; |
| 6958 | UVTokenAuthenticatorImplTest(const UVTokenAuthenticatorImplTest&) = delete; |
| 6959 | |
| 6960 | void SetUp() override { |
| 6961 | UVAuthenticatorImplTest::SetUp(); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6962 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 6963 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6964 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 6965 | config.pin_uv_auth_token_support = true; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6966 | virtual_device_factory_->SetCtap2Config(config); |
| 6967 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 6968 | } |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6969 | }; |
| 6970 | |
| 6971 | TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUVToken) { |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6972 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 6973 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6974 | |
| 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 Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6986 | // 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6995 | GetAssertionResult result = |
| 6996 | AuthenticatorGetAssertion(get_credential_options(uv)); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 6997 | |
| 6998 | if (should_be_unrecognized) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 6999 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, result.status); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7000 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7001 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7002 | EXPECT_EQ(fingerprints_enrolled && |
| 7003 | uv != device::UserVerificationRequirement::kDiscouraged, |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7004 | HasUV(result.response)); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7005 | } |
| 7006 | } |
| 7007 | } |
| 7008 | } |
| 7009 | |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7010 | // Test exhausting all internal user verification attempts on an authenticator |
| 7011 | // that does not support PINs. |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7012 | TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUvFails) { |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7013 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7014 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7015 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7016 | config.pin_uv_auth_token_support = true; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7017 | config.user_verification_succeeds = false; |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7018 | config.pin_support = false; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7019 | virtual_device_factory_->SetCtap2Config(config); |
| 7020 | virtual_device_factory_->mutable_state()->fingerprints_enrolled = true; |
| 7021 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7022 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7023 | |
Nina Satragno | 48ce2e62 | 2020-02-18 21:30:23 | [diff] [blame] | 7024 | 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7033 | EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status, |
| 7034 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 7035 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kUvNotSupported, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 7036 | AuthenticationRequestMode::kModalWebAuthn); |
Nina Satragno | 48ce2e62 | 2020-02-18 21:30:23 | [diff] [blame] | 7037 | EXPECT_EQ(0, expected_retries); |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7038 | } |
| 7039 | |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7040 | // Test exhausting all internal user verification attempts on an authenticator |
| 7041 | // that supports PINs. |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7042 | TEST_F(UVTokenAuthenticatorImplTest, GetAssertionFallBackToPin) { |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7043 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7044 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7045 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7046 | config.pin_uv_auth_token_support = true; |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7047 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7053 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7054 | |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7055 | int taps = 0; |
| 7056 | virtual_device_factory_->mutable_state()->uv_retries = 5; |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7057 | virtual_device_factory_->mutable_state()->simulate_press_callback = |
| 7058 | base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) { |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7059 | ++taps; |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7060 | return true; |
| 7061 | }); |
| 7062 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7063 | EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status, |
| 7064 | AuthenticatorStatus::SUCCESS); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7065 | // 5 retries + 1 tap for the actual get assertion request. |
| 7066 | EXPECT_EQ(taps, 6); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 7067 | EXPECT_TRUE(test_client_.collected_pin); |
| 7068 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7069 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries); |
Nina Satragno | 427eaca | 2020-02-27 19:07:27 | [diff] [blame] | 7070 | } |
| 7071 | |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7072 | // 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. |
| 7074 | TEST_F(UVTokenAuthenticatorImplTest, GetAssertionUvBlockedFallBackToPin) { |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7075 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7076 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7077 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7078 | config.pin_uv_auth_token_support = true; |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7079 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7087 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7088 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7089 | EXPECT_EQ(AuthenticatorGetAssertion(get_credential_options()).status, |
| 7090 | AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 7091 | EXPECT_TRUE(test_client_.collected_pin); |
| 7092 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7093 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7094 | } |
| 7095 | |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7096 | TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUVToken) { |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7097 | 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 Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7108 | // UV cannot be satisfied without fingerprints. |
| 7109 | const bool should_timeout = |
| 7110 | !fingerprints_enrolled && |
| 7111 | uv == device::UserVerificationRequirement::kRequired; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7112 | |
| 7113 | if (should_timeout) { |
| 7114 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7115 | AuthenticatorMakeCredentialAndWaitForTimeout( |
| 7116 | make_credential_options(uv)) |
| 7117 | .status); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7118 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7119 | MakeCredentialResult result = |
| 7120 | AuthenticatorMakeCredential(make_credential_options(uv)); |
| 7121 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 7122 | EXPECT_EQ(fingerprints_enrolled, HasUV(result.response)); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7123 | } |
| 7124 | } |
| 7125 | } |
| 7126 | } |
| 7127 | |
| 7128 | // Test exhausting all internal user verification attempts on an authenticator |
| 7129 | // that does not support PINs. |
| 7130 | TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUvFails) { |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7131 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7132 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7133 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7134 | config.pin_uv_auth_token_support = true; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7135 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7140 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7141 | |
| 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 Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7151 | EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status, |
| 7152 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7153 | EXPECT_EQ(0, expected_retries); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7154 | } |
| 7155 | |
| 7156 | // Test exhausting all internal user verification attempts on an authenticator |
| 7157 | // that supports PINs. |
| 7158 | TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialFallBackToPin) { |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7159 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7160 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7161 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7162 | config.pin_uv_auth_token_support = true; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7163 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7169 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7170 | |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7171 | int taps = 0; |
| 7172 | virtual_device_factory_->mutable_state()->uv_retries = 5; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7173 | virtual_device_factory_->mutable_state()->simulate_press_callback = |
| 7174 | base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) { |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7175 | ++taps; |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7176 | return true; |
| 7177 | }); |
| 7178 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7179 | EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status, |
| 7180 | AuthenticatorStatus::SUCCESS); |
Nina Satragno | b187004 | 2020-12-03 03:14:01 | [diff] [blame] | 7181 | // 5 retries + 1 tap for the actual get assertion request. |
| 7182 | EXPECT_EQ(taps, 6); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 7183 | EXPECT_TRUE(test_client_.collected_pin); |
| 7184 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7185 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries); |
Nina Satragno | b4f200b | 2020-02-28 19:26:03 | [diff] [blame] | 7186 | } |
| 7187 | |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7188 | // 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. |
| 7190 | TEST_F(UVTokenAuthenticatorImplTest, MakeCredentialUvBlockedFallBackToPin) { |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7191 | device::VirtualCtap2Device::Config config; |
Nina Satragno | edad4c5 | 2020-06-16 21:31:57 | [diff] [blame] | 7192 | config.ctap2_versions = {device::Ctap2Version::kCtap2_1}; |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7193 | config.internal_uv_support = true; |
Nina Satragno | 20a78cf9 | 2020-06-22 18:02:19 | [diff] [blame] | 7194 | config.pin_uv_auth_token_support = true; |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7195 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7203 | get_credential_options()->allow_credentials[0].id, kTestRelyingPartyId)); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7204 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7205 | EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status, |
| 7206 | AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | 339413a8 | 2021-05-05 03:02:55 | [diff] [blame] | 7207 | EXPECT_TRUE(test_client_.collected_pin); |
| 7208 | EXPECT_EQ(device::kMinPinLength, test_client_.min_pin_length); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7209 | EXPECT_EQ(5, virtual_device_factory_->mutable_state()->uv_retries); |
Nina Satragno | fa20f27 | 2020-05-18 18:19:30 | [diff] [blame] | 7210 | } |
| 7211 | |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7212 | class BlockingAuthenticatorRequestDelegate |
| 7213 | : public AuthenticatorRequestClientDelegate { |
| 7214 | public: |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7215 | BlockingAuthenticatorRequestDelegate() = default; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7216 | |
| 7217 | void RegisterActionCallbacks( |
| 7218 | base::OnceClosure cancel_callback, |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 7219 | base::OnceClosure immediate_not_found_callback, |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7220 | base::RepeatingClosure start_over_callback, |
Nina Satragno | fe6e52ad7 | 2022-06-01 14:04:14 | [diff] [blame] | 7221 | AccountPreselectedCallback account_preselected_callback, |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 7222 | PasswordSelectedCallback password_selected_callback, |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7223 | device::FidoRequestHandlerBase::RequestCallback request_callback, |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7224 | base::OnceClosure cancel_ui_timeout_callback, |
Nina Satragno | 6e0f1ab | 2024-06-13 22:28:11 | [diff] [blame] | 7225 | base::RepeatingClosure bluetooth_adapter_power_on_callback, |
| 7226 | base::RepeatingCallback< |
| 7227 | void(device::FidoRequestHandlerBase::BlePermissionCallback)> |
| 7228 | ble_status_callback) override { |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7229 | cancel_callback_ = std::move(cancel_callback); |
| 7230 | } |
| 7231 | |
| 7232 | bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override { |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7233 | // Post a task to cancel the request to give the second authenticator a |
| 7234 | // chance to return a status from the cancelled request. |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 7235 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7236 | FROM_HERE, std::move(cancel_callback_)); |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7237 | return true; |
| 7238 | } |
| 7239 | |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7240 | private: |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7241 | base::OnceClosure cancel_callback_; |
| 7242 | }; |
| 7243 | |
| 7244 | class BlockingDelegateContentBrowserClient : public ContentBrowserClient { |
| 7245 | public: |
Nina Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7246 | BlockingDelegateContentBrowserClient() = default; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7247 | |
| 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 Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7255 | auto ret = std::make_unique<BlockingAuthenticatorRequestDelegate>(); |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7256 | delegate_ = ret.get(); |
| 7257 | return ret; |
| 7258 | } |
| 7259 | |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7260 | private: |
| 7261 | TestWebAuthenticationDelegate web_authentication_delegate_; |
Pâris | e6361d0 | 2023-07-19 09:00:43 | [diff] [blame] | 7262 | raw_ptr<BlockingAuthenticatorRequestDelegate, AcrossTasksDanglingUntriaged> |
| 7263 | delegate_ = nullptr; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7264 | }; |
| 7265 | |
| 7266 | class 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 Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7287 | BlockingDelegateContentBrowserClient test_client_; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7288 | |
| 7289 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 7290 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7291 | }; |
| 7292 | |
| 7293 | TEST_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 Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7297 | // This will cancel the request on the fingerprint device, which will resolve |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7298 | // the UV with an error. Don't crash (crbug.com/1225899). |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7299 | 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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 7306 | base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) -> bool { |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7307 | // 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 Satragno | d18d206 | 2021-10-08 16:00:49 | [diff] [blame] | 7321 | device_2.state->cancel_response_code = |
| 7322 | device::CtapDeviceResponseCode::kCtap2ErrOperationDenied; |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7323 | device_2.state->simulate_press_callback = |
Peter Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 7324 | base::BindLambdaForTesting([&](VirtualFidoDevice* ignore) -> bool { |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7325 | // If asked for a fingerprint, fail the makeCredential request by |
| 7326 | // simulating a matched excluded credential by the other authenticator. |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 7327 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7328 | 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 P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 7339 | ReplaceDiscoveryFactory(std::move(discovery)); |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7340 | |
Adam Langley | 6b6a1371 | 2021-07-14 20:35:31 | [diff] [blame] | 7341 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 7342 | AuthenticatorStatus::CREDENTIAL_EXCLUDED); |
| 7343 | } |
| 7344 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7345 | // 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|. |
| 7351 | class ResidentKeyTestAuthenticatorRequestDelegate |
| 7352 | : public AuthenticatorRequestClientDelegate { |
| 7353 | public: |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7354 | 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 Kreichgauer | 77def30f0 | 2024-11-11 20:16:58 | [diff] [blame] | 7362 | // Indicates whether `SetUIPresentation(kAutofill)` is expected to be |
| 7363 | // called. |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7364 | bool expect_conditional = false; |
| 7365 | |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7366 | // Indicates whether `RegisterActionCallbacks()` should run the cancel UI |
| 7367 | // timeout callback. |
| 7368 | bool run_cancel_ui_timeout_callback = false; |
| 7369 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7370 | // If set, indicates that `DoesBlockRequestOnFailure()` is expected to be |
| 7371 | // called with this value. |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7372 | std::optional<AuthenticatorRequestClientDelegate::InterestingFailureReason> |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7373 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7379 | std::optional<std::vector<uint8_t>> preselected_credential_id; |
| 7380 | std::optional<std::string> preselected_authenticator_id; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7381 | }; |
| 7382 | |
| 7383 | explicit ResidentKeyTestAuthenticatorRequestDelegate(Config config) |
| 7384 | : config_(std::move(config)) {} |
| 7385 | |
| 7386 | ~ResidentKeyTestAuthenticatorRequestDelegate() override { |
Martin Kreichgauer | 77def30f0 | 2024-11-11 20:16:58 | [diff] [blame] | 7387 | CHECK(!config_.expect_conditional || expect_conditional_satisfied_) |
| 7388 | << "SetUIPresentation(kAutofill) expected but not called"; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7389 | DCHECK(!config_.expected_failure_reason || |
| 7390 | expected_failure_reason_satisfied_) |
| 7391 | << "DoesRequestBlockOnFailure() expected but not called"; |
| 7392 | } |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7393 | |
Martin Kreichgauer | a97e2a3 | 2021-02-05 23:10:59 | [diff] [blame] | 7394 | ResidentKeyTestAuthenticatorRequestDelegate( |
| 7395 | const ResidentKeyTestAuthenticatorRequestDelegate&) = delete; |
| 7396 | ResidentKeyTestAuthenticatorRequestDelegate& operator=( |
| 7397 | const ResidentKeyTestAuthenticatorRequestDelegate&) = delete; |
| 7398 | |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 7399 | bool SupportsPIN() const override { return true; } |
| 7400 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7401 | void CollectPIN( |
Nina Satragno | 164bc11 | 2020-12-04 02:44:15 | [diff] [blame] | 7402 | CollectPINOptions options, |
Jan Wilken Dörrie | aace0cfef | 2021-03-11 22:01:58 | [diff] [blame] | 7403 | base::OnceCallback<void(std::u16string)> provide_pin_cb) override { |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 7404 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Peter Kasting | cc07b33 | 2021-05-07 22:58:25 | [diff] [blame] | 7405 | FROM_HERE, base::BindOnce(std::move(provide_pin_cb), kTestPIN16)); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7406 | } |
| 7407 | |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7408 | void FinishCollectToken() override {} |
Adam Langley | 989fa49 | 2019-03-28 22:03:18 | [diff] [blame] | 7409 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7410 | void RegisterActionCallbacks( |
| 7411 | base::OnceClosure cancel_callback, |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 7412 | base::OnceClosure immediate_not_found_callback, |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7413 | base::RepeatingClosure start_over_callback, |
| 7414 | AccountPreselectedCallback account_preselected_callback, |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 7415 | PasswordSelectedCallback password_selected_callback, |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7416 | device::FidoRequestHandlerBase::RequestCallback request_callback, |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7417 | base::OnceClosure cancel_ui_timeout_callback, |
Nina Satragno | 6e0f1ab | 2024-06-13 22:28:11 | [diff] [blame] | 7418 | base::RepeatingClosure bluetooth_adapter_power_on_callback, |
| 7419 | base::RepeatingCallback< |
| 7420 | void(device::FidoRequestHandlerBase::BlePermissionCallback)> |
| 7421 | ble_status_callback) override { |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7422 | account_preselected_callback_ = account_preselected_callback; |
| 7423 | request_callback_ = request_callback; |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7424 | if (config_.run_cancel_ui_timeout_callback) { |
| 7425 | std::move(cancel_ui_timeout_callback).Run(); |
| 7426 | } |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7427 | } |
| 7428 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7429 | 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 Langley | b1b6ace | 2021-03-09 20:23:49 | [diff] [blame] | 7436 | return a.user_entity->id < b.user_entity->id; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7437 | }); |
| 7438 | |
| 7439 | std::vector<std::string> string_reps; |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 7440 | std::ranges::transform( |
Peter Kasting | 1d48f35 | 2023-03-07 18:29:57 | [diff] [blame] | 7441 | responses, std::back_inserter(string_reps), |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7442 | [](const device::AuthenticatorGetAssertionResponse& response) { |
| 7443 | const device::PublicKeyCredentialUserEntity& user = |
Adam Langley | b1b6ace | 2021-03-09 20:23:49 | [diff] [blame] | 7444 | response.user_entity.value(); |
Tom Sepez | 80e9e6e5 | 2024-02-01 02:34:42 | [diff] [blame] | 7445 | return base::HexEncode(user.id) + ":" + user.name.value_or("") + ":" + |
| 7446 | user.display_name.value_or(""); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7447 | }); |
| 7448 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7449 | EXPECT_EQ(config_.expected_accounts, base::JoinString(string_reps, "/")); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7450 | |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 7451 | const auto selected = std::ranges::find( |
Peter Kasting | 837e2ccb | 2022-09-07 14:13:17 | [diff] [blame] | 7452 | responses, config_.selected_user_id, |
| 7453 | [](const device::AuthenticatorGetAssertionResponse& response) { |
| 7454 | return response.user_entity->id; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7455 | }); |
| 7456 | ASSERT_TRUE(selected != responses.end()); |
| 7457 | |
Sean Maher | 52fa5a7 | 2022-11-14 15:53:25 | [diff] [blame] | 7458 | base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7459 | FROM_HERE, base::BindOnce(std::move(callback), std::move(*selected))); |
| 7460 | } |
| 7461 | |
Martin Kreichgauer | f45542a | 2019-08-30 16:45:50 | [diff] [blame] | 7462 | bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override { |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7463 | if (config_.expected_failure_reason) { |
| 7464 | EXPECT_EQ(*config_.expected_failure_reason, reason); |
| 7465 | expected_failure_reason_satisfied_ = true; |
| 7466 | } |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7467 | return AuthenticatorRequestClientDelegate::DoesBlockRequestOnFailure( |
Martin Kreichgauer | f45542a | 2019-08-30 16:45:50 | [diff] [blame] | 7468 | reason); |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7469 | } |
| 7470 | |
Martin Kreichgauer | 77def30f0 | 2024-11-11 20:16:58 | [diff] [blame] | 7471 | void SetUIPresentation(UIPresentation ui_presentation) override { |
| 7472 | if (config_.expect_conditional) { |
| 7473 | EXPECT_EQ(ui_presentation, UIPresentation::kAutofill); |
| 7474 | } else { |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7475 | EXPECT_TRUE(ui_presentation == UIPresentation::kModal || |
| 7476 | ui_presentation == UIPresentation::kModalImmediate); |
Martin Kreichgauer | 77def30f0 | 2024-11-11 20:16:58 | [diff] [blame] | 7477 | } |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7478 | 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 Satragno | 1757045 | 2024-03-18 16:48:23 | [diff] [blame] | 7500 | const auto cred = std::ranges::find( |
Nina Satragno | 775ab71 | 2023-06-21 21:07:16 | [diff] [blame] | 7501 | info.recognized_credentials, *config_.preselected_credential_id, |
Nina Satragno | 1757045 | 2024-03-18 16:48:23 | [diff] [blame] | 7502 | &device::DiscoverableCredentialMetadata::cred_id); |
| 7503 | ASSERT_NE(cred, info.recognized_credentials.end()); |
| 7504 | std::move(account_preselected_callback_).Run(*cred); |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7505 | request_callback_.Run(*config_.preselected_authenticator_id); |
| 7506 | } |
Nina Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 7507 | } |
| 7508 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7509 | private: |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7510 | 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 Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 7515 | base::OnceClosure cancel_ui_timeout_callback_; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7516 | }; |
| 7517 | |
| 7518 | class ResidentKeyTestAuthenticatorContentBrowserClient |
| 7519 | : public ContentBrowserClient { |
| 7520 | public: |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 7521 | ResidentKeyTestAuthenticatorContentBrowserClient() { |
| 7522 | web_authentication_delegate.supports_resident_keys = true; |
| 7523 | } |
| 7524 | |
| 7525 | WebAuthenticationDelegate* GetWebAuthenticationDelegate() override { |
| 7526 | return &web_authentication_delegate; |
| 7527 | } |
| 7528 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7529 | std::unique_ptr<AuthenticatorRequestClientDelegate> |
| 7530 | GetWebAuthenticationRequestDelegate( |
Adam Langley | 5f3963f1 | 2020-01-21 19:10:33 | [diff] [blame] | 7531 | RenderFrameHost* render_frame_host) override { |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7532 | return std::make_unique<ResidentKeyTestAuthenticatorRequestDelegate>( |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7533 | delegate_config); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7534 | } |
| 7535 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 7536 | TestWebAuthenticationDelegate web_authentication_delegate; |
| 7537 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7538 | ResidentKeyTestAuthenticatorRequestDelegate::Config delegate_config; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7539 | }; |
| 7540 | |
| 7541 | class ResidentKeyAuthenticatorImplTest : public UVAuthenticatorImplTest { |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 7542 | public: |
Peter Boström | 9b03653 | 2021-10-28 23:37:28 | [diff] [blame] | 7543 | ResidentKeyAuthenticatorImplTest(const ResidentKeyAuthenticatorImplTest&) = |
| 7544 | delete; |
| 7545 | ResidentKeyAuthenticatorImplTest& operator=( |
| 7546 | const ResidentKeyAuthenticatorImplTest&) = delete; |
| 7547 | |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 7548 | protected: |
| 7549 | ResidentKeyAuthenticatorImplTest() = default; |
| 7550 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7551 | void SetUp() override { |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7552 | 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7557 | virtual_device_factory_->SetCtap2Config(config); |
| 7558 | virtual_device_factory_->mutable_state()->pin = kTestPIN; |
Nina Satragno | d976d1b | 2020-01-06 22:45:43 | [diff] [blame] | 7559 | virtual_device_factory_->mutable_state()->pin_retries = |
| 7560 | device::kMaxPinRetries; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7561 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 7562 | } |
| 7563 | |
| 7564 | void TearDown() override { |
| 7565 | SetBrowserClientForTesting(old_client_); |
Martin Kreichgauer | 85a723bb | 2020-06-08 17:25:19 | [diff] [blame] | 7566 | UVAuthenticatorImplTest::TearDown(); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7567 | } |
| 7568 | |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7569 | static PublicKeyCredentialCreationOptionsPtr make_credential_options( |
| 7570 | device::ResidentKeyRequirement resident_key = |
| 7571 | device::ResidentKeyRequirement::kRequired) { |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7572 | PublicKeyCredentialCreationOptionsPtr options = |
| 7573 | UVAuthenticatorImplTest::make_credential_options(); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 7574 | options->authenticator_selection->resident_key = resident_key; |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 7575 | options->user.id = {1, 2, 3, 4}; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7576 | 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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7586 | ResidentKeyTestAuthenticatorContentBrowserClient test_client_; |
| 7587 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7588 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 7589 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7590 | }; |
| 7591 | |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7592 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkRequired) { |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7593 | for (const bool internal_uv : {false, true}) { |
| 7594 | SCOPED_TRACE(::testing::Message() << "internal_uv=" << internal_uv); |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7595 | |
| 7596 | if (internal_uv) { |
| 7597 | device::VirtualCtap2Device::Config config; |
| 7598 | config.resident_key_support = true; |
| 7599 | config.internal_uv_support = true; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7600 | virtual_device_factory_->SetCtap2Config(config); |
| 7601 | virtual_device_factory_->mutable_state()->fingerprints_enrolled = true; |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7602 | } |
| 7603 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7604 | MakeCredentialResult result = |
| 7605 | AuthenticatorMakeCredential(make_credential_options()); |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7606 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7607 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7608 | EXPECT_TRUE(HasUV(result.response)); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7609 | ASSERT_EQ(1u, |
| 7610 | virtual_device_factory_->mutable_state()->registrations.size()); |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7611 | const device::VirtualFidoDevice::RegistrationData& registration = |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7612 | virtual_device_factory_->mutable_state()->registrations.begin()->second; |
Adam Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7613 | EXPECT_TRUE(registration.is_resident); |
| 7614 | ASSERT_TRUE(registration.user.has_value()); |
| 7615 | const auto options = make_credential_options(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 7616 | 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 Langley | eeac87e | 2019-04-13 22:58:22 | [diff] [blame] | 7619 | } |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7620 | } |
| 7621 | |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7622 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferred) { |
| 7623 | for (const bool supports_rk : {false, true}) { |
| 7624 | SCOPED_TRACE(::testing::Message() << "supports_rk=" << supports_rk); |
| 7625 | ResetVirtualDevice(); |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7626 | |
| 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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7637 | 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 | |
| 7646 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferredStorageFull) { |
| 7647 | // Making a credential on an authenticator with full storage falls back to |
| 7648 | // making a non-resident key. |
Martin Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 7649 | for (bool is_ctap_2_1 : {false, true}) { |
| 7650 | ResetVirtualDevice(); |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7651 | |
Martin Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 7652 | 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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7659 | |
Martin Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 7660 | 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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7665 | |
Martin Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 7666 | 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 Kreichgauer | 08be2d7 | 2020-10-27 04:26:35 | [diff] [blame] | 7676 | 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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7688 | } |
| 7689 | |
Nina Satragno | 09476477 | 2024-11-26 23:35:43 | [diff] [blame] | 7690 | TEST_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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7729 | TEST_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 Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 7741 | 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 Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7749 | TEST_F(ResidentKeyAuthenticatorImplTest, StorageFull) { |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7750 | device::VirtualCtap2Device::Config config; |
| 7751 | config.resident_key_support = true; |
| 7752 | config.internal_uv_support = true; |
| 7753 | config.resident_credential_storage = 1; |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7754 | virtual_device_factory_->SetCtap2Config(config); |
| 7755 | virtual_device_factory_->mutable_state()->fingerprints_enrolled = true; |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7756 | |
| 7757 | // Add a resident key to fill the authenticator. |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7758 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7759 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
| 7760 | /*user_id=*/{{1, 1, 1, 1}}, "[email protected]", "Test User")); |
| 7761 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7762 | test_client_.delegate_config.expected_failure_reason = |
| 7763 | AuthenticatorRequestClientDelegate::InterestingFailureReason:: |
| 7764 | kStorageFull; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7765 | EXPECT_EQ(AuthenticatorMakeCredential(make_credential_options()).status, |
| 7766 | AuthenticatorStatus::NOT_ALLOWED_ERROR); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 7767 | VerifyMakeCredentialOutcomeUkm(0, MakeCredentialOutcome::kStorageFull, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 7768 | AuthenticationRequestMode::kModalWebAuthn); |
Adam Langley | 77fab61 | 2019-05-03 05:19:04 | [diff] [blame] | 7769 | } |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 7770 | |
Nina Satragno | bc2c91d1 | 2024-06-17 22:55:28 | [diff] [blame] | 7771 | TEST_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 Langley | 9a97915 | 2022-09-02 22:52:26 | [diff] [blame] | 7781 | PublicKeyCredentialCreationOptionsPtr options = make_credential_options(); |
Nina Satragno | bc2c91d1 | 2024-06-17 22:55:28 | [diff] [blame] | 7782 | |
Adam Langley | 9a97915 | 2022-09-02 22:52:26 | [diff] [blame] | 7783 | // 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 Satragno | bc2c91d1 | 2024-06-17 22:55:28 | [diff] [blame] | 7788 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, |
| 7789 | AuthenticatorMakeCredential(std::move(options)).status); |
| 7790 | } |
Adam Langley | 9a97915 | 2022-09-02 22:52:26 | [diff] [blame] | 7791 | |
Nina Satragno | bc2c91d1 | 2024-06-17 22:55:28 | [diff] [blame] | 7792 | // Regression test for crbug.com/346835891. |
| 7793 | TEST_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 Langley | 9a97915 | 2022-09-02 22:52:26 | [diff] [blame] | 7807 | } |
| 7808 | |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7809 | TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionSingleNoPII) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7810 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Martin Kreichgauer | 0fabc5d | 2019-04-24 19:00:56 | [diff] [blame] | 7811 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7812 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7813 | |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7814 | // |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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7817 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7818 | GetAssertionResult result = |
| 7819 | AuthenticatorGetAssertion(get_credential_options()); |
| 7820 | |
| 7821 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 7822 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7823 | } |
| 7824 | |
Adam Langley | 8abb472 | 2022-02-01 02:06:48 | [diff] [blame] | 7825 | TEST_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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7841 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Adam Langley | 8abb472 | 2022-02-01 02:06:48 | [diff] [blame] | 7842 | } else { |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7843 | test_client_.delegate_config.expected_accounts = "01020304:Test:User"; |
| 7844 | test_client_.delegate_config.selected_user_id = {1, 2, 3, 4}; |
Adam Langley | 8abb472 | 2022-02-01 02:06:48 | [diff] [blame] | 7845 | } |
| 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 Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7854 | TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionSingleWithPII) { |
| 7855 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
| 7856 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7857 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, "Test User")); |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7858 | |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7859 | // |SelectAccount| should be called when PII is available. |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7860 | test_client_.delegate_config.expected_accounts = "01020304::Test User"; |
| 7861 | test_client_.delegate_config.selected_user_id = {1, 2, 3, 4}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7862 | GetAssertionResult result = |
| 7863 | AuthenticatorGetAssertion(get_credential_options()); |
| 7864 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 7865 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7866 | } |
| 7867 | |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7868 | TEST_F(ResidentKeyAuthenticatorImplTest, GetAssertionMulti) { |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7869 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Martin Kreichgauer | 0fabc5d | 2019-04-24 19:00:56 | [diff] [blame] | 7870 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
| 7871 | /*user_id=*/{{1, 2, 3, 4}}, "[email protected]", "Test User")); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7872 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Martin Kreichgauer | 0fabc5d | 2019-04-24 19:00:56 | [diff] [blame] | 7873 | /*credential_id=*/{{4, 3, 2, 2}}, kTestRelyingPartyId, |
| 7874 | /*user_id=*/{{5, 6, 7, 8}}, "[email protected]", "Test User 2")); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7875 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7876 | test_client_.delegate_config.expected_accounts = |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7877 | "01020304:[email protected]:Test User/" |
| 7878 | "05060708:[email protected]:Test User 2"; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7879 | test_client_.delegate_config.selected_user_id = {1, 2, 3, 4}; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7880 | |
| 7881 | GetAssertionResult result = |
| 7882 | AuthenticatorGetAssertion(get_credential_options()); |
| 7883 | |
| 7884 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 7885 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 7886 | } |
| 7887 | |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7888 | TEST_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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7893 | virtual_device_factory_->SetCtap2Config(config); |
| 7894 | virtual_device_factory_->mutable_state()->fingerprints_enrolled = true; |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7895 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 7896 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7897 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7898 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7899 | |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 7900 | // |SelectAccount| should not be called when there's only a single response |
| 7901 | // without identifying information. |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 7902 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7903 | PublicKeyCredentialRequestOptionsPtr options(get_credential_options()); |
| 7904 | options->user_verification = |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 7905 | device::UserVerificationRequirement::kDiscouraged; |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7906 | |
| 7907 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 7908 | |
| 7909 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7910 | // The UV=discouraged should have been ignored for a resident-credential |
| 7911 | // request. |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 7912 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | 3c77c50 | 2019-05-23 08:36:34 | [diff] [blame] | 7913 | } |
| 7914 | |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7915 | static 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 | |
| 7926 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialLargeBlob) { |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7927 | constexpr auto BlobRequired = device::LargeBlobSupport::kRequired; |
| 7928 | constexpr auto BlobPreferred = device::LargeBlobSupport::kPreferred; |
| 7929 | constexpr auto BlobNotRequested = device::LargeBlobSupport::kNotRequested; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7930 | constexpr auto nullopt = std::nullopt; |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7931 | |
| 7932 | constexpr struct { |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7933 | bool large_blob_extension; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 7934 | std::optional<bool> large_blob_support; |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7935 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7941 | // 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 Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7963 | // clang-format on |
| 7964 | }; |
| 7965 | for (auto& test : kLargeBlobTestCases) { |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7966 | 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 Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7972 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7979 | SCOPED_TRACE(::testing::Message() |
| 7980 | << "large_blob_extension=" << test.large_blob_extension); |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7981 | |
| 7982 | device::VirtualCtap2Device::Config config; |
| 7983 | config.pin_support = true; |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 7984 | config.pin_uv_auth_token_support = true; |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7985 | config.resident_key_support = true; |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 7986 | config.ctap2_versions = {std::begin(device::kCtap2Versions2_1), |
| 7987 | std::end(device::kCtap2Versions2_1)}; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 7988 | 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 Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 7993 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8010 | EXPECT_EQ(test.did_create_large_blob && !test.large_blob_extension, |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 8011 | 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 Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8022 | virtual_device_factory_->mutable_state()->ClearLargeBlobs(); |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 8023 | } |
| 8024 | } |
| 8025 | |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8026 | TEST_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 Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8051 | config.pin_uv_auth_token_support = true; |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8052 | config.resident_key_support = true; |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8053 | config.ctap2_versions = {std::begin(device::kCtap2Versions2_1), |
| 8054 | std::end(device::kCtap2Versions2_1)}; |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8055 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8059 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8060 | |
| 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 Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 8066 | CompressLargeBlob(large_blob)); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8067 | } 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8074 | options->extensions->large_blob_read = true; |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8075 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8076 | |
| 8077 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8078 | EXPECT_TRUE(result.response->extensions->echo_large_blob); |
| 8079 | EXPECT_FALSE(result.response->extensions->echo_large_blob_written); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8080 | if (test.did_read_large_blob) { |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8081 | EXPECT_EQ(large_blob, *result.response->extensions->large_blob); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8082 | } else { |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8083 | EXPECT_FALSE(result.response->extensions->large_blob.has_value()); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8084 | } |
| 8085 | virtual_device_factory_->mutable_state()->registrations.clear(); |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8086 | virtual_device_factory_->mutable_state()->ClearLargeBlobs(); |
| 8087 | } |
| 8088 | } |
| 8089 | |
| 8090 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8124 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8125 | |
| 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 Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 8131 | CompressLargeBlob(large_blob)); |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8132 | } 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8141 | options->extensions->large_blob_write = large_blob; |
Nina Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8142 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8143 | |
| 8144 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8145 | 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 Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8150 | if (test.did_write_large_blob) { |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8151 | std::optional<device::LargeBlob> compressed_blob = |
Nina Satragno | aed99fb | 2020-10-15 22:21:56 | [diff] [blame] | 8152 | 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 Satragno | 9de1788d | 2020-10-08 21:00:51 | [diff] [blame] | 8157 | } |
| 8158 | virtual_device_factory_->mutable_state()->registrations.clear(); |
| 8159 | virtual_device_factory_->mutable_state()->ClearLargeBlobs(); |
Nina Satragno | 2bf83434 | 2020-10-06 22:05:50 | [diff] [blame] | 8160 | } |
| 8161 | } |
| 8162 | |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8163 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8176 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8177 | |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8183 | options->extensions->large_blob_read = true; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8184 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8185 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8186 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8189 | } |
| 8190 | |
| 8191 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8205 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8206 | |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8212 | options->extensions->large_blob_read = true; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8213 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8214 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8215 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8218 | } |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8225 | options->extensions->large_blob_write = large_blob; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8226 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8227 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8228 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8231 | } |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8238 | options->extensions->large_blob_read = true; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8239 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8240 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8241 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8245 | } |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8257 | options->extensions->large_blob_read = true; |
Adam Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8258 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8259 | ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8260 | 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 Langley | a254f0b5 | 2023-02-14 03:38:03 | [diff] [blame] | 8263 | } |
| 8264 | } |
| 8265 | |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8266 | static 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 Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8280 | static 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8291 | TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) { |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8292 | 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 Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8300 | 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8306 | |
| 8307 | const struct { |
| 8308 | bool supported_by_authenticator; |
| 8309 | bool is_resident; |
| 8310 | blink::mojom::ProtectionPolicy protection; |
| 8311 | bool enforce; |
Adam Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8312 | device::UserVerificationRequirement uv; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8313 | 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 Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8318 | { 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8341 | |
| 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 Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8346 | { 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8362 | // 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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8370 | virtual_device_factory_->SetCtap2Config(config); |
| 8371 | virtual_device_factory_->mutable_state()->registrations.clear(); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8372 | |
Adam Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8373 | SCOPED_TRACE(::testing::Message() << "uv=" << UVToString(test.uv)); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8374 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8382 | options->authenticator_selection->resident_key = |
Martin Kreichgauer | 2e9d807 | 2020-08-31 15:20:47 | [diff] [blame] | 8383 | test.is_resident ? device::ResidentKeyRequirement::kRequired |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8384 | : device::ResidentKeyRequirement::kDiscouraged; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8385 | options->protection_policy = test.protection; |
| 8386 | options->enforce_protection_policy = test.enforce; |
Adam Langley | afd522f | 2023-01-27 21:12:33 | [diff] [blame] | 8387 | options->authenticator_selection->user_verification_requirement = test.uv; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8388 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8389 | AuthenticatorStatus status = |
| 8390 | AuthenticatorMakeCredential(std::move(options)).status; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8391 | |
| 8392 | switch (test.expected_outcome) { |
| 8393 | case kOk: { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8394 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, status); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8395 | ASSERT_EQ( |
| 8396 | 1u, virtual_device_factory_->mutable_state()->registrations.size()); |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8397 | const device::CredProtect result = |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8398 | virtual_device_factory_->mutable_state() |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8399 | ->registrations.begin() |
| 8400 | ->second.protection; |
| 8401 | |
| 8402 | switch (test.resulting_policy) { |
| 8403 | case UNSPECIFIED: |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 8404 | NOTREACHED(); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8405 | case NONE: |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8406 | EXPECT_EQ(device::CredProtect::kUVOptional, result); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8407 | break; |
| 8408 | case UV_OR_CRED: |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8409 | EXPECT_EQ(device::CredProtect::kUVOrCredIDRequired, result); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8410 | break; |
| 8411 | case UV_REQ: |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8412 | EXPECT_EQ(device::CredProtect::kUVRequired, result); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8413 | break; |
| 8414 | } |
| 8415 | break; |
| 8416 | } |
| 8417 | case kNonsense: |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8418 | EXPECT_EQ(AuthenticatorStatus::PROTECTION_POLICY_INCONSISTENT, status); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8419 | break; |
| 8420 | case kNotAllow: |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8421 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, status); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8422 | break; |
| 8423 | default: |
Peter Boström | fc7ddc18 | 2024-10-31 19:37:21 | [diff] [blame] | 8424 | NOTREACHED(); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8425 | } |
| 8426 | } |
| 8427 | } |
| 8428 | |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8429 | TEST_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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 8433 | constexpr std::array<blink::mojom::ProtectionPolicy, 3> kMojoLevels = { |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8434 | blink::mojom::ProtectionPolicy::NONE, |
| 8435 | blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED, |
| 8436 | blink::mojom::ProtectionPolicy::UV_REQUIRED, |
| 8437 | }; |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 8438 | constexpr std::array<device::CredProtect, 3> kDeviceLevels = { |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8439 | device::CredProtect::kUVOptional, |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8440 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8457 | options->authenticator_selection->resident_key = |
| 8458 | device::ResidentKeyRequirement::kRequired; |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8459 | options->protection_policy = kMojoLevels[requested_level]; |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8460 | options->authenticator_selection->user_verification_requirement = |
| 8461 | device::UserVerificationRequirement::kRequired; |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8462 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8463 | AuthenticatorStatus status = |
| 8464 | AuthenticatorMakeCredential(std::move(options)).status; |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8465 | |
| 8466 | if (requested_level <= forced_level) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8467 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, status); |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8468 | ASSERT_EQ( |
| 8469 | 1u, virtual_device_factory_->mutable_state()->registrations.size()); |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8470 | const std::optional<device::CredProtect> result = |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8471 | virtual_device_factory_->mutable_state() |
| 8472 | ->registrations.begin() |
| 8473 | ->second.protection; |
| 8474 | EXPECT_EQ(*result, config.force_cred_protect); |
| 8475 | } else { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8476 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, status); |
Adam Langley | e6327a5 | 2020-01-03 22:36:10 | [diff] [blame] | 8477 | } |
| 8478 | } |
| 8479 | } |
| 8480 | } |
| 8481 | |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8482 | TEST_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 Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8485 | 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 Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8547 | options->authenticator_selection->resident_key = |
| 8548 | device::ResidentKeyRequirement::kRequired; |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8549 | options->protection_policy = test.requested_level; |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8550 | options->authenticator_selection->user_verification_requirement = |
| 8551 | device::UserVerificationRequirement::kRequired; |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8552 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8553 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
| 8554 | AuthenticatorStatus::SUCCESS); |
Adam Langley | 6f8b030d | 2020-04-06 20:10:57 | [diff] [blame] | 8555 | 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8565 | TEST_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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8573 | virtual_device_factory_->SetCtap2Config(config); |
| 8574 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8575 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId)); |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8576 | 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 Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8580 | |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8581 | // |SelectAccount| should not be called when there's only a single response. |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 8582 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8583 | |
| 8584 | PublicKeyCredentialRequestOptionsPtr options = get_credential_options(); |
| 8585 | options->allow_credentials = GetTestCredentials(5); |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8586 | options->allow_credentials[0].id = {4, 3, 2, 1}; |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8587 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8588 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8589 | |
| 8590 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 8591 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | aa78995 | 2019-05-01 19:20:27 | [diff] [blame] | 8592 | } |
Adam Langley | b307ff6 | 2019-03-27 23:36:33 | [diff] [blame] | 8593 | |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8594 | TEST_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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 8601 | virtual_device_factory_->SetCtap2Config(config); |
| 8602 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8603 | /*credential_id=*/{{4, 3, 2, 1}}, kTestRelyingPartyId, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8604 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8605 | |
Adam Langley | 10a207e69 | 2019-08-22 01:38:23 | [diff] [blame] | 8606 | // |SelectAccount| should not be called when there's only a single response |
| 8607 | // without identifying information. |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 8608 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8609 | |
| 8610 | PublicKeyCredentialRequestOptionsPtr options = get_credential_options(); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8611 | options->extensions->appid = kTestOrigin1; |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8612 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8613 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 8614 | |
| 8615 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
| 8616 | EXPECT_TRUE(HasUV(result.response)); |
Adam Langley | aad4c839 | 2019-05-21 04:25:55 | [diff] [blame] | 8617 | } |
| 8618 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 8619 | #if BUILDFLAG(IS_WIN) |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8620 | // 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. |
| 8623 | TEST_F(ResidentKeyAuthenticatorImplTest, WinCredProtectApiVersion) { |
| 8624 | // The canned response returned by the Windows API fake is for acme.com. |
Adam Langley | 3037544 | 2023-06-19 17:20:26 | [diff] [blame] | 8625 | virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true); |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 8626 | fake_win_webauthn_api_.set_available(true); |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8627 | NavigateAndCommit(GURL("https://acme.com")); |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8628 | for (const bool supports_cred_protect : {false, true}) { |
| 8629 | SCOPED_TRACE(testing::Message() |
| 8630 | << "supports_cred_protect: " << supports_cred_protect); |
| 8631 | |
Martin Kreichgauer | 37ace49 | 2021-04-08 23:36:46 | [diff] [blame] | 8632 | fake_win_webauthn_api_.set_version(supports_cred_protect |
| 8633 | ? WEBAUTHN_API_VERSION_2 |
| 8634 | : WEBAUTHN_API_VERSION_1); |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8635 | |
| 8636 | PublicKeyCredentialCreationOptionsPtr options = make_credential_options(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 8637 | options->relying_party = device::PublicKeyCredentialRpEntity(); |
| 8638 | options->relying_party.id = device::test_data::kRelyingPartyId; |
| 8639 | options->relying_party.name = ""; |
Martin Kreichgauer | bece642a | 2021-12-07 21:02:46 | [diff] [blame] | 8640 | options->authenticator_selection->user_verification_requirement = |
| 8641 | device::UserVerificationRequirement::kRequired; |
| 8642 | options->authenticator_selection->resident_key = |
| 8643 | device::ResidentKeyRequirement::kRequired; |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8644 | options->protection_policy = |
| 8645 | blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED; |
| 8646 | options->enforce_protection_policy = true; |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8647 | |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 8648 | EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status, |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8649 | supports_cred_protect ? AuthenticatorStatus::SUCCESS |
| 8650 | : AuthenticatorStatus::NOT_ALLOWED_ERROR); |
| 8651 | } |
| 8652 | } |
Nina Satragno | 8824f9a | 2023-02-02 15:54:46 | [diff] [blame] | 8653 | |
| 8654 | // Tests that the incognito flag is plumbed through conditional UI requests. |
| 8655 | TEST_F(ResidentKeyAuthenticatorImplTest, ConditionalUI_Incognito) { |
Adam Langley | 3037544 | 2023-06-19 17:20:26 | [diff] [blame] | 8656 | virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true); |
Nina Satragno | 8824f9a | 2023-02-02 15:54:46 | [diff] [blame] | 8657 | 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 Satragno | fdc85d82 | 2025-02-18 19:17:17 | [diff] [blame] | 8663 | /*credential_id=*/{{4, 3, 2, 1}}, std::move(rp), std::move(user), |
| 8664 | /*provider_name=*/std::nullopt); |
Nina Satragno | 8824f9a | 2023-02-02 15:54:46 | [diff] [blame] | 8665 | |
| 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 Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 8675 | options->mediation = blink::mojom::Mediation::CONDITIONAL; |
Nina Satragno | 8824f9a | 2023-02-02 15:54:46 | [diff] [blame] | 8676 | 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 Satragno | 5b07479 | 2023-02-18 00:45:06 | [diff] [blame] | 8684 | |
| 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). |
| 8690 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialLargeBlobWinPlatform) { |
Nina Satragno | 0cace239 | 2024-09-12 14:32:39 | [diff] [blame] | 8691 | virtual_device_factory_->set_discover_win_webauthn_api_authenticator(true); |
Nina Satragno | 5b07479 | 2023-02-18 00:45:06 | [diff] [blame] | 8692 | 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 Satragno | 0cace239 | 2024-09-12 14:32:39 | [diff] [blame] | 8705 | |
| 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. |
| 8709 | TEST_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 Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 8730 | #endif // BUILDFLAG(IS_WIN) |
Martin Kreichgauer | e5cdcf6 | 2019-05-07 22:55:00 | [diff] [blame] | 8731 | |
Nina Satragno | 404d5124 | 2023-01-19 19:28:26 | [diff] [blame] | 8732 | // 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. |
| 8736 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8756 | std::vector<uint8_t>{1, 2, 3, 4}, std::nullopt, std::nullopt)); |
Nina Satragno | 404d5124 | 2023-01-19 19:28:26 | [diff] [blame] | 8757 | |
| 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 8762 | options->extensions->prf = true; |
| 8763 | options->extensions->prf_inputs = std::move(inputs); |
Nina Satragno | 404d5124 | 2023-01-19 19:28:26 | [diff] [blame] | 8764 | options->allow_credentials.clear(); |
| 8765 | EXPECT_EQ(AuthenticatorGetAssertion(std::move(options)).status, |
| 8766 | AuthenticatorStatus::SUCCESS); |
| 8767 | } |
| 8768 | |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8769 | TEST_F(ResidentKeyAuthenticatorImplTest, PRFExtension) { |
| 8770 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 8771 | |
Adam Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8772 | for (bool use_prf_extension_instead : {false, true}) { |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8773 | 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 Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8777 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 8778 | std::optional<device::PublicKeyCredentialDescriptor> credential; |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8779 | 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 Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8787 | } |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8788 | 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 Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8820 | } |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8821 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8822 | 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8843 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8844 | GetAssertionResult result = |
| 8845 | AuthenticatorGetAssertion(std::move(options)); |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8846 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8847 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 8848 | CHECK(result.response->extensions->prf_results); |
| 8849 | CHECK(!result.response->extensions->prf_results->id); |
| 8850 | return std::move(result.response->extensions->prf_results); |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8851 | }; |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8852 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8853 | 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8857 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8858 | { |
| 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 Langley | d5aa7240 | 2023-02-01 00:43:30 | [diff] [blame] | 8866 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8867 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8876 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8877 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8890 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8891 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8904 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8905 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8917 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8918 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8931 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8932 | // 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 Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8948 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8949 | // ... 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 Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8964 | |
Adam Langley | 7d1df6667 | 2023-09-01 23:23:21 | [diff] [blame] | 8965 | // 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 Langley | d27d7db | 2023-02-08 16:45:37 | [diff] [blame] | 8996 | } |
Nina Satragno | 5ba7846 | 2020-10-02 17:25:15 | [diff] [blame] | 8997 | } |
Adam Langley | c296f39 | 2020-07-16 03:55:24 | [diff] [blame] | 8998 | } |
| 8999 | |
Nina Satragno | 8d73f197 | 2024-02-02 19:13:34 | [diff] [blame] | 9000 | // Tests that the PRF function is evaluated for all credentials in an empty |
| 9001 | // allow-list request. Regression test for crbug.com/1520646. |
| 9002 | TEST_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 Langley | 5c0c8ab7 | 2023-10-10 23:06:12 | [diff] [blame] | 9093 | TEST_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 | |
| 9132 | TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialPRFExtension) { |
| 9133 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 9134 | } |
| 9135 | |
Adam Langley | 2bb75bd | 2023-03-06 01:39:55 | [diff] [blame] | 9136 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 9155 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Adam Langley | 2bb75bd | 2023-03-06 01:39:55 | [diff] [blame] | 9156 | 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 9168 | options->extensions->prf = true; |
| 9169 | options->extensions->prf_inputs = std::move(inputs); |
Adam Langley | 2bb75bd | 2023-03-06 01:39:55 | [diff] [blame] | 9170 | options->user_verification = |
| 9171 | device::UserVerificationRequirement::kDiscouraged; |
| 9172 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 9173 | |
| 9174 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Slobodan Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 9175 | EXPECT_FALSE(result.response->extensions->prf_results); |
Adam Langley | 2bb75bd | 2023-03-06 01:39:55 | [diff] [blame] | 9176 | } |
| 9177 | |
Nina Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 9178 | TEST_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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 9187 | /*user_id=*/{{1, 2, 3, 4}}, std::nullopt, std::nullopt)); |
Nina Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 9188 | |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9189 | // |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 Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 9192 | PublicKeyCredentialRequestOptionsPtr options(get_credential_options()); |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 9193 | options->mediation = blink::mojom::Mediation::CONDITIONAL; |
Nina Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 9194 | GetAssertionResult result = AuthenticatorGetAssertion(std::move(options)); |
| 9195 | EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status); |
Ken Buchanan | 23dce91 | 2024-07-11 16:41:27 | [diff] [blame] | 9196 | VerifyGetAssertionOutcomeUkm(0, GetAssertionOutcome::kSuccess, |
Ken Buchanan | 1ea549c1 | 2024-10-10 21:31:05 | [diff] [blame] | 9197 | AuthenticationRequestMode::kConditional); |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9198 | } |
| 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. |
| 9204 | TEST_F(ResidentKeyAuthenticatorImplTest, PreselectDiscoverableCredential) { |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9205 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 9217 | kFirstCredentialId, kTestRelyingPartyId, kFirstUserId, std::nullopt, |
| 9218 | std::nullopt)); |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9219 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectResidentKey( |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 9220 | kSecondCredentialId, kTestRelyingPartyId, kSecondUserId, std::nullopt, |
| 9221 | std::nullopt)); |
Nina Satragno | 9b54821c | 2024-11-22 19:21:56 | [diff] [blame] | 9222 | 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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9230 | |
Nina Satragno | 9b54821c | 2024-11-22 19:21:56 | [diff] [blame] | 9231 | // |SelectAccount| should not be called if an account was chosen from |
| 9232 | // pre-select UI. |
| 9233 | test_client_.delegate_config.expected_accounts = "<invalid>"; |
Martin Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9234 | |
Nina Satragno | 9b54821c | 2024-11-22 19:21:56 | [diff] [blame] | 9235 | 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 Kreichgauer | b3689d06 | 2022-07-12 09:36:51 | [diff] [blame] | 9244 | } |
Nina Satragno | b93b842 | 2021-02-04 21:50:44 | [diff] [blame] | 9245 | } |
| 9246 | |
Nina Satragno | 1757045 | 2024-03-18 16:48:23 | [diff] [blame] | 9247 | // 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. |
| 9250 | TEST_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 Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 9281 | class InternalAuthenticatorImplTest : public AuthenticatorTestBase { |
Martin Kreichgauer | 4faa9baf | 2019-07-17 17:57:39 | [diff] [blame] | 9282 | protected: |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9283 | InternalAuthenticatorImplTest() = default; |
| 9284 | |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9285 | void SetUp() override { |
| 9286 | AuthenticatorTestBase::SetUp(); |
| 9287 | old_client_ = SetBrowserClientForTesting(&test_client_); |
| 9288 | } |
| 9289 | |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9290 | void TearDown() override { |
| 9291 | // The |RenderFrameHost| must outlive |AuthenticatorImpl|. |
| 9292 | internal_authenticator_impl_.reset(); |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9293 | SetBrowserClientForTesting(old_client_); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 9294 | AuthenticatorTestBase::TearDown(); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9295 | } |
| 9296 | |
| 9297 | void NavigateAndCommit(const GURL& url) { |
| 9298 | // The |RenderFrameHost| must outlive |AuthenticatorImpl|. |
| 9299 | internal_authenticator_impl_.reset(); |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 9300 | RenderViewHostTestHarness::NavigateAndCommit(url); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9301 | } |
| 9302 | |
Manas Verma | 694e12f | 2020-03-30 17:19:36 | [diff] [blame] | 9303 | 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 Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9309 | } |
| 9310 | |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9311 | protected: |
| 9312 | std::unique_ptr<InternalAuthenticatorImpl> internal_authenticator_impl_; |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9313 | TestAuthenticatorContentBrowserClient test_client_; |
| 9314 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9315 | }; |
| 9316 | |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9317 | // Regression test for crbug.com/1433416. |
| 9318 | TEST_F(InternalAuthenticatorImplTest, MakeCredentialSkipTLSCheck) { |
| 9319 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 9320 | InternalAuthenticatorImpl* authenticator = |
| 9321 | GetAuthenticator(url::Origin::Create(GURL(kTestOrigin1))); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 9322 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9323 | PublicKeyCredentialCreationOptionsPtr options = |
| 9324 | GetTestPublicKeyCredentialCreationOptions(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9325 | 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 Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9330 | } |
| 9331 | |
| 9332 | // Regression test for crbug.com/1433416. |
| 9333 | TEST_F(InternalAuthenticatorImplTest, GetAssertionSkipTLSCheck) { |
| 9334 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 9335 | InternalAuthenticatorImpl* authenticator = |
| 9336 | GetAuthenticator(url::Origin::Create(GURL(kTestOrigin1))); |
Ken Buchanan | 6e992937 | 2023-10-31 14:05:43 | [diff] [blame] | 9337 | test_client_.is_webauthn_security_level_acceptable = false; |
Nina Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9338 | PublicKeyCredentialRequestOptionsPtr options = |
| 9339 | GetTestPublicKeyCredentialRequestOptions(); |
| 9340 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
| 9341 | options->allow_credentials[0].id, options->relying_party_id)); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9342 | 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 Satragno | 8d67dec3 | 2023-04-18 22:10:44 | [diff] [blame] | 9347 | } |
| 9348 | |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9349 | // Verify behavior for various combinations of origins and RP IDs. |
| 9350 | TEST_F(InternalAuthenticatorImplTest, MakeCredentialOriginAndRpIds) { |
| 9351 | // These instances should return security errors (for circumstances |
| 9352 | // that would normally crash the renderer). |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 9353 | for (auto test_case : kInvalidRpTestCases) { |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9354 | 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 Verma | 694e12f | 2020-03-30 17:19:36 | [diff] [blame] | 9364 | InternalAuthenticatorImpl* authenticator = |
| 9365 | GetAuthenticator(url::Origin::Create(origin)); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9366 | PublicKeyCredentialCreationOptionsPtr options = |
| 9367 | GetTestPublicKeyCredentialCreationOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 9368 | options->relying_party.id = test_case.claimed_authority; |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9369 | 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 Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9373 | } |
| 9374 | |
| 9375 | // These instances should bypass security errors, by setting the effective |
| 9376 | // origin to a valid one. |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 9377 | for (auto test_case : kValidRpTestCases) { |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9378 | SCOPED_TRACE(std::string(test_case.claimed_authority) + " " + |
| 9379 | std::string(test_case.origin)); |
| 9380 | |
| 9381 | NavigateAndCommit(GURL("https://this.isthewrong.origin")); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 9382 | auto* authenticator = |
| 9383 | GetAuthenticator(url::Origin::Create(GURL(test_case.origin))); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9384 | PublicKeyCredentialCreationOptionsPtr options = |
| 9385 | GetTestPublicKeyCredentialCreationOptions(); |
Ken Buchanan | 4a33ef0d | 2019-06-20 17:34:04 | [diff] [blame] | 9386 | options->relying_party.id = test_case.claimed_authority; |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9387 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 9388 | ResetVirtualDevice(); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9389 | 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 Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9393 | } |
| 9394 | } |
| 9395 | |
| 9396 | // Verify behavior for various combinations of origins and RP IDs. |
| 9397 | TEST_F(InternalAuthenticatorImplTest, GetAssertionOriginAndRpIds) { |
| 9398 | // These instances should return security errors (for circumstances |
| 9399 | // that would normally crash the renderer). |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 9400 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 9401 | SCOPED_TRACE( |
| 9402 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9403 | |
| 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 Verma | 694e12f | 2020-03-30 17:19:36 | [diff] [blame] | 9411 | InternalAuthenticatorImpl* authenticator = |
| 9412 | GetAuthenticator(url::Origin::Create(origin)); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9413 | PublicKeyCredentialRequestOptionsPtr options = |
| 9414 | GetTestPublicKeyCredentialRequestOptions(); |
| 9415 | options->relying_party_id = test_case.claimed_authority; |
| 9416 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9417 | 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 Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9421 | } |
| 9422 | |
| 9423 | // These instances should bypass security errors, by setting the effective |
| 9424 | // origin to a valid one. |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 9425 | for (const OriginClaimedAuthorityPair& test_case : kValidRpTestCases) { |
| 9426 | SCOPED_TRACE( |
| 9427 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9428 | |
| 9429 | NavigateAndCommit(GURL("https://this.isthewrong.origin")); |
Martin Kreichgauer | 70fc0cf | 2020-07-17 01:01:00 | [diff] [blame] | 9430 | InternalAuthenticatorImpl* authenticator = |
| 9431 | GetAuthenticator(url::Origin::Create(GURL(test_case.origin))); |
Manas Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9432 | PublicKeyCredentialRequestOptionsPtr options = |
| 9433 | GetTestPublicKeyCredentialRequestOptions(); |
| 9434 | options->relying_party_id = test_case.claimed_authority; |
| 9435 | |
Nina Satragno | acf403f9 | 2019-05-23 17:16:52 | [diff] [blame] | 9436 | ResetVirtualDevice(); |
| 9437 | ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration( |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 9438 | options->allow_credentials[0].id, |
| 9439 | std::string(test_case.claimed_authority))); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 9440 | 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 Verma | 77aeb178 | 2019-05-13 20:40:52 | [diff] [blame] | 9444 | } |
| 9445 | } |
| 9446 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 9447 | #if BUILDFLAG(IS_MAC) |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9448 | class TouchIdAuthenticatorImplTest : public AuthenticatorImplTest { |
Martin Kreichgauer | e11b8a99 | 2022-06-23 17:08:26 | [diff] [blame] | 9449 | protected: |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9450 | using Credential = device::fido::mac::Credential; |
| 9451 | using CredentialMetadata = device::fido::mac::CredentialMetadata; |
| 9452 | |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9453 | void SetUp() override { |
| 9454 | AuthenticatorImplTest::SetUp(); |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9455 | test_client_.web_authentication_delegate.touch_id_authenticator_config = |
| 9456 | config_; |
| 9457 | test_client_.web_authentication_delegate.supports_resident_keys = true; |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9458 | old_client_ = SetBrowserClientForTesting(&test_client_); |
| 9459 | } |
| 9460 | |
| 9461 | void TearDown() override { |
| 9462 | SetBrowserClientForTesting(old_client_); |
| 9463 | AuthenticatorImplTest::TearDown(); |
| 9464 | } |
| 9465 | |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9466 | void ResetVirtualDevice() override {} |
| 9467 | |
Martin Kreichgauer | 00b68759 | 2022-07-29 19:23:26 | [diff] [blame] | 9468 | std::vector<Credential> GetCredentials(const std::string& rp_id) { |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9469 | return device::fido::mac::TouchIdCredentialStore::FindCredentialsForTesting( |
| 9470 | config_, rp_id); |
| 9471 | } |
| 9472 | |
Martin Kreichgauer | fefb377 | 2021-04-12 23:02:48 | [diff] [blame] | 9473 | TestAuthenticatorContentBrowserClient test_client_; |
Keishi Hattori | 7c3c718 | 2022-06-24 22:18:14 | [diff] [blame] | 9474 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Martin Kreichgauer | e11b8a99 | 2022-06-23 17:08:26 | [diff] [blame] | 9475 | 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 Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9480 | }; |
| 9481 | |
| 9482 | TEST_F(TouchIdAuthenticatorImplTest, IsUVPAA) { |
Martin Kreichgauer | 5583440 | 2020-08-03 21:54:31 | [diff] [blame] | 9483 | NavigateAndCommit(GURL(kTestOrigin1)); |
Avi Drissman | ad89a390 | 2022-05-17 21:21:30 | [diff] [blame] | 9484 | for (const bool touch_id_available : {false, true}) { |
| 9485 | SCOPED_TRACE(::testing::Message() |
| 9486 | << "touch_id_available=" << touch_id_available); |
Martin Kreichgauer | e11b8a99 | 2022-06-23 17:08:26 | [diff] [blame] | 9487 | touch_id_test_environment_.SetTouchIdAvailable(touch_id_available); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 9488 | EXPECT_EQ(AuthenticatorIsUvpaa(), touch_id_available); |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9489 | } |
| 9490 | } |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9491 | |
| 9492 | TEST_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 Kreichgauer | 00b68759 | 2022-07-29 19:23:26 | [diff] [blame] | 9504 | const CredentialMetadata& metadata = credentials.at(0).metadata; |
Ioana Pandele | b6e0b2b | 2022-10-13 16:45:50 | [diff] [blame] | 9505 | // New credentials are always created discoverable. |
| 9506 | EXPECT_TRUE(metadata.is_resident); |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9507 | auto expected_user = GetTestPublicKeyCredentialUserEntity(); |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9508 | EXPECT_EQ(metadata.ToPublicKeyCredentialUserEntity(), expected_user); |
| 9509 | } |
| 9510 | |
Adam Langley | 7640c14 | 2024-09-05 15:53:35 | [diff] [blame] | 9511 | TEST_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 Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 9526 | TEST_F(TouchIdAuthenticatorImplTest, OptionalUv) { |
| 9527 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 9528 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 9529 | ConnectToAuthenticator(); |
Nina Satragno | 1a81c85 | 2024-03-28 20:18:16 | [diff] [blame] | 9530 | // 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-Jones | 38c29b6 | 2025-07-23 22:54:53 | [diff] [blame] | 9533 | crypto::apple::ScopedFakeKeychainV2::UVMethod::kPasswordOnly); |
Martin Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 9534 | for (const auto uv : {device::UserVerificationRequirement::kDiscouraged, |
| 9535 | device::UserVerificationRequirement::kPreferred, |
| 9536 | device::UserVerificationRequirement::kRequired}) { |
Nina Satragno | 1a81c85 | 2024-03-28 20:18:16 | [diff] [blame] | 9537 | SCOPED_TRACE(static_cast<int>(uv)); |
Martin Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 9538 | auto options = GetTestPublicKeyCredentialCreationOptions(); |
| 9539 | options->authenticator_selection->authenticator_attachment = |
| 9540 | device::AuthenticatorAttachment::kPlatform; |
Martin Kreichgauer | 3bdaa1b | 2023-07-12 01:20:50 | [diff] [blame] | 9541 | // 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 Kreichgauer | 5f7a707b | 2023-03-02 22:22:25 | [diff] [blame] | 9546 | 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 Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9576 | TEST_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 Kreichgauer | 00b68759 | 2022-07-29 19:23:26 | [diff] [blame] | 9590 | EXPECT_TRUE(credentials.at(0).metadata.is_resident); |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9591 | } |
| 9592 | |
| 9593 | TEST_F(TouchIdAuthenticatorImplTest, MakeCredential_Eviction) { |
| 9594 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 9595 | mojo::Remote<blink::mojom::Authenticator> authenticator = |
| 9596 | ConnectToAuthenticator(); |
| 9597 | |
Martin Kreichgauer | 108eb10 | 2022-06-29 23:08:41 | [diff] [blame] | 9598 | // 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 Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9633 | class ICloudKeychainAuthenticatorImplTest : public AuthenticatorImplTest { |
| 9634 | protected: |
| 9635 | class InspectTAIAuthenticatorRequestDelegate |
| 9636 | : public AuthenticatorRequestClientDelegate { |
| 9637 | public: |
| 9638 | using Callback = base::RepeatingCallback<void( |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9639 | const device::FidoRequestHandlerBase::TransportAvailabilityInfo&, |
| 9640 | const std::optional<std::string>& icloud_keychain_id, |
| 9641 | device::FidoRequestHandlerBase::RequestCallback request_callback)>; |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9642 | explicit InspectTAIAuthenticatorRequestDelegate(Callback callback) |
| 9643 | : callback_(std::move(callback)) {} |
| 9644 | |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9645 | void RegisterActionCallbacks( |
| 9646 | base::OnceClosure cancel_callback, |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 9647 | base::OnceClosure immediate_not_found_callback, |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9648 | base::RepeatingClosure start_over_callback, |
| 9649 | AccountPreselectedCallback account_preselected_callback, |
Adem Derinel | 72e11db | 2025-02-11 15:58:00 | [diff] [blame] | 9650 | PasswordSelectedCallback password_selected_callback, |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9651 | device::FidoRequestHandlerBase::RequestCallback request_callback, |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 9652 | base::OnceClosure cancel_ui_timeout_callback, |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9653 | 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 Langley | b5b7258 | 2023-09-13 19:41:46 | [diff] [blame] | 9660 | void ConfigureDiscoveries( |
| 9661 | const url::Origin& origin, |
| 9662 | const std::string& rp_id, |
| 9663 | RequestSource request_source, |
| 9664 | device::FidoRequestType request_type, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 9665 | std::optional<device::ResidentKeyRequirement> resident_key_requirement, |
Ken Buchanan | c4ae130ac | 2024-02-12 19:49:25 | [diff] [blame] | 9666 | device::UserVerificationRequirement user_verification_requirement, |
Adam Langley | f2f838a6 | 2024-05-03 20:37:09 | [diff] [blame] | 9667 | std::optional<std::string_view> user_name, |
Adam Langley | b5b7258 | 2023-09-13 19:41:46 | [diff] [blame] | 9668 | base::span<const device::CableDiscoveryData> pairings_from_extension, |
Ken Buchanan | bc703ec0 | 2023-11-20 17:15:13 | [diff] [blame] | 9669 | bool is_enclave_authenticator_available, |
Adam Langley | b5b7258 | 2023-09-13 19:41:46 | [diff] [blame] | 9670 | device::FidoDiscoveryFactory* fido_discovery_factory) override { |
Avi Drissman | 1bade823 | 2025-03-19 18:00:32 | [diff] [blame] | 9671 | fido_discovery_factory->set_allow_no_nswindow_for_testing(true); |
Adam Langley | b5b7258 | 2023-09-13 19:41:46 | [diff] [blame] | 9672 | } |
| 9673 | |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9674 | void OnTransportAvailabilityEnumerated( |
| 9675 | device::FidoRequestHandlerBase::TransportAvailabilityInfo tai) |
| 9676 | override { |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9677 | 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 Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9687 | } |
| 9688 | |
| 9689 | private: |
| 9690 | Callback callback_; |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9691 | device::FidoRequestHandlerBase::RequestCallback request_callback_; |
| 9692 | std::optional<std::string> icloud_keychain_id_; |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9693 | }; |
| 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 P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 9717 | virtual_device_factory_ = nullptr; |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9718 | AuthenticatorEnvironment::GetInstance()->Reset(); |
| 9719 | } |
| 9720 | |
| 9721 | void TearDown() override { |
| 9722 | SetBrowserClientForTesting(old_client_); |
| 9723 | AuthenticatorImplTest::TearDown(); |
| 9724 | } |
| 9725 | |
| 9726 | void OnTransportAvailabilityEnumerated( |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9727 | const device::FidoRequestHandlerBase::TransportAvailabilityInfo& tai, |
| 9728 | const std::optional<std::string>& icloud_keychain_id, |
| 9729 | device::FidoRequestHandlerBase::RequestCallback request_callback) { |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9730 | if (tai_callback_) { |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9731 | std::move(tai_callback_).Run(tai, icloud_keychain_id, request_callback); |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9732 | } |
| 9733 | } |
| 9734 | |
| 9735 | static std::vector<device::DiscoverableCredentialMetadata> GetCredentials() { |
| 9736 | device::DiscoverableCredentialMetadata metadata( |
| 9737 | device::AuthenticatorType::kICloudKeychain, kTestRelyingPartyId, |
Nina Satragno | dffa0e82 | 2025-02-13 15:27:37 | [diff] [blame] | 9738 | {1, 2, 3, 4}, {{5, 6, 7, 8}, "name", "displayName"}, |
| 9739 | /*provider_name=*/std::nullopt); |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9740 | 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 Langley | 7a4e535 | 2024-11-01 21:06:22 | [diff] [blame] | 9750 | TEST_F(ICloudKeychainAuthenticatorImplTest, Discovery) { |
Adam Langley | 06a884e | 2023-08-21 15:22:15 | [diff] [blame] | 9751 | if (__builtin_available(macOS 13.5, *)) { |
Adem Derinel | a80f7792 | 2024-04-25 17:19:23 | [diff] [blame] | 9752 | 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 Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9759 | tai, |
| 9760 | const std::optional<std::string>& icloud_keychain_id, |
| 9761 | device::FidoRequestHandlerBase::RequestCallback request_callback) { |
Adem Derinel | a80f7792 | 2024-04-25 17:19:23 | [diff] [blame] | 9762 | 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 Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9768 | |
Adem Derinel | a80f7792 | 2024-04-25 17:19:23 | [diff] [blame] | 9769 | CHECK_EQ(tai.recognized_credentials[0].user.name.value(), "name"); |
| 9770 | }); |
Adam Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9771 | |
Adem Derinel | a80f7792 | 2024-04-25 17:19:23 | [diff] [blame] | 9772 | 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 Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9780 | } else { |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9781 | GTEST_SKIP() << "Need macOS 13.5 for this test"; |
| 9782 | } |
| 9783 | } |
| 9784 | |
| 9785 | TEST_F(ICloudKeychainAuthenticatorImplTest, PRFOnCreate) { |
| 9786 | if (__builtin_available(macOS 15.0, *)) { |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9787 | 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 | |
| 9820 | TEST_F(ICloudKeychainAuthenticatorImplTest, PRFOnGet) { |
| 9821 | if (__builtin_available(macOS 15.0, *)) { |
Adam Langley | 0d42424 | 2024-11-05 18:46:35 | [diff] [blame] | 9822 | 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 Langley | 9134ffe | 2023-05-26 19:14:17 | [diff] [blame] | 9867 | } |
| 9868 | } |
| 9869 | |
Xiaohan Wang | 2ba85e3 | 2022-01-15 17:19:40 | [diff] [blame] | 9870 | #endif // BUILDFLAG(IS_MAC) |
Martin Kreichgauer | 263f6d8 | 2020-03-10 05:46:45 | [diff] [blame] | 9871 | |
Adem Derinel | 38626c2 | 2025-05-22 13:31:58 | [diff] [blame] | 9872 | TEST_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 | |
| 9908 | TEST_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 Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 9945 | // AuthenticatorCableV2Test tests features of the caBLEv2 transport and |
| 9946 | // protocol. |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 9947 | class AuthenticatorCableV2Test : public AuthenticatorImplRequestDelegateTest { |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9948 | public: |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9949 | void SetUp() override { |
| 9950 | AuthenticatorImplTest::SetUp(); |
| 9951 | |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9952 | NavigateAndCommit(GURL(kTestOrigin1)); |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 9953 | ResetNetworkService(); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9954 | |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 9955 | old_client_ = SetBrowserClientForTesting(&browser_client_); |
| 9956 | |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9957 | 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 Langley | bd60444 | 2021-03-31 00:39:17 | [diff] [blame] | 9966 | |
| 9967 | std::tie(ble_advert_callback_, ble_advert_events_) = |
| 9968 | device::cablev2::Discovery::AdvertEventStream::New(); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9969 | } |
| 9970 | |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 9971 | void TearDown() override { |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 9972 | // Ensure that all pending caBLE connections have timed out and closed. |
| 9973 | task_environment()->FastForwardBy(base::Minutes(10)); |
| 9974 | |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 9975 | SetBrowserClientForTesting(old_client_); |
| 9976 | AuthenticatorImplTest::TearDown(); |
Adam Langley | 8d24987 | 2022-03-07 23:16:06 | [diff] [blame] | 9977 | |
| 9978 | // All `EstablishedConnection` instances should have been destroyed. |
| 9979 | CHECK_EQ(device::cablev2::FidoTunnelDevice:: |
| 9980 | GetNumEstablishedConnectionInstancesForTesting(), |
| 9981 | 0); |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 9982 | } |
| 9983 | |
Adam Langley | a08e113 | 2022-03-07 23:28:28 | [diff] [blame] | 9984 | base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)> |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9985 | GetPairingCallback() { |
Adam Langley | a08e113 | 2022-03-07 23:28:28 | [diff] [blame] | 9986 | return base::BindRepeating(&AuthenticatorCableV2Test::OnNewPairing, |
| 9987 | base::Unretained(this)); |
| 9988 | } |
| 9989 | |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 9990 | base::RepeatingCallback<void(std::unique_ptr<device::cablev2::Pairing>)> |
| 9991 | GetInvalidatedPairingCallback() { |
Adam Langley | a08e113 | 2022-03-07 23:28:28 | [diff] [blame] | 9992 | return base::BindRepeating(&AuthenticatorCableV2Test::OnInvalidatedPairing, |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 9993 | base::Unretained(this)); |
| 9994 | } |
| 9995 | |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 9996 | 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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10009 | 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 Langley | cbafdd4f | 2022-08-15 02:48:23 | [diff] [blame] | 10018 | if (transport != device::FidoTransportProtocol::kHybrid || !discovery_) { |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10019 | return {}; |
| 10020 | } |
| 10021 | |
| 10022 | return SingleDiscovery(std::move(discovery_)); |
| 10023 | } |
| 10024 | |
| 10025 | private: |
| 10026 | std::unique_ptr<device::cablev2::Discovery> discovery_; |
| 10027 | }; |
| 10028 | |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10029 | 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 Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10036 | 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 Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10066 | WebAuthenticationDelegate* GetWebAuthenticationDelegate() override { |
| 10067 | return &authentication_delegate_; |
| 10068 | } |
| 10069 | |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10070 | private: |
| 10071 | base::RepeatingClosure callback_; |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10072 | TestAuthenticationDelegate authentication_delegate_; |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10073 | }; |
| 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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10083 | void OnContact( |
| 10084 | base::span<const uint8_t, device::cablev2::kTunnelIdSize> tunnel_id, |
Adam Langley | 670c6c1b | 2021-04-01 23:46:45 | [diff] [blame] | 10085 | base::span<const uint8_t, device::cablev2::kPairingIDSize> pairing_id, |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10086 | 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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10090 | } |
| 10091 | |
Adam Langley | a08e113 | 2022-03-07 23:28:28 | [diff] [blame] | 10092 | void OnNewPairing(std::unique_ptr<device::cablev2::Pairing> pairing) { |
| 10093 | pairings_.emplace_back(std::move(pairing)); |
| 10094 | } |
| 10095 | |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10096 | void OnInvalidatedPairing( |
| 10097 | std::unique_ptr<device::cablev2::Pairing> disabled_pairing) { |
Peter Kasting | 1557e5f | 2025-01-28 01:14:08 | [diff] [blame] | 10098 | pairings_.erase(std::ranges::find_if( |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10099 | pairings_, [&disabled_pairing](const auto& pairing) { |
| 10100 | return device::cablev2::Pairing::EqualPublicKeys(pairing, |
| 10101 | disabled_pairing); |
| 10102 | })); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10103 | } |
| 10104 | |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10105 | 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10110 | device::FidoRequestType::kGetAssertion, |
| 10111 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10112 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10113 | /*contact_device_stream=*/nullptr, |
| 10114 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
| 10115 | GetPairingCallback(), GetInvalidatedPairingCallback(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10116 | GetEventCallback(), /*must_support_ctap=*/true); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10117 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10118 | ReplaceDiscoveryFactory( |
| 10119 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10120 | |
| 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10127 | base::BindLambdaForTesting( |
| 10128 | [&]() { return network_context_.get(); }), |
| 10129 | root_secret_, "Test Authenticator", zero_qr_secret_, |
| 10130 | peer_identity_x962_, contact_id); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10131 | |
| 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 Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10149 | auto callback_and_event_stream = device::cablev2::Discovery::EventStream< |
| 10150 | std::unique_ptr<device::cablev2::Pairing>>::New(); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10151 | discovery = std::make_unique<device::cablev2::Discovery>( |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10152 | request_type, |
| 10153 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
| 10154 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10155 | std::move(callback_and_event_stream.second), |
| 10156 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
| 10157 | GetPairingCallback(), GetInvalidatedPairingCallback(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10158 | GetEventCallback(), /*must_support_ctap=*/true); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10159 | |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10160 | maybe_contact_phones_callback_ = base::BindLambdaForTesting([&]() { |
| 10161 | callback_and_event_stream.first.Run( |
| 10162 | std::make_unique<device::cablev2::Pairing>(*pairings_[0])); |
| 10163 | }); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10164 | |
| 10165 | const std::array<uint8_t, device::cablev2::kRoutingIdSize> routing_id = {0}; |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10166 | bool contact_callback_IsReady = false; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10167 | // 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 Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10174 | [this, &transaction, routing_id, contact_id, &contact_callback_IsReady, |
| 10175 | &expected_request_type_string]( |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10176 | 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 Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10182 | contact_callback_IsReady = true; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10183 | 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10188 | base::BindLambdaForTesting( |
| 10189 | [&]() { return network_context_.get(); }), |
| 10190 | root_secret_, routing_id, tunnel_id, pairing_id, client_nonce, |
| 10191 | contact_id); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10192 | }); |
| 10193 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10194 | ReplaceDiscoveryFactory( |
| 10195 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10196 | |
| 10197 | EXPECT_EQ(AuthenticatorMakeCredential().status, |
| 10198 | AuthenticatorStatus::SUCCESS); |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10199 | EXPECT_TRUE(contact_callback_IsReady); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10200 | } |
| 10201 | |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10202 | void ResetNetworkService() { |
| 10203 | network_context_ = device::cablev2::NewMockTunnelServer(base::BindRepeating( |
| 10204 | &AuthenticatorCableV2Test::OnContact, base::Unretained(this))); |
| 10205 | } |
| 10206 | |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10207 | const std::array<uint8_t, device::cablev2::kRootSecretSize> root_secret_ = { |
| 10208 | 0}; |
Adam Langley | 5c3cbb2 | 2020-09-29 00:35:13 | [diff] [blame] | 10209 | 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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10214 | |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10215 | std::unique_ptr<network::mojom::NetworkContext> network_context_; |
Arthur Sonzogni | fe7eb63 | 2024-12-04 18:03:47 | [diff] [blame] | 10216 | uint8_t peer_identity_x962_[device::kP256X962Length] = {}; |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10217 | device::VirtualCtap2Device virtual_device_{DeviceState(), DeviceConfig()}; |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10218 | 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 Langley | 670c6c1b | 2021-04-01 23:46:45 | [diff] [blame] | 10221 | base::span<const uint8_t, device::cablev2::kPairingIDSize> pairing_id, |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10222 | base::span<const uint8_t, device::cablev2::kClientNonceSize> client_nonce, |
| 10223 | const std::string& request_type_hint)> |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10224 | contact_callback_; |
Adam Langley | bd60444 | 2021-03-31 00:39:17 | [diff] [blame] | 10225 | std::unique_ptr<device::cablev2::Discovery::AdvertEventStream> |
| 10226 | ble_advert_events_; |
| 10227 | device::cablev2::Discovery::AdvertEventStream::Callback ble_advert_callback_; |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10228 | ContactWhenReadyContentBrowserClient browser_client_{ |
| 10229 | base::BindRepeating(&AuthenticatorCableV2Test::MaybeContactPhones, |
| 10230 | base::Unretained(this))}; |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 10231 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10232 | base::OnceClosure maybe_contact_phones_callback_; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10233 | std::vector<Event> events_; |
Ken Buchanan | 19fb9c5 | 2021-12-03 20:15:51 | [diff] [blame] | 10234 | |
| 10235 | private: |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10236 | static VirtualCtap2Device::State* DeviceState() { |
| 10237 | VirtualCtap2Device::State* state = new VirtualCtap2Device::State; |
| 10238 | state->fingerprints_enrolled = true; |
Nina Satragno | 96397ea | 2023-11-30 19:51:38 | [diff] [blame] | 10239 | state->default_backup_eligibility = true; |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10240 | return state; |
| 10241 | } |
| 10242 | |
Adam Langley | 47a4a63f | 2022-01-25 15:14:58 | [diff] [blame] | 10243 | 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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10249 | ret.prf_support = true; |
| 10250 | ret.internal_account_chooser = true; |
| 10251 | ret.internal_uv_support = true; |
| 10252 | ret.always_uv = true; |
Adam Langley | 47a4a63f | 2022-01-25 15:14:58 | [diff] [blame] | 10253 | return ret; |
| 10254 | } |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10255 | }; |
| 10256 | |
Adam Langley | 42a89db | 2023-04-03 21:55:27 | [diff] [blame] | 10257 | TEST_F(AuthenticatorCableV2Test, QRBasedWithNoPairing) { |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10258 | auto discovery = std::make_unique<device::cablev2::Discovery>( |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10259 | device::FidoRequestType::kGetAssertion, |
| 10260 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10261 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10262 | /*contact_device_stream=*/nullptr, |
Adam Langley | df4231b | 2020-11-21 00:35:40 | [diff] [blame] | 10263 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10264 | GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10265 | /*must_support_ctap=*/true); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10266 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10267 | ReplaceDiscoveryFactory( |
| 10268 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10269 | |
| 10270 | std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = |
| 10271 | device::cablev2::authenticator::TransactFromQRCode( |
Adam Langley | bd60444 | 2021-03-31 00:39:17 | [diff] [blame] | 10272 | device::cablev2::authenticator::NewMockPlatform( |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10273 | std::move(ble_advert_callback_), &virtual_device_, |
| 10274 | /*observer=*/nullptr), |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10275 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
| 10276 | root_secret_, "Test Authenticator", zero_qr_secret_, |
| 10277 | peer_identity_x962_, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10278 | /*contact_id=*/std::nullopt); |
Adam Langley | cdccc9c1 | 2022-01-31 23:24:37 | [diff] [blame] | 10279 | |
| 10280 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
| 10281 | EXPECT_EQ(pairings_.size(), 0u); |
| 10282 | } |
| 10283 | |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10284 | TEST_F(AuthenticatorCableV2Test, HandshakeError) { |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10285 | // A handshake error should be fatal to the request with |
| 10286 | // `kHybridTransportError`. |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10287 | auto network_context_factory = |
| 10288 | base::BindLambdaForTesting([&]() { return network_context_.get(); }); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10289 | auto discovery = std::make_unique<device::cablev2::Discovery>( |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10290 | device::FidoRequestType::kGetAssertion, network_context_factory, |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10291 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10292 | /*contact_device_stream=*/nullptr, |
Adam Langley | df4231b | 2020-11-21 00:35:40 | [diff] [blame] | 10293 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10294 | GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10295 | /*must_support_ctap=*/true); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10296 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10297 | ReplaceDiscoveryFactory( |
| 10298 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10299 | |
| 10300 | std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10301 | device::cablev2::authenticator::NewHandshakeErrorDevice( |
Adam Langley | bd60444 | 2021-03-31 00:39:17 | [diff] [blame] | 10302 | device::cablev2::authenticator::NewMockPlatform( |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10303 | std::move(ble_advert_callback_), &virtual_device_, |
| 10304 | /*observer=*/nullptr), |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10305 | network_context_factory, zero_qr_secret_); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10306 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10307 | FailureReasonFuture failure_reason_future; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10308 | auto mock_delegate = std::make_unique< |
| 10309 | ::testing::NiceMock<MockAuthenticatorRequestDelegateObserver>>( |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10310 | failure_reason_future.GetCallback()); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10311 | auto authenticator = ConnectToFakeAuthenticator(std::move(mock_delegate)); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10312 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10313 | TestMakeCredentialFuture future; |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10314 | authenticator->MakeCredential(GetTestPublicKeyCredentialCreationOptions(), |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10315 | future.GetCallback()); |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10316 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10317 | EXPECT_TRUE(future.Wait()); |
| 10318 | EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, std::get<0>(future.Get())); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10319 | |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10320 | ASSERT_TRUE(failure_reason_future.IsReady()); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10321 | EXPECT_EQ(AuthenticatorRequestClientDelegate::InterestingFailureReason:: |
| 10322 | kHybridTransportError, |
Adem Derinel | e637876 | 2024-06-27 06:05:07 | [diff] [blame] | 10323 | failure_reason_future.Get()); |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10324 | } |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10325 | |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10326 | // Test having the network service crash between creating a discovery and |
| 10327 | // performing a cable transaction. Regression test for crbug.com/332724843. |
| 10328 | TEST_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 Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10335 | GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10336 | /*must_support_ctap=*/true); |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10337 | |
| 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 Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10358 | TEST_F(AuthenticatorCableV2Test, PairingBased) { |
| 10359 | DoPairingConnection(); |
Adam Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10360 | |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10361 | 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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10371 | |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10372 | TEST_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 Langley | 0e3a642 | 2020-09-25 18:36:38 | [diff] [blame] | 10386 | } |
| 10387 | |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10388 | static std::unique_ptr<device::cablev2::Pairing> DummyPairing() { |
| 10389 | auto ret = std::make_unique<device::cablev2::Pairing>(); |
Nina Satragno | 60af84df | 2023-08-14 19:25:28 | [diff] [blame] | 10390 | ret->tunnel_server_domain = device::cablev2::kTunnelServer; |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10391 | 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 Langley | 42a89db | 2023-04-03 21:55:27 | [diff] [blame] | 10401 | TEST_F(AuthenticatorCableV2Test, ContactIDDisabled) { |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10402 | // Passing |nullopt| as the callback here causes all contact IDs to be |
| 10403 | // rejected. |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10404 | network_context_ = device::cablev2::NewMockTunnelServer(std::nullopt); |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10405 | auto callback_and_event_stream = device::cablev2::Discovery::EventStream< |
| 10406 | std::unique_ptr<device::cablev2::Pairing>>::New(); |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10407 | auto discovery = std::make_unique<device::cablev2::Discovery>( |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10408 | device::FidoRequestType::kGetAssertion, |
| 10409 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10410 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 0a145aa3 | 2021-04-23 20:23:58 | [diff] [blame] | 10411 | std::move(callback_and_event_stream.second), |
Adam Langley | df4231b | 2020-11-21 00:35:40 | [diff] [blame] | 10412 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10413 | GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10414 | /*must_support_ctap=*/true); |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10415 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10416 | ReplaceDiscoveryFactory( |
| 10417 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10418 | |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10419 | maybe_contact_phones_callback_ = |
| 10420 | base::BindLambdaForTesting([&callback_and_event_stream]() { |
Nina Satragno | ff3baed | 2023-08-03 20:24:40 | [diff] [blame] | 10421 | callback_and_event_stream.first.Run(DummyPairing()); |
Adam Langley | 7c60321 | 2021-04-15 02:23:53 | [diff] [blame] | 10422 | }); |
| 10423 | |
Adam Langley | f05091c | 2020-11-17 02:54:47 | [diff] [blame] | 10424 | 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 Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10435 | // ServerLinkValues contains keys that mimic those created by a site doing |
| 10436 | // caBLEv2 server-link. |
| 10437 | struct 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. |
| 10450 | static ServerLinkValues CreateServerLink() { |
| 10451 | std::vector<uint8_t> seed(device::cablev2::kQRSeedSize); |
danakj | 95305d27 | 2024-05-09 20:38:44 | [diff] [blame] | 10452 | base::RandBytes(seed); |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10453 | |
| 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; |
danakj | 95305d27 | 2024-05-09 20:38:44 | [diff] [blame] | 10460 | base::RandBytes(ret.secret); |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10461 | 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 Langley | ae76553 | 2022-04-19 21:18:36 | [diff] [blame] | 10468 | 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 Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10472 | |
| 10473 | return ret; |
| 10474 | } |
| 10475 | |
Adam Langley | 42a89db | 2023-04-03 21:55:27 | [diff] [blame] | 10476 | TEST_F(AuthenticatorCableV2Test, ServerLink) { |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10477 | 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10483 | device::FidoRequestType::kGetAssertion, |
| 10484 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10485 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | a08e113 | 2022-03-07 23:28:28 | [diff] [blame] | 10486 | /*contact_device_stream=*/nullptr, extension_values, GetPairingCallback(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10487 | GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10488 | /*must_support_ctap=*/true); |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10489 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10490 | ReplaceDiscoveryFactory( |
| 10491 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10492 | |
| 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10504 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
| 10505 | root_secret_, "Test Authenticator", server_link.secret, |
| 10506 | server_link.peer_identity, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10507 | /*contact_id=*/std::nullopt); |
Adam Langley | 9cc7148 | 2022-02-24 21:26:37 | [diff] [blame] | 10508 | |
| 10509 | EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); |
| 10510 | EXPECT_EQ(pairings_.size(), 0u); |
| 10511 | } |
| 10512 | |
Adam Langley | 42a89db | 2023-04-03 21:55:27 | [diff] [blame] | 10513 | TEST_F(AuthenticatorCableV2Test, LateLinking) { |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10514 | auto network_context_factory = |
| 10515 | base::BindLambdaForTesting([&]() { return network_context_.get(); }); |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10516 | auto discovery = std::make_unique<device::cablev2::Discovery>( |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10517 | device::FidoRequestType::kGetAssertion, network_context_factory, |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10518 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10519 | /*contact_device_stream=*/nullptr, |
| 10520 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10521 | GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), |
| 10522 | /*must_support_ctap=*/true); |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10523 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10524 | ReplaceDiscoveryFactory( |
| 10525 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10526 | |
| 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10534 | network_context_factory, zero_qr_secret_, peer_identity_x962_); |
Adam Langley | a13992e | 2022-03-08 00:18:51 | [diff] [blame] | 10535 | |
| 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 Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10549 | // AuthenticatorCableV2AuthenticatorTest tests aspects of the authenticator |
| 10550 | // implementation, rather than of the underlying caBLEv2 transport. |
| 10551 | class 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 Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10559 | device::FidoRequestType::kGetAssertion, |
| 10560 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10561 | qr_generator_key_, std::move(ble_advert_events_), |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10562 | /*contact_device_stream=*/nullptr, |
| 10563 | /*extension_contents=*/std::vector<device::CableDiscoveryData>(), |
Adam Langley | 63b3481 | 2023-06-05 23:08:19 | [diff] [blame] | 10564 | GetPairingCallback(), GetInvalidatedPairingCallback(), |
Adam Langley | eef90c2 | 2024-08-06 18:45:57 | [diff] [blame] | 10565 | GetEventCallback(), /*must_support_ctap=*/true); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10566 | |
Jagadesh P | 8db17bf | 2023-11-28 04:14:15 | [diff] [blame] | 10567 | ReplaceDiscoveryFactory( |
| 10568 | std::make_unique<DiscoveryFactory>(std::move(discovery))); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10569 | |
| 10570 | transaction_ = device::cablev2::authenticator::TransactFromQRCode( |
| 10571 | device::cablev2::authenticator::NewMockPlatform( |
| 10572 | std::move(ble_advert_callback_), &virtual_device_, this), |
Nina Satragno | e7188af | 2024-04-08 20:07:43 | [diff] [blame] | 10573 | base::BindLambdaForTesting([&]() { return network_context_.get(); }), |
| 10574 | root_secret_, "Test Authenticator", zero_qr_secret_, |
| 10575 | peer_identity_x962_, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10576 | /*contact_id=*/std::nullopt); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10577 | } |
| 10578 | |
| 10579 | protected: |
| 10580 | // device::cablev2::authenticator::Observer |
| 10581 | void OnStatus(device::cablev2::authenticator::Platform::Status) override {} |
| 10582 | void OnCompleted( |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10583 | std::optional<device::cablev2::authenticator::Platform::Error> error) |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10584 | override { |
Adam Langley | 3fc8a52 | 2022-03-07 23:51:17 | [diff] [blame] | 10585 | CHECK(!did_complete_); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10586 | did_complete_ = true; |
| 10587 | error_ = error; |
| 10588 | } |
| 10589 | |
| 10590 | std::unique_ptr<device::cablev2::authenticator::Transaction> transaction_; |
| 10591 | bool did_complete_ = false; |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10592 | std::optional<device::cablev2::authenticator::Platform::Error> error_; |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10593 | }; |
| 10594 | |
| 10595 | TEST_F(AuthenticatorCableV2AuthenticatorTest, GetAssertion) { |
| 10596 | PublicKeyCredentialRequestOptionsPtr options = |
| 10597 | GetTestPublicKeyCredentialRequestOptions(); |
| 10598 | options->allow_credentials[0].transports.insert( |
Adam Langley | cbafdd4f | 2022-08-15 02:48:23 | [diff] [blame] | 10599 | device::FidoTransportProtocol::kHybrid); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10600 | 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 | |
| 10607 | TEST_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 Langley | fb32dbf | 2022-08-16 01:08:58 | [diff] [blame] | 10618 | DISCOVERABLE_CREDENTIALS_REQUEST); |
Adam Langley | 76e6509 | 2022-02-23 23:26:03 | [diff] [blame] | 10619 | } |
| 10620 | |
| 10621 | TEST_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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10634 | TEST_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 Langley | 1d1adaf | 2023-07-12 22:55:26 | [diff] [blame] | 10645 | static std::vector<uint8_t> HashPRFInput(base::span<const uint8_t> input) { |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 10646 | 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 Langley | 1d1adaf | 2023-07-12 22:55:26 | [diff] [blame] | 10659 | } |
| 10660 | |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10661 | static std::tuple<PublicKeyCredentialRequestOptionsPtr, |
| 10662 | std::vector<uint8_t>, |
| 10663 | std::vector<uint8_t>> |
| 10664 | BuildPRFGetAssertion(device::VirtualCtap2Device& virtual_device, |
| 10665 | bool use_eval_by_credential) { |
Adam Langley | 1d1adaf | 2023-07-12 22:55:26 | [diff] [blame] | 10666 | 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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10670 | const std::array<uint8_t, 32> key1 = {1}; |
| 10671 | const std::array<uint8_t, 32> key2 = {2}; |
Elly | 2f1928d6 | 2025-07-14 15:31:25 | [diff] [blame] | 10672 | 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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10674 | 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 Langley | 1d1adaf | 2023-07-12 22:55:26 | [diff] [blame] | 10684 | prf_value->first = input1; |
| 10685 | prf_value->second = input2; |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10686 | 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 10693 | options->extensions->prf = true; |
| 10694 | options->extensions->prf_inputs = std::move(prf_inputs); |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10695 | options->user_verification = device::UserVerificationRequirement::kRequired; |
| 10696 | |
Elly | 450ad9fe | 2025-07-09 17:13:22 | [diff] [blame] | 10697 | return std::make_tuple(std::move(options), base::ToVector(output1), |
| 10698 | base::ToVector(output2)); |
Adam Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10699 | } |
| 10700 | |
| 10701 | TEST_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 Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 10710 | 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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10715 | } |
| 10716 | |
| 10717 | TEST_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 Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 10726 | 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 Langley | b8d77fc | 2023-02-15 23:27:40 | [diff] [blame] | 10731 | } |
| 10732 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10733 | // AuthenticatorImplWithRequestProxyTest tests behavior with an installed |
| 10734 | // TestWebAuthenticationRequestProxy that takes over WebAuthn request handling. |
| 10735 | class AuthenticatorImplWithRequestProxyTest : public AuthenticatorImplTest { |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10736 | protected: |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10737 | 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 Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 10754 | raw_ptr<ContentBrowserClient> old_client_ = nullptr; |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10755 | TestAuthenticatorContentBrowserClient test_client_; |
| 10756 | }; |
| 10757 | |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10758 | TEST_F(AuthenticatorImplWithRequestProxyTest, Inactive) { |
| 10759 | request_proxy().config().is_active = false; |
| 10760 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 10761 | AuthenticatorIsUvpaa(); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10762 | EXPECT_EQ(request_proxy().observations().num_isuvpaa, 0u); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10763 | } |
| 10764 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10765 | TEST_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 Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 10771 | EXPECT_EQ(AuthenticatorIsUvpaa(), is_uvpaa); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10772 | EXPECT_EQ(request_proxy().observations().num_isuvpaa, ++i); |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10773 | } |
| 10774 | } |
| 10775 | |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10776 | TEST_F(AuthenticatorImplWithRequestProxyTest, IsConditionalMediationAvailable) { |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 10777 | // We can't autofill credentials over the request proxy. Hence, conditional |
| 10778 | // mediation is unavailable, even if IsUVPAA returns true. |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10779 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 10780 | |
| 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 10784 | std::nullopt); |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 10785 | |
| 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 Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10795 | } |
| 10796 | |
Andrii Natiahlyi | 6b2f4b1 | 2024-09-03 14:58:42 | [diff] [blame] | 10797 | TEST_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 Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10812 | TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredential) { |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10813 | request_proxy().config().request_success = true; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10814 | request_proxy().config().make_credential_response = |
| 10815 | MakeCredentialAuthenticatorResponse::New(); |
| 10816 | request_proxy().config().make_credential_response->info = |
| 10817 | CommonCredentialInfo::New(); |
| 10818 | |
Martin Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10819 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10820 | auto request = GetTestPublicKeyCredentialCreationOptions(); |
| 10821 | MakeCredentialResult result = AuthenticatorMakeCredential(request->Clone()); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10822 | |
| 10823 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10824 | 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 Kreichgauer | 165ff72 | 2021-08-26 01:33:52 | [diff] [blame] | 10833 | } |
| 10834 | |
Martin Kreichgauer | 26c2cec | 2022-01-26 01:00:50 | [diff] [blame] | 10835 | // Verify requests with an attached proxy run RP ID checks. |
| 10836 | TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredentialOriginAndRpIds) { |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10837 | request_proxy().config().request_success = true; |
Martin Kreichgauer | 26c2cec | 2022-01-26 01:00:50 | [diff] [blame] | 10838 | request_proxy().config().make_credential_response = |
| 10839 | MakeCredentialAuthenticatorResponse::New(); |
| 10840 | request_proxy().config().make_credential_response->info = |
| 10841 | CommonCredentialInfo::New(); |
| 10842 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 10843 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 10844 | SCOPED_TRACE( |
| 10845 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Martin Kreichgauer | 26c2cec | 2022-01-26 01:00:50 | [diff] [blame] | 10846 | |
| 10847 | NavigateAndCommit(GURL(test_case.origin)); |
Martin Kreichgauer | 328ef9f1 | 2022-12-22 10:45:55 | [diff] [blame] | 10848 | BrowserContext* context = main_rfh()->GetBrowserContext(); |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 10849 | ASSERT_TRUE( |
| 10850 | test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy( |
| 10851 | context, url::Origin::Create(GURL(test_case.origin)))); |
Martin Kreichgauer | 26c2cec | 2022-01-26 01:00:50 | [diff] [blame] | 10852 | |
| 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 Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10859 | EXPECT_EQ(request_proxy().observations().create_requests.size(), 0u); |
Martin Kreichgauer | 26c2cec | 2022-01-26 01:00:50 | [diff] [blame] | 10860 | } |
| 10861 | } |
| 10862 | |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10863 | // Tests that attempting to make a credential when a request is already proxied |
| 10864 | // fails with NotAllowedError. |
| 10865 | TEST_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 Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10879 | TEST_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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 10887 | SCOPED_TRACE( |
| 10888 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10889 | |
Martin Kreichgauer | 328ef9f1 | 2022-12-22 10:45:55 | [diff] [blame] | 10890 | BrowserContext* context = main_rfh()->GetBrowserContext(); |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 10891 | ASSERT_TRUE( |
| 10892 | test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy( |
| 10893 | context, url::Origin::Create(GURL(test_case.origin)))); |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10894 | |
| 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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 10908 | // Test invalid cases that should be rejected. `kInvalidRpTestCases` |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10909 | // contains a mix of RP ID an App ID cases, but they should all be rejected. |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 10910 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 10911 | SCOPED_TRACE( |
| 10912 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10913 | |
Adem Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 10914 | if (test_case.claimed_authority.empty()) { |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10915 | // In this case, no AppID is actually being tested. |
| 10916 | continue; |
| 10917 | } |
| 10918 | |
Martin Kreichgauer | 328ef9f1 | 2022-12-22 10:45:55 | [diff] [blame] | 10919 | BrowserContext* context = main_rfh()->GetBrowserContext(); |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 10920 | ASSERT_TRUE( |
| 10921 | test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy( |
| 10922 | context, url::Origin::Create(GURL(test_case.origin)))); |
Martin Kreichgauer | 2d878b07 | 2022-05-02 18:33:11 | [diff] [blame] | 10923 | |
| 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 Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10938 | TEST_F(AuthenticatorImplWithRequestProxyTest, MakeCredential_Timeout) { |
| 10939 | request_proxy().config().resolve_callbacks = false; |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10940 | request_proxy().config().request_success = true; |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10941 | 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 Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10951 | EXPECT_EQ(request_proxy().observations().create_requests.size(), 1u); |
| 10952 | EXPECT_EQ(request_proxy().observations().num_cancel, 1u); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10953 | |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 10954 | // Proxy should not hold a pending request after cancellation. |
| 10955 | EXPECT_FALSE(request_proxy().HasPendingRequest()); |
Martin Kreichgauer | 8c97189a | 2022-01-10 20:31:43 | [diff] [blame] | 10956 | } |
Martin Kreichgauer | b27f631 | 2022-01-25 00:03:32 | [diff] [blame] | 10957 | |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10958 | TEST_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 Pejic | a0f2b9d | 2023-09-28 01:56:25 | [diff] [blame] | 10964 | request_proxy().config().get_assertion_response->extensions = |
| 10965 | AuthenticationExtensionsClientOutputs::New(); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10966 | |
| 10967 | NavigateAndCommit(GURL(kTestOrigin1)); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10968 | auto request = GetTestPublicKeyCredentialRequestOptions(); |
| 10969 | GetAssertionResult result = AuthenticatorGetAssertion(request->Clone()); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10970 | |
| 10971 | EXPECT_EQ(result.status, AuthenticatorStatus::SUCCESS); |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10972 | 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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 10976 | expected->extensions->remote_desktop_client_override = |
| 10977 | RemoteDesktopClientOverride::New(); |
| 10978 | expected->extensions->remote_desktop_client_override->origin = |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10979 | url::Origin::Create(GURL(kTestOrigin1)); |
Slobodan Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 10980 | expected->extensions->remote_desktop_client_override |
| 10981 | ->same_origin_with_ancestors = true; |
Martin Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 10982 | EXPECT_EQ(request_proxy().observations().get_requests.at(0), expected); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 10983 | } |
| 10984 | |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10985 | // Tests that attempting to get an assertion when a request is already proxied |
| 10986 | // fails with NotAllowedError. |
| 10987 | TEST_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 Pejic | c8ce88b | 2023-06-19 15:41:12 | [diff] [blame] | 10993 | request->extensions->remote_desktop_client_override = |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 10994 | 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. |
| 11002 | TEST_F(AuthenticatorImplWithRequestProxyTest, GetAssertionConditionalUI) { |
| 11003 | NavigateAndCommit(GURL(kTestOrigin1)); |
| 11004 | auto request = GetTestPublicKeyCredentialRequestOptions(); |
Adem Derinel | 2154d4b1 | 2025-02-04 12:29:55 | [diff] [blame] | 11005 | request->mediation = blink::mojom::Mediation::CONDITIONAL; |
Nina Satragno | 867bca5 | 2022-10-13 15:02:02 | [diff] [blame] | 11006 | 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 Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 11012 | // Verify requests with an attached proxy run RP ID checks. |
| 11013 | TEST_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 Derinel | 620520b4 | 2024-11-04 15:45:44 | [diff] [blame] | 11020 | for (const OriginClaimedAuthorityPair& test_case : kInvalidRpTestCases) { |
| 11021 | SCOPED_TRACE( |
| 11022 | base::StrCat({test_case.claimed_authority, " ", test_case.origin})); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 11023 | |
| 11024 | NavigateAndCommit(GURL(test_case.origin)); |
Martin Kreichgauer | 328ef9f1 | 2022-12-22 10:45:55 | [diff] [blame] | 11025 | BrowserContext* context = main_rfh()->GetBrowserContext(); |
Martin Kreichgauer | 1f4aa59 | 2023-01-06 18:39:37 | [diff] [blame] | 11026 | ASSERT_TRUE( |
| 11027 | test_client_.GetWebAuthenticationDelegate()->MaybeGetRequestProxy( |
| 11028 | context, url::Origin::Create(GURL(test_case.origin)))); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 11029 | |
| 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 Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 11036 | EXPECT_EQ(request_proxy().observations().get_requests.size(), 0u); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 11037 | } |
| 11038 | } |
| 11039 | |
| 11040 | TEST_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 Kreichgauer | bf776d7 | 2022-04-18 23:38:16 | [diff] [blame] | 11053 | EXPECT_EQ(request_proxy().observations().get_requests.size(), 1u); |
| 11054 | EXPECT_EQ(request_proxy().observations().num_cancel, 1u); |
Martin Kreichgauer | 1beaff0 | 2022-02-02 18:58:42 | [diff] [blame] | 11055 | |
| 11056 | // Proxy should not hold a pending request after cancellation. |
| 11057 | EXPECT_FALSE(request_proxy().HasPendingRequest()); |
| 11058 | } |
| 11059 | |
Martin Kreichgauer | ca18b43 | 2023-04-25 18:58:47 | [diff] [blame] | 11060 | TEST_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 | |
kpaulhamus | 7c9f0094 | 2017-06-30 11:08:45 | [diff] [blame] | 11094 | } // namespace content |