1 // Copyright 2019 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 <vector>
6 
7 #include "base/files/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "chrome/browser/extensions/extension_service.h"
10 #include "chrome/browser/extensions/install_verifier.h"
11 #include "chrome/browser/extensions/test_extension_system.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/test/base/in_process_browser_test.h"
14 #include "chrome/test/base/ui_test_utils.h"
15 #include "content/public/browser/authenticator_environment.h"
16 #include "content/public/test/browser_test.h"
17 #include "content/public/test/browser_test_utils.h"
18 #include "device/fido/virtual_fido_device_factory.h"
19 #include "extensions/common/extension_builder.h"
20 #include "url/gurl.h"
21 
22 class WebAuthnBrowserTest : public InProcessBrowserTest {
23  public:
24   WebAuthnBrowserTest() = default;
25 
26  private:
27   DISALLOW_COPY_AND_ASSIGN(WebAuthnBrowserTest);
28 };
29 
IN_PROC_BROWSER_TEST_F(WebAuthnBrowserTest,ChromeExtensions)30 IN_PROC_BROWSER_TEST_F(WebAuthnBrowserTest, ChromeExtensions) {
31   // Test that WebAuthn works inside of Chrome extensions. WebAuthn is based on
32   // Relying Party IDs, which are domain names. But Chrome extensions don't have
33   // domain names therefore the origin is used in their case.
34   //
35   // This test creates and installs an extension and then loads an HTML page
36   // from inside that extension. A WebAuthn call is injected into that context
37   // and it should get an assertion from a credential that's injected into the
38   // virtual authenticator, scoped to the origin string.
39   base::ScopedAllowBlockingForTesting allow_blocking;
40   extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass;
41 
42   base::ScopedTempDir temp_dir;
43   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
44 
45   static constexpr char kPageFile[] = "page.html";
46 
47   std::vector<base::Value> resources;
48   resources.emplace_back(std::string(kPageFile));
49   static constexpr char kContents[] = R"(
50 <html>
51   <head>
52     <title>WebAuthn in extensions test</title>
53   </head>
54   <body>
55   </body>
56 </html>
57 )";
58   WriteFile(temp_dir.GetPath().AppendASCII(kPageFile), kContents,
59             sizeof(kContents) - 1);
60 
61   extensions::ExtensionBuilder builder("test");
62   builder.SetPath(temp_dir.GetPath())
63       .SetVersion("1.0")
64       .SetLocation(extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD)
65       .SetManifestKey("web_accessible_resources", std::move(resources));
66 
67   extensions::ExtensionService* service =
68       extensions::ExtensionSystem::Get(browser()->profile())
69           ->extension_service();
70   scoped_refptr<const extensions::Extension> extension = builder.Build();
71   service->OnExtensionInstalled(extension.get(), syncer::StringOrdinal(), 0);
72 
73   auto virtual_device_factory =
74       std::make_unique<device::test::VirtualFidoDeviceFactory>();
75   const GURL url = extension->GetResourceURL(kPageFile);
76   auto extension_id = url.host();
77   static const uint8_t kCredentialID[] = {1, 2, 3, 4};
78   virtual_device_factory->mutable_state()->InjectRegistration(
79       kCredentialID, "chrome-extension://" + extension_id);
80 
81   content::AuthenticatorEnvironment::GetInstance()
82       ->ReplaceDefaultDiscoveryFactoryForTesting(
83           std::move(virtual_device_factory));
84 
85   EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
86   std::string result;
87   ASSERT_TRUE(content::ExecuteScriptAndExtractString(
88       browser()->tab_strip_model()->GetActiveWebContents(), R"((() => {
89   let cred_id = new Uint8Array([1,2,3,4]);
90   navigator.credentials.get({ publicKey: {
91     challenge: cred_id,
92     timeout: 10000,
93     userVerification: 'discouraged',
94     allowCredentials: [{type: 'public-key', id: cred_id}],
95   }}).then(c => window.domAutomationController.send('webauthn: OK'),
96            e => window.domAutomationController.send('error ' + e));
97 })())",
98       &result));
99 
100   EXPECT_EQ("webauthn: OK", result);
101 }
102