// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <memory>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/hash/sha1.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/certificate_provider/certificate_provider.h"
#include "chrome/browser/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/certificate_provider/test_certificate_provider_extension.h"
#include "chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/notifications/request_pin_view_chromeos.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "crypto/rsa_private_key.h"
#include "extensions/browser/api/test/test_api_observer.h"
#include "extensions/browser/api/test/test_api_observer_registry.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/extension.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_status_code.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
#include "third_party/boringssl/src/include/openssl/pool.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/lacros/cert/cert_db_initializer_factory.h"
#endif
using testing::Return;
using testing::_;
namespace {
bool RsaSignRawData(uint16_t openssl_signature_algorithm,
const std::vector<uint8_t>& input,
crypto::RSAPrivateKey* key,
std::vector<uint8_t>* signature) {
const EVP_MD* const digest_algorithm =
SSL_get_signature_algorithm_digest(openssl_signature_algorithm);
bssl::ScopedEVP_MD_CTX ctx;
EVP_PKEY_CTX* pkey_ctx = nullptr;
if (!EVP_DigestSignInit(ctx.get(), &pkey_ctx, digest_algorithm,
/*ENGINE* e=*/nullptr, key->key()))
return false;
if (SSL_is_signature_algorithm_rsa_pss(openssl_signature_algorithm)) {
// For RSA-PSS, configure the special padding and set the salt length to be
// equal to the hash size.
if (!EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) ||
!EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, /*salt_len=*/-1)) {
return false;
}
}
size_t sig_len = 0;
// Determine the signature length for the buffer.
if (!EVP_DigestSign(ctx.get(), /*out_sig=*/nullptr, &sig_len, input.data(),
input.size()))
return false;
signature->resize(sig_len);
return EVP_DigestSign(ctx.get(), signature->data(), &sig_len, input.data(),
input.size()) != 0;
}
bool RsaSignPrehashed(uint16_t openssl_signature_algorithm,
const std::vector<uint8_t>& digest,
crypto::RSAPrivateKey* key,
std::vector<uint8_t>* signature) {
// RSA-PSS is not supported for prehashed data.
EXPECT_FALSE(SSL_is_signature_algorithm_rsa_pss(openssl_signature_algorithm));
RSA* rsa_key = EVP_PKEY_get0_RSA(key->key());
if (!rsa_key)
return false;
const int digest_algorithm_nid = EVP_MD_type(
SSL_get_signature_algorithm_digest(openssl_signature_algorithm));
unsigned len = 0;
signature->resize(RSA_size(rsa_key));
if (!RSA_sign(digest_algorithm_nid, digest.data(), digest.size(),
signature->data(), &len, rsa_key)) {
signature->clear();
return false;
}
signature->resize(len);
return true;
}
// Create a string that if evaluated in JavaScript returns a Uint8Array with
// |bytes| as content.
std::string JsUint8Array(const std::vector<uint8_t>& bytes) {
std::string res = "new Uint8Array([";
for (const uint8_t byte : bytes) {
res += base::NumberToString(byte);
res += ", ";
}
res += "])";
return res;
}
std::string GetPageTextContent(content::WebContents* web_contents) {
return content::EvalJs(web_contents->GetPrimaryMainFrame(),
"document.body.textContent;")
.ExtractString();
;
}
std::string GetCertFingerprint1(const net::X509Certificate& cert) {
return base::ToLowerASCII(base::HexEncode(base::SHA1Hash(cert.cert_span())));
}
// Generates a gtest failure whenever extension JS reports failure.
class JsFailureObserver : public extensions::TestApiObserver {
public:
JsFailureObserver() {
test_api_observation_.Observe(
extensions::TestApiObserverRegistry::GetInstance());
}
~JsFailureObserver() override = default;
void OnTestFailed(content::BrowserContext* browser_context,
const std::string& message) override {
ADD_FAILURE() << "Received failure notification from the JS side: "
<< message;
}
private:
base::ScopedObservation<extensions::TestApiObserverRegistry,
extensions::TestApiObserver>
test_api_observation_{this};
};
class CertificateProviderApiTest : public extensions::ExtensionApiTest {
public:
CertificateProviderApiTest() {}
void SetUpInProcessBrowserTestFixture() override {
provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
extensions::ExtensionApiTest::SetUpOnMainThread();
// Observe all assertion failures in the JS code, even those that happen
// when there's no active `ResultCatcher`.
js_failure_observer_ = std::make_unique<JsFailureObserver>();
// Set up the AutoSelectCertificateForUrls policy to avoid the client
// certificate selection dialog.
const std::string autoselect_pattern = R"({"pattern": "*", "filter": {}})";
base::Value::List autoselect_policy;
autoselect_policy.Append(autoselect_pattern);
policy_map_.Set(policy::key::kAutoSelectCertificateForUrls,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_CLOUD,
base::Value(std::move(autoselect_policy)), nullptr);
provider_.UpdateChromePolicy(policy_map_);
content::RunAllPendingInMessageLoop();
cert_provider_service_ =
chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
profile());
}
// Starts an HTTPS test server that requests a client certificate.
bool StartHttpsServer(uint16_t ssl_protocol_version) {
net::SSLServerConfig ssl_server_config;
ssl_server_config.client_cert_type =
net::SSLServerConfig::REQUIRE_CLIENT_CERT;
ssl_server_config.version_max = ssl_protocol_version;
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_OK,
ssl_server_config);
https_server_->RegisterRequestHandler(
base::BindRepeating(&CertificateProviderApiTest::OnHttpsServerRequested,
base::Unretained(this)));
return https_server_->Start();
}
void CheckCertificateProvidedByExtension(
const net::X509Certificate& certificate,
const extensions::Extension& extension) {
bool is_currently_provided = false;
std::string provider_extension_id;
cert_provider_service_->LookUpCertificate(
certificate, &is_currently_provided, &provider_extension_id);
EXPECT_TRUE(is_currently_provided);
EXPECT_EQ(provider_extension_id, extension.id());
}
void CheckCertificateAbsent(const net::X509Certificate& certificate) {
bool is_currently_provided = true;
std::string provider_extension_id;
cert_provider_service_->LookUpCertificate(
certificate, &is_currently_provided, &provider_extension_id);
EXPECT_FALSE(is_currently_provided);
}
std::vector<scoped_refptr<net::X509Certificate>>
GetAllProvidedCertificates() {
std::unique_ptr<chromeos::CertificateProvider> cert_provider =
cert_provider_service_->CreateCertificateProvider();
base::test::TestFuture<net::ClientCertIdentityList> get_certificates_future;
cert_provider->GetCertificates(get_certificates_future.GetCallback());
std::vector<scoped_refptr<net::X509Certificate>> all_provided_certificates;
for (const auto& cert_identity : get_certificates_future.Get()) {
all_provided_certificates.push_back(cert_identity->certificate());
}
return all_provided_certificates;
}
GURL GetHttpsClientCertUrl() const {
return https_server_->GetURL(kClientCertUrl);
}
protected:
testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
raw_ptr<chromeos::CertificateProviderService, AcrossTasksDanglingUntriaged>
cert_provider_service_ = nullptr;
policy::PolicyMap policy_map_;
private:
const char* const kClientCertUrl = "/client-cert";
std::unique_ptr<net::test_server::HttpResponse> OnHttpsServerRequested(
const net::test_server::HttpRequest& request) const {
if (request.relative_url != kClientCertUrl)
return nullptr;
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (!request.ssl_info || !request.ssl_info->cert) {
response->set_code(net::HTTP_FORBIDDEN);
return response;
}
response->set_content("got client cert with fingerprint: " +
GetCertFingerprint1(*request.ssl_info->cert));
response->set_content_type("text/plain");
return response;
}
std::unique_ptr<JsFailureObserver> js_failure_observer_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
};
// Tests the API with a test extension in place. Tests can cause the extension
// to trigger API calls.
class CertificateProviderApiMockedExtensionTest
: public CertificateProviderApiTest {
public:
void SetUpInProcessBrowserTestFixture() override {
#if BUILDFLAG(IS_CHROMEOS_LACROS) // Needed for ClientCertStoreLacros
CertDbInitializerFactory::GetInstance()
->SetCreateWithBrowserContextForTesting(
/*should_create=*/true);
#endif
CertificateProviderApiTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
CertificateProviderApiTest::SetUpOnMainThread();
extension_path_ = test_data_dir_.AppendASCII("certificate_provider");
extension_ = LoadExtension(extension_path_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension_->GetResourceURL("basic.html")));
extension_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
std::string raw_certificate = GetCertificateData();
std::vector<uint8_t> certificate_bytes(raw_certificate.begin(),
raw_certificate.end());
ExecuteJavascript("initialize(" + JsUint8Array(certificate_bytes) + ");");
}
content::RenderFrameHost* GetExtensionMainFrame() const {
return extension_contents_->GetPrimaryMainFrame();
}
void ExecuteJavascript(const std::string& function) const {
ASSERT_TRUE(content::ExecJs(GetExtensionMainFrame(), function));
}
// Calls |function| in the extension. |function| needs to return a bool or a
// Promise<bool>. If it returns a Promise<bool>, this waits for the promise to
// resolve.
void ExecuteJavascriptAndWaitForCallback(const std::string& function) const {
ASSERT_EQ(true, content::EvalJs(GetExtensionMainFrame(), function));
}
const extensions::Extension* extension() const { return extension_; }
std::string GetKeyPk8() const {
std::string key_pk8;
base::ScopedAllowBlockingForTesting allow_io;
EXPECT_TRUE(base::ReadFileToString(
extension_path_.AppendASCII("l1_leaf.pk8"), &key_pk8));
return key_pk8;
}
// Returns the certificate stored in
// chrome/test/data/extensions/api_test/certificate_provider
scoped_refptr<net::X509Certificate> GetCertificate() const {
std::string raw_certificate = GetCertificateData();
return net::X509Certificate::CreateFromBytes(
base::as_bytes(base::make_span(raw_certificate)));
}
// Tests the api by navigating to a webpage that requests to perform a
// signature operation with the available certificate.
// This signs the request using the algorithm specified by
// `openssl_signature_algorithm`, with additionally hashing it if
// `is_raw_data` is true, and replies to the page.
void TestNavigationToCertificateRequestingWebPage(
const std::string& expected_request_signature_algorithm,
uint16_t openssl_signature_algorithm,
bool is_raw_data) {
content::TestNavigationObserver navigation_observer(
nullptr /* no WebContents */);
navigation_observer.StartWatchingNewWebContents();
ExtensionTestMessageListener sign_digest_listener(
"signature request received");
// Navigate to a page which triggers a sign request. Navigation is blocked
// by completion of this request, so we don't wait for navigation to finish.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GetHttpsClientCertUrl(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
content::WebContents* const https_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Wait for the extension to receive the sign request.
ASSERT_TRUE(sign_digest_listener.WaitUntilSatisfied());
EXPECT_GT(cert_provider_service_->pin_dialog_manager()
->StoredSignRequestsForTesting(),
0);
// Check that the certificate is available.
scoped_refptr<net::X509Certificate> certificate = GetCertificate();
CheckCertificateProvidedByExtension(*certificate, *extension());
// Fetch the data from the sign request.
EXPECT_EQ(
expected_request_signature_algorithm,
content::EvalJs(GetExtensionMainFrame(), "signatureRequestAlgorithm;"));
base::test::TestFuture<base::Value> exec_js_future;
GetExtensionMainFrame()->ExecuteJavaScriptForTests(
u"signatureRequestData;", exec_js_future.GetCallback(),
content::ISOLATED_WORLD_ID_GLOBAL);
std::vector<uint8_t> request_data(exec_js_future.Get().GetBlob());
// Load the private key.
std::string key_pk8 = GetKeyPk8();
std::unique_ptr<crypto::RSAPrivateKey> key(
crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(
base::as_bytes(base::make_span(key_pk8))));
ASSERT_TRUE(key);
// Sign using the private key.
std::vector<uint8_t> signature;
if (is_raw_data) {
EXPECT_TRUE(RsaSignRawData(openssl_signature_algorithm, request_data,
key.get(), &signature));
} else {
EXPECT_TRUE(RsaSignPrehashed(openssl_signature_algorithm, request_data,
key.get(), &signature));
}
// Inject the signature back to the extension and let it reply.
ExecuteJavascript("replyWithSignature(" + JsUint8Array(signature) + ");");
// Wait for the https navigation to finish.
navigation_observer.Wait();
// Make sure that sign request is removed from pin dialog manager.
EXPECT_EQ(cert_provider_service_->pin_dialog_manager()
->StoredSignRequestsForTesting(),
0);
// Check whether the server acknowledged that a client certificate was
// presented.
const std::string client_cert_fingerprint =
GetCertFingerprint1(*certificate);
EXPECT_EQ(GetPageTextContent(https_contents),
"got client cert with fingerprint: " + client_cert_fingerprint);
}
void SetInterstitialBypass() {
// Navigate to the test server in a new tab (to not clobber the test
// fixture setup.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GetHttpsClientCertUrl(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
// Proceed through the interstitial to set an SSL bypass for this host.
content::TestNavigationObserver nav_observer(tab,
/*number_of_navigations=*/1);
ASSERT_TRUE(content::ExecJs(
tab, "window.certificateErrorPageController.proceed();"));
nav_observer.Wait();
// Close the new tab to go back to the state set up by SetUpOnMainThread().
tab->Close();
}
private:
std::string GetCertificateData() const {
const base::FilePath certificate_path =
test_data_dir_.AppendASCII("certificate_provider")
.AppendASCII("l1_leaf.der");
std::string certificate_data;
base::ScopedAllowBlockingForTesting allow_io;
EXPECT_TRUE(base::ReadFileToString(certificate_path, &certificate_data));
return certificate_data;
}
raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged>
extension_contents_ = nullptr;
raw_ptr<const extensions::Extension, AcrossTasksDanglingUntriaged>
extension_ = nullptr;
base::FilePath extension_path_;
};
class CertificateProviderRequestPinTest : public CertificateProviderApiTest {
protected:
static constexpr int kFakeSignRequestId = 123;
static constexpr int kWrongPinAttemptsLimit = 3;
static constexpr const char* kCorrectPin = "1234";
static constexpr const char* kWrongPin = "567";
void SetUpOnMainThread() override {
CertificateProviderApiTest::SetUpOnMainThread();
command_request_listener_ = std::make_unique<ExtensionTestMessageListener>(
"GetCommand", ReplyBehavior::kWillReply);
LoadRequestPinExtension();
}
void TearDownOnMainThread() override {
if (command_request_listener_->was_satisfied()) {
// Avoid destroying a non-replied extension function without.
command_request_listener_->Reply(/*message=*/std::string());
}
command_request_listener_.reset();
CertificateProviderApiTest::TearDownOnMainThread();
}
std::string pin_request_extension_id() const { return extension_->id(); }
void AddFakeSignRequest(int sign_request_id) {
cert_provider_service_->pin_dialog_manager()->AddSignRequestId(
extension_->id(), sign_request_id, {});
}
void NavigateTo(const std::string& test_page_file_name) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), extension_->GetResourceURL(test_page_file_name)));
}
RequestPinView* GetActivePinDialogView() {
return cert_provider_service_->pin_dialog_manager()
->default_dialog_host_for_testing()
->active_view_for_testing();
}
views::Widget* GetActivePinDialogWindow() {
return cert_provider_service_->pin_dialog_manager()
->default_dialog_host_for_testing()
->active_window_for_testing();
}
// Enters the code in the ShowPinDialog window and pushes the OK event.
void EnterCode(const std::string& code) {
GetActivePinDialogView()->textfield_for_testing()->SetText(
base::ASCIIToUTF16(code));
GetActivePinDialogView()->Accept();
base::RunLoop().RunUntilIdle();
}
// Enters the valid code for extensions from local example folders, in the
// ShowPinDialog window and waits for the window to close. The extension code
// is expected to send "Success" message after the validation and request to
// stopPinRequest is done.
void EnterCorrectPinAndWaitForMessage() {
ExtensionTestMessageListener listener("Success");
EnterCode(kCorrectPin);
ASSERT_TRUE(listener.WaitUntilSatisfied());
}
// Enters an invalid code for extensions from local example folders, in the
// ShowPinDialog window and waits for the window to update with the error. The
// extension code is expected to send "Invalid PIN" message after the
// validation and the new requestPin (with the error) is done.
void EnterWrongPinAndWaitForMessage() {
ExtensionTestMessageListener listener("Invalid PIN");
EnterCode(kWrongPin);
ASSERT_TRUE(listener.WaitUntilSatisfied());
// Check that we have an error message displayed.
EXPECT_TRUE(
GetActivePinDialogView()->IsTextStyleOfErrorLabelCorrectForTesting());
}
bool SendCommand(const std::string& command) {
if (!command_request_listener_->WaitUntilSatisfied())
return false;
command_request_listener_->Reply(command);
command_request_listener_->Reset();
return true;
}
bool SendCommandAndWaitForMessage(const std::string& command,
const std::string& expected_message) {
ExtensionTestMessageListener listener(expected_message);
if (!SendCommand(command))
return false;
return listener.WaitUntilSatisfied();
}
private:
void LoadRequestPinExtension() {
const base::FilePath extension_path =
test_data_dir_.AppendASCII("certificate_provider/request_pin");
extension_ = LoadExtension(extension_path);
}
raw_ptr<const extensions::Extension, AcrossTasksDanglingUntriaged>
extension_ = nullptr;
std::unique_ptr<ExtensionTestMessageListener> command_request_listener_;
};
} // namespace
// Tests an extension that only provides certificates in response to the
// onCertificatesUpdateRequested event.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
ResponsiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsCertificateProvider();");
ExecuteJavascript("registerForSignatureRequests();");
TestNavigationToCertificateRequestingWebPage(
"RSASSA_PKCS1_v1_5_SHA1", SSL_SIGN_RSA_PKCS1_SHA1, /*is_raw_data=*/true);
}
// Tests an extension that only provides certificates in response to the
// legacy onCertificatesRequested event.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyResponsiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
TestNavigationToCertificateRequestingWebPage("SHA1", SSL_SIGN_RSA_PKCS1_SHA1,
/*is_raw_data=*/false);
}
// Tests that signing a request twice in response to the legacy
// onSignDigestRequested event will fail.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyExtensionSigningTwice) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
// This causes a signature request that will be replied to.
TestNavigationToCertificateRequestingWebPage("SHA1", SSL_SIGN_RSA_PKCS1_SHA1,
/*is_raw_data=*/false);
// Replying to the signature request a second time must fail.
ASSERT_EQ(false, content::EvalJs(GetExtensionMainFrame(),
"replyWithSignatureSecondTime();"));
}
// Tests an extension that provides certificates both proactively with
// setCertificates() and in response to onCertificatesUpdateRequested.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
ProactiveAndResponsiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsCertificateProvider();");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
scoped_refptr<net::X509Certificate> certificate = GetCertificate();
CheckCertificateProvidedByExtension(*certificate, *extension());
TestNavigationToCertificateRequestingWebPage(
"RSASSA_PKCS1_v1_5_SHA1", SSL_SIGN_RSA_PKCS1_SHA1, /*is_raw_data=*/true);
// Remove the certificate.
ExecuteJavascriptAndWaitForCallback("unsetCertificates();");
CheckCertificateAbsent(*certificate);
}
// Tests an extension that provides certificates both proactively with
// setCertificates() and in response to the legacy onCertificatesRequested.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
ProactiveAndLegacyResponsiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
scoped_refptr<net::X509Certificate> certificate = GetCertificate();
CheckCertificateProvidedByExtension(*certificate, *extension());
TestNavigationToCertificateRequestingWebPage("SHA1", SSL_SIGN_RSA_PKCS1_SHA1,
/*is_raw_data=*/false);
// Remove the certificate.
ExecuteJavascriptAndWaitForCallback("unsetCertificates();");
CheckCertificateAbsent(*certificate);
}
// Tests an extension that provides certificates both proactively with
// setCertificates() and in response to both events:
// onCertificatesUpdateRequested and legacy onCertificatesRequested. Verify that
// the non-legacy signature event is used.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
ProactiveAndRedundantLegacyResponsiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerAsCertificateProvider();");
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascript("registerForLegacySignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
scoped_refptr<net::X509Certificate> certificate = GetCertificate();
CheckCertificateProvidedByExtension(*certificate, *extension());
TestNavigationToCertificateRequestingWebPage(
"RSASSA_PKCS1_v1_5_SHA1", SSL_SIGN_RSA_PKCS1_SHA1, /*is_raw_data=*/true);
// Remove the certificate.
ExecuteJavascriptAndWaitForCallback("unsetCertificates();");
CheckCertificateAbsent(*certificate);
}
// Tests an extension that only provides certificates proactively via
// setCertificates().
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
ProactiveExtension) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
scoped_refptr<net::X509Certificate> certificate = GetCertificate();
CheckCertificateProvidedByExtension(*certificate, *extension());
EXPECT_EQ(GetAllProvidedCertificates().size(), 1U);
TestNavigationToCertificateRequestingWebPage(
"RSASSA_PKCS1_v1_5_SHA1", SSL_SIGN_RSA_PKCS1_SHA1, /*is_raw_data=*/true);
// Remove the certificate.
ExecuteJavascriptAndWaitForCallback("unsetCertificates();");
CheckCertificateAbsent(*certificate);
EXPECT_TRUE(GetAllProvidedCertificates().empty());
}
// Tests that all of invalid certificates are rejected.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
OnlyInvalidCertificates) {
ExecuteJavascriptAndWaitForCallback("setInvalidCertificates();");
EXPECT_TRUE(GetAllProvidedCertificates().empty());
}
// Tests the RSA SHA-1 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest, RsaSha1) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA1'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PKCS1_v1_5_SHA1",
SSL_SIGN_RSA_PKCS1_SHA1,
/*is_raw_data=*/true);
}
// Tests the RSA SHA-1 signature algorithm using the legacy version of the API.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyRsaSha1) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedLegacyHashes = ['SHA1'];");
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
TestNavigationToCertificateRequestingWebPage("SHA1", SSL_SIGN_RSA_PKCS1_SHA1,
/*is_raw_data=*/false);
}
// Tests the RSA SHA-256 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest, RsaSha256) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA256'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PKCS1_v1_5_SHA256",
SSL_SIGN_RSA_PKCS1_SHA256,
/*is_raw_data=*/true);
}
// Tests the RSA SHA-256 signature algorithm using the legacy version of the
// API.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyRsaSha256) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedLegacyHashes = ['SHA256'];");
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
TestNavigationToCertificateRequestingWebPage("SHA256",
SSL_SIGN_RSA_PKCS1_SHA256,
/*is_raw_data=*/false);
}
// Tests the RSA SHA-384 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest, RsaSha384) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA384'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PKCS1_v1_5_SHA384",
SSL_SIGN_RSA_PKCS1_SHA384,
/*is_raw_data=*/true);
}
// Tests the RSA SHA-384 signature algorithm using the legacy version of the
// API.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyRsaSha384) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedLegacyHashes = ['SHA384'];");
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
TestNavigationToCertificateRequestingWebPage("SHA384",
SSL_SIGN_RSA_PKCS1_SHA384,
/*is_raw_data=*/false);
}
// Tests the RSA SHA-512 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest, RsaSha512) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA512'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PKCS1_v1_5_SHA512",
SSL_SIGN_RSA_PKCS1_SHA512,
/*is_raw_data=*/true);
}
// Tests the RSA SHA-512 signature algorithm using the legacy version of the
// API.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
LegacyRsaSha512) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript("supportedLegacyHashes = ['SHA512'];");
ExecuteJavascript("registerAsLegacyCertificateProvider();");
ExecuteJavascript("registerForLegacySignatureRequests();");
TestNavigationToCertificateRequestingWebPage("SHA512",
SSL_SIGN_RSA_PKCS1_SHA512,
/*is_raw_data=*/false);
}
// Tests that the RSA SHA-512 signature algorithm is still used when there are
// other, less strong, algorithms specified after it.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
RsaSha512AndOthers) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
ExecuteJavascript(
"supportedAlgorithms = ['RSASSA_PKCS1_v1_5_SHA512', "
"'RSASSA_PKCS1_v1_5_SHA1', 'RSASSA_PKCS1_v1_5_MD5_SHA1'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PKCS1_v1_5_SHA512",
SSL_SIGN_RSA_PKCS1_SHA512,
/*is_raw_data=*/true);
}
// Tests the RSA-PSS SHA-256 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
RsaPssSha256) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_3));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PSS_SHA256'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PSS_SHA256",
SSL_SIGN_RSA_PSS_RSAE_SHA256,
/*is_raw_data=*/true);
}
// Tests the RSA-PSS SHA-384 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
RsaPssSha384) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_3));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PSS_SHA384'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PSS_SHA384",
SSL_SIGN_RSA_PSS_RSAE_SHA384,
/*is_raw_data=*/true);
}
// Tests the RSA-PSS SHA-512 signature algorithm.
IN_PROC_BROWSER_TEST_F(CertificateProviderApiMockedExtensionTest,
RsaPssSha512) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_3));
ExecuteJavascript("supportedAlgorithms = ['RSASSA_PSS_SHA512'];");
ExecuteJavascript("registerForSignatureRequests();");
ExecuteJavascriptAndWaitForCallback("setCertificates();");
TestNavigationToCertificateRequestingWebPage("RSASSA_PSS_SHA512",
SSL_SIGN_RSA_PSS_RSAE_SHA512,
/*is_raw_data=*/true);
}
// Test that the certificateProvider events are delivered correctly in the
// scenario when the event listener is in a lazy background page that gets idle.
// Disabled due to flakiness - https://crbug.com/1279724
IN_PROC_BROWSER_TEST_F(CertificateProviderApiTest,
DISABLED_LazyBackgroundPage) {
ASSERT_TRUE(StartHttpsServer(net::SSL_PROTOCOL_VERSION_TLS1_2));
// Make extension background pages idle immediately.
extensions::ProcessManager::SetEventPageIdleTimeForTesting(1);
extensions::ProcessManager::SetEventPageSuspendingTimeForTesting(1);
// Load the test extension.
ash::TestCertificateProviderExtension test_certificate_provider_extension(
profile());
extensions::ExtensionHostTestHelper host_helper(
profile(), ash::TestCertificateProviderExtension::extension_id());
host_helper.RestrictToType(
extensions::mojom::ViewType::kExtensionBackgroundPage);
const extensions::Extension* const extension =
LoadExtension(base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
.AppendASCII("extensions")
.AppendASCII("test_certificate_provider")
.AppendASCII("extension"));
ASSERT_TRUE(extension);
EXPECT_EQ(extension->id(),
ash::TestCertificateProviderExtension::extension_id());
host_helper.WaitForHostCompletedFirstLoad();
// Navigate to the page that requests the client authentication. Use the
// incognito profile in order to force re-authentication in the later request
// made by the test.
const std::string client_cert_fingerprint = GetCertFingerprint1(
*ash::TestCertificateProviderExtension::GetCertificate());
Browser* const incognito_browser = CreateIncognitoBrowser(profile());
ASSERT_TRUE(incognito_browser);
ui_test_utils::NavigateToURLWithDisposition(
incognito_browser, GetHttpsClientCertUrl(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
EXPECT_EQ(test_certificate_provider_extension.certificate_request_count(), 1);
EXPECT_EQ(GetPageTextContent(
incognito_browser->tab_strip_model()->GetActiveWebContents()),
"got client cert with fingerprint: " + client_cert_fingerprint);
CheckCertificateProvidedByExtension(
*ash::TestCertificateProviderExtension::GetCertificate(), *extension);
// Let the extension's background page become idle.
WaitForExtensionIdle(extension->id());
// Navigate again to the page with the client authentication. The extension
// gets awakened and handles the request.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GetHttpsClientCertUrl(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
EXPECT_EQ(test_certificate_provider_extension.certificate_request_count(), 2);
EXPECT_EQ(
GetPageTextContent(browser()->tab_strip_model()->GetActiveWebContents()),
"got client cert with fingerprint: " + client_cert_fingerprint);
}
// User enters the correct PIN.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ShowPinDialogAccept) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("basic.html");
// Enter the valid PIN.
EnterCorrectPinAndWaitForMessage();
// The view should be set to nullptr when the window is closed.
EXPECT_FALSE(GetActivePinDialogView());
}
// User closes the dialog kMaxClosedDialogsPerMinute times, and the extension
// should be blocked from showing it again.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ShowPinDialogClose) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("basic.html");
for (int i = 0;
i < extensions::api::certificate_provider::kMaxClosedDialogsPerMinute;
i++) {
ExtensionTestMessageListener listener("User closed the dialog");
GetActivePinDialogWindow()->Close();
ASSERT_TRUE(listener.WaitUntilSatisfied());
}
ExtensionTestMessageListener close_listener("User closed the dialog",
ReplyBehavior::kWillReply);
GetActivePinDialogWindow()->Close();
ASSERT_TRUE(close_listener.WaitUntilSatisfied());
close_listener.Reply("GetLastError");
ExtensionTestMessageListener last_error_listener(
"This request exceeds the MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.");
ASSERT_TRUE(last_error_listener.WaitUntilSatisfied());
EXPECT_FALSE(GetActivePinDialogView());
}
// User enters a wrong PIN first and a correct PIN on the second try.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
ShowPinDialogWrongPin) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("basic.html");
EnterWrongPinAndWaitForMessage();
// The window should be active.
EXPECT_TRUE(GetActivePinDialogWindow()->IsVisible());
EXPECT_TRUE(GetActivePinDialogView());
// Enter the valid PIN.
EnterCorrectPinAndWaitForMessage();
// The view should be set to nullptr when the window is closed.
EXPECT_FALSE(GetActivePinDialogView());
}
// User enters wrong PIN three times.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
ShowPinDialogWrongPinThreeTimes) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("basic.html");
for (int i = 0; i < kWrongPinAttemptsLimit; i++)
EnterWrongPinAndWaitForMessage();
// The textfield has to be disabled, as extension does not allow input now.
EXPECT_FALSE(GetActivePinDialogView()->textfield_for_testing()->GetEnabled());
// Close the dialog.
ExtensionTestMessageListener listener("No attempt left");
GetActivePinDialogWindow()->Close();
ASSERT_TRUE(listener.WaitUntilSatisfied());
EXPECT_FALSE(GetActivePinDialogView());
}
// User closes the dialog while the extension is processing the request.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
ShowPinDialogCloseWhileProcessing) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request1:begun"));
ExtensionTestMessageListener listener(
base::StringPrintf("request1:success:%s", kWrongPin));
EnterCode(kWrongPin);
EXPECT_TRUE(listener.WaitUntilSatisfied());
GetActivePinDialogWindow()->Close();
base::RunLoop().RunUntilIdle();
// The view should be set to nullptr when the window is closed.
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension closes the dialog kMaxClosedDialogsPerMinute times after the user
// inputs some value, and it should be blocked from showing it again.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
RepeatedProgrammaticCloseAfterInput) {
NavigateTo("operated.html");
for (int i = 0;
i <
extensions::api::certificate_provider::kMaxClosedDialogsPerMinute + 1;
i++) {
AddFakeSignRequest(kFakeSignRequestId);
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request", base::StringPrintf("request%d:begun", i + 1)));
EnterCode(kCorrectPin);
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Stop", base::StringPrintf("stop%d:success", i + 1)));
EXPECT_FALSE(GetActivePinDialogView());
}
AddFakeSignRequest(kFakeSignRequestId);
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request",
base::StringPrintf(
"request%d:error:This request exceeds the "
"MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.",
extensions::api::certificate_provider::kMaxClosedDialogsPerMinute +
2)));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension erroneously attempts to close the PIN dialog twice.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, DoubleClose) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommand("Request"));
EXPECT_TRUE(SendCommandAndWaitForMessage("Stop", "stop1:success"));
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Stop", "stop2:error:No active dialog from extension."));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension closes the dialog kMaxClosedDialogsPerMinute times before the user
// inputs anything, and it should be blocked from showing it again.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
RepeatedProgrammaticCloseBeforeInput) {
NavigateTo("operated.html");
for (int i = 0;
i <
extensions::api::certificate_provider::kMaxClosedDialogsPerMinute + 1;
i++) {
AddFakeSignRequest(kFakeSignRequestId);
EXPECT_TRUE(SendCommand("Request"));
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Stop", base::StringPrintf("stop%d:success", i + 1)));
EXPECT_FALSE(GetActivePinDialogView());
}
AddFakeSignRequest(kFakeSignRequestId);
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request",
base::StringPrintf(
"request%d:error:This request exceeds the "
"MAX_PIN_DIALOGS_CLOSED_PER_MINUTE quota.",
extensions::api::certificate_provider::kMaxClosedDialogsPerMinute +
2)));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension erroneously attempts to stop the PIN request with an error before
// the user provided any input.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
StopWithErrorBeforeInput) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommand("Request"));
EXPECT_TRUE(SendCommandAndWaitForMessage(
"StopWithUnknownError", "stop1:error:No user input received"));
EXPECT_TRUE(GetActivePinDialogView()->textfield_for_testing()->GetEnabled());
}
// Extension erroneously uses an invalid sign request ID.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, InvalidRequestId) {
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request", "request1:error:Invalid signRequestId"));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension specifies zero left attempts in the very first PIN request.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ZeroAttemptsAtStart) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("RequestWithZeroAttempts",
"request1:begun"));
// The textfield has to be disabled, as there are no attempts left.
EXPECT_FALSE(GetActivePinDialogView()->textfield_for_testing()->GetEnabled());
ExtensionTestMessageListener listener("request1:empty");
GetActivePinDialogWindow()->Close();
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
// Extension erroneously passes a negative attempts left count.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, NegativeAttempts) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage(
"RequestWithNegativeAttempts", "request1:error:Invalid attemptsLeft"));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension erroneously attempts to close a non-existing dialog.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, CloseNonExisting) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Stop", "stop1:error:No active dialog from extension."));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension erroneously attempts to stop a non-existing dialog with an error.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, StopNonExisting) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage(
"StopWithUnknownError", "stop1:error:No active dialog from extension."));
EXPECT_FALSE(GetActivePinDialogView());
}
// Extension erroneously attempts to start or stop the PIN request before the
// user closed the previously stopped with an error PIN request.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
UpdateAlreadyStopped) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request1:begun"));
EnterCode(kWrongPin);
EXPECT_TRUE(SendCommand("StopWithUnknownError"));
EXPECT_TRUE(SendCommandAndWaitForMessage(
"StopWithUnknownError", "stop2:error:No user input received"));
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request", "request2:error:Previous request not finished"));
EXPECT_FALSE(GetActivePinDialogView()->textfield_for_testing()->GetEnabled());
}
// Extension starts a new PIN request after it stopped the previous one with an
// error.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, StartAfterStop) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request1:begun"));
EnterCode(kWrongPin);
EXPECT_TRUE(SendCommandAndWaitForMessage("Stop", "stop1:success"));
EXPECT_FALSE(GetActivePinDialogView());
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request2:begun"));
ExtensionTestMessageListener listener(
base::StringPrintf("request2:success:%s", kCorrectPin));
EnterCode(kCorrectPin);
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_FALSE(GetActivePinDialogView()->textfield_for_testing()->GetEnabled());
}
// Test that no quota is applied to the first PIN requests for each requestId.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest,
RepeatedCloseWithDifferentIds) {
NavigateTo("operated.html");
for (int i = 0;
i <
extensions::api::certificate_provider::kMaxClosedDialogsPer10Minutes + 2;
i++) {
AddFakeSignRequest(kFakeSignRequestId + i);
EXPECT_TRUE(SendCommandAndWaitForMessage(
"Request", base::StringPrintf("request%d:begun", i + 1)));
ExtensionTestMessageListener listener(
base::StringPrintf("request%d:empty", i + 1));
ASSERT_TRUE(GetActivePinDialogView());
GetActivePinDialogView()->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kCloseButtonClicked);
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_FALSE(GetActivePinDialogView());
EXPECT_TRUE(SendCommand("IncrementRequestId"));
}
}
// Test that disabling the extension closes its PIN dialog.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ExtensionDisable) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request1:begun"));
EXPECT_TRUE(GetActivePinDialogView());
extensions::TestExtensionRegistryObserver registry_observer(
extensions::ExtensionRegistry::Get(profile()),
pin_request_extension_id());
extensions::ExtensionSystem::Get(profile())
->extension_service()
->DisableExtension(pin_request_extension_id(),
extensions::disable_reason::DISABLE_USER_ACTION);
registry_observer.WaitForExtensionUnloaded();
// Let the events from the extensions subsystem propagate to the code that
// manages the PIN dialog.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetActivePinDialogView());
}
// Test that reloading the extension closes its PIN dialog.
IN_PROC_BROWSER_TEST_F(CertificateProviderRequestPinTest, ExtensionReload) {
AddFakeSignRequest(kFakeSignRequestId);
NavigateTo("operated.html");
EXPECT_TRUE(SendCommandAndWaitForMessage("Request", "request1:begun"));
EXPECT_TRUE(GetActivePinDialogView());
// Create a second browser, in order to suppress Chrome shutdown logic when
// reloading the extension (as the tab with the extension's file gets closed).
CreateBrowser(profile());
// Trigger the chrome.runtime.reload() call from the extension.
extensions::TestExtensionRegistryObserver registry_observer(
extensions::ExtensionRegistry::Get(profile()),
pin_request_extension_id());
EXPECT_TRUE(SendCommand("Reload"));
registry_observer.WaitForExtensionUnloaded();
registry_observer.WaitForExtensionLoaded();
EXPECT_FALSE(GetActivePinDialogView());
}