1 // Copyright 2015 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.
5 #include <openssl/evp.h>
6 #include <openssl/rsa.h>
14 #include "base/bind.h"
15 #include "base/callback.h"
16 #include "base/files/file_path.h"
17 #include "base/files/file_util.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/run_loop.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/values.h"
24 #include "chrome/browser/extensions/extension_apitest.h"
25 #include "chrome/browser/ui/tabs/tab_strip_model.h"
26 #include "chrome/test/base/ui_test_utils.h"
27 #include "components/policy/core/browser/browser_policy_connector.h"
28 #include "components/policy/core/common/mock_configuration_policy_provider.h"
29 #include "components/policy/core/common/policy_map.h"
30 #include "components/policy/core/common/policy_types.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/test/test_navigation_observer.h"
34 #include "content/public/test/test_utils.h"
35 #include "crypto/rsa_private_key.h"
36 #include "crypto/scoped_openssl_types.h"
37 #include "extensions/common/extension.h"
38 #include "extensions/test/result_catcher.h"
39 #include "net/test/spawned_test_server/spawned_test_server.h"
40 #include "policy/policy_constants.h"
41 #include "testing/gmock/include/gmock/gmock.h"
43 using testing::Return
;
48 void IgnoreResult(const base::Closure
& callback
, const base::Value
* value
) {
52 void StoreBool(bool* result
,
53 const base::Closure
& callback
,
54 const base::Value
* value
) {
55 value
->GetAsBoolean(result
);
59 void StoreString(std::string
* result
,
60 const base::Closure
& callback
,
61 const base::Value
* value
) {
62 value
->GetAsString(result
);
66 void StoreDigest(std::vector
<uint8_t>* digest
,
67 const base::Closure
& callback
,
68 const base::Value
* value
) {
69 const base::BinaryValue
* binary
= nullptr;
70 value
->GetAsBinary(&binary
);
71 const uint8_t* const binary_begin
=
72 reinterpret_cast<const uint8_t*>(binary
->GetBuffer());
73 digest
->assign(binary_begin
, binary_begin
+ binary
->GetSize());
78 // See net::SSLPrivateKey::SignDigest for the expected padding and DigestInfo
80 bool RsaSign(const std::vector
<uint8_t>& digest
,
81 crypto::RSAPrivateKey
* key
,
82 std::vector
<uint8_t>* signature
) {
83 crypto::ScopedRSA
rsa_key(EVP_PKEY_get1_RSA(key
->key()));
87 uint8_t* prefixed_digest
= nullptr;
88 size_t prefixed_digest_len
= 0;
90 if (!RSA_add_pkcs1_prefix(&prefixed_digest
, &prefixed_digest_len
, &is_alloced
,
91 NID_sha1
, vector_as_array(&digest
),
96 signature
->resize(RSA_size(rsa_key
.get()));
97 const int rv
= RSA_sign_raw(rsa_key
.get(), &len
, vector_as_array(signature
),
98 signature
->size(), prefixed_digest
,
99 prefixed_digest_len
, RSA_PKCS1_PADDING
);
101 free(prefixed_digest
);
104 signature
->resize(len
);
112 // Create a string that if evaluated in JavaScript returns a Uint8Array with
113 // |bytes| as content.
114 std::string
JsUint8Array(const std::vector
<uint8_t>& bytes
) {
115 std::string res
= "new Uint8Array([";
116 for (const uint8_t byte
: bytes
) {
117 res
+= base::UintToString(byte
);
124 class CertificateProviderApiTest
: public ExtensionApiTest
{
126 CertificateProviderApiTest() {}
128 void SetUpInProcessBrowserTestFixture() override
{
129 EXPECT_CALL(provider_
, IsInitializationComplete(_
))
130 .WillRepeatedly(Return(true));
131 policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_
);
133 ExtensionApiTest::SetUpInProcessBrowserTestFixture();
136 void SetUpOnMainThread() override
{
137 // Set up the AutoSelectCertificateForUrls policy to avoid the client
138 // certificate selection dialog.
139 const std::string autoselect_pattern
=
140 "{\"pattern\": \"*\", \"filter\": {\"ISSUER\": {\"CN\": \"root\"}}}";
142 scoped_ptr
<base::ListValue
> autoselect_policy(new base::ListValue
);
143 autoselect_policy
->AppendString(autoselect_pattern
);
145 policy::PolicyMap policy
;
146 policy
.Set(policy::key::kAutoSelectCertificateForUrls
,
147 policy::POLICY_LEVEL_MANDATORY
, policy::POLICY_SCOPE_USER
,
148 policy::POLICY_SOURCE_CLOUD
, autoselect_policy
.release(),
150 provider_
.UpdateChromePolicy(policy
);
152 content::RunAllPendingInMessageLoop();
156 policy::MockConfigurationPolicyProvider provider_
;
161 IN_PROC_BROWSER_TEST_F(CertificateProviderApiTest
, Basic
) {
162 // Start an HTTPS test server that requests a client certificate.
163 net::SpawnedTestServer::SSLOptions ssl_options
;
164 ssl_options
.request_client_certificate
= true;
165 net::SpawnedTestServer
https_server(net::SpawnedTestServer::TYPE_HTTPS
,
166 ssl_options
, base::FilePath());
167 ASSERT_TRUE(https_server
.Start());
169 extensions::ResultCatcher catcher
;
171 const base::FilePath extension_path
=
172 test_data_dir_
.AppendASCII("certificate_provider");
173 const extensions::Extension
* const extension
= LoadExtension(extension_path
);
174 ui_test_utils::NavigateToURL(browser(),
175 extension
->GetResourceURL("basic.html"));
177 ASSERT_TRUE(catcher
.GetNextResult()) << catcher
.message();
178 VLOG(1) << "Extension registered. Navigate to the test https page.";
180 content::WebContents
* const extension_contents
=
181 browser()->tab_strip_model()->GetActiveWebContents();
183 content::TestNavigationObserver
navigation_observer(
184 nullptr /* no WebContents */);
185 navigation_observer
.StartWatchingNewWebContents();
186 ui_test_utils::NavigateToURLWithDisposition(
187 browser(), https_server
.GetURL("client-cert"), NEW_FOREGROUND_TAB
,
188 ui_test_utils::BROWSER_TEST_NONE
);
190 content::WebContents
* const https_contents
=
191 browser()->tab_strip_model()->GetActiveWebContents();
193 VLOG(1) << "Wait for the extension to receive the sign request.";
194 ASSERT_TRUE(catcher
.GetNextResult()) << catcher
.message();
196 VLOG(1) << "Fetch the digest from the sign request.";
197 std::vector
<uint8_t> request_digest
;
199 base::RunLoop run_loop
;
200 extension_contents
->GetMainFrame()->ExecuteJavaScriptForTests(
201 base::ASCIIToUTF16("signDigestRequest.digest;"),
202 base::Bind(&StoreDigest
, &request_digest
, run_loop
.QuitClosure()));
206 VLOG(1) << "Sign the digest using the private key.";
208 base::ReadFileToString(extension_path
.AppendASCII("l1_leaf.pk8"), &key_pk8
);
210 const uint8_t* const key_pk8_begin
=
211 reinterpret_cast<const uint8_t*>(key_pk8
.data());
212 scoped_ptr
<crypto::RSAPrivateKey
> key(
213 crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(
214 std::vector
<uint8_t>(key_pk8_begin
, key_pk8_begin
+ key_pk8
.size())));
217 std::vector
<uint8_t> signature
;
218 EXPECT_TRUE(RsaSign(request_digest
, key
.get(), &signature
));
220 VLOG(1) << "Inject the signature back to the extension and let it reply.";
222 base::RunLoop run_loop
;
223 const std::string code
=
224 "replyWithSignature(" + JsUint8Array(signature
) + ");";
225 extension_contents
->GetMainFrame()->ExecuteJavaScriptForTests(
226 base::ASCIIToUTF16(code
),
227 base::Bind(&IgnoreResult
, run_loop
.QuitClosure()));
231 VLOG(1) << "Wait for the https navigation to finish.";
232 navigation_observer
.Wait();
234 VLOG(1) << "Check whether the server acknowledged that a client certificate "
237 base::RunLoop run_loop
;
238 std::string https_reply
;
239 https_contents
->GetMainFrame()->ExecuteJavaScriptForTests(
240 base::ASCIIToUTF16("document.body.textContent;"),
241 base::Bind(&StoreString
, &https_reply
, run_loop
.QuitClosure()));
243 // Expect the server to return the fingerprint of the client cert that we
244 // presented, which should be the fingerprint of 'l1_leaf.der'.
245 // The fingerprint can be calculated independently using:
246 // openssl x509 -inform DER -noout -fingerprint -in
247 // chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.der
249 "got client cert with fingerprint: "
250 "2ab3f55e06eb8b36a741fe285a769da45edb2695",
254 // Replying to the same signature request a second time must fail.
256 base::RunLoop run_loop
;
257 const std::string code
= "replyWithSignatureSecondTime();";
259 extension_contents
->GetMainFrame()->ExecuteJavaScriptForTests(
260 base::ASCIIToUTF16(code
),
261 base::Bind(&StoreBool
, &result
, run_loop
.QuitClosure()));