1 // Copyright 2018 The Chromium Authors. All rights reserved.
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 <stdint.h>
6
7 #include <memory>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/command_line.h"
13 #include "base/json/json_reader.h"
14 #include "base/macros.h"
15 #include "base/run_loop.h"
16 #include "base/test/bind.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "base/values.h"
19 #include "build/build_config.h"
20 #include "components/network_session_configurator/common/network_switches.h"
21 #include "content/browser/renderer_host/render_frame_host_impl.h"
22 #include "content/browser/webauth/authenticator_environment_impl.h"
23 #include "content/browser/webauth/authenticator_impl.h"
24 #include "content/public/browser/authenticator_request_client_delegate.h"
25 #include "content/public/browser/navigation_handle.h"
26 #include "content/public/browser/navigation_throttle.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_observer.h"
29 #include "content/public/common/content_client.h"
30 #include "content/public/common/content_features.h"
31 #include "content/public/common/content_switches.h"
32 #include "content/public/test/back_forward_cache_util.h"
33 #include "content/public/test/browser_test.h"
34 #include "content/public/test/browser_test_utils.h"
35 #include "content/public/test/content_browser_test.h"
36 #include "content/public/test/content_browser_test_utils.h"
37 #include "content/public/test/test_utils.h"
38 #include "content/shell/browser/shell.h"
39 #include "content/test/did_commit_navigation_interceptor.h"
40 #include "device/base/features.h"
41 #include "device/fido/fake_fido_discovery.h"
42 #include "device/fido/features.h"
43 #include "device/fido/fido_discovery_factory.h"
44 #include "device/fido/fido_test_data.h"
45 #include "device/fido/fido_types.h"
46 #include "device/fido/hid/fake_hid_impl_for_testing.h"
47 #include "device/fido/mock_fido_device.h"
48 #include "device/fido/test_callback_receiver.h"
49 #include "device/fido/virtual_fido_device_factory.h"
50 #include "mojo/public/cpp/bindings/remote.h"
51 #include "net/dns/mock_host_resolver.h"
52 #include "net/test/embedded_test_server/embedded_test_server.h"
53 #include "testing/gmock/include/gmock/gmock.h"
54 #include "testing/gtest/include/gtest/gtest.h"
55 #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
56
57 #if defined(OS_WIN)
58 #include <windows.h>
59
60 #include "device/fido/win/fake_webauthn_api.h"
61 #endif
62
63 namespace content {
64
65 namespace {
66
67 using blink::mojom::Authenticator;
68 using blink::mojom::AuthenticatorStatus;
69 using blink::mojom::GetAssertionAuthenticatorResponsePtr;
70 using blink::mojom::MakeCredentialAuthenticatorResponsePtr;
71
72 using TestCreateCallbackReceiver =
73 ::device::test::StatusAndValueCallbackReceiver<
74 AuthenticatorStatus,
75 MakeCredentialAuthenticatorResponsePtr>;
76
77 using TestGetCallbackReceiver = ::device::test::StatusAndValueCallbackReceiver<
78 AuthenticatorStatus,
79 GetAssertionAuthenticatorResponsePtr>;
80
81 constexpr char kOkMessage[] = "webauth: OK";
82
83 constexpr char kPublicKeyErrorMessage[] =
84 "webauth: NotSupportedError: Required parameters missing in "
85 "`options.publicKey`.";
86
87 constexpr char kNotAllowedErrorMessage[] =
88 "webauth: NotAllowedError: The operation either timed out or was not "
89 "allowed. See: "
90 "https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client.";
91
92 #if defined(OS_WIN)
93 constexpr char kInvalidStateErrorMessage[] =
94 "webauth: InvalidStateError: The user attempted to register an "
95 "authenticator that contains one of the credentials already registered "
96 "with the relying party.";
97 #endif // defined(OS_WIN)
98
99 constexpr char kResidentCredentialsErrorMessage[] =
100 "webauth: NotSupportedError: Resident credentials or empty "
101 "'allowCredentials' lists are not supported at this time.";
102
103 constexpr char kRelyingPartySecurityErrorMessage[] =
104 "webauth: SecurityError: The relying party ID is not a registrable domain "
105 "suffix of, nor equal to the current domain.";
106
107 constexpr char kRelyingPartyUserIconUrlSecurityErrorMessage[] =
108 "webauth: SecurityError: 'user.icon' should be a secure URL";
109
110 constexpr char kRelyingPartyRpIconUrlSecurityErrorMessage[] =
111 "webauth: SecurityError: 'rp.icon' should be a secure URL";
112
113 constexpr char kAbortErrorMessage[] =
114 "webauth: AbortError: Request has been aborted.";
115
116 constexpr char kGetFeaturePolicyMissingMessage[] =
117 "webauth: NotAllowedError: The 'publickey-credentials-get' feature is "
118 "not enabled in this document. Feature Policy may be used to delegate Web "
119 "Authentication capabilities to cross-origin child frames.";
120
121 constexpr char kCrossOriginAncestorMessage[] =
122 "webauth: NotAllowedError: The following credential operations can only "
123 "occur in a document which is same-origin with all of its ancestors: "
124 "storage/retrieval of 'PasswordCredential' and 'FederatedCredential', "
125 "storage of 'PublicKeyCredential'.";
126
127 // Templates to be used with base::ReplaceStringPlaceholders. Can be
128 // modified to include up to 9 replacements. The default values for
129 // any additional replacements added should also be added to the
130 // CreateParameters struct.
131 constexpr char kCreatePublicKeyTemplate[] =
132 "navigator.credentials.create({ publicKey: {"
133 " challenge: new TextEncoder().encode('climb a mountain'),"
134 " rp: { id: '$3', name: 'Acme', icon: '$7'},"
135 " user: { "
136 " id: new TextEncoder().encode('1098237235409872'),"
137 " name: 'avery.a.jones@example.com',"
138 " displayName: 'Avery A. Jones', "
139 " icon: '$8'},"
140 " pubKeyCredParams: [{ type: 'public-key', alg: '$4'}],"
141 " timeout: _timeout_,"
142 " excludeCredentials: [],"
143 " authenticatorSelection: {"
144 " requireResidentKey: $1,"
145 " userVerification: '$2',"
146 " authenticatorAttachment: '$5',"
147 " },"
148 " attestation: '$6',"
149 "}}).then(c => window.domAutomationController.send('webauth: OK' + $9),"
150 " e => window.domAutomationController.send("
151 " 'webauth: ' + e.toString()));";
152
153 constexpr char kCreatePublicKeyWithAbortSignalTemplate[] =
154 "navigator.credentials.create({ publicKey: {"
155 " challenge: new TextEncoder().encode('climb a mountain'),"
156 " rp: { id: '$3', name: 'Acme', icon: '$7'},"
157 " user: { "
158 " id: new TextEncoder().encode('1098237235409872'),"
159 " name: 'avery.a.jones@example.com',"
160 " displayName: 'Avery A. Jones', "
161 " icon: '$8'},"
162 " pubKeyCredParams: [{ type: 'public-key', alg: '$4'}],"
163 " timeout: _timeout_,"
164 " excludeCredentials: [],"
165 " authenticatorSelection: {"
166 " requireResidentKey: $1,"
167 " userVerification: '$2',"
168 " authenticatorAttachment: '$5',"
169 " },"
170 " attestation: '$6',"
171 "}, signal: $9}"
172 ").then(c => window.domAutomationController.send('webauth: OK'),"
173 " e => window.domAutomationController.send("
174 " 'webauth: ' + e.toString()));";
175
176 constexpr char kPlatform[] = "platform";
177 constexpr char kCrossPlatform[] = "cross-platform";
178 constexpr char kPreferredVerification[] = "preferred";
179 constexpr char kRequiredVerification[] = "required";
180 constexpr char kShortTimeout[] = "100";
181
182 // Default values for kCreatePublicKeyTemplate.
183 struct CreateParameters {
184 const char* rp_id = "acme.com";
185 bool require_resident_key = false;
186 const char* user_verification = kPreferredVerification;
187 const char* authenticator_attachment = kCrossPlatform;
188 const char* algorithm_identifier = "-7";
189 const char* attestation = "none";
190 const char* rp_icon = "https://pics.acme.com/00/p/aBjjjpqPb.png";
191 const char* user_icon = "https://pics.acme.com/00/p/aBjjjpqPb.png";
192 const char* signal = "";
193 // extra_ok_output is a Javascript expression which must evaluate to a string.
194 // It can use the |PublicKeyCredential| object named |c| to extract useful
195 // fields.
196 const char* extra_ok_output = "''";
197 const char* timeout = "1000";
198 };
199
BuildCreateCallWithParameters(const CreateParameters & parameters)200 std::string BuildCreateCallWithParameters(const CreateParameters& parameters) {
201 std::vector<std::string> substitutions;
202 substitutions.push_back(parameters.require_resident_key ? "true" : "false");
203 substitutions.push_back(parameters.user_verification);
204 substitutions.push_back(parameters.rp_id);
205 substitutions.push_back(parameters.algorithm_identifier);
206 substitutions.push_back(parameters.authenticator_attachment);
207 substitutions.push_back(parameters.attestation);
208 substitutions.push_back(parameters.rp_icon);
209 substitutions.push_back(parameters.user_icon);
210
211 std::string result;
212 if (strlen(parameters.signal) == 0) {
213 substitutions.push_back(parameters.extra_ok_output);
214 result = base::ReplaceStringPlaceholders(kCreatePublicKeyTemplate,
215 substitutions, nullptr);
216 } else {
217 substitutions.push_back(parameters.signal);
218 result = base::ReplaceStringPlaceholders(
219 kCreatePublicKeyWithAbortSignalTemplate, substitutions, nullptr);
220 }
221
222 base::ReplaceFirstSubstringAfterOffset(&result, 0, "_timeout_",
223 parameters.timeout);
224 return result;
225 }
226
227 constexpr char kGetPublicKeyTemplate[] =
228 "navigator.credentials.get({ publicKey: {"
229 " challenge: new TextEncoder().encode('climb a mountain'),"
230 " timeout: $4,"
231 " userVerification: '$1',"
232 " $2}"
233 "}).then(c => window.domAutomationController.send('webauth: OK' + $3),"
234 " e => window.domAutomationController.send("
235 " 'webauth: ' + e.toString()));";
236
237 constexpr char kGetPublicKeyWithAbortSignalTemplate[] =
238 "navigator.credentials.get({ publicKey: {"
239 " challenge: new TextEncoder().encode('climb a mountain'),"
240 " timeout: $4,"
241 " userVerification: '$1',"
242 " $2},"
243 " signal: $5"
244 "}).catch(c => window.domAutomationController.send("
245 " 'webauth: ' + c.toString()));";
246
247 // Default values for kGetPublicKeyTemplate.
248 struct GetParameters {
249 const char* user_verification = kPreferredVerification;
250 const char* allow_credentials =
251 "allowCredentials: [{ type: 'public-key',"
252 " id: new TextEncoder().encode('allowedCredential'),"
253 " transports: ['usb', 'nfc', 'ble']}]";
254 const char* signal = "";
255 const char* timeout = "1000";
256 // extra_ok_output is a Javascript expression which must evaluate to a string.
257 // It can use the |PublicKeyCredential| object named |c| to extract useful
258 // fields.
259 const char* extra_ok_output = "''";
260 };
261
BuildGetCallWithParameters(const GetParameters & parameters)262 std::string BuildGetCallWithParameters(const GetParameters& parameters) {
263 std::vector<std::string> substitutions;
264 substitutions.push_back(parameters.user_verification);
265 substitutions.push_back(parameters.allow_credentials);
266 substitutions.push_back(parameters.extra_ok_output);
267 substitutions.push_back(parameters.timeout);
268 if (strlen(parameters.signal) == 0) {
269 return base::ReplaceStringPlaceholders(kGetPublicKeyTemplate, substitutions,
270 nullptr);
271 }
272 substitutions.push_back(parameters.signal);
273 return base::ReplaceStringPlaceholders(kGetPublicKeyWithAbortSignalTemplate,
274 substitutions, nullptr);
275 }
276
277 // Helper class that executes the given |closure| the very last moment before
278 // the next navigation commits in a given WebContents.
279 class ClosureExecutorBeforeNavigationCommit
280 : public DidCommitNavigationInterceptor {
281 public:
ClosureExecutorBeforeNavigationCommit(WebContents * web_contents,base::OnceClosure closure)282 ClosureExecutorBeforeNavigationCommit(WebContents* web_contents,
283 base::OnceClosure closure)
284 : DidCommitNavigationInterceptor(web_contents),
285 closure_(std::move(closure)) {}
286 ~ClosureExecutorBeforeNavigationCommit() override = default;
287
288 protected:
WillProcessDidCommitNavigation(RenderFrameHost * render_frame_host,NavigationRequest * navigation_request,::FrameHostMsg_DidCommitProvisionalLoad_Params * params,mojom::DidCommitProvisionalLoadInterfaceParamsPtr * interface_params)289 bool WillProcessDidCommitNavigation(
290 RenderFrameHost* render_frame_host,
291 NavigationRequest* navigation_request,
292 ::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
293 mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
294 override {
295 if (closure_)
296 std::move(closure_).Run();
297 return true;
298 }
299
300 private:
301 base::OnceClosure closure_;
302 DISALLOW_COPY_AND_ASSIGN(ClosureExecutorBeforeNavigationCommit);
303 };
304
305 // Cancels all navigations in a WebContents while in scope.
306 class ScopedNavigationCancellingThrottleInstaller : public WebContentsObserver {
307 public:
ScopedNavigationCancellingThrottleInstaller(WebContents * web_contents)308 explicit ScopedNavigationCancellingThrottleInstaller(
309 WebContents* web_contents)
310 : WebContentsObserver(web_contents) {}
311 ~ScopedNavigationCancellingThrottleInstaller() override = default;
312
313 protected:
314 class CancellingThrottle : public NavigationThrottle {
315 public:
CancellingThrottle(NavigationHandle * handle)316 explicit CancellingThrottle(NavigationHandle* handle)
317 : NavigationThrottle(handle) {}
318 ~CancellingThrottle() override = default;
319
320 protected:
GetNameForLogging()321 const char* GetNameForLogging() override {
322 return "ScopedNavigationCancellingThrottleInstaller::CancellingThrottle";
323 }
324
WillStartRequest()325 ThrottleCheckResult WillStartRequest() override {
326 return ThrottleCheckResult(CANCEL);
327 }
328
329 private:
330 DISALLOW_COPY_AND_ASSIGN(CancellingThrottle);
331 };
332
DidStartNavigation(NavigationHandle * navigation_handle)333 void DidStartNavigation(NavigationHandle* navigation_handle) override {
334 navigation_handle->RegisterThrottleForTesting(
335 std::make_unique<CancellingThrottle>(navigation_handle));
336 }
337
338 private:
339 DISALLOW_COPY_AND_ASSIGN(ScopedNavigationCancellingThrottleInstaller);
340 };
341
342 struct WebAuthBrowserTestState {
343 // Called when the browser is asked to display an attestation prompt. There is
344 // no default so if no callback is installed then the test will crash.
345 base::OnceCallback<void(base::OnceCallback<void(bool)>)>
346 attestation_prompt_callback_;
347
348 // Set when |IsFocused| is called.
349 bool focus_checked = false;
350
351 // This is incremented when an |AuthenticatorRequestClientDelegate| is
352 // created.
353 int delegate_create_count = 0;
354 };
355
356 class WebAuthBrowserTestClientDelegate
357 : public AuthenticatorRequestClientDelegate {
358 public:
WebAuthBrowserTestClientDelegate(WebAuthBrowserTestState * test_state)359 explicit WebAuthBrowserTestClientDelegate(WebAuthBrowserTestState* test_state)
360 : test_state_(test_state) {}
361
ShouldReturnAttestation(const std::string & relying_party_id,const::device::FidoAuthenticator * authenticator,bool is_enterprise_attestation,base::OnceCallback<void (bool)> callback)362 void ShouldReturnAttestation(
363 const std::string& relying_party_id,
364 const ::device::FidoAuthenticator* authenticator,
365 bool is_enterprise_attestation,
366 base::OnceCallback<void(bool)> callback) override {
367 std::move(test_state_->attestation_prompt_callback_)
368 .Run(std::move(callback));
369 }
370
IsFocused()371 bool IsFocused() override {
372 test_state_->focus_checked = true;
373 return AuthenticatorRequestClientDelegate::IsFocused();
374 }
375
376 private:
377 WebAuthBrowserTestState* const test_state_;
378
379 DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserTestClientDelegate);
380 };
381
382 // Implements ContentBrowserClient and allows webauthn-related calls to be
383 // mocked.
384 class WebAuthBrowserTestContentBrowserClient : public ContentBrowserClient {
385 public:
WebAuthBrowserTestContentBrowserClient(WebAuthBrowserTestState * test_state)386 explicit WebAuthBrowserTestContentBrowserClient(
387 WebAuthBrowserTestState* test_state)
388 : test_state_(test_state) {}
389
390 std::unique_ptr<AuthenticatorRequestClientDelegate>
GetWebAuthenticationRequestDelegate(RenderFrameHost * render_frame_host)391 GetWebAuthenticationRequestDelegate(
392 RenderFrameHost* render_frame_host) override {
393 test_state_->delegate_create_count++;
394 return std::make_unique<WebAuthBrowserTestClientDelegate>(test_state_);
395 }
396
397 private:
398 WebAuthBrowserTestState* const test_state_;
399
400 DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserTestContentBrowserClient);
401 };
402
403 // Test fixture base class for common tasks.
404 class WebAuthBrowserTestBase : public content::ContentBrowserTest {
405 protected:
406 WebAuthBrowserTestBase() = default;
407
GetFeaturesToEnable()408 virtual std::vector<base::Feature> GetFeaturesToEnable() { return {}; }
409
SetUpOnMainThread()410 void SetUpOnMainThread() override {
411 ContentBrowserTest::SetUpOnMainThread();
412
413 host_resolver()->AddRule("*", "127.0.0.1");
414 https_server().ServeFilesFromSourceDirectory(GetTestDataFilePath());
415 ASSERT_TRUE(https_server().Start());
416
417 test_client_ =
418 std::make_unique<WebAuthBrowserTestContentBrowserClient>(&test_state_);
419 old_client_ = SetBrowserClientForTesting(test_client_.get());
420
421 EXPECT_TRUE(
422 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")));
423 }
424
TearDown()425 void TearDown() override {
426 CHECK_EQ(SetBrowserClientForTesting(old_client_), test_client_.get());
427 ContentBrowserTest::TearDown();
428 }
429
GetHttpsURL(const std::string & hostname,const std::string & relative_url)430 GURL GetHttpsURL(const std::string& hostname,
431 const std::string& relative_url) {
432 return https_server_.GetURL(hostname, relative_url);
433 }
434
InjectVirtualFidoDeviceFactory()435 device::test::VirtualFidoDeviceFactory* InjectVirtualFidoDeviceFactory() {
436 auto owned_virtual_device_factory =
437 std::make_unique<device::test::VirtualFidoDeviceFactory>();
438 auto* virtual_device_factory = owned_virtual_device_factory.get();
439 AuthenticatorEnvironmentImpl::GetInstance()
440 ->ReplaceDefaultDiscoveryFactoryForTesting(
441 std::move(owned_virtual_device_factory));
442 return virtual_device_factory;
443 }
444
https_server()445 net::EmbeddedTestServer& https_server() { return https_server_; }
446
test_state()447 WebAuthBrowserTestState* test_state() { return &test_state_; }
448
449 private:
SetUpCommandLine(base::CommandLine * command_line)450 void SetUpCommandLine(base::CommandLine* command_line) override {
451 scoped_feature_list_.InitWithFeatures(GetFeaturesToEnable(), {});
452 command_line->AppendSwitch(
453 switches::kEnableExperimentalWebPlatformFeatures);
454 command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
455 }
456
457 base::test::ScopedFeatureList scoped_feature_list_;
458 net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
459 std::unique_ptr<WebAuthBrowserTestContentBrowserClient> test_client_;
460 WebAuthBrowserTestState test_state_;
461 ContentBrowserClient* old_client_ = nullptr;
462
463 DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserTestBase);
464 };
465
466 // WebAuthLocalClientBrowserTest ----------------------------------------------
467
468 // Browser test fixture where the blink::mojom::Authenticator interface is
469 // accessed from a testing client in the browser process.
470 class WebAuthLocalClientBrowserTest : public WebAuthBrowserTestBase {
471 public:
WebAuthLocalClientBrowserTest()472 WebAuthLocalClientBrowserTest() {
473 auto discovery_factory =
474 std::make_unique<device::test::FakeFidoDiscoveryFactory>();
475 discovery_factory_ = discovery_factory.get();
476 AuthenticatorEnvironmentImpl::GetInstance()
477 ->ReplaceDefaultDiscoveryFactoryForTesting(
478 std::move(discovery_factory));
479 }
480 ~WebAuthLocalClientBrowserTest() override = default;
481
482 protected:
SetUpOnMainThread()483 void SetUpOnMainThread() override {
484 WebAuthBrowserTestBase::SetUpOnMainThread();
485 ConnectToAuthenticator();
486 }
487
ConnectToAuthenticator()488 void ConnectToAuthenticator() {
489 auto* render_frame_host_impl = static_cast<RenderFrameHostImpl*>(
490 shell()->web_contents()->GetMainFrame());
491 if (authenticator_remote_.is_bound())
492 authenticator_remote_.reset();
493 render_frame_host_impl->GetAuthenticator(
494 authenticator_remote_.BindNewPipeAndPassReceiver());
495 }
496
497 blink::mojom::PublicKeyCredentialCreationOptionsPtr
BuildBasicCreateOptions()498 BuildBasicCreateOptions() {
499 device::PublicKeyCredentialRpEntity rp("acme.com");
500 rp.name = "acme.com";
501
502 std::vector<uint8_t> kTestUserId{0, 0, 0};
503 device::PublicKeyCredentialUserEntity user(kTestUserId);
504 user.name = "name";
505 user.display_name = "displayName";
506
507 static constexpr int32_t kCOSEAlgorithmIdentifierES256 = -7;
508 device::PublicKeyCredentialParams::CredentialInfo param;
509 param.type = device::CredentialType::kPublicKey;
510 param.algorithm = kCOSEAlgorithmIdentifierES256;
511 std::vector<device::PublicKeyCredentialParams::CredentialInfo> parameters;
512 parameters.push_back(param);
513
514 std::vector<uint8_t> kTestChallenge{0, 0, 0};
515 auto mojo_options = blink::mojom::PublicKeyCredentialCreationOptions::New(
516 rp, user, kTestChallenge, parameters, base::TimeDelta::FromSeconds(30),
517 std::vector<device::PublicKeyCredentialDescriptor>(),
518 device::AuthenticatorSelectionCriteria(),
519 device::AttestationConveyancePreference::kNone,
520 /*cable_registration_data=*/nullptr,
521 /*hmac_create_secret=*/false, /*prf_enable=*/false,
522 blink::mojom::ProtectionPolicy::UNSPECIFIED,
523 /*enforce_protection_policy=*/false, /*appid_exclude=*/base::nullopt,
524 /*cred_props=*/false, device::LargeBlobSupport::kNotRequested);
525
526 return mojo_options;
527 }
528
BuildBasicGetOptions()529 blink::mojom::PublicKeyCredentialRequestOptionsPtr BuildBasicGetOptions() {
530 std::vector<device::PublicKeyCredentialDescriptor> credentials;
531 base::flat_set<device::FidoTransportProtocol> transports;
532 transports.emplace(device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
533
534 device::PublicKeyCredentialDescriptor descriptor(
535 device::CredentialType::kPublicKey,
536 device::fido_parsing_utils::Materialize(
537 device::test_data::kTestGetAssertionCredentialId),
538 transports);
539 credentials.push_back(descriptor);
540
541 std::vector<uint8_t> kTestChallenge{0, 0, 0};
542 auto mojo_options = blink::mojom::PublicKeyCredentialRequestOptions::New(
543 kTestChallenge, base::TimeDelta::FromSeconds(30), "acme.com",
544 std::move(credentials), device::UserVerificationRequirement::kPreferred,
545 base::nullopt, std::vector<device::CableDiscoveryData>(), /*prf=*/false,
546 /*prf_inputs=*/std::vector<blink::mojom::PRFValuesPtr>(),
547 /*large_blob_read=*/false, /*large_blob_write=*/base::nullopt);
548 return mojo_options;
549 }
550
WaitForConnectionError()551 void WaitForConnectionError() {
552 ASSERT_TRUE(authenticator_remote_);
553 ASSERT_TRUE(authenticator_remote_.is_bound());
554 if (!authenticator_remote_.is_connected())
555 return;
556
557 base::RunLoop run_loop;
558 authenticator_remote_.set_disconnect_handler(run_loop.QuitClosure());
559 run_loop.Run();
560 }
561
authenticator()562 blink::mojom::Authenticator* authenticator() {
563 return authenticator_remote_.get();
564 }
565
566 device::test::FakeFidoDiscoveryFactory* discovery_factory_;
567
568 private:
569 mojo::Remote<blink::mojom::Authenticator> authenticator_remote_;
570
571 DISALLOW_COPY_AND_ASSIGN(WebAuthLocalClientBrowserTest);
572 };
573
574 // Tests that no crash occurs when the implementation is destroyed with a
575 // pending navigator.credentials.create({publicKey: ...}) call.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,CreatePublicKeyCredentialThenNavigateAway)576 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
577 CreatePublicKeyCredentialThenNavigateAway) {
578 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
579 TestCreateCallbackReceiver create_callback_receiver;
580 authenticator()->MakeCredential(BuildBasicCreateOptions(),
581 create_callback_receiver.callback());
582
583 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
584 EXPECT_TRUE(
585 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title2.html")));
586 WaitForConnectionError();
587
588 // The next active document should be able to successfully call
589 // navigator.credentials.create({publicKey: ...}) again.
590 ConnectToAuthenticator();
591 fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
592 authenticator()->MakeCredential(BuildBasicCreateOptions(),
593 create_callback_receiver.callback());
594 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
595 }
596
597 // Tests that no crash occurs when the implementation is destroyed with a
598 // pending navigator.credentials.get({publicKey: ...}) call.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,GetPublicKeyCredentialThenNavigateAway)599 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
600 GetPublicKeyCredentialThenNavigateAway) {
601 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
602 TestGetCallbackReceiver get_callback_receiver;
603 authenticator()->GetAssertion(BuildBasicGetOptions(),
604 get_callback_receiver.callback());
605
606 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
607 EXPECT_TRUE(
608 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title2.html")));
609 WaitForConnectionError();
610
611 // The next active document should be able to successfully call
612 // navigator.credentials.get({publicKey: ...}) again.
613 ConnectToAuthenticator();
614 fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
615 authenticator()->GetAssertion(BuildBasicGetOptions(),
616 get_callback_receiver.callback());
617 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
618 }
619
620 enum class AttestationCallbackBehavior {
621 IGNORE_CALLBACK,
622 BEFORE_NAVIGATION,
623 AFTER_NAVIGATION,
624 };
625
AttestationCallbackBehaviorToString(AttestationCallbackBehavior behavior)626 const char* AttestationCallbackBehaviorToString(
627 AttestationCallbackBehavior behavior) {
628 switch (behavior) {
629 case AttestationCallbackBehavior::IGNORE_CALLBACK:
630 return "IGNORE_CALLBACK";
631 case AttestationCallbackBehavior::BEFORE_NAVIGATION:
632 return "BEFORE_NAVIGATION";
633 case AttestationCallbackBehavior::AFTER_NAVIGATION:
634 return "AFTER_NAVIGATION";
635 }
636 }
637
638 const AttestationCallbackBehavior kAllAttestationCallbackBehaviors[] = {
639 AttestationCallbackBehavior::IGNORE_CALLBACK,
640 AttestationCallbackBehavior::BEFORE_NAVIGATION,
641 AttestationCallbackBehavior::AFTER_NAVIGATION,
642 };
643
644 // Tests navigating while an attestation permission prompt is showing.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,PromptForAttestationThenNavigateAway)645 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
646 PromptForAttestationThenNavigateAway) {
647 for (auto behavior : kAllAttestationCallbackBehaviors) {
648 SCOPED_TRACE(AttestationCallbackBehaviorToString(behavior));
649
650 InjectVirtualFidoDeviceFactory();
651 TestCreateCallbackReceiver create_callback_receiver;
652 auto options = BuildBasicCreateOptions();
653 options->attestation = device::AttestationConveyancePreference::kDirect;
654 authenticator()->MakeCredential(std::move(options),
655 create_callback_receiver.callback());
656 bool attestation_callback_was_invoked = false;
657 test_state()->attestation_prompt_callback_ = base::BindLambdaForTesting(
658 [&](base::OnceCallback<void(bool)> callback) {
659 attestation_callback_was_invoked = true;
660
661 if (behavior == AttestationCallbackBehavior::BEFORE_NAVIGATION) {
662 std::move(callback).Run(false);
663 }
664 EXPECT_TRUE(NavigateToURL(
665 shell(), GetHttpsURL("www.acme.com", "/title2.html")));
666 if (behavior == AttestationCallbackBehavior::AFTER_NAVIGATION) {
667 std::move(callback).Run(false);
668 }
669 });
670
671 WaitForConnectionError();
672 ASSERT_TRUE(attestation_callback_was_invoked);
673 ConnectToAuthenticator();
674 }
675 }
676
677 // Tests that the blink::mojom::Authenticator connection is not closed on a
678 // cancelled navigation.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,CreatePublicKeyCredentialAfterCancelledNavigation)679 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
680 CreatePublicKeyCredentialAfterCancelledNavigation) {
681 ScopedNavigationCancellingThrottleInstaller navigation_canceller(
682 shell()->web_contents());
683
684 // This navigation should be canceled and hence should not succeed.
685 EXPECT_FALSE(
686 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title2.html")));
687
688 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
689 TestCreateCallbackReceiver create_callback_receiver;
690 authenticator()->MakeCredential(BuildBasicCreateOptions(),
691 create_callback_receiver.callback());
692
693 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
694 }
695
696 // Tests that a navigator.credentials.create({publicKey: ...}) issued at the
697 // moment just before a navigation commits is not serviced.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,CreatePublicKeyCredentialRacingWithNavigation)698 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
699 CreatePublicKeyCredentialRacingWithNavigation) {
700 TestCreateCallbackReceiver create_callback_receiver;
701 auto request_options = BuildBasicCreateOptions();
702
703 ClosureExecutorBeforeNavigationCommit executor(
704 shell()->web_contents(), base::BindLambdaForTesting([&]() {
705 authenticator()->MakeCredential(std::move(request_options),
706 create_callback_receiver.callback());
707 }));
708
709 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
710 EXPECT_TRUE(
711 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title2.html")));
712 WaitForConnectionError();
713
714 // Normally, when the request is serviced, the implementation retrieves the
715 // factory as one of the first steps. Here, the request should not have been
716 // serviced at all, so the fake request should still be pending on the fake
717 // factory.
718 std::vector<std::unique_ptr<device::FidoDiscoveryBase>> discoveries =
719 discovery_factory_->Create(
720 ::device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
721 EXPECT_EQ(discoveries.size(), 1u);
722
723 // The next active document should be able to successfully call
724 // navigator.credentials.create({publicKey: ...}) again.
725 ConnectToAuthenticator();
726 fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
727 authenticator()->MakeCredential(BuildBasicCreateOptions(),
728 create_callback_receiver.callback());
729 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
730 }
731
732 // Regression test for https://crbug.com/818219.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,CreatePublicKeyCredentialTwiceInARow)733 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
734 CreatePublicKeyCredentialTwiceInARow) {
735 TestCreateCallbackReceiver callback_receiver_1;
736 TestCreateCallbackReceiver callback_receiver_2;
737 authenticator()->MakeCredential(BuildBasicCreateOptions(),
738 callback_receiver_1.callback());
739 authenticator()->MakeCredential(BuildBasicCreateOptions(),
740 callback_receiver_2.callback());
741 callback_receiver_2.WaitForCallback();
742
743 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, callback_receiver_2.status());
744 EXPECT_FALSE(callback_receiver_1.was_called());
745 }
746
747 // Regression test for https://crbug.com/818219.
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,GetPublicKeyCredentialTwiceInARow)748 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
749 GetPublicKeyCredentialTwiceInARow) {
750 TestGetCallbackReceiver callback_receiver_1;
751 TestGetCallbackReceiver callback_receiver_2;
752 authenticator()->GetAssertion(BuildBasicGetOptions(),
753 callback_receiver_1.callback());
754 authenticator()->GetAssertion(BuildBasicGetOptions(),
755 callback_receiver_2.callback());
756 callback_receiver_2.WaitForCallback();
757
758 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, callback_receiver_2.status());
759 EXPECT_FALSE(callback_receiver_1.was_called());
760 }
761
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,CreatePublicKeyCredentialWhileRequestIsPending)762 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
763 CreatePublicKeyCredentialWhileRequestIsPending) {
764 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
765 TestCreateCallbackReceiver callback_receiver_1;
766 TestCreateCallbackReceiver callback_receiver_2;
767 authenticator()->MakeCredential(BuildBasicCreateOptions(),
768 callback_receiver_1.callback());
769 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
770
771 authenticator()->MakeCredential(BuildBasicCreateOptions(),
772 callback_receiver_2.callback());
773 callback_receiver_2.WaitForCallback();
774
775 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, callback_receiver_2.status());
776 EXPECT_FALSE(callback_receiver_1.was_called());
777 }
778
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,GetPublicKeyCredentialWhileRequestIsPending)779 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBrowserTest,
780 GetPublicKeyCredentialWhileRequestIsPending) {
781 auto* fake_hid_discovery = discovery_factory_->ForgeNextHidDiscovery();
782 TestGetCallbackReceiver callback_receiver_1;
783 TestGetCallbackReceiver callback_receiver_2;
784 authenticator()->GetAssertion(BuildBasicGetOptions(),
785 callback_receiver_1.callback());
786 fake_hid_discovery->WaitForCallToStartAndSimulateSuccess();
787
788 authenticator()->GetAssertion(BuildBasicGetOptions(),
789 callback_receiver_2.callback());
790 callback_receiver_2.WaitForCallback();
791
792 EXPECT_EQ(AuthenticatorStatus::PENDING_REQUEST, callback_receiver_2.status());
793 EXPECT_FALSE(callback_receiver_1.was_called());
794 }
795
796 // WebAuthJavascriptClientBrowserTest -----------------------------------------
797
798 // Browser test fixture where the blink::mojom::Authenticator interface is
799 // normally accessed from Javascript in the renderer process.
800 class WebAuthJavascriptClientBrowserTest : public WebAuthBrowserTestBase {
801 public:
802 WebAuthJavascriptClientBrowserTest() = default;
803 ~WebAuthJavascriptClientBrowserTest() override = default;
804
805 protected:
GetFeaturesToEnable()806 std::vector<base::Feature> GetFeaturesToEnable() override {
807 return {device::kWebAuthGetAssertionFeaturePolicy};
808 }
809
810 private:
811 DISALLOW_COPY_AND_ASSIGN(WebAuthJavascriptClientBrowserTest);
812 };
813
814 constexpr device::ProtocolVersion kAllProtocols[] = {
815 device::ProtocolVersion::kCtap2, device::ProtocolVersion::kU2f};
816
817 // Tests that when navigator.credentials.create() is called with an invalid
818 // relying party id, we get a SecurityError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialInvalidRp)819 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
820 CreatePublicKeyCredentialInvalidRp) {
821 CreateParameters parameters;
822 parameters.rp_id = "localhost";
823 std::string result;
824 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
825 shell()->web_contents()->GetMainFrame(),
826 BuildCreateCallWithParameters(parameters), &result));
827
828 ASSERT_EQ(kRelyingPartySecurityErrorMessage,
829 result.substr(0, strlen(kRelyingPartySecurityErrorMessage)));
830 }
831
832 // Tests that when navigator.credentials.create() is called with a null
833 // relying party, we get a NotSupportedError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyWithNullRp)834 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
835 CreatePublicKeyWithNullRp) {
836 CreateParameters parameters;
837 parameters.rp_icon = "";
838 std::string script = BuildCreateCallWithParameters(parameters);
839 const char kExpectedSubstr[] = "{ id: 'acme.com', name: 'Acme', icon: ''}";
840 const std::string::size_type offset = script.find(kExpectedSubstr);
841 ASSERT_TRUE(offset != std::string::npos);
842 script.replace(offset, sizeof(kExpectedSubstr) - 1, "null");
843
844 std::string result;
845 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
846 shell()->web_contents()->GetMainFrame(), script, &result));
847 ASSERT_EQ(kPublicKeyErrorMessage, result);
848 }
849
850 // Tests that when navigator.credentials.create() is called with an insecure
851 // user icon URL, we get a SecurityError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyWithInsecureUserIconURL)852 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
853 CreatePublicKeyWithInsecureUserIconURL) {
854 CreateParameters parameters;
855 parameters.user_icon = "http://fidoalliance.co.nz/testimages/catimage.png";
856 std::string result;
857 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
858 shell()->web_contents()->GetMainFrame(),
859 BuildCreateCallWithParameters(parameters), &result));
860 ASSERT_EQ(kRelyingPartyUserIconUrlSecurityErrorMessage, result);
861 }
862
863 // Tests that when navigator.credentials.create() is called with an insecure
864 // Relying Party icon URL, we get a SecurityError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyWithInsecureRpIconURL)865 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
866 CreatePublicKeyWithInsecureRpIconURL) {
867 CreateParameters parameters;
868 parameters.rp_icon = "http://fidoalliance.co.nz/testimages/catimage.png";
869 std::string result;
870 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
871 shell()->web_contents()->GetMainFrame(),
872 BuildCreateCallWithParameters(parameters), &result));
873 ASSERT_EQ(kRelyingPartyRpIconUrlSecurityErrorMessage, result);
874 }
875
876 // Tests that when navigator.credentials.create() is called with user
877 // verification required, request times out.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialWithUserVerification)878 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
879 CreatePublicKeyCredentialWithUserVerification) {
880 for (const auto protocol : kAllProtocols) {
881 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
882 virtual_device_factory->SetSupportedProtocol(protocol);
883
884 CreateParameters parameters;
885 parameters.user_verification = kRequiredVerification;
886 parameters.timeout = kShortTimeout;
887 std::string result;
888 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
889 shell()->web_contents()->GetMainFrame(),
890 BuildCreateCallWithParameters(parameters), &result));
891 ASSERT_EQ(kNotAllowedErrorMessage, result);
892 }
893 }
894
895 // Tests that when navigator.credentials.create() is called with resident key
896 // required, request times out.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialWithResidentKeyRequired)897 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
898 CreatePublicKeyCredentialWithResidentKeyRequired) {
899 for (const auto protocol : kAllProtocols) {
900 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
901 virtual_device_factory->SetSupportedProtocol(protocol);
902
903 CreateParameters parameters;
904 parameters.require_resident_key = true;
905 std::string result;
906 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
907 shell()->web_contents()->GetMainFrame(),
908 BuildCreateCallWithParameters(parameters), &result));
909
910 ASSERT_EQ(kResidentCredentialsErrorMessage, result);
911 }
912 }
913
914 // Tests that when navigator.credentials.create() is called with an
915 // unsupported algorithm, request times out.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialAlgorithmNotSupported)916 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
917 CreatePublicKeyCredentialAlgorithmNotSupported) {
918 for (const auto protocol : kAllProtocols) {
919 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
920 virtual_device_factory->SetSupportedProtocol(protocol);
921
922 CreateParameters parameters;
923 parameters.algorithm_identifier = "123";
924 parameters.timeout = kShortTimeout;
925 std::string result;
926 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
927 shell()->web_contents()->GetMainFrame(),
928 BuildCreateCallWithParameters(parameters), &result));
929
930 ASSERT_EQ(kNotAllowedErrorMessage, result);
931 }
932 }
933
934 // Tests that when navigator.credentials.create() is called with a
935 // platform authenticator requested, request times out.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialPlatformAuthenticator)936 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
937 CreatePublicKeyCredentialPlatformAuthenticator) {
938 for (const auto protocol : kAllProtocols) {
939 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
940 virtual_device_factory->SetSupportedProtocol(protocol);
941
942 CreateParameters parameters;
943 parameters.authenticator_attachment = kPlatform;
944 parameters.timeout = kShortTimeout;
945 std::string result;
946 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
947 shell()->web_contents()->GetMainFrame(),
948 BuildCreateCallWithParameters(parameters), &result));
949
950 ASSERT_EQ(kNotAllowedErrorMessage, result);
951 }
952 }
953 // Tests that when navigator.credentials.create() is called with abort
954 // signal's aborted flag not set, we get a SUCCESS.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialWithAbortNotSet)955 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
956 CreatePublicKeyCredentialWithAbortNotSet) {
957 for (const auto protocol : kAllProtocols) {
958 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
959 virtual_device_factory->SetSupportedProtocol(protocol);
960
961 CreateParameters parameters;
962 parameters.signal = "authAbortSignal";
963 std::string result;
964 std::string script =
965 "authAbortController = new AbortController();"
966 "authAbortSignal = authAbortController.signal;" +
967 BuildCreateCallWithParameters(parameters);
968
969 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
970 shell()->web_contents()->GetMainFrame(), script, &result));
971 ASSERT_EQ(kOkMessage, result);
972 }
973 }
974
975 // Tests that when navigator.credentials.create() is called with abort
976 // signal's aborted flag set before sending request, we get an AbortError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialWithAbortSetBeforeCreate)977 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
978 CreatePublicKeyCredentialWithAbortSetBeforeCreate) {
979 CreateParameters parameters;
980 parameters.signal = "authAbortSignal";
981 std::string result;
982 std::string script =
983 "authAbortController = new AbortController();"
984 "authAbortSignal = authAbortController.signal;"
985 "authAbortController.abort();" +
986 BuildCreateCallWithParameters(parameters);
987
988 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
989 shell()->web_contents()->GetMainFrame(), script, &result));
990 ASSERT_EQ(kAbortErrorMessage, result.substr(0, strlen(kAbortErrorMessage)));
991 }
992
993 // Tests that when navigator.credentials.create() is called with abort
994 // signal's aborted flag set after sending request, we get an AbortError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,CreatePublicKeyCredentialWithAbortSetAfterCreate)995 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
996 CreatePublicKeyCredentialWithAbortSetAfterCreate) {
997 InjectVirtualFidoDeviceFactory();
998 CreateParameters parameters;
999 parameters.signal = "authAbortSignal";
1000 std::string result;
1001 std::string script =
1002 "authAbortController = new AbortController();"
1003 "authAbortSignal = authAbortController.signal;" +
1004 BuildCreateCallWithParameters(parameters) +
1005 "authAbortController.abort();";
1006
1007 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1008 shell()->web_contents()->GetMainFrame(), script, &result));
1009 ASSERT_EQ(kAbortErrorMessage, result.substr(0, strlen(kAbortErrorMessage)));
1010 }
1011
1012 // Tests that when navigator.credentials.get() is called with user verification
1013 // required, we get an NotAllowedError because the virtual device isn't
1014 // configured with UV and GetAssertionRequestHandler will return
1015 // |kAuthenticatorMissingUserVerification| when such an authenticator is
1016 // touched in that case.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,GetPublicKeyCredentialUserVerification)1017 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1018 GetPublicKeyCredentialUserVerification) {
1019 for (const auto protocol : kAllProtocols) {
1020 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1021 virtual_device_factory->SetSupportedProtocol(protocol);
1022
1023 GetParameters parameters;
1024 parameters.user_verification = "required";
1025 std::string result;
1026 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1027 shell()->web_contents()->GetMainFrame(),
1028 BuildGetCallWithParameters(parameters), &result));
1029 ASSERT_EQ(kNotAllowedErrorMessage, result);
1030 }
1031 }
1032
1033 // Test that unknown transport types are ignored.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,UnknownTransportType)1034 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1035 UnknownTransportType) {
1036 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1037 virtual_device_factory->SetSupportedProtocol(device::ProtocolVersion::kCtap2);
1038
1039 GetParameters parameters;
1040 parameters.allow_credentials =
1041 "allowCredentials: [{"
1042 " type: 'public-key',"
1043 " id: new TextEncoder().encode('allowedCredential'),"
1044 " transports: ['carrierpigeon'],"
1045 "}]";
1046 parameters.timeout = kShortTimeout;
1047 std::string result;
1048 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1049 shell()->web_contents()->GetMainFrame(),
1050 BuildGetCallWithParameters(parameters), &result));
1051 ASSERT_EQ(kNotAllowedErrorMessage, result);
1052 }
1053
1054 // Tests that when navigator.credentials.get() is called with an empty
1055 // allowCredentials list, we get a NotSupportedError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,GetPublicKeyCredentialEmptyAllowCredentialsList)1056 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1057 GetPublicKeyCredentialEmptyAllowCredentialsList) {
1058 InjectVirtualFidoDeviceFactory();
1059 GetParameters parameters;
1060 parameters.allow_credentials = "";
1061 std::string result;
1062 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1063 shell()->web_contents()->GetMainFrame(),
1064 BuildGetCallWithParameters(parameters), &result));
1065 ASSERT_EQ(kResidentCredentialsErrorMessage, result);
1066 }
1067
1068 // Tests that when navigator.credentials.get() is called with abort
1069 // signal's aborted flag not set, we get a NOT_ALLOWED_ERROR, because the
1070 // virtual device does not have any registered credentials.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,GetPublicKeyCredentialWithAbortNotSet)1071 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1072 GetPublicKeyCredentialWithAbortNotSet) {
1073 for (const auto protocol : kAllProtocols) {
1074 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1075 virtual_device_factory->SetSupportedProtocol(protocol);
1076
1077 GetParameters parameters;
1078 parameters.signal = "authAbortSignal";
1079 std::string result;
1080 std::string script =
1081 "authAbortController = new AbortController();"
1082 "authAbortSignal = authAbortController.signal;" +
1083 BuildGetCallWithParameters(parameters);
1084
1085 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1086 shell()->web_contents()->GetMainFrame(), script, &result));
1087 ASSERT_EQ(kNotAllowedErrorMessage, result);
1088 }
1089 }
1090
1091 // Tests that when navigator.credentials.get() is called with abort
1092 // signal's aborted flag set before sending request, we get an AbortError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,GetPublicKeyCredentialWithAbortSetBeforeGet)1093 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1094 GetPublicKeyCredentialWithAbortSetBeforeGet) {
1095 GetParameters parameters;
1096 parameters.signal = "authAbortSignal";
1097 std::string result;
1098 std::string script =
1099 "authAbortController = new AbortController();"
1100 "authAbortSignal = authAbortController.signal;"
1101 "authAbortController.abort();" +
1102 BuildGetCallWithParameters(parameters);
1103
1104 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1105 shell()->web_contents()->GetMainFrame(), script, &result));
1106 ASSERT_EQ(kAbortErrorMessage, result.substr(0, strlen(kAbortErrorMessage)));
1107 }
1108
1109 // Tests that when navigator.credentials.get() is called with abort
1110 // signal's aborted flag set after sending request, we get an AbortError.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,GetPublicKeyCredentialWithAbortSetAfterGet)1111 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1112 GetPublicKeyCredentialWithAbortSetAfterGet) {
1113 InjectVirtualFidoDeviceFactory();
1114 GetParameters parameters;
1115 parameters.signal = "authAbortSignal";
1116 std::string result;
1117 std::string script =
1118 "authAbortController = new AbortController();"
1119 "authAbortSignal = authAbortController.signal;" +
1120 BuildGetCallWithParameters(parameters) + "authAbortController.abort();";
1121
1122 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1123 shell()->web_contents()->GetMainFrame(), script, &result));
1124 ASSERT_EQ(kAbortErrorMessage, result.substr(0, strlen(kAbortErrorMessage)));
1125 }
1126
1127 // Executes Javascript in the given WebContents and waits until a string with
1128 // the given prefix is received. It will ignore values other than strings, and
1129 // strings without the given prefix. Since messages are broadcast to
1130 // DOMMessageQueues, this allows other functions that depend on ExecuteScript
1131 // (and thus trigger the broadcast of values) to run while this function is
1132 // waiting for a specific result.
ExecuteScriptAndExtractPrefixedString(WebContents * web_contents,const std::string & script,const std::string & result_prefix)1133 base::Optional<std::string> ExecuteScriptAndExtractPrefixedString(
1134 WebContents* web_contents,
1135 const std::string& script,
1136 const std::string& result_prefix) {
1137 DOMMessageQueue dom_message_queue(web_contents);
1138 web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
1139 base::UTF8ToUTF16(script), base::NullCallback());
1140
1141 for (;;) {
1142 std::string json;
1143 if (!dom_message_queue.WaitForMessage(&json)) {
1144 return base::nullopt;
1145 }
1146
1147 base::Optional<base::Value> result =
1148 base::JSONReader::Read(json, base::JSON_ALLOW_TRAILING_COMMAS);
1149 if (!result) {
1150 return base::nullopt;
1151 }
1152
1153 std::string str;
1154 if (result->GetAsString(&str) && str.find(result_prefix) == 0) {
1155 return str;
1156 }
1157 }
1158 }
1159
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,RequestsFromIFrames)1160 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1161 RequestsFromIFrames) {
1162 static constexpr char kOuterHost[] = "acme.com";
1163 static constexpr char kInnerHost[] = "notacme.com";
1164 EXPECT_TRUE(NavigateToURL(shell(),
1165 GetHttpsURL(kOuterHost, "/page_with_iframe.html")));
1166
1167 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1168 static constexpr uint8_t kOuterCredentialID = 1;
1169 static constexpr uint8_t kOuterCredentialIDArray[] = {kOuterCredentialID};
1170 static constexpr uint8_t kInnerCredentialID = 2;
1171 static constexpr uint8_t kInnerCredentialIDArray[] = {kInnerCredentialID};
1172 ASSERT_TRUE(virtual_device_factory->mutable_state()->InjectRegistration(
1173 kOuterCredentialIDArray, kOuterHost));
1174 ASSERT_TRUE(virtual_device_factory->mutable_state()->InjectRegistration(
1175 kInnerCredentialIDArray, kInnerHost));
1176
1177 static constexpr struct kTestCase {
1178 // Whether the iframe loads from a different origin.
1179 bool cross_origin;
1180 bool create_should_work;
1181 bool get_should_work;
1182 // The contents of an "allow" attribute on the iframe.
1183 const char allow_value[32];
1184 } kTestCases[] = {
1185 // XO |Create|Get | Allow
1186 {false, true, true, ""},
1187 {true, false, false, ""},
1188 {true, false, true, "publickey-credentials-get"},
1189 };
1190
1191 for (const auto& test : kTestCases) {
1192 SCOPED_TRACE(test.allow_value);
1193 SCOPED_TRACE(test.cross_origin);
1194
1195 const std::string setAllowJS = base::StringPrintf(
1196 "document.getElementById('test_iframe').setAttribute('allow', '%s'); "
1197 "window.domAutomationController.send('OK');",
1198 test.allow_value);
1199 std::string result;
1200 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1201 shell()->web_contents()->GetMainFrame(), setAllowJS.c_str(), &result));
1202 ASSERT_EQ("OK", result);
1203
1204 if (test.cross_origin) {
1205 // Create a cross-origin iframe by loading it from notacme.com.
1206 NavigateIframeToURL(shell()->web_contents(), "test_iframe",
1207 GetHttpsURL(kInnerHost, "/title2.html"));
1208 } else {
1209 // Create a same-origin iframe by loading it from acme.com.
1210 NavigateIframeToURL(shell()->web_contents(), "test_iframe",
1211 GetHttpsURL(kOuterHost, "/title2.html"));
1212 }
1213
1214 std::vector<RenderFrameHost*> frames =
1215 shell()->web_contents()->GetAllFrames();
1216 // GetAllFrames is documented to return a breadth-first list of frames. Thus
1217 // there should be exactly two: the main frame and the contained iframe.
1218 ASSERT_EQ(2u, frames.size());
1219 RenderFrameHost* const iframe = frames[1];
1220
1221 CreateParameters create_parameters;
1222 create_parameters.rp_id = test.cross_origin ? "notacme.com" : "acme.com";
1223 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1224 iframe, BuildCreateCallWithParameters(create_parameters), &result));
1225 if (test.create_should_work) {
1226 EXPECT_EQ(std::string(kOkMessage), result);
1227 } else {
1228 EXPECT_EQ(kCrossOriginAncestorMessage, result);
1229 }
1230
1231 const int credential_id =
1232 test.cross_origin ? kInnerCredentialID : kOuterCredentialID;
1233 const std::string allow_credentials = base::StringPrintf(
1234 "allowCredentials: "
1235 "[{ type: 'public-key',"
1236 " id: new Uint8Array([%d]),"
1237 "}]",
1238 credential_id);
1239 GetParameters get_params;
1240 get_params.allow_credentials = allow_credentials.c_str();
1241 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1242 iframe, BuildGetCallWithParameters(get_params), &result));
1243 if (test.get_should_work) {
1244 EXPECT_EQ(std::string(kOkMessage), result);
1245 } else {
1246 EXPECT_EQ(kGetFeaturePolicyMissingMessage, result);
1247 }
1248 }
1249 }
1250
1251 // Tests that a credentials.create() call triggered by the main frame will
1252 // successfully complete even if a subframe navigation takes place while the
1253 // request is waiting for user consent.
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,NavigateSubframeDuringPress)1254 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1255 NavigateSubframeDuringPress) {
1256 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1257 bool prompt_callback_was_invoked = false;
1258 virtual_device_factory->mutable_state()->simulate_press_callback =
1259 base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
1260 prompt_callback_was_invoked = true;
1261 NavigateIframeToURL(shell()->web_contents(), "test_iframe",
1262 GURL("/title2.html"));
1263 return true;
1264 });
1265
1266 EXPECT_TRUE(NavigateToURL(
1267 shell(), GetHttpsURL("www.acme.com", "/page_with_iframe.html")));
1268
1269 // The plain ExecuteScriptAndExtractString cannot be used because
1270 // NavigateIframeToURL uses it internally and they get confused about which
1271 // message is for whom.
1272 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1273 shell()->web_contents(),
1274 BuildCreateCallWithParameters(CreateParameters()), "webauth: ");
1275 ASSERT_TRUE(result);
1276 ASSERT_EQ(kOkMessage, *result);
1277 ASSERT_TRUE(prompt_callback_was_invoked);
1278 }
1279
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,NavigateSubframeDuringAttestationPrompt)1280 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1281 NavigateSubframeDuringAttestationPrompt) {
1282 InjectVirtualFidoDeviceFactory();
1283
1284 for (auto behavior : kAllAttestationCallbackBehaviors) {
1285 if (behavior == AttestationCallbackBehavior::IGNORE_CALLBACK) {
1286 // If the callback is ignored, then the registration will not complete and
1287 // that hangs the test.
1288 continue;
1289 }
1290
1291 SCOPED_TRACE(AttestationCallbackBehaviorToString(behavior));
1292
1293 bool prompt_callback_was_invoked = false;
1294 test_state()->attestation_prompt_callback_ = base::BindOnce(
1295 [](WebContents* web_contents, bool* prompt_callback_was_invoked,
1296 AttestationCallbackBehavior behavior,
1297 base::OnceCallback<void(bool)> callback) {
1298 *prompt_callback_was_invoked = true;
1299
1300 if (behavior == AttestationCallbackBehavior::BEFORE_NAVIGATION) {
1301 std::move(callback).Run(true);
1302 }
1303 // Can't use NavigateIframeToURL here because in the
1304 // BEFORE_NAVIGATION case we are racing AuthenticatorImpl and
1305 // NavigateIframeToURL can get confused by the "OK" message.
1306 base::Optional<std::string> result =
1307 ExecuteScriptAndExtractPrefixedString(
1308 web_contents,
1309 "document.getElementById('test_iframe').src = "
1310 "'/title2.html'; "
1311 "window.domAutomationController.send('iframe: done');",
1312 "iframe: ");
1313 CHECK(result);
1314 CHECK_EQ("iframe: done", *result);
1315 if (behavior == AttestationCallbackBehavior::AFTER_NAVIGATION) {
1316 std::move(callback).Run(true);
1317 }
1318 },
1319 shell()->web_contents(), &prompt_callback_was_invoked, behavior);
1320
1321 EXPECT_TRUE(NavigateToURL(
1322 shell(), GetHttpsURL("www.acme.com", "/page_with_iframe.html")));
1323
1324 CreateParameters parameters;
1325 parameters.attestation = "direct";
1326 // The plain ExecuteScriptAndExtractString cannot be used because
1327 // NavigateIframeToURL uses it internally and they get confused about which
1328 // message is for whom.
1329 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1330 shell()->web_contents(), BuildCreateCallWithParameters(parameters),
1331 "webauth: ");
1332 ASSERT_TRUE(result);
1333 ASSERT_EQ(kOkMessage, *result);
1334 ASSERT_TRUE(prompt_callback_was_invoked);
1335 }
1336 }
1337
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,BadCableExtensionVersions)1338 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1339 BadCableExtensionVersions) {
1340 // The caBLE extension should only contain v1 data. Test that nothing crashes
1341 // if a site tries to set other versions.
1342
1343 InjectVirtualFidoDeviceFactory();
1344 GetParameters parameters;
1345 parameters.allow_credentials =
1346 "allowCredentials: [{ type: 'public-key',"
1347 " id: new TextEncoder().encode('allowedCredential'),"
1348 " transports: ['cable']}],"
1349 "extensions: {"
1350 " cableAuthentication: [{"
1351 " version: 1,"
1352 " clientEid: new Uint8Array(Array(16).fill(1)),"
1353 " authenticatorEid: new Uint8Array(Array(16).fill(2)),"
1354 " sessionPreKey: new Uint8Array(Array(32).fill(3)),"
1355 " },{"
1356 " version: 2,"
1357 " clientEid: new Uint8Array(Array(16).fill(1)),"
1358 " authenticatorEid: new Uint8Array(Array(16).fill(2)),"
1359 " sessionPreKey: new Uint8Array(Array(32).fill(3)),"
1360 " },{"
1361 " version: 3,"
1362 " clientEid: new Uint8Array(Array(16).fill(1)),"
1363 " authenticatorEid: new Uint8Array(Array(16).fill(2)),"
1364 " sessionPreKey: new Uint8Array(Array(32).fill(3)),"
1365 " }]"
1366 "}";
1367 std::string result;
1368 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
1369 shell()->web_contents()->GetMainFrame(),
1370 BuildGetCallWithParameters(parameters), &result));
1371 ASSERT_EQ(kNotAllowedErrorMessage, result);
1372 }
1373
1374 #if defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,WinMakeCredential)1375 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, WinMakeCredential) {
1376 EXPECT_TRUE(
1377 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")));
1378
1379 device::FakeWinWebAuthnApi fake_api;
1380 fake_api.set_is_uvpaa(true);
1381 fake_api.set_hresult(S_OK);
1382 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1383 virtual_device_factory->set_win_webauthn_api(&fake_api);
1384
1385 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1386 shell()->web_contents(),
1387 BuildCreateCallWithParameters(CreateParameters()), "webauth: ");
1388 ASSERT_TRUE(result);
1389 ASSERT_EQ(kOkMessage, *result);
1390 }
1391
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,WinMakeCredentialReturnCodeFailure)1392 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1393 WinMakeCredentialReturnCodeFailure) {
1394 EXPECT_TRUE(
1395 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")));
1396 device::FakeWinWebAuthnApi fake_api;
1397 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1398 virtual_device_factory->set_win_webauthn_api(&fake_api);
1399
1400 // Errors documented for WebAuthNGetErrorName() in <webauthn.h>.
1401 const std::map<HRESULT, std::string> errors{
1402 // NTE_EXISTS is the error for using an authenticator that matches the
1403 // exclude list, which should result in "InvalidStateError".
1404 {NTE_EXISTS, kInvalidStateErrorMessage},
1405 // All other errors should yield "NotAllowedError".
1406 {HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), kNotAllowedErrorMessage},
1407 {NTE_TOKEN_KEYSET_STORAGE_FULL, kNotAllowedErrorMessage},
1408 {NTE_TOKEN_KEYSET_STORAGE_FULL, kNotAllowedErrorMessage},
1409 {NTE_INVALID_PARAMETER, kNotAllowedErrorMessage},
1410 {NTE_DEVICE_NOT_FOUND, kNotAllowedErrorMessage},
1411 {NTE_NOT_FOUND, kNotAllowedErrorMessage},
1412 {HRESULT_FROM_WIN32(ERROR_CANCELLED), kNotAllowedErrorMessage},
1413 {NTE_USER_CANCELLED, kNotAllowedErrorMessage},
1414 {HRESULT_FROM_WIN32(ERROR_TIMEOUT), kNotAllowedErrorMessage},
1415 // Undocumented errors should default to NOT_ALLOWED_ERROR.
1416 {ERROR_FILE_NOT_FOUND, kNotAllowedErrorMessage},
1417 };
1418
1419 for (const auto& error : errors) {
1420 fake_api.set_hresult(error.first);
1421
1422 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1423 shell()->web_contents(),
1424 BuildCreateCallWithParameters(CreateParameters()), "webauth: ");
1425 EXPECT_TRUE(result);
1426 EXPECT_EQ(*result, error.second);
1427 }
1428 }
1429
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,WinGetAssertion)1430 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, WinGetAssertion) {
1431 EXPECT_TRUE(
1432 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")));
1433
1434 constexpr uint8_t credential_id[] = {'A', 'A', 'A'};
1435
1436 device::FakeWinWebAuthnApi fake_api;
1437 fake_api.InjectNonDiscoverableCredential(credential_id, "www.acme.com");
1438 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1439 virtual_device_factory->set_win_webauthn_api(&fake_api);
1440
1441 GetParameters get_parameters;
1442 get_parameters.allow_credentials =
1443 "allowCredentials: [{ type: 'public-key', id: new "
1444 "TextEncoder().encode('AAA')}]";
1445
1446 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1447 shell()->web_contents(), BuildGetCallWithParameters(get_parameters),
1448 "webauth: ");
1449 ASSERT_TRUE(result);
1450 ASSERT_EQ(kOkMessage, *result);
1451 }
1452
IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,WinGetAssertionReturnCodeFailure)1453 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
1454 WinGetAssertionReturnCodeFailure) {
1455 EXPECT_TRUE(
1456 NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")));
1457 device::FakeWinWebAuthnApi fake_api;
1458 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1459 virtual_device_factory->set_win_webauthn_api(&fake_api);
1460
1461 // Errors documented for WebAuthNGetErrorName() in <webauthn.h>.
1462 const std::set<HRESULT> errors{
1463 // NTE_EXISTS -- should not be returned for WebAuthNGetAssertion().
1464 HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), NTE_TOKEN_KEYSET_STORAGE_FULL,
1465 NTE_TOKEN_KEYSET_STORAGE_FULL, NTE_INVALID_PARAMETER,
1466 NTE_DEVICE_NOT_FOUND, NTE_NOT_FOUND, HRESULT_FROM_WIN32(ERROR_CANCELLED),
1467 NTE_USER_CANCELLED, HRESULT_FROM_WIN32(ERROR_TIMEOUT),
1468 // Other errors should also result in NOT_ALLOWED_ERROR.
1469 ERROR_FILE_NOT_FOUND};
1470
1471 for (const auto& error : errors) {
1472 fake_api.set_hresult(error);
1473
1474 base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString(
1475 shell()->web_contents(), BuildGetCallWithParameters(GetParameters()),
1476 "webauth: ");
1477 ASSERT_EQ(*result, kNotAllowedErrorMessage);
1478 }
1479 }
1480 #endif
1481
1482 class WebAuthLocalClientBackForwardCacheBrowserTest
1483 : public WebAuthLocalClientBrowserTest {
1484 protected:
1485 BackForwardCacheDisabledTester tester_;
1486 };
1487
IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBackForwardCacheBrowserTest,WebAuthDisablesBackForwardCache)1488 IN_PROC_BROWSER_TEST_F(WebAuthLocalClientBackForwardCacheBrowserTest,
1489 WebAuthDisablesBackForwardCache) {
1490 // Initialisation of the test should disable bfcache.
1491 EXPECT_TRUE(tester_.IsDisabledForFrameWithReason(
1492 shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(),
1493 shell()->web_contents()->GetMainFrame()->GetRoutingID(),
1494 "WebAuthenticationAPI"));
1495 }
1496
1497 // WebAuthBrowserCtapTest ----------------------------------------------
1498
1499 class WebAuthBrowserCtapTest : public WebAuthLocalClientBrowserTest {
1500 public:
1501 WebAuthBrowserCtapTest() = default;
1502 ~WebAuthBrowserCtapTest() override = default;
1503
1504 DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserCtapTest);
1505 };
1506
IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,TestMakeCredential)1507 IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest, TestMakeCredential) {
1508 for (const auto protocol : kAllProtocols) {
1509 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1510 virtual_device_factory->SetSupportedProtocol(protocol);
1511
1512 TestCreateCallbackReceiver create_callback_receiver;
1513 authenticator()->MakeCredential(BuildBasicCreateOptions(),
1514 create_callback_receiver.callback());
1515
1516 create_callback_receiver.WaitForCallback();
1517 EXPECT_EQ(AuthenticatorStatus::SUCCESS, create_callback_receiver.status());
1518 }
1519 }
1520
IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,TestMakeCredentialWithDuplicateKeyHandle)1521 IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,
1522 TestMakeCredentialWithDuplicateKeyHandle) {
1523 for (const auto protocol : kAllProtocols) {
1524 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1525 virtual_device_factory->SetSupportedProtocol(protocol);
1526 auto make_credential_request = BuildBasicCreateOptions();
1527 device::PublicKeyCredentialDescriptor excluded_credential(
1528 device::CredentialType::kPublicKey,
1529 device::fido_parsing_utils::Materialize(
1530 device::test_data::kCtap2MakeCredentialCredentialId),
1531 std::vector<device::FidoTransportProtocol>{
1532 device::FidoTransportProtocol::kUsbHumanInterfaceDevice});
1533 make_credential_request->exclude_credentials.push_back(excluded_credential);
1534
1535 ASSERT_TRUE(virtual_device_factory->mutable_state()->InjectRegistration(
1536 device::fido_parsing_utils::Materialize(
1537 device::test_data::kCtap2MakeCredentialCredentialId),
1538 make_credential_request->relying_party.id));
1539
1540 TestCreateCallbackReceiver create_callback_receiver;
1541 authenticator()->MakeCredential(std::move(make_credential_request),
1542 create_callback_receiver.callback());
1543
1544 create_callback_receiver.WaitForCallback();
1545 EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_EXCLUDED,
1546 create_callback_receiver.status());
1547 }
1548 }
1549
IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,TestGetAssertion)1550 IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest, TestGetAssertion) {
1551 for (const auto protocol : kAllProtocols) {
1552 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1553 virtual_device_factory->SetSupportedProtocol(protocol);
1554 auto get_assertion_request_params = BuildBasicGetOptions();
1555 ASSERT_TRUE(virtual_device_factory->mutable_state()->InjectRegistration(
1556 device::fido_parsing_utils::Materialize(
1557 device::test_data::kTestGetAssertionCredentialId),
1558 get_assertion_request_params->relying_party_id));
1559
1560 TestGetCallbackReceiver get_callback_receiver;
1561 authenticator()->GetAssertion(std::move(get_assertion_request_params),
1562 get_callback_receiver.callback());
1563 get_callback_receiver.WaitForCallback();
1564 EXPECT_EQ(AuthenticatorStatus::SUCCESS, get_callback_receiver.status());
1565 }
1566 }
1567
IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,TestGetAssertionWithNoMatchingKeyHandles)1568 IN_PROC_BROWSER_TEST_F(WebAuthBrowserCtapTest,
1569 TestGetAssertionWithNoMatchingKeyHandles) {
1570 for (const auto protocol : kAllProtocols) {
1571 auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
1572 virtual_device_factory->SetSupportedProtocol(protocol);
1573 auto get_assertion_request_params = BuildBasicGetOptions();
1574
1575 TestGetCallbackReceiver get_callback_receiver;
1576 authenticator()->GetAssertion(std::move(get_assertion_request_params),
1577 get_callback_receiver.callback());
1578 get_callback_receiver.WaitForCallback();
1579 EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
1580 get_callback_receiver.status());
1581 }
1582 }
1583
1584 } // namespace
1585
1586 } // namespace content
1587