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