Move certificateProvider out of //ash
This change is a necessary part of smart cards migration to Lacros.
Bug: 1291887
Change-Id: I418d617da74b4eda7a8d660f509c53926536cf4d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3645419
Reviewed-by: Hidehiko Abe <[email protected]>
Reviewed-by: Reilly Grant <[email protected]>
Commit-Queue: Oleksandr Kulkov <[email protected]>
Reviewed-by: Maksim Ivanov <[email protected]>
Reviewed-by: Vasilii Sukhanov <[email protected]>
Reviewed-by: Peter Kasting <[email protected]>
Reviewed-by: Denis Kuznetsov <[email protected]>
Reviewed-by: Ted Choc <[email protected]>
Reviewed-by: Alexander Alekseev <[email protected]>
Reviewed-by: David Benjamin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1010040}
diff --git a/chrome/browser/certificate_provider/DIR_METADATA b/chrome/browser/certificate_provider/DIR_METADATA
new file mode 100644
index 0000000..6bbb1c66
--- /dev/null
+++ b/chrome/browser/certificate_provider/DIR_METADATA
@@ -0,0 +1,3 @@
+monorail: {
+ component: "OS>Software>Enterprise>Smartcard"
+}
diff --git a/chrome/browser/certificate_provider/OWNERS b/chrome/browser/certificate_provider/OWNERS
new file mode 100644
index 0000000..325f7c3
--- /dev/null
+++ b/chrome/browser/certificate_provider/OWNERS
@@ -0,0 +1 @@
[email protected]
diff --git a/chrome/browser/certificate_provider/certificate_info.cc b/chrome/browser/certificate_provider/certificate_info.cc
new file mode 100644
index 0000000..19e1235
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_info.cc
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/certificate_info.h"
+
+#include "net/cert/x509_certificate.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+CertificateInfo::CertificateInfo() {}
+
+CertificateInfo::CertificateInfo(const CertificateInfo& other) = default;
+
+CertificateInfo::~CertificateInfo() {}
+
+bool CertificateInfo::operator==(const CertificateInfo& other) const {
+ return net::X509Certificate::CalculateFingerprint256(
+ this->certificate->cert_buffer()) ==
+ net::X509Certificate::CalculateFingerprint256(
+ other.certificate->cert_buffer()) &&
+ this->supported_algorithms == other.supported_algorithms;
+}
+
+} // namespace certificate_provider
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/certificate_info.h b/chrome/browser/certificate_provider/certificate_info.h
new file mode 100644
index 0000000..e6ffe8d
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_info.h
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_INFO_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_INFO_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/cert/x509_certificate.h"
+#include "net/ssl/ssl_private_key.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+// Holds all information of a certificate that must be synchronously available
+// to implement net::SSLPrivateKey.
+struct CertificateInfo {
+ CertificateInfo();
+ CertificateInfo(const CertificateInfo& other);
+ ~CertificateInfo();
+
+ bool operator==(const CertificateInfo& other) const;
+
+ scoped_refptr<net::X509Certificate> certificate;
+ // Contains the list of supported signature algorithms, using TLS 1.3's
+ // SignatureScheme values. See net::SSLPrivateKey documentation for details.
+ std::vector<uint16_t> supported_algorithms;
+};
+using CertificateInfoList = std::vector<CertificateInfo>;
+
+} // namespace certificate_provider
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_INFO_H_
diff --git a/chrome/browser/certificate_provider/certificate_provider.h b/chrome/browser/certificate_provider/certificate_provider.h
new file mode 100644
index 0000000..1d44c33
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider.h
@@ -0,0 +1,25 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_H_
+
+#include "net/ssl/client_cert_identity.h"
+
+namespace chromeos {
+
+class CertificateProvider {
+ public:
+ CertificateProvider() {}
+ CertificateProvider(const CertificateProvider&) = delete;
+ CertificateProvider& operator=(const CertificateProvider&) = delete;
+ virtual ~CertificateProvider() {}
+
+ virtual void GetCertificates(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback) = 0;
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_H_
diff --git a/chrome/browser/certificate_provider/certificate_provider_service.cc b/chrome/browser/certificate_provider/certificate_provider_service.cc
new file mode 100644
index 0000000..b0b5661
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider_service.cc
@@ -0,0 +1,492 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/certificate_provider_service.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_piece.h"
+#include "base/task/task_runner.h"
+#include "base/task/task_runner_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "chrome/browser/certificate_provider/certificate_provider.h"
+#include "net/base/net_errors.h"
+
+namespace chromeos {
+
+namespace {
+
+void PostSignResult(net::SSLPrivateKey::SignCallback callback,
+ net::Error error,
+ const std::vector<uint8_t>& signature) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), error, signature));
+}
+
+void PostIdentities(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback,
+ net::ClientCertIdentityList certs) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::move(certs)));
+}
+
+} // namespace
+
+class CertificateProviderService::CertificateProviderImpl
+ : public CertificateProvider {
+ public:
+ // This provider must be used on the same thread as the
+ // CertificateProviderService.
+ explicit CertificateProviderImpl(
+ const base::WeakPtr<CertificateProviderService>& service);
+
+ CertificateProviderImpl(const CertificateProviderImpl&) = delete;
+ CertificateProviderImpl& operator=(const CertificateProviderImpl&) = delete;
+
+ ~CertificateProviderImpl() override;
+
+ void GetCertificates(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback) override;
+
+ private:
+ const base::WeakPtr<CertificateProviderService> service_;
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// Implements an SSLPrivateKey backed by the signing function exposed by an
+// extension through the certificateProvider API.
+// Objects of this class must be used the CertificateProviderService's sequence.
+class CertificateProviderService::SSLPrivateKey : public net::SSLPrivateKey {
+ public:
+ SSLPrivateKey(const std::string& extension_id,
+ const CertificateInfo& cert_info,
+ const base::WeakPtr<CertificateProviderService>& service);
+
+ SSLPrivateKey(const SSLPrivateKey&) = delete;
+ SSLPrivateKey& operator=(const SSLPrivateKey&) = delete;
+
+ // net::SSLPrivateKey:
+ std::string GetProviderName() override;
+ std::vector<uint16_t> GetAlgorithmPreferences() override;
+ void Sign(uint16_t algorithm,
+ base::span<const uint8_t> input,
+ SignCallback callback) override;
+
+ private:
+ ~SSLPrivateKey() override;
+
+ const std::string extension_id_;
+ const CertificateInfo cert_info_;
+ const base::WeakPtr<CertificateProviderService> service_;
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+class CertificateProviderService::ClientCertIdentity
+ : public net::ClientCertIdentity {
+ public:
+ ClientCertIdentity(scoped_refptr<net::X509Certificate> cert,
+ base::WeakPtr<CertificateProviderService> service)
+ : net::ClientCertIdentity(std::move(cert)), service_(service) {}
+
+ ClientCertIdentity(const ClientCertIdentity&) = delete;
+ ClientCertIdentity& operator=(const ClientCertIdentity&) = delete;
+
+ ~ClientCertIdentity() override;
+
+ void AcquirePrivateKey(
+ base::OnceCallback<void(scoped_refptr<net::SSLPrivateKey>)>
+ private_key_callback) override;
+
+ private:
+ SEQUENCE_CHECKER(sequence_checker_);
+ const base::WeakPtr<CertificateProviderService> service_;
+};
+
+CertificateProviderService::ClientCertIdentity::~ClientCertIdentity() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CertificateProviderService::ClientCertIdentity::AcquirePrivateKey(
+ base::OnceCallback<void(scoped_refptr<net::SSLPrivateKey>)>
+ private_key_callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!service_) {
+ std::move(private_key_callback).Run(nullptr);
+ return;
+ }
+
+ bool is_currently_provided = false;
+ CertificateInfo info;
+ std::string extension_id;
+ // TODO(mattm): can the ClientCertIdentity store a handle directly to the
+ // extension instead of having to go through service_->certificate_map_ ?
+ service_->certificate_map_.LookUpCertificate(
+ *certificate(), &is_currently_provided, &info, &extension_id);
+ if (!is_currently_provided) {
+ std::move(private_key_callback).Run(nullptr);
+ return;
+ }
+
+ std::move(private_key_callback)
+ .Run(base::MakeRefCounted<SSLPrivateKey>(extension_id, info, service_));
+}
+
+CertificateProviderService::CertificateProviderImpl::CertificateProviderImpl(
+ const base::WeakPtr<CertificateProviderService>& service)
+ : service_(service) {}
+
+CertificateProviderService::CertificateProviderImpl::
+ ~CertificateProviderImpl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CertificateProviderService::CertificateProviderImpl::GetCertificates(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Avoid running the callback reentrantly.
+ callback = base::BindOnce(&PostIdentities, std::move(callback));
+
+ if (!service_) {
+ std::move(callback).Run(net::ClientCertIdentityList());
+ return;
+ }
+ service_->GetCertificatesFromExtensions(std::move(callback));
+}
+
+CertificateProviderService::SSLPrivateKey::SSLPrivateKey(
+ const std::string& extension_id,
+ const CertificateInfo& cert_info,
+ const base::WeakPtr<CertificateProviderService>& service)
+ : extension_id_(extension_id), cert_info_(cert_info), service_(service) {}
+
+std::string CertificateProviderService::SSLPrivateKey::GetProviderName() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return "extension \"" + extension_id_ + "\"";
+}
+
+std::vector<uint16_t>
+CertificateProviderService::SSLPrivateKey::GetAlgorithmPreferences() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return cert_info_.supported_algorithms;
+}
+
+void CertificateProviderService::SSLPrivateKey::Sign(
+ uint16_t algorithm,
+ base::span<const uint8_t> input,
+ SignCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // The Sign() method should not call its callback reentrantly, so wrap it in
+ // PostSignResult().
+ callback = base::BindOnce(&PostSignResult, std::move(callback));
+
+ if (!service_) {
+ std::move(callback).Run(net::ERR_FAILED, /*signature=*/{});
+ return;
+ }
+
+ service_->RequestSignatureFromExtension(
+ extension_id_, cert_info_.certificate, algorithm, input,
+ /*authenticating_user_account_id=*/{}, std::move(callback));
+}
+
+CertificateProviderService::SSLPrivateKey::~SSLPrivateKey() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+CertificateProviderService::CertificateProviderService() {}
+
+CertificateProviderService::~CertificateProviderService() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void CertificateProviderService::SetDelegate(
+ std::unique_ptr<Delegate> delegate) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!delegate_);
+ DCHECK(delegate);
+
+ delegate_ = std::move(delegate);
+}
+
+void CertificateProviderService::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+}
+
+void CertificateProviderService::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+void CertificateProviderService::SetCertificatesProvidedByExtension(
+ const std::string& extension_id,
+ const CertificateInfoList& certificate_infos) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ certificate_map_.UpdateCertificatesForExtension(extension_id,
+ certificate_infos);
+ for (auto& observer : observers_)
+ observer.OnCertificatesUpdated(extension_id, certificate_infos);
+}
+
+bool CertificateProviderService::SetExtensionCertificateReplyReceived(
+ const std::string& extension_id,
+ int cert_request_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ bool completed = false;
+ if (!certificate_requests_.SetExtensionReplyReceived(
+ extension_id, cert_request_id, &completed)) {
+ DLOG(WARNING) << "Unexpected reply of extension " << extension_id
+ << " to request " << cert_request_id;
+ return false;
+ }
+ if (completed) {
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback;
+ certificate_requests_.RemoveRequest(cert_request_id, &callback);
+ CollectCertificatesAndRun(std::move(callback));
+ }
+ return true;
+}
+
+bool CertificateProviderService::ReplyToSignRequest(
+ const std::string& extension_id,
+ int sign_request_id,
+ const std::vector<uint8_t>& signature) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // TODO(crbug.com/1046860): Remove logging after stabilizing the feature.
+ LOG(WARNING) << "Extension " << extension_id
+ << " replied to signature request " << sign_request_id
+ << ", size " << signature.size();
+
+ scoped_refptr<net::X509Certificate> certificate;
+ net::SSLPrivateKey::SignCallback callback;
+ if (!sign_requests_.RemoveRequest(extension_id, sign_request_id, &certificate,
+ &callback)) {
+ return false;
+ }
+ pin_dialog_manager_.RemoveSignRequest(extension_id, sign_request_id);
+
+ const net::Error error_code = signature.empty() ? net::ERR_FAILED : net::OK;
+ std::move(callback).Run(error_code, signature);
+
+ if (!signature.empty()) {
+ for (auto& observer : observers_)
+ observer.OnSignCompleted(certificate, extension_id);
+ }
+ return true;
+}
+
+bool CertificateProviderService::LookUpCertificate(
+ const net::X509Certificate& cert,
+ bool* has_extension,
+ std::string* extension_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ CertificateInfo unused_info;
+ return certificate_map_.LookUpCertificate(cert, has_extension, &unused_info,
+ extension_id);
+}
+
+std::unique_ptr<CertificateProvider>
+CertificateProviderService::CreateCertificateProvider() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ return std::make_unique<CertificateProviderImpl>(weak_factory_.GetWeakPtr());
+}
+
+void CertificateProviderService::OnExtensionUnregistered(
+ const std::string& extension_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ certificate_map_.RemoveExtension(extension_id);
+}
+
+void CertificateProviderService::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ certificate_map_.RemoveExtension(extension_id);
+
+ for (const int completed_cert_request_id :
+ certificate_requests_.DropExtension(extension_id)) {
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback;
+ certificate_requests_.RemoveRequest(completed_cert_request_id, &callback);
+ CollectCertificatesAndRun(std::move(callback));
+ }
+
+ for (auto& callback : sign_requests_.RemoveAllRequests(extension_id))
+ std::move(callback).Run(net::ERR_FAILED, std::vector<uint8_t>());
+
+ pin_dialog_manager_.ExtensionUnloaded(extension_id);
+}
+
+void CertificateProviderService::RequestSignatureBySpki(
+ const std::string& subject_public_key_info,
+ uint16_t algorithm,
+ base::span<const uint8_t> input,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ bool is_currently_provided = false;
+ CertificateInfo info;
+ std::string extension_id;
+ certificate_map_.LookUpCertificateBySpki(
+ subject_public_key_info, &is_currently_provided, &info, &extension_id);
+ if (!is_currently_provided) {
+ LOG(ERROR) << "no certificate with the specified spki was found";
+ std::move(callback).Run(net::ERR_FAILED, std::vector<uint8_t>());
+ return;
+ }
+
+ RequestSignatureFromExtension(extension_id, info.certificate, algorithm,
+ input, authenticating_user_account_id,
+ std::move(callback));
+}
+
+bool CertificateProviderService::LookUpSpki(
+ const std::string& subject_public_key_info,
+ std::vector<uint16_t>* supported_algorithms,
+ std::string* extension_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ bool is_currently_provided = false;
+ CertificateInfo info;
+ certificate_map_.LookUpCertificateBySpki(
+ subject_public_key_info, &is_currently_provided, &info, extension_id);
+ if (!is_currently_provided) {
+ LOG(ERROR) << "no certificate with the specified spki was found";
+ return false;
+ }
+ *supported_algorithms = info.supported_algorithms;
+ return true;
+}
+
+void CertificateProviderService::AbortSignatureRequestsForAuthenticatingUser(
+ const AccountId& authenticating_user_account_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ using ExtensionNameRequestIdPair =
+ certificate_provider::SignRequests::ExtensionNameRequestIdPair;
+
+ const std::vector<ExtensionNameRequestIdPair> sign_requests_to_abort =
+ sign_requests_.FindRequestsForAuthenticatingUser(
+ authenticating_user_account_id);
+
+ for (const ExtensionNameRequestIdPair& sign_request :
+ sign_requests_to_abort) {
+ const std::string& extension_id = sign_request.first;
+ const int sign_request_id = sign_request.second;
+
+ // TODO(crbug.com/1046860): Remove logging after stabilizing the feature.
+ LOG(WARNING) << "Aborting user login signature request from extension "
+ << extension_id << " id " << sign_request_id;
+
+ pin_dialog_manager_.RemoveSignRequest(extension_id, sign_request_id);
+
+ scoped_refptr<net::X509Certificate> certificate;
+ net::SSLPrivateKey::SignCallback sign_callback;
+ if (sign_requests_.RemoveRequest(extension_id, sign_request_id,
+ &certificate, &sign_callback)) {
+ std::move(sign_callback).Run(net::ERR_FAILED, std::vector<uint8_t>());
+ }
+ }
+}
+
+void CertificateProviderService::GetCertificatesFromExtensions(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ const std::vector<std::string> provider_extensions(
+ delegate_->CertificateProviderExtensions());
+
+ if (provider_extensions.empty()) {
+ DVLOG(2) << "No provider extensions left.";
+ // Note that there could still be unfinished requests to extensions that
+ // were previously registered.
+ CollectCertificatesAndRun(std::move(callback));
+ return;
+ }
+
+ const int cert_request_id = certificate_requests_.AddRequest(
+ provider_extensions, std::move(callback),
+ base::BindOnce(&CertificateProviderService::TerminateCertificateRequest,
+ base::Unretained(this)));
+
+ DVLOG(2) << "Start certificate request " << cert_request_id;
+ delegate_->BroadcastCertificateRequest(cert_request_id);
+}
+
+void CertificateProviderService::CollectCertificatesAndRun(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ net::ClientCertIdentityList client_cert_identity_list;
+ std::vector<scoped_refptr<net::X509Certificate>> certificates =
+ certificate_map_.GetCertificates();
+ for (const scoped_refptr<net::X509Certificate>& certificate : certificates) {
+ client_cert_identity_list.push_back(std::make_unique<ClientCertIdentity>(
+ certificate, weak_factory_.GetWeakPtr()));
+ }
+
+ std::move(callback).Run(std::move(client_cert_identity_list));
+}
+
+void CertificateProviderService::TerminateCertificateRequest(
+ int cert_request_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback;
+ if (!certificate_requests_.RemoveRequest(cert_request_id, &callback)) {
+ DLOG(WARNING) << "Request id " << cert_request_id << " unknown.";
+ return;
+ }
+
+ DVLOG(1) << "Time out certificate request " << cert_request_id;
+ CollectCertificatesAndRun(std::move(callback));
+}
+
+void CertificateProviderService::RequestSignatureFromExtension(
+ const std::string& extension_id,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ uint16_t algorithm,
+ base::span<const uint8_t> input,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ const int sign_request_id = sign_requests_.AddRequest(
+ extension_id, certificate, authenticating_user_account_id,
+ std::move(callback));
+
+ // TODO(crbug.com/1046860): Remove logging after stabilizing the feature.
+ LOG(WARNING) << "Starting signature request to extension " << extension_id
+ << " id " << sign_request_id;
+
+ pin_dialog_manager_.AddSignRequestId(extension_id, sign_request_id,
+ authenticating_user_account_id);
+ if (!delegate_->DispatchSignRequestToExtension(
+ extension_id, sign_request_id, algorithm, certificate, input)) {
+ // TODO(crbug.com/1046860): Remove logging after stabilizing the feature.
+ LOG(WARNING) << "Failed to dispatch signature request to extension "
+ << extension_id << " id " << sign_request_id;
+ scoped_refptr<net::X509Certificate> local_certificate;
+ sign_requests_.RemoveRequest(extension_id, sign_request_id,
+ &local_certificate, &callback);
+ pin_dialog_manager_.RemoveSignRequest(extension_id, sign_request_id);
+ std::move(callback).Run(net::ERR_FAILED, std::vector<uint8_t>());
+ }
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/certificate_provider_service.h b/chrome/browser/certificate_provider/certificate_provider_service.h
new file mode 100644
index 0000000..f4c0b535
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider_service.h
@@ -0,0 +1,280 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/span.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/certificate_provider/certificate_info.h"
+#include "chrome/browser/certificate_provider/certificate_requests.h"
+#include "chrome/browser/certificate_provider/pin_dialog_manager.h"
+#include "chrome/browser/certificate_provider/sign_requests.h"
+#include "chrome/browser/certificate_provider/thread_safe_certificate_map.h"
+#include "components/account_id/account_id.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "net/cert/x509_certificate.h"
+#include "net/ssl/client_cert_identity.h"
+#include "net/ssl/ssl_private_key.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+class CertificateProvider;
+
+// A keyed service that manages registrations of extensions as certificate
+// providers. It exposes all certificates that are provided by extensions
+// through a |CertificateProvider| object that can be created using
+// |CreateCertificateProvider()|. Private key handles are exposed through
+// net::ClientKeyStore. Sign operations are routed to the extension that exposed
+// the certificate.
+//
+// The typical order of execution is as follows:
+// 1. HTTPS server requests client certs or
+// chrome.platformKeys.selectClientCertificates is called.
+// 2. This starts the certificate request with ID x.
+// 3. All extensions registered for the event onClientCertificatesRequested are
+// notified, the exposed callback is bound to request ID x.
+// 4. Wait for all extensions to reply to request with ID x
+// or time out.
+// 5. Filter all certificates from extensions that replied to request with ID x
+// and from the platform.
+// 6. Show the selection dialog, user will select one.
+// 7. Create private key handle. As this call is not associated with a specific
+// certificate request it looks at the certificate list obtained by the most
+// recent certificate request (execution of 3-5), which may or may not have
+// had ID x.
+// 8. Sign() function of the key handle is called.
+// 9. Forward the sign request to the extension that registered the
+// certificate. This request has a new sign request ID y.
+// 10. Wait until the extension replies with the signature or fails the sign
+// request with ID y.
+// 11. Forward the signature or failure as result of the key handle's Sign()
+// function.
+class CertificateProviderService : public KeyedService {
+ public:
+ using CertificateInfo = certificate_provider::CertificateInfo;
+ using CertificateInfoList = certificate_provider::CertificateInfoList;
+
+ class Delegate {
+ public:
+ Delegate() {}
+
+ Delegate(const Delegate&) = delete;
+ Delegate& operator=(const Delegate&) = delete;
+
+ virtual ~Delegate() {}
+
+ // Returns the ids of the extensions that want to provide certificates and
+ // therefore want to be notified about certificate requests. This is called
+ // once per client certificate request by the net layer.
+ virtual std::vector<std::string> CertificateProviderExtensions() = 0;
+
+ // Broadcasts a certificate request with |cert_request_id| to all
+ // certificate provider extensions.
+ virtual void BroadcastCertificateRequest(int cert_request_id) = 0;
+
+ // Dispatches a sign request with the given arguments to the extension with
+ // id |extension_id|. |algorithm| is a TLS 1.3 SignatureScheme value. See
+ // net::SSLPrivateKey for details. Returns whether that extension is
+ // actually a listener for that event.
+ virtual bool DispatchSignRequestToExtension(
+ const std::string& extension_id,
+ int sign_request_id,
+ uint16_t algorithm,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ base::span<const uint8_t> input) = 0;
+ };
+
+ class Observer : public base::CheckedObserver {
+ public:
+ // Called when an extension updates the certificates it provides.
+ virtual void OnCertificatesUpdated(
+ const std::string& extension_id,
+ const CertificateInfoList& certificate_infos) {}
+
+ // Called when a sign request gets successfully completed.
+ virtual void OnSignCompleted(
+ const scoped_refptr<net::X509Certificate>& certificate,
+ const std::string& extension_id) {}
+ };
+
+ // |SetDelegate| must be called exactly once directly after construction.
+ CertificateProviderService();
+ CertificateProviderService(const CertificateProviderService&) = delete;
+ CertificateProviderService& operator=(const CertificateProviderService&) =
+ delete;
+ ~CertificateProviderService() override;
+
+ // Must be called exactly once after construction and before other methods are
+ // called. The delegate will be destroyed in the destructor of the service and
+ // not before, which allows to unregister observers (e.g. for
+ // OnExtensionUnloaded) in the delegate's destructor on behalf of the service.
+ void SetDelegate(std::unique_ptr<Delegate> delegate);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Updates the certificates provided by the extension with |extension_id| to
+ // be |certificates_infos|.
+ void SetCertificatesProvidedByExtension(
+ const std::string& extension_id,
+ const CertificateInfoList& certificate_infos);
+
+ // Must be called when an extension replied to a previous certificate
+ // request, after the new certificates were registered with
+ // SetCertificatesProvidedByExtension(). For each request, it is expected that
+ // every registered extension replies exactly once. |cert_request_id| must
+ // refer to a previously broadcast certificate request. Returns false if the
+ // request id is unknown or it was called before with the same combination of
+ // request id and extension id. E.g. the request could have timed out before
+ // an extension replies.
+ bool SetExtensionCertificateReplyReceived(const std::string& extension_id,
+ int cert_request_id);
+
+ // Must be called with the reply of an extension to a previous sign request.
+ // |sign_request_id| is provided in the reply of the extension and must refer
+ // to a previous sign request. The extension id must be provided, because
+ // not the sign request id alone but only the pair (extension id, sign request
+ // id) is unambiguous.
+ // If the signature could be calculated by the extension, |signature| is
+ // provided in the reply and should be the signature of the data sent in the
+ // sign request. Otherwise, in case of a failure, |signature| must be empty.
+ // Returns false if |sign_request_id| is not referring to a pending request.
+ bool ReplyToSignRequest(const std::string& extension_id,
+ int sign_request_id,
+ const std::vector<uint8_t>& signature);
+
+ // Returns whether this certificate was provided by any extension during the
+ // lifetime of this service. If this certificate is currently provided by an
+ // extension, sets |is_currently_provided| to true and |extension_id| to that
+ // extension's id. If this certificate was provided before but not anymore,
+ // |is_currently_provided| will be set to false and |extension_id| will not be
+ // modified.
+ bool LookUpCertificate(const net::X509Certificate& cert,
+ bool* is_currently_provided,
+ std::string* extension_id);
+
+ // Returns a CertificateProvider that always returns the latest list of
+ // certificates that are provided by all registered extensions. Therefore, it
+ // is sufficient to create the CertificateProvider once and then repeatedly
+ // call its |GetCertificates()|. The returned provider is valid even after the
+ // destruction of this service.
+ std::unique_ptr<CertificateProvider> CreateCertificateProvider();
+
+ // Called whenever the extension with id |extension_id| unregisters from
+ // receiving future certificate requests. This will clear certificates
+ // currently provided by the extension.
+ void OnExtensionUnregistered(const std::string& extension_id);
+
+ // Must be called if extension with id |extension_id| is unloaded and cannot
+ // serve certificates anymore. This should be called everytime the
+ // corresponding notification of the ExtensionRegistry is triggered.
+ void OnExtensionUnloaded(const std::string& extension_id);
+
+ // Requests the extension which provided the certificate identified by
+ // |subject_public_key_info| to sign the unhashed |input| with the
+ // corresponding private key. |algorithm| is a TLS 1.3 SignatureScheme value.
+ // See net::SSLPrivateKey for details. |callback| will be run with the reply
+ // of the extension or an error.
+ void RequestSignatureBySpki(
+ const std::string& subject_public_key_info,
+ uint16_t algorithm,
+ base::span<const uint8_t> input,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback);
+
+ // Looks up the certificate identified by |subject_public_key_info|. If any
+ // extension is currently providing such a certificate, fills |extension_id|,
+ // fills *|supported_algorithms| with the algorithms supported for that
+ // certificate, and returns true. Values used for |supported_algorithms| are
+ // TLS 1.3 SignatureSchemes. See net::SSLPrivateKey for details. If no
+ // extension is currently providing such a certificate, returns false.
+ bool LookUpSpki(const std::string& subject_public_key_info,
+ std::vector<uint16_t>* supported_algorithms,
+ std::string* extension_id);
+
+ // Aborts all signature requests and related PIN dialogs that are associated
+ // with the authentication of the given user.
+ void AbortSignatureRequestsForAuthenticatingUser(
+ const AccountId& authenticating_user_account_id);
+
+ PinDialogManager* pin_dialog_manager() { return &pin_dialog_manager_; }
+
+ private:
+ class ClientCertIdentity;
+ class CertificateProviderImpl;
+ class SSLPrivateKey;
+
+ // Requests the current list of certificates from every registered extension.
+ // Once all extensions replied or a timeout was reached, the internal
+ // |extension_to_certificates_| is updated and |callback| is run with the
+ // retrieved list of certificates.
+ void GetCertificatesFromExtensions(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback);
+
+ // Collects all currently available certificates and passes them to
+ // |callback|.
+ void CollectCertificatesAndRun(
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback);
+
+ // Terminates the certificate request with id |cert_request_id| by ignoring
+ // pending replies from extensions. Certificates that were already reported
+ // are processed.
+ void TerminateCertificateRequest(int cert_request_id);
+
+ // Requests extension with |extension_id| to sign the unhashed |input| with
+ // the private key certified by |certificate|. |algorithm| is a TLS 1.3
+ // SignatureScheme value. See net::SSLPrivateKey for details. |callback| will
+ // be run with the reply of the extension or an error.
+ void RequestSignatureFromExtension(
+ const std::string& extension_id,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ uint16_t algorithm,
+ base::span<const uint8_t> input,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback);
+
+ std::unique_ptr<Delegate> delegate_;
+
+ base::ObserverList<Observer> observers_;
+
+ // The object to manage the dialog displayed when requestPin is called by the
+ // extension.
+ PinDialogManager pin_dialog_manager_;
+
+ // State about all pending sign requests.
+ certificate_provider::SignRequests sign_requests_;
+
+ // Contains all pending certificate requests.
+ certificate_provider::CertificateRequests certificate_requests_;
+
+ // Contains all certificates that the extensions returned during the lifetime
+ // of this service. Each certificate is associated with the extension that
+ // reported the certificate in response to the most recent certificate
+ // request. If a certificate was reported previously but in the most recent
+ // responses, it is still cached but not loses it's association with any
+ // extension. This ensures that a certificate can't magically appear as
+ // platform certificate (e.g. in the client certificate selection dialog)
+ // after an extension doesn't report it anymore.
+ certificate_provider::ThreadSafeCertificateMap certificate_map_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ base::WeakPtrFactory<CertificateProviderService> weak_factory_{this};
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_H_
diff --git a/chrome/browser/certificate_provider/certificate_provider_service_factory.cc b/chrome/browser/certificate_provider/certificate_provider_service_factory.cc
new file mode 100644
index 0000000..cce0016
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider_service_factory.cc
@@ -0,0 +1,352 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/containers/span.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/values.h"
+#include "chrome/browser/certificate_provider/certificate_provider_service.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/common/extensions/api/certificate_provider.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "extensions/browser/event_listener_map.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/extension.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/ssl/ssl_private_key.h"
+#include "third_party/boringssl/src/include/openssl/digest.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
+
+namespace chromeos {
+
+namespace {
+
+namespace api_cp = extensions::api::certificate_provider;
+
+class DefaultDelegate : public CertificateProviderService::Delegate,
+ public extensions::EventRouter::Observer,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ // |event_router| may be null in tests.
+ DefaultDelegate(CertificateProviderService* service,
+ extensions::ExtensionRegistry* registry,
+ extensions::EventRouter* event_router);
+ DefaultDelegate(const DefaultDelegate&) = delete;
+ DefaultDelegate& operator=(const DefaultDelegate&) = delete;
+ ~DefaultDelegate() override;
+
+ // CertificateProviderService::Delegate:
+ std::vector<std::string> CertificateProviderExtensions() override;
+ void BroadcastCertificateRequest(int request_id) override;
+ bool DispatchSignRequestToExtension(
+ const std::string& extension_id,
+ int request_id,
+ uint16_t algorithm,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ base::span<const uint8_t> input) override;
+
+ // extensions::EventRouter::Observer:
+ void OnListenerAdded(const extensions::EventListenerInfo& details) override {}
+ void OnListenerRemoved(const extensions::EventListenerInfo& details) override;
+
+ // extensions::ExtensionRegistryObserver:
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UnloadedExtensionReason reason) override;
+
+ private:
+ // Returns extension IDs that currently have event listeners for the given
+ // event,
+ base::flat_set<std::string> GetSubscribedExtensions(
+ const std::string& event_name);
+
+ CertificateProviderService* const service_;
+ extensions::ExtensionRegistry* const registry_;
+ extensions::EventRouter* const event_router_;
+};
+
+// Constructs the "onCertificatesUpdateRequested" event.
+std::unique_ptr<extensions::Event> BuildOnCertificatesUpdateRequestedEvent(
+ int request_id) {
+ api_cp::CertificatesUpdateRequest certificates_update_request;
+ certificates_update_request.certificates_request_id = request_id;
+ std::vector<base::Value> event_args;
+ event_args.push_back(
+ base::Value::FromUniquePtrValue(certificates_update_request.ToValue()));
+ return std::make_unique<extensions::Event>(
+ extensions::events::CERTIFICATEPROVIDER_ON_CERTIFICATES_UPDATE_REQUESTED,
+ api_cp::OnCertificatesUpdateRequested::kEventName, std::move(event_args));
+}
+
+// Constructs the legacy "onCertificatesRequested" event.
+std::unique_ptr<extensions::Event> BuildOnCertificatesRequestedEvent(
+ int request_id) {
+ std::vector<base::Value> event_args;
+ event_args.push_back(base::Value(request_id));
+ return std::make_unique<extensions::Event>(
+ extensions::events::CERTIFICATEPROVIDER_ON_CERTIFICATES_REQUESTED,
+ api_cp::OnCertificatesRequested::kEventName, std::move(event_args));
+}
+
+// Constructs the "onSignatureRequested" event.
+std::unique_ptr<extensions::Event> BuildOnSignatureRequestedEvent(
+ int request_id,
+ uint16_t algorithm,
+ const net::X509Certificate& certificate,
+ base::span<const uint8_t> input) {
+ api_cp::SignatureRequest request;
+ request.sign_request_id = request_id;
+ switch (algorithm) {
+ case SSL_SIGN_RSA_PKCS1_MD5_SHA1:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_MD5_SHA1;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA1:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA1;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA256:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA256;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA384:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA384;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA512:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA512;
+ break;
+ case SSL_SIGN_RSA_PSS_RSAE_SHA256:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PSS_SHA256;
+ break;
+ case SSL_SIGN_RSA_PSS_RSAE_SHA384:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PSS_SHA384;
+ break;
+ case SSL_SIGN_RSA_PSS_RSAE_SHA512:
+ request.algorithm = api_cp::ALGORITHM_RSASSA_PSS_SHA512;
+ break;
+ default:
+ LOG(ERROR) << "Unknown signature algorithm";
+ return nullptr;
+ }
+ request.input.assign(input.begin(), input.end());
+ base::StringPiece cert_der =
+ net::x509_util::CryptoBufferAsStringPiece(certificate.cert_buffer());
+ request.certificate.assign(cert_der.begin(), cert_der.end());
+
+ std::vector<base::Value> event_args;
+ event_args.push_back(base::Value::FromUniquePtrValue(request.ToValue()));
+
+ return std::make_unique<extensions::Event>(
+ extensions::events::CERTIFICATEPROVIDER_ON_SIGNATURE_REQUESTED,
+ api_cp::OnSignatureRequested::kEventName, std::move(event_args));
+}
+
+// Constructs the legacy "onSignDigestRequested" event.
+std::unique_ptr<extensions::Event> BuildOnSignDigestRequestedEvent(
+ int request_id,
+ uint16_t algorithm,
+ const net::X509Certificate& certificate,
+ base::span<const uint8_t> input) {
+ api_cp::SignRequest request;
+
+ request.sign_request_id = request_id;
+ switch (algorithm) {
+ case SSL_SIGN_RSA_PKCS1_MD5_SHA1:
+ request.hash = api_cp::HASH_MD5_SHA1;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA1:
+ request.hash = api_cp::HASH_SHA1;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA256:
+ request.hash = api_cp::HASH_SHA256;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA384:
+ request.hash = api_cp::HASH_SHA384;
+ break;
+ case SSL_SIGN_RSA_PKCS1_SHA512:
+ request.hash = api_cp::HASH_SHA512;
+ break;
+ default:
+ LOG(ERROR) << "Unknown signature algorithm";
+ return nullptr;
+ }
+ base::StringPiece cert_der =
+ net::x509_util::CryptoBufferAsStringPiece(certificate.cert_buffer());
+ request.certificate.assign(cert_der.begin(), cert_der.end());
+
+ // The extension expects the input to be hashed ahead of time.
+ request.digest.resize(EVP_MAX_MD_SIZE);
+ const EVP_MD* md = SSL_get_signature_algorithm_digest(algorithm);
+ unsigned digest_len;
+ if (!md || !EVP_Digest(input.data(), input.size(), request.digest.data(),
+ &digest_len, md, /*ENGINE *impl=*/nullptr)) {
+ return nullptr;
+ }
+ request.digest.resize(digest_len);
+
+ std::vector<base::Value> event_args;
+ event_args.push_back(base::Value(request_id));
+ event_args.push_back(base::Value::FromUniquePtrValue(request.ToValue()));
+
+ return std::make_unique<extensions::Event>(
+ extensions::events::CERTIFICATEPROVIDER_ON_SIGN_DIGEST_REQUESTED,
+ api_cp::OnSignDigestRequested::kEventName, std::move(event_args));
+}
+
+DefaultDelegate::DefaultDelegate(CertificateProviderService* service,
+ extensions::ExtensionRegistry* registry,
+ extensions::EventRouter* event_router)
+ : service_(service), registry_(registry), event_router_(event_router) {
+ DCHECK(service_);
+ registry_->AddObserver(this);
+ event_router_->RegisterObserver(
+ this, api_cp::OnCertificatesUpdateRequested::kEventName);
+ event_router_->RegisterObserver(this,
+ api_cp::OnCertificatesRequested::kEventName);
+}
+
+DefaultDelegate::~DefaultDelegate() {
+ event_router_->UnregisterObserver(this);
+ registry_->RemoveObserver(this);
+}
+
+std::vector<std::string> DefaultDelegate::CertificateProviderExtensions() {
+ base::flat_set<std::string> ids = GetSubscribedExtensions(
+ api_cp::OnCertificatesUpdateRequested::kEventName);
+ const base::flat_set<std::string> legacy_ids =
+ GetSubscribedExtensions(api_cp::OnCertificatesRequested::kEventName);
+ ids.insert(legacy_ids.begin(), legacy_ids.end());
+ return std::vector<std::string>(ids.begin(), ids.end());
+}
+
+void DefaultDelegate::BroadcastCertificateRequest(int request_id) {
+ // First, broadcast the event to the extensions that use the up-to-date
+ // version of the API.
+ const auto up_to_date_api_extension_ids = GetSubscribedExtensions(
+ api_cp::OnCertificatesUpdateRequested::kEventName);
+ for (const std::string& extension_id : up_to_date_api_extension_ids) {
+ event_router_->DispatchEventToExtension(
+ extension_id, BuildOnCertificatesUpdateRequestedEvent(request_id));
+ }
+ // Second, broadcast the event to the extensions that only listen for the
+ // legacy event.
+ for (const std::string& extension_id :
+ GetSubscribedExtensions(api_cp::OnCertificatesRequested::kEventName)) {
+ if (up_to_date_api_extension_ids.contains(extension_id))
+ continue;
+ event_router_->DispatchEventToExtension(
+ extension_id, BuildOnCertificatesRequestedEvent(request_id));
+ }
+}
+
+bool DefaultDelegate::DispatchSignRequestToExtension(
+ const std::string& extension_id,
+ int request_id,
+ uint16_t algorithm,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ base::span<const uint8_t> input) {
+ DCHECK(certificate);
+ std::unique_ptr<extensions::Event> event;
+ // Send the up-to-date version of the event, and fall back to the legacy event
+ // if the extension is only listening for that one.
+ if (event_router_->ExtensionHasEventListener(
+ extension_id, api_cp::OnSignatureRequested::kEventName)) {
+ event = BuildOnSignatureRequestedEvent(request_id, algorithm, *certificate,
+ input);
+ } else if (event_router_->ExtensionHasEventListener(
+ extension_id, api_cp::OnSignDigestRequested::kEventName)) {
+ event = BuildOnSignDigestRequestedEvent(request_id, algorithm, *certificate,
+ input);
+ }
+ if (!event)
+ return false;
+ event_router_->DispatchEventToExtension(extension_id, std::move(event));
+ return true;
+}
+
+void DefaultDelegate::OnListenerRemoved(
+ const extensions::EventListenerInfo& details) {
+ if (!event_router_->ExtensionHasEventListener(
+ details.extension_id,
+ api_cp::OnCertificatesUpdateRequested::kEventName) &&
+ !event_router_->ExtensionHasEventListener(
+ details.extension_id, api_cp::OnCertificatesRequested::kEventName)) {
+ service_->OnExtensionUnregistered(details.extension_id);
+ }
+}
+
+void DefaultDelegate::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const extensions::Extension* extension,
+ extensions::UnloadedExtensionReason reason) {
+ service_->OnExtensionUnloaded(extension->id());
+}
+
+base::flat_set<std::string> DefaultDelegate::GetSubscribedExtensions(
+ const std::string& event_name) {
+ std::vector<std::string> ids;
+ for (const std::unique_ptr<extensions::EventListener>& listener :
+ event_router_->listeners().GetEventListenersByName(event_name)) {
+ ids.push_back(listener->extension_id());
+ }
+ return base::flat_set<std::string>(ids.begin(), ids.end());
+}
+
+} // namespace
+
+// static
+CertificateProviderService*
+CertificateProviderServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<CertificateProviderService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+CertificateProviderServiceFactory*
+CertificateProviderServiceFactory::GetInstance() {
+ return base::Singleton<CertificateProviderServiceFactory>::get();
+}
+
+CertificateProviderServiceFactory::CertificateProviderServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "CertificateProviderService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(extensions::EventRouterFactory::GetInstance());
+ DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
+}
+
+content::BrowserContext*
+CertificateProviderServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return chrome::GetBrowserContextRedirectedInIncognito(context);
+}
+
+bool CertificateProviderServiceFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+KeyedService* CertificateProviderServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ CertificateProviderService* const service = new CertificateProviderService();
+ service->SetDelegate(std::make_unique<DefaultDelegate>(
+ service,
+ extensions::ExtensionRegistryFactory::GetForBrowserContext(context),
+ extensions::EventRouterFactory::GetForBrowserContext(context)));
+ return service;
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/certificate_provider_service_factory.h b/chrome/browser/certificate_provider/certificate_provider_service_factory.h
new file mode 100644
index 0000000..afdce85
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider_service_factory.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_
+
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace content {
+class BrowserContext;
+}
+
+namespace chromeos {
+
+class CertificateProviderService;
+
+// Factory to create CertificateProviderService.
+class CertificateProviderServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static CertificateProviderService* GetForBrowserContext(
+ content::BrowserContext* context);
+
+ static CertificateProviderServiceFactory* GetInstance();
+
+ CertificateProviderServiceFactory(const CertificateProviderServiceFactory&) =
+ delete;
+ CertificateProviderServiceFactory& operator=(
+ const CertificateProviderServiceFactory&) = delete;
+
+ private:
+ friend struct base::DefaultSingletonTraits<CertificateProviderServiceFactory>;
+
+ CertificateProviderServiceFactory();
+
+ // BrowserContextKeyedServiceFactory:
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_
diff --git a/chrome/browser/certificate_provider/certificate_provider_service_unittest.cc b/chrome/browser/certificate_provider/certificate_provider_service_unittest.cc
new file mode 100644
index 0000000..a5582187
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_provider_service_unittest.cc
@@ -0,0 +1,617 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/certificate_provider_service.h"
+
+#include <stdint.h>
+#include <set>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/containers/span.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/certificate_provider/certificate_provider.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
+
+namespace chromeos {
+
+namespace {
+
+const char kExtension1[] = "extension1";
+const char kExtension2[] = "extension2";
+
+void ExpectEmptySignatureAndStoreError(net::Error* out_error,
+ net::Error error,
+ const std::vector<uint8_t>& signature) {
+ EXPECT_TRUE(signature.empty());
+ *out_error = error;
+}
+
+void ExpectOKAndStoreSignature(std::vector<uint8_t>* out_signature,
+ net::Error error,
+ const std::vector<uint8_t>& signature) {
+ EXPECT_EQ(net::OK, error);
+ *out_signature = signature;
+}
+
+void StoreCertificates(net::ClientCertIdentityList* out_certs,
+ net::ClientCertIdentityList certs) {
+ if (out_certs)
+ *out_certs = std::move(certs);
+}
+
+void StorePrivateKey(scoped_refptr<net::SSLPrivateKey>* out_key,
+ scoped_refptr<net::SSLPrivateKey> in_key) {
+ *out_key = std::move(in_key);
+}
+
+certificate_provider::CertificateInfo CreateCertInfo(
+ const std::string& cert_filename) {
+ certificate_provider::CertificateInfo cert_info;
+ cert_info.certificate =
+ net::ImportCertFromFile(net::GetTestCertsDirectory(), cert_filename);
+ EXPECT_TRUE(cert_info.certificate) << "Could not load " << cert_filename;
+ cert_info.supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA256);
+
+ return cert_info;
+}
+
+bool IsKeyEqualToCertInfo(const certificate_provider::CertificateInfo& info,
+ net::SSLPrivateKey* key) {
+ return info.supported_algorithms == key->GetAlgorithmPreferences();
+}
+
+bool ClientCertIdentityAlphabeticSorter(
+ const std::unique_ptr<net::ClientCertIdentity>& a_identity,
+ const std::unique_ptr<net::ClientCertIdentity>& b_identity) {
+ return a_identity->certificate()->subject().GetDisplayName() <
+ b_identity->certificate()->subject().GetDisplayName();
+}
+
+class TestDelegate : public CertificateProviderService::Delegate {
+ public:
+ enum class RequestType { NONE, SIGN, GET_CERTIFICATES };
+
+ TestDelegate() {}
+ TestDelegate(const TestDelegate&) = delete;
+ TestDelegate& operator=(const TestDelegate&) = delete;
+
+ std::vector<std::string> CertificateProviderExtensions() override {
+ return std::vector<std::string>(provider_extensions_.begin(),
+ provider_extensions_.end());
+ }
+
+ void BroadcastCertificateRequest(int cert_request_id) override {
+ EXPECT_EQ(expected_request_type_, RequestType::GET_CERTIFICATES);
+ last_cert_request_id_ = cert_request_id;
+ expected_request_type_ = RequestType::NONE;
+ }
+
+ bool DispatchSignRequestToExtension(
+ const std::string& extension_id,
+ int sign_request_id,
+ uint16_t algorithm,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ base::span<const uint8_t> input) override {
+ EXPECT_EQ(expected_request_type_, RequestType::SIGN);
+ last_sign_request_id_ = sign_request_id;
+ last_extension_id_ = extension_id;
+ last_certificate_ = certificate;
+ expected_request_type_ = RequestType::NONE;
+ return true;
+ }
+
+ // Prepares this delegate for the dispatch of a request of type
+ // |expected_request_type|. The first request of the right type will cause
+ // |expected_request_type_| to be reset to NONE. The request's arguments will
+ // be stored in |last_*_request_id_| and |last_extension_id_|. Any additional
+ // request and any request of the wrong type will fail the test.
+ void ClearAndExpectRequest(RequestType expected_request_type) {
+ last_extension_id_.clear();
+ last_sign_request_id_ = -1;
+ last_cert_request_id_ = -1;
+ last_certificate_ = nullptr;
+ expected_request_type_ = expected_request_type;
+ }
+
+ int last_sign_request_id_ = -1;
+ int last_cert_request_id_ = -1;
+ scoped_refptr<net::X509Certificate> last_certificate_;
+ std::string last_extension_id_;
+ std::set<std::string> provider_extensions_;
+ RequestType expected_request_type_ = RequestType::NONE;
+};
+
+class MockObserver : public CertificateProviderService::Observer {
+ public:
+ MOCK_METHOD2(
+ OnCertificatesUpdated,
+ void(const std::string& extension_id,
+ const certificate_provider::CertificateInfoList& certificate_infos));
+ MOCK_METHOD2(OnSignCompleted,
+ void(const scoped_refptr<net::X509Certificate>& certificate,
+ const std::string& extension_id));
+};
+
+} // namespace
+
+class CertificateProviderServiceTest : public testing::Test {
+ public:
+ CertificateProviderServiceTest()
+ : task_runner_(new base::TestMockTimeTaskRunner()),
+ task_runner_handle_(task_runner_),
+ service_(new CertificateProviderService()),
+ cert_info1_(CreateCertInfo("client_1.pem")),
+ cert_info2_(CreateCertInfo("client_2.pem")) {
+ std::unique_ptr<TestDelegate> test_delegate(new TestDelegate);
+ test_delegate_ = test_delegate.get();
+ service_->SetDelegate(std::move(test_delegate));
+
+ service_->AddObserver(&observer_);
+
+ certificate_provider_ = service_->CreateCertificateProvider();
+ EXPECT_TRUE(certificate_provider_);
+
+ test_delegate_->provider_extensions_.insert(kExtension1);
+ }
+
+ CertificateProviderServiceTest(const CertificateProviderServiceTest&) =
+ delete;
+ CertificateProviderServiceTest& operator=(
+ const CertificateProviderServiceTest&) = delete;
+
+ // Triggers a GetCertificates request and returns the request id. Assumes that
+ // at least one extension is registered as a certificate provider.
+ int RequestCertificatesFromExtensions(net::ClientCertIdentityList* certs) {
+ test_delegate_->ClearAndExpectRequest(
+ TestDelegate::RequestType::GET_CERTIFICATES);
+
+ certificate_provider_->GetCertificates(
+ base::BindOnce(&StoreCertificates, certs));
+
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(TestDelegate::RequestType::NONE,
+ test_delegate_->expected_request_type_);
+ return test_delegate_->last_cert_request_id_;
+ }
+
+ scoped_refptr<net::SSLPrivateKey> FetchIdentityPrivateKey(
+ net::ClientCertIdentity* identity) {
+ scoped_refptr<net::SSLPrivateKey> ssl_private_key;
+ identity->AcquirePrivateKey(
+ base::BindOnce(StorePrivateKey, &ssl_private_key));
+ task_runner_->RunUntilIdle();
+ return ssl_private_key;
+ }
+
+ // Provides |cert_info1_| through kExtension1.
+ std::unique_ptr<net::ClientCertIdentity> ProvideDefaultCert() {
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id,
+ cert_info1_);
+ task_runner_->RunUntilIdle();
+ if (certs.empty())
+ return nullptr;
+ return std::move(certs[0]);
+ }
+
+ // Like service_->SetCertificatesProvidedByExtension but taking a single
+ // CertificateInfo instead of a list.
+ void SetCertificateProvidedByExtension(
+ const std::string& extension_id,
+ int cert_request_id,
+ const certificate_provider::CertificateInfo& cert_info) {
+ certificate_provider::CertificateInfoList infos;
+ infos.push_back(cert_info);
+ EXPECT_CALL(observer_, OnCertificatesUpdated(extension_id, infos));
+ service_->SetCertificatesProvidedByExtension(extension_id, infos);
+ service_->SetExtensionCertificateReplyReceived(extension_id,
+ cert_request_id);
+ }
+
+ bool CheckLookUpCertificate(
+ const certificate_provider::CertificateInfo& cert_info,
+ bool expected_is_certificate_known,
+ bool expected_is_currently_provided,
+ const std::string& expected_extension_id) {
+ bool is_currently_provided = !expected_is_currently_provided;
+ std::string extension_id;
+ if (expected_is_certificate_known !=
+ service_->LookUpCertificate(*cert_info.certificate,
+ &is_currently_provided, &extension_id)) {
+ LOG(ERROR) << "Wrong return value.";
+ return false;
+ }
+ if (expected_is_currently_provided != is_currently_provided) {
+ LOG(ERROR) << "Wrong |is_currently_provided|.";
+ return false;
+ }
+ if (expected_extension_id != extension_id) {
+ LOG(ERROR) << "Wrong extension id. Got " << extension_id;
+ return false;
+ }
+ return true;
+ }
+
+ scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle task_runner_handle_;
+ TestDelegate* test_delegate_ = nullptr;
+ testing::StrictMock<MockObserver> observer_;
+ std::unique_ptr<CertificateProvider> certificate_provider_;
+ std::unique_ptr<CertificateProviderService> service_;
+ const certificate_provider::CertificateInfo cert_info1_;
+ const certificate_provider::CertificateInfo cert_info2_;
+};
+
+TEST_F(CertificateProviderServiceTest, GetCertificates) {
+ test_delegate_->provider_extensions_.insert(kExtension2);
+
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+
+ task_runner_->RunUntilIdle();
+ // No certificates set until all registered extensions replied.
+ EXPECT_TRUE(certs.empty());
+
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id, cert_info1_);
+
+ task_runner_->RunUntilIdle();
+ // No certificates set until all registered extensions replied.
+ EXPECT_TRUE(certs.empty());
+
+ SetCertificateProvidedByExtension(kExtension2, cert_request_id, cert_info2_);
+
+ task_runner_->RunUntilIdle();
+ ASSERT_EQ(2u, certs.size());
+
+ // Verify that the ClientCertIdentity returns key handles for the provided
+ // certs.
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[0].get()));
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[1].get()));
+
+ // Deregister the extensions as certificate providers. The next
+ // GetCertificates call must report an empty list of certs.
+ test_delegate_->provider_extensions_.clear();
+ service_->OnExtensionUnloaded(kExtension1);
+ service_->OnExtensionUnloaded(kExtension2);
+
+ // No request expected.
+ test_delegate_->ClearAndExpectRequest(TestDelegate::RequestType::NONE);
+
+ certificate_provider_->GetCertificates(
+ base::BindOnce(&StoreCertificates, &certs));
+
+ task_runner_->RunUntilIdle();
+ // As |certs| was not empty before, this ensures that StoreCertificates() was
+ // called.
+ EXPECT_TRUE(certs.empty());
+}
+
+TEST_F(CertificateProviderServiceTest, LookUpCertificate) {
+ // Provide only |cert_info1_|.
+ {
+ const int cert_request_id = RequestCertificatesFromExtensions(nullptr);
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id,
+ cert_info1_);
+ task_runner_->RunUntilIdle();
+ }
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info1_, true /* is known */,
+ true /* is currently provided */,
+ kExtension1));
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info2_, false /* is not known */,
+ false /* is currently not provided */,
+ std::string()));
+
+ // Provide only |cert_info2_| from |kExtension2|.
+ test_delegate_->provider_extensions_.insert(kExtension2);
+ {
+ const int cert_request_id = RequestCertificatesFromExtensions(nullptr);
+ EXPECT_CALL(observer_,
+ OnCertificatesUpdated(
+ kExtension1, certificate_provider::CertificateInfoList()));
+ service_->SetCertificatesProvidedByExtension(
+ kExtension1, certificate_provider::CertificateInfoList());
+ service_->SetExtensionCertificateReplyReceived(kExtension1,
+ cert_request_id);
+ SetCertificateProvidedByExtension(kExtension2, cert_request_id,
+ cert_info2_);
+ task_runner_->RunUntilIdle();
+ }
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info1_, true /* is known */,
+ false /* is currently not provided */,
+ std::string()));
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info2_, true /* is known */,
+ true /* is currently provided */,
+ kExtension2));
+
+ // Deregister |kExtension2| as certificate provider and provide |cert_info1_|
+ // from |kExtension1|.
+ test_delegate_->provider_extensions_.erase(kExtension2);
+ service_->OnExtensionUnloaded(kExtension2);
+
+ {
+ const int cert_request_id = RequestCertificatesFromExtensions(nullptr);
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id,
+ cert_info1_);
+ task_runner_->RunUntilIdle();
+ }
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info1_, true /* is known */,
+ true /* is currently provided */,
+ kExtension1));
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info2_, true /* is known */,
+ false /* is currently not provided */,
+ std::string()));
+
+ // Provide |cert_info2_| from |kExtension1|.
+ {
+ const int cert_request_id = RequestCertificatesFromExtensions(nullptr);
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id,
+ cert_info2_);
+ task_runner_->RunUntilIdle();
+ }
+
+ {
+ bool is_currently_provided = true;
+ std::string extension_id;
+ // |cert_info1_.certificate| was provided before, so this must return true.
+ EXPECT_TRUE(service_->LookUpCertificate(
+ *cert_info1_.certificate, &is_currently_provided, &extension_id));
+ EXPECT_FALSE(is_currently_provided);
+ EXPECT_TRUE(extension_id.empty());
+ }
+
+ {
+ bool is_currently_provided = false;
+ std::string extension_id;
+ EXPECT_TRUE(service_->LookUpCertificate(
+ *cert_info2_.certificate, &is_currently_provided, &extension_id));
+ EXPECT_TRUE(is_currently_provided);
+ EXPECT_EQ(kExtension1, extension_id);
+ }
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info1_, true /* is known */,
+ false /* is currently not provided */,
+ std::string()));
+
+ EXPECT_TRUE(CheckLookUpCertificate(cert_info2_, true /* is known */,
+ true /* is currently provided */,
+ kExtension1));
+}
+
+TEST_F(CertificateProviderServiceTest, GetCertificatesTimeout) {
+ test_delegate_->provider_extensions_.insert(kExtension2);
+
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+
+ certificate_provider::CertificateInfoList infos;
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id, cert_info1_);
+
+ task_runner_->RunUntilIdle();
+ // No certificates set until all registered extensions replied or a timeout
+ // occurred.
+ EXPECT_TRUE(certs.empty());
+
+ task_runner_->FastForwardUntilNoTasksRemain();
+ // After the timeout, only extension1_'s certificates are returned.
+ // This verifies that the timeout delay is > 0 but not how long the delay is.
+ ASSERT_EQ(1u, certs.size());
+
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[0].get()));
+}
+
+TEST_F(CertificateProviderServiceTest, UnloadExtensionAfterGetCertificates) {
+ test_delegate_->provider_extensions_.insert(kExtension2);
+
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id, cert_info1_);
+ SetCertificateProvidedByExtension(kExtension2, cert_request_id, cert_info2_);
+ task_runner_->RunUntilIdle();
+
+ ASSERT_EQ(2u, certs.size());
+
+ // Sort the returned certs to ensure that the test results are stable.
+ std::sort(certs.begin(), certs.end(), ClientCertIdentityAlphabeticSorter);
+
+ // Private key handles for both certificates must be available now.
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[0].get()));
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[1].get()));
+
+ // Unload one of the extensions.
+ service_->OnExtensionUnloaded(kExtension2);
+
+ // extension1 isn't affected by the uninstall.
+ EXPECT_TRUE(FetchIdentityPrivateKey(certs[0].get()));
+ // No key handles that were backed by the uninstalled extension must be
+ // returned.
+ EXPECT_FALSE(FetchIdentityPrivateKey(certs[1].get()));
+}
+
+TEST_F(CertificateProviderServiceTest, DestroyServiceAfterGetCertificates) {
+ test_delegate_->provider_extensions_.insert(kExtension2);
+
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id, cert_info1_);
+ SetCertificateProvidedByExtension(kExtension2, cert_request_id, cert_info2_);
+ task_runner_->RunUntilIdle();
+
+ ASSERT_EQ(2u, certs.size());
+
+ // Destroy the service.
+ service_.reset();
+
+ // Private key handles for both certificates should return nullptr now.
+ EXPECT_FALSE(FetchIdentityPrivateKey(certs[0].get()));
+ EXPECT_FALSE(FetchIdentityPrivateKey(certs[1].get()));
+}
+
+TEST_F(CertificateProviderServiceTest, UnloadExtensionDuringGetCertificates) {
+ test_delegate_->provider_extensions_.insert(kExtension2);
+
+ net::ClientCertIdentityList certs;
+ const int cert_request_id = RequestCertificatesFromExtensions(&certs);
+
+ SetCertificateProvidedByExtension(kExtension1, cert_request_id, cert_info1_);
+
+ // The pending certificate request is only waiting for kExtension2. Unloading
+ // that extension must cause the request to be finished.
+ service_->OnExtensionUnloaded(kExtension2);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(1u, certs.size());
+}
+
+// Trying to sign data using the exposed SSLPrivateKey must cause a sign
+// request. The reply must be correctly routed back to the private key.
+TEST_F(CertificateProviderServiceTest, SignRequest) {
+ std::unique_ptr<net::ClientCertIdentity> cert(ProvideDefaultCert());
+ ASSERT_TRUE(cert);
+
+ scoped_refptr<net::SSLPrivateKey> private_key(
+ FetchIdentityPrivateKey(cert.get()));
+
+ ASSERT_TRUE(private_key);
+ EXPECT_TRUE(IsKeyEqualToCertInfo(cert_info1_, private_key.get()));
+ EXPECT_NE(std::string::npos,
+ private_key->GetProviderName().find(kExtension1));
+
+ test_delegate_->ClearAndExpectRequest(TestDelegate::RequestType::SIGN);
+
+ std::string input = "any input data";
+ std::vector<uint8_t> received_signature;
+ private_key->Sign(
+ SSL_SIGN_RSA_PKCS1_SHA256,
+ std::vector<uint8_t>(input.begin(), input.end()),
+ base::BindOnce(&ExpectOKAndStoreSignature, &received_signature));
+
+ task_runner_->RunUntilIdle();
+
+ const int sign_request_id = test_delegate_->last_sign_request_id_;
+ EXPECT_EQ(TestDelegate::RequestType::NONE,
+ test_delegate_->expected_request_type_);
+ EXPECT_TRUE(cert_info1_.certificate->EqualsExcludingChain(
+ test_delegate_->last_certificate_.get()));
+
+ // No signature received until the extension replied to the service.
+ EXPECT_TRUE(received_signature.empty());
+
+ EXPECT_CALL(observer_, OnSignCompleted(cert_info1_.certificate, kExtension1));
+
+ std::vector<uint8_t> signature_reply;
+ signature_reply.push_back(5);
+ signature_reply.push_back(7);
+ signature_reply.push_back(8);
+ service_->ReplyToSignRequest(kExtension1, sign_request_id, signature_reply);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(signature_reply, received_signature);
+}
+
+TEST_F(CertificateProviderServiceTest, UnloadExtensionDuringSign) {
+ std::unique_ptr<net::ClientCertIdentity> cert(ProvideDefaultCert());
+ ASSERT_TRUE(cert);
+
+ scoped_refptr<net::SSLPrivateKey> private_key(
+ FetchIdentityPrivateKey(cert.get()));
+ ASSERT_TRUE(private_key);
+
+ test_delegate_->ClearAndExpectRequest(TestDelegate::RequestType::SIGN);
+
+ std::string input = "any input data";
+ net::Error error = net::OK;
+ private_key->Sign(SSL_SIGN_RSA_PKCS1_SHA256,
+ std::vector<uint8_t>(input.begin(), input.end()),
+ base::BindOnce(&ExpectEmptySignatureAndStoreError, &error));
+
+ task_runner_->RunUntilIdle();
+
+ // No signature received until the extension replied to the service or is
+ // unloaded.
+ EXPECT_EQ(net::OK, error);
+
+ // Unload the extension.
+ service_->OnExtensionUnloaded(kExtension1);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(net::ERR_FAILED, error);
+}
+
+// Try to sign data using key; using the Subject Public Key Info (SPKI) to
+// identify the key.
+TEST_F(CertificateProviderServiceTest, SignUsingSpkiAsIdentification) {
+ base::StringPiece client1_spki_piece;
+ ASSERT_TRUE(net::asn1::ExtractSPKIFromDERCert(
+ net::x509_util::CryptoBufferAsStringPiece(
+ cert_info1_.certificate->cert_buffer()),
+ &client1_spki_piece));
+ std::string client1_spki(client1_spki_piece);
+
+ std::unique_ptr<net::ClientCertIdentity> cert(ProvideDefaultCert());
+ ASSERT_TRUE(cert);
+
+ std::vector<uint16_t> supported_algorithms;
+ std::string extension_id;
+ // If this fails, try to regenerate kClient1SpkiBase64 using the command shown
+ // above.
+ EXPECT_TRUE(
+ service_->LookUpSpki(client1_spki, &supported_algorithms, &extension_id));
+ EXPECT_EQ(extension_id, kExtension1);
+ EXPECT_THAT(supported_algorithms,
+ testing::UnorderedElementsAre(SSL_SIGN_RSA_PKCS1_SHA256));
+
+ test_delegate_->ClearAndExpectRequest(TestDelegate::RequestType::SIGN);
+ std::vector<uint8_t> input{'d', 'a', 't', 'a'};
+ std::vector<uint8_t> received_signature;
+ service_->RequestSignatureBySpki(
+ client1_spki, SSL_SIGN_RSA_PKCS1_SHA256, input,
+ /*authenticating_user_account_id=*/{},
+ base::BindOnce(&ExpectOKAndStoreSignature, &received_signature));
+
+ task_runner_->RunUntilIdle();
+
+ const int sign_request_id = test_delegate_->last_sign_request_id_;
+ EXPECT_EQ(TestDelegate::RequestType::NONE,
+ test_delegate_->expected_request_type_);
+ EXPECT_TRUE(cert_info1_.certificate->EqualsExcludingChain(
+ test_delegate_->last_certificate_.get()));
+
+ // No signature received until the extension replied to the service.
+ EXPECT_TRUE(received_signature.empty());
+
+ EXPECT_CALL(observer_, OnSignCompleted(cert_info1_.certificate, kExtension1));
+
+ std::vector<uint8_t> signature_reply;
+ signature_reply.push_back(5);
+ signature_reply.push_back(7);
+ signature_reply.push_back(8);
+ service_->ReplyToSignRequest(kExtension1, sign_request_id, signature_reply);
+
+ task_runner_->RunUntilIdle();
+ EXPECT_EQ(signature_reply, received_signature);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/certificate_requests.cc b/chrome/browser/certificate_provider/certificate_requests.cc
new file mode 100644
index 0000000..dad3ab9
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_requests.cc
@@ -0,0 +1,111 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/certificate_requests.h"
+
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+namespace {
+const int kGetCertificatesTimeoutInMinutes = 5;
+} // namespace
+
+// Holds state for a single certificate request.
+struct CertificateRequests::CertificateRequestState {
+ CertificateRequestState() {}
+
+ ~CertificateRequestState() {}
+
+ // Extensions that are too slow are eventually dropped from a request.
+ base::OneShotTimer timeout;
+
+ // Extensions that this request is still waiting for.
+ std::set<std::string> pending_extensions;
+
+ // The callback that must be run with the final list of certificates.
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback;
+};
+
+CertificateRequests::CertificateRequests() {}
+
+CertificateRequests::~CertificateRequests() {}
+
+int CertificateRequests::AddRequest(
+ const std::vector<std::string>& extension_ids,
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback,
+ base::OnceCallback<void(int)> timeout_callback) {
+ auto state = std::make_unique<CertificateRequestState>();
+ state->callback = std::move(callback);
+ state->pending_extensions.insert(extension_ids.begin(), extension_ids.end());
+
+ const int request_id = next_free_request_id_++;
+ state->timeout.Start(FROM_HERE,
+ base::Minutes(kGetCertificatesTimeoutInMinutes),
+ base::BindOnce(std::move(timeout_callback), request_id));
+
+ const auto insert_result =
+ requests_.insert(std::make_pair(request_id, std::move(state)));
+ DCHECK(insert_result.second) << "request id already in use.";
+ return request_id;
+}
+
+bool CertificateRequests::SetExtensionReplyReceived(
+ const std::string& extension_id,
+ int request_id,
+ bool* completed) {
+ *completed = false;
+ const auto it = requests_.find(request_id);
+ if (it == requests_.end())
+ return false;
+
+ CertificateRequestState& state = *it->second;
+ if (state.pending_extensions.erase(extension_id) == 0)
+ return false;
+
+ *completed = state.pending_extensions.empty();
+ return true;
+}
+
+bool CertificateRequests::RemoveRequest(
+ int request_id,
+ base::OnceCallback<void(net::ClientCertIdentityList)>* callback) {
+ const auto it = requests_.find(request_id);
+ if (it == requests_.end())
+ return false;
+
+ CertificateRequestState& state = *it->second;
+ *callback = std::move(state.callback);
+ requests_.erase(it);
+ DVLOG(2) << "Completed certificate request " << request_id;
+ return true;
+}
+
+std::vector<int> CertificateRequests::DropExtension(
+ const std::string& extension_id) {
+ std::vector<int> completed_requests;
+ for (const auto& entry : requests_) {
+ DVLOG(2) << "Remove extension " << extension_id
+ << " from certificate request " << entry.first;
+
+ CertificateRequestState& state = *entry.second.get();
+ state.pending_extensions.erase(extension_id);
+ if (state.pending_extensions.empty())
+ completed_requests.push_back(entry.first);
+ }
+ return completed_requests;
+}
+
+} // namespace certificate_provider
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/certificate_requests.h b/chrome/browser/certificate_provider/certificate_requests.h
new file mode 100644
index 0000000..86229423
--- /dev/null
+++ b/chrome/browser/certificate_provider/certificate_requests.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_REQUESTS_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_REQUESTS_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "net/ssl/client_cert_identity.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+class CertificateRequests {
+ public:
+ CertificateRequests();
+ CertificateRequests(const CertificateRequests&) = delete;
+ CertificateRequests& operator=(const CertificateRequests&) = delete;
+ ~CertificateRequests();
+
+ // Returns the id of the new request. |callback| will be stored with this
+ // request and be returned by RemoveRequest(). |timeout_callback| will be
+ // called with the request id if this request times out before
+ // SetExtensionReplyReceived() was called for all extensions in
+ // |extension_ids|.
+ int AddRequest(const std::vector<std::string>& extension_ids,
+ base::OnceCallback<void(net::ClientCertIdentityList)> callback,
+ base::OnceCallback<void(int)> timeout_callback);
+
+ // Returns whether this reply was expected, i.e. the request with |request_id|
+ // was waiting for a reply from this extension. If it was expected,
+ // |completed| is set to whether this request has no more pending replies.
+ // Otherwise |completed| will be set to false.
+ bool SetExtensionReplyReceived(const std::string& extension_id,
+ int request_id,
+ bool* completed);
+
+ // If this request is pending, sets |callback|, drops the request, and returns
+ // true. Otherwise returns false.
+ bool RemoveRequest(
+ int request_id,
+ base::OnceCallback<void(net::ClientCertIdentityList)>* callback);
+
+ // Removes this extension from all pending requests and returns the ids of
+ // all completed requests.
+ std::vector<int> DropExtension(const std::string& extension_id);
+
+ private:
+ struct CertificateRequestState;
+
+ std::map<int, std::unique_ptr<CertificateRequestState>> requests_;
+ int next_free_request_id_ = 0;
+};
+
+} // namespace certificate_provider
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_CERTIFICATE_REQUESTS_H_
diff --git a/chrome/browser/certificate_provider/pin_dialog_manager.cc b/chrome/browser/certificate_provider/pin_dialog_manager.cc
new file mode 100644
index 0000000..0502fa2
--- /dev/null
+++ b/chrome/browser/certificate_provider/pin_dialog_manager.cc
@@ -0,0 +1,264 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/pin_dialog_manager.h"
+
+#include "base/bind.h"
+#include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
+#include "base/logging.h"
+
+namespace chromeos {
+
+// Define timeout for issued sign_request_id.
+constexpr base::TimeDelta kSignRequestIdTimeout = base::Minutes(10);
+
+PinDialogManager::PinDialogManager() = default;
+
+PinDialogManager::~PinDialogManager() = default;
+
+void PinDialogManager::AddSignRequestId(
+ const std::string& extension_id,
+ int sign_request_id,
+ const absl::optional<AccountId>& authenticating_user_account_id) {
+ ExtensionNameRequestIdPair key(extension_id, sign_request_id);
+ sign_requests_.insert(
+ std::make_pair(key, SignRequestState(/*begin_time=*/base::Time::Now(),
+ authenticating_user_account_id)));
+}
+
+void PinDialogManager::RemoveSignRequest(const std::string& extension_id,
+ int sign_request_id) {
+ if (active_dialog_state_ &&
+ active_dialog_state_->extension_id == extension_id &&
+ active_dialog_state_->sign_request_id == sign_request_id) {
+ CloseActiveDialog();
+ }
+
+ ExtensionNameRequestIdPair key(extension_id, sign_request_id);
+ sign_requests_.erase(key);
+}
+
+int PinDialogManager::StoredSignRequestsForTesting() const {
+ return sign_requests_.size();
+}
+
+PinDialogManager::RequestPinResult PinDialogManager::RequestPin(
+ const std::string& extension_id,
+ const std::string& extension_name,
+ int sign_request_id,
+ security_token_pin::CodeType code_type,
+ security_token_pin::ErrorLabel error_label,
+ int attempts_left,
+ RequestPinCallback callback) {
+ DCHECK_GE(attempts_left, -1);
+ const bool accept_input = (attempts_left != 0);
+
+ // Check the validity of sign_request_id.
+ const SignRequestState* const sign_request_state =
+ FindSignRequestState(extension_id, sign_request_id);
+ if (!sign_request_state)
+ return RequestPinResult::kInvalidId;
+
+ // Start from sanity checks, as the extension might have issued this call
+ // incorrectly.
+ if (active_dialog_state_) {
+ // The active dialog exists already, so we need to make sure it belongs to
+ // the same extension and the user submitted some input.
+ if (extension_id != active_dialog_state_->extension_id)
+ return RequestPinResult::kOtherFlowInProgress;
+ if (active_dialog_state_->request_pin_callback ||
+ active_dialog_state_->stop_pin_request_callback) {
+ // Extension requests a PIN without having received any input from its
+ // previous request. Reject the new request.
+ return RequestPinResult::kDialogDisplayedAlready;
+ }
+ } else {
+ // Check that the sign request hasn't timed out yet.
+ const base::Time current_time = base::Time::Now();
+ if (current_time - sign_request_state->begin_time > kSignRequestIdTimeout)
+ return RequestPinResult::kInvalidId;
+
+ // A new dialog will be opened, so initialize the related internal state.
+ active_dialog_state_.emplace(GetHostForNewDialog(), extension_id,
+ extension_name, sign_request_id, code_type);
+ }
+
+ active_dialog_state_->request_pin_callback = std::move(callback);
+ active_dialog_state_->host->ShowSecurityTokenPinDialog(
+ extension_name, code_type, accept_input, error_label, attempts_left,
+ sign_request_state->authenticating_user_account_id,
+ base::BindOnce(&PinDialogManager::OnPinEntered,
+ weak_factory_.GetWeakPtr()),
+ base::BindOnce(&PinDialogManager::OnPinDialogClosed,
+ weak_factory_.GetWeakPtr()));
+
+ return RequestPinResult::kSuccess;
+}
+
+PinDialogManager::StopPinRequestResult
+PinDialogManager::StopPinRequestWithError(
+ const std::string& extension_id,
+ security_token_pin::ErrorLabel error_label,
+ StopPinRequestCallback callback) {
+ DCHECK_NE(error_label, security_token_pin::ErrorLabel::kNone);
+
+ // Perform sanity checks, as the extension might have issued this call
+ // incorrectly.
+ if (!active_dialog_state_ ||
+ active_dialog_state_->extension_id != extension_id) {
+ return StopPinRequestResult::kNoActiveDialog;
+ }
+ if (active_dialog_state_->request_pin_callback ||
+ active_dialog_state_->stop_pin_request_callback) {
+ return StopPinRequestResult::kNoUserInput;
+ }
+
+ const SignRequestState* const sign_request_state =
+ FindSignRequestState(extension_id, active_dialog_state_->sign_request_id);
+ if (!sign_request_state)
+ return StopPinRequestResult::kNoActiveDialog;
+
+ active_dialog_state_->stop_pin_request_callback = std::move(callback);
+ active_dialog_state_->host->ShowSecurityTokenPinDialog(
+ active_dialog_state_->extension_name, active_dialog_state_->code_type,
+ /*enable_user_input=*/false, error_label,
+ /*attempts_left=*/-1, sign_request_state->authenticating_user_account_id,
+ base::BindOnce(&PinDialogManager::OnPinEntered,
+ weak_factory_.GetWeakPtr()),
+ base::BindOnce(&PinDialogManager::OnPinDialogClosed,
+ weak_factory_.GetWeakPtr()));
+
+ return StopPinRequestResult::kSuccess;
+}
+
+bool PinDialogManager::LastPinDialogClosed(
+ const std::string& extension_id) const {
+ auto iter = last_response_closed_.find(extension_id);
+ return iter != last_response_closed_.end() && iter->second;
+}
+
+bool PinDialogManager::CloseDialog(const std::string& extension_id) {
+ // Perform sanity checks, as the extension might have issued this call
+ // incorrectly.
+ if (!active_dialog_state_ ||
+ extension_id != active_dialog_state_->extension_id) {
+ LOG(ERROR) << "StopPinRequest called by unexpected extension: "
+ << extension_id;
+ return false;
+ }
+
+ CloseActiveDialog();
+ return true;
+}
+
+void PinDialogManager::ExtensionUnloaded(const std::string& extension_id) {
+ if (active_dialog_state_ &&
+ active_dialog_state_->extension_id == extension_id) {
+ CloseActiveDialog();
+ }
+
+ last_response_closed_[extension_id] = false;
+
+ for (auto it = sign_requests_.cbegin(); it != sign_requests_.cend();) {
+ if (it->first.first == extension_id)
+ sign_requests_.erase(it++);
+ else
+ ++it;
+ }
+}
+
+void PinDialogManager::AddPinDialogHost(
+ SecurityTokenPinDialogHost* pin_dialog_host) {
+ DCHECK(!base::Contains(added_dialog_hosts_, pin_dialog_host));
+ added_dialog_hosts_.push_back(pin_dialog_host);
+}
+
+void PinDialogManager::RemovePinDialogHost(
+ SecurityTokenPinDialogHost* pin_dialog_host) {
+ if (active_dialog_state_ && active_dialog_state_->host == pin_dialog_host)
+ CloseActiveDialog();
+ DCHECK(base::Contains(added_dialog_hosts_, pin_dialog_host));
+ base::Erase(added_dialog_hosts_, pin_dialog_host);
+}
+
+PinDialogManager::SignRequestState::SignRequestState(
+ base::Time begin_time,
+ const absl::optional<AccountId>& authenticating_user_account_id)
+ : begin_time(begin_time),
+ authenticating_user_account_id(authenticating_user_account_id) {}
+
+PinDialogManager::SignRequestState::SignRequestState(const SignRequestState&) =
+ default;
+PinDialogManager::SignRequestState&
+PinDialogManager::SignRequestState::operator=(const SignRequestState&) =
+ default;
+
+PinDialogManager::SignRequestState::~SignRequestState() = default;
+
+PinDialogManager::ActiveDialogState::ActiveDialogState(
+ SecurityTokenPinDialogHost* host,
+ const std::string& extension_id,
+ const std::string& extension_name,
+ int sign_request_id,
+ security_token_pin::CodeType code_type)
+ : host(host),
+ extension_id(extension_id),
+ extension_name(extension_name),
+ sign_request_id(sign_request_id),
+ code_type(code_type) {}
+
+PinDialogManager::ActiveDialogState::~ActiveDialogState() = default;
+
+PinDialogManager::SignRequestState* PinDialogManager::FindSignRequestState(
+ const std::string& extension_id,
+ int sign_request_id) {
+ const ExtensionNameRequestIdPair key(extension_id, sign_request_id);
+ const auto sign_request_iter = sign_requests_.find(key);
+ if (sign_request_iter == sign_requests_.end())
+ return nullptr;
+ return &sign_request_iter->second;
+}
+
+void PinDialogManager::OnPinEntered(const std::string& user_input) {
+ DCHECK(!active_dialog_state_->stop_pin_request_callback);
+ last_response_closed_[active_dialog_state_->extension_id] = false;
+ if (active_dialog_state_->request_pin_callback)
+ std::move(active_dialog_state_->request_pin_callback).Run(user_input);
+}
+
+void PinDialogManager::OnPinDialogClosed() {
+ DCHECK(!active_dialog_state_->request_pin_callback ||
+ !active_dialog_state_->stop_pin_request_callback);
+
+ last_response_closed_[active_dialog_state_->extension_id] = true;
+ if (active_dialog_state_->request_pin_callback) {
+ std::move(active_dialog_state_->request_pin_callback)
+ .Run(/*user_input=*/std::string());
+ }
+ if (active_dialog_state_->stop_pin_request_callback)
+ std::move(active_dialog_state_->stop_pin_request_callback).Run();
+ active_dialog_state_.reset();
+}
+
+SecurityTokenPinDialogHost* PinDialogManager::GetHostForNewDialog() {
+ if (added_dialog_hosts_.empty())
+ return &default_dialog_host_;
+ return added_dialog_hosts_.back();
+}
+
+void PinDialogManager::CloseActiveDialog() {
+ if (!active_dialog_state_)
+ return;
+
+ // Ignore any further callbacks from the host. Instead of relying on the host
+ // to call the closing callback, run OnPinDialogClosed() below explicitly.
+ weak_factory_.InvalidateWeakPtrs();
+
+ active_dialog_state_->host->CloseSecurityTokenPinDialog();
+ OnPinDialogClosed();
+ DCHECK(!active_dialog_state_);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/pin_dialog_manager.h b/chrome/browser/certificate_provider/pin_dialog_manager.h
new file mode 100644
index 0000000..3956092
--- /dev/null
+++ b/chrome/browser/certificate_provider/pin_dialog_manager.h
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "chrome/browser/certificate_provider/security_token_pin_dialog_host.h"
+#include "chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h"
+#include "chromeos/components/security_token_pin/constants.h"
+#include "components/account_id/account_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+// Manages the state of the dialog that requests the PIN from user. Used by the
+// extensions that need to request the PIN. Implemented as requirement for
+// crbug.com/612886
+class PinDialogManager final {
+ public:
+ enum class RequestPinResult {
+ kSuccess,
+ kInvalidId,
+ kOtherFlowInProgress,
+ kDialogDisplayedAlready,
+ };
+
+ enum class StopPinRequestResult {
+ kSuccess,
+ kNoActiveDialog,
+ kNoUserInput,
+ };
+
+ using RequestPinCallback =
+ base::OnceCallback<void(const std::string& user_input)>;
+ using StopPinRequestCallback = base::OnceClosure;
+
+ PinDialogManager();
+ PinDialogManager(const PinDialogManager&) = delete;
+ PinDialogManager& operator=(const PinDialogManager&) = delete;
+ ~PinDialogManager();
+
+ // Stores internally the |signRequestId| along with current timestamp.
+ void AddSignRequestId(
+ const std::string& extension_id,
+ int sign_request_id,
+ const absl::optional<AccountId>& authenticating_user_account_id);
+
+ // Removes the specified sign request, aborting both the current and the
+ // future PIN dialogs related to it.
+ void RemoveSignRequest(const std::string& extension_id, int sign_request_id);
+
+ // Returns the number of pending sign requests stored in sign_requests_
+ int StoredSignRequestsForTesting() const;
+
+ // Creates and displays a new PIN dialog, or reuses the old dialog with just
+ // updating the parameters if active one exists.
+ // |extension_id| - the ID of the extension requesting the dialog.
+ // |extension_name| - the name of the extension requesting the dialog.
+ // |sign_request_id| - the ID given by Chrome when the extension was asked to
+ // sign the data. It should be a valid, not expired ID at the time the
+ // extension is requesting PIN the first time.
+ // |code_type| - the type of input requested: either "PIN" or "PUK".
+ // |error_label| - the error template to be displayed inside the dialog. If
+ // |kNone|, no error is displayed.
+ // |attempts_left| - the number of attempts the user has to try the code. It
+ // is informational only, and enforced on Chrome side only in case it's
+ // zero. In that case the textfield is disabled and the user can't provide
+ // any input to extension. If -1 the textfield from the dialog is enabled
+ // but no information about the attepts left is not given to the user.
+ // |callback| - used to notify about the user input in the text_field from the
+ // dialog.
+ // Returns |kSuccess| if the dialog is displayed and extension owns it.
+ // Otherwise the specific error is returned.
+ RequestPinResult RequestPin(const std::string& extension_id,
+ const std::string& extension_name,
+ int sign_request_id,
+ security_token_pin::CodeType code_type,
+ security_token_pin::ErrorLabel error_label,
+ int attempts_left,
+ RequestPinCallback callback);
+
+ // Updates the existing dialog with the error message. Returns whether the
+ // provided |extension_id| matches the extension owning the active dialog.
+ // When it is, the |callback| will be executed once the UI is completed (e.g.,
+ // the dialog with the error message is closed by the user).
+ StopPinRequestResult StopPinRequestWithError(
+ const std::string& extension_id,
+ security_token_pin::ErrorLabel error_label,
+ StopPinRequestCallback callback);
+
+ // Returns whether the last PIN dialog from this extension was closed by the
+ // user.
+ bool LastPinDialogClosed(const std::string& extension_id) const;
+
+ // Called when extension calls the stopPinRequest method. The active dialog is
+ // closed if the |extension_id| matches the |active_dialog_extension_id_|.
+ // Returns whether the dialog was closed.
+ bool CloseDialog(const std::string& extension_id);
+
+ // Resets the manager data related to the extension.
+ void ExtensionUnloaded(const std::string& extension_id);
+
+ // Dynamically adds the dialog host that can be used by this instance for
+ // showing new dialogs. There may be multiple hosts added, in which case the
+ // most recently added is used. Before any hosts have been added, the default
+ // (popup-based) host is used.
+ void AddPinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
+ // Removes the previously added dialog host. If a dialog is still opened in
+ // this host, closes it beforehand.
+ void RemovePinDialogHost(SecurityTokenPinDialogHost* pin_dialog_host);
+
+ SecurityTokenPinDialogHostPopupImpl* default_dialog_host_for_testing() {
+ return &default_dialog_host_;
+ }
+
+ private:
+ struct SignRequestState {
+ SignRequestState(
+ base::Time begin_time,
+ const absl::optional<AccountId>& authenticating_user_account_id);
+ SignRequestState(const SignRequestState&);
+ SignRequestState& operator=(const SignRequestState&);
+ ~SignRequestState();
+
+ base::Time begin_time;
+ absl::optional<AccountId> authenticating_user_account_id;
+ };
+
+ // Holds information related to the currently opened PIN dialog.
+ struct ActiveDialogState {
+ ActiveDialogState(SecurityTokenPinDialogHost* host,
+ const std::string& extension_id,
+ const std::string& extension_name,
+ int sign_request_id,
+ security_token_pin::CodeType code_type);
+ ~ActiveDialogState();
+
+ // Remember the host that was used to open the active dialog, as new hosts
+ // could have been added since the dialog was opened, but we want to
+ // continue calling the same host when dealing with the same active dialog.
+ SecurityTokenPinDialogHost* const host;
+
+ const std::string extension_id;
+ const std::string extension_name;
+ const int sign_request_id;
+ const security_token_pin::CodeType code_type;
+ RequestPinCallback request_pin_callback;
+ StopPinRequestCallback stop_pin_request_callback;
+ };
+
+ using ExtensionNameRequestIdPair = std::pair<std::string, int>;
+
+ // Returns the sign request state for the given key, or null if not found.
+ SignRequestState* FindSignRequestState(const std::string& extension_id,
+ int sign_request_id);
+
+ // The callback that gets invoked once the user sends some input into the PIN
+ // dialog.
+ void OnPinEntered(const std::string& user_input);
+ // The callback that gets invoked once the PIN dialog gets closed.
+ void OnPinDialogClosed();
+
+ // Returns the dialog host that should own the new dialog. Currently returns
+ // the most recently added dialog host (falling back to the default one when
+ // no host has been added).
+ SecurityTokenPinDialogHost* GetHostForNewDialog();
+
+ // Closes the active dialog, if there's any, and runs the necessary callbacks.
+ void CloseActiveDialog();
+
+ // Tells whether user closed the last request PIN dialog issued by an
+ // extension. The extension_id is the key and value is true if user closed the
+ // dialog. Used to determine if the limit of dialogs rejected by the user has
+ // been exceeded.
+ std::unordered_map<std::string, bool> last_response_closed_;
+
+ // The map from extension_id and an active sign request id to the state of the
+ // request.
+ std::map<ExtensionNameRequestIdPair, SignRequestState> sign_requests_;
+
+ SecurityTokenPinDialogHostPopupImpl default_dialog_host_;
+ // The list of dynamically added dialog hosts, in the same order as they were
+ // added.
+ std::vector<SecurityTokenPinDialogHost*> added_dialog_hosts_;
+
+ // There can be only one active dialog to request the PIN at any point of
+ // time.
+ absl::optional<ActiveDialogState> active_dialog_state_;
+
+ base::WeakPtrFactory<PinDialogManager> weak_factory_{this};
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_PIN_DIALOG_MANAGER_H_
diff --git a/chrome/browser/certificate_provider/security_token_pin_dialog_host.h b/chrome/browser/certificate_provider/security_token_pin_dialog_host.h
new file mode 100644
index 0000000..a361d26
--- /dev/null
+++ b/chrome/browser/certificate_provider/security_token_pin_dialog_host.h
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "chromeos/components/security_token_pin/constants.h"
+#include "components/account_id/account_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+// The interface that allows showing a PIN dialog requested by a certificate
+// provider extension (see the chrome.certificateProvider API).
+//
+// Only one dialog is supported at a time by a single host instance.
+class SecurityTokenPinDialogHost {
+ public:
+ using SecurityTokenPinEnteredCallback =
+ base::OnceCallback<void(const std::string& user_input)>;
+ using SecurityTokenPinDialogClosedCallback = base::OnceClosure;
+
+ // Note that this does NOT execute the callback.
+ virtual ~SecurityTokenPinDialogHost() = default;
+
+ // Shows the PIN dialog, or updates the existing one if it's already shown.
+ //
+ // Note that the caller is still responsible for closing the opened dialog, by
+ // calling CloseSecurityTokenPinDialog(), after the |callback| got executed
+ // with a non-empty |user_input|.
+ //
+ // Note also that when the existing dialog is updated, its old callbacks will
+ // NOT be called at all.
+ //
+ // |caller_extension_name| - name of the extension that requested this dialog.
+ // |code_type| - type of the code requested from the user.
+ // |enable_user_input| - when false, the UI will disable the controls that
+ // allow user to enter the PIN/PUK. MUST be |false| when |attempts_left|
+ // is zero.
+ // |error_label| - optionally, specifies the error that the UI should display
+ // (note that a non-empty error does NOT disable the user input per se).
+ // |attempts_left| - when non-negative, the UI should indicate this number to
+ // the user; otherwise must be equal to -1.
+ // |authenticating_user_account_id| - when set, is the ID of the user whose
+ // authentication triggered this PIN request.
+ // |pin_entered_callback| - called when the user submits the input.
+ // |pin_dialog_closed_callback| - called when the dialog is closed (either by
+ // the user or programmatically; it's optional whether to call it after
+ // CloseSecurityTokenPinDialog()).
+ virtual void ShowSecurityTokenPinDialog(
+ const std::string& caller_extension_name,
+ security_token_pin::CodeType code_type,
+ bool enable_user_input,
+ security_token_pin::ErrorLabel error_label,
+ int attempts_left,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ SecurityTokenPinEnteredCallback pin_entered_callback,
+ SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) = 0;
+
+ // Closes the currently shown PIN dialog, if there's any. The implementation
+ // is NOT required to run |pin_dialog_closed_callback| after the closing.
+ virtual void CloseSecurityTokenPinDialog() = 0;
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_H_
diff --git a/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.cc b/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.cc
new file mode 100644
index 0000000..d03dd9bb
--- /dev/null
+++ b/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.cc
@@ -0,0 +1,117 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/views/notifications/request_pin_view_chromeos.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_delegate.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ash/login/ui/login_display_host.h"
+#endif
+
+namespace chromeos {
+
+namespace {
+
+gfx::NativeWindow GetBrowserParentWindow() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ if (LoginDisplayHost::default_host())
+ return LoginDisplayHost::default_host()->GetNativeWindow();
+#endif
+ Browser* browser =
+ chrome::FindTabbedBrowser(ProfileManager::GetPrimaryUserProfile(), true);
+ if (browser)
+ return browser->window()->GetNativeWindow();
+
+ return nullptr;
+}
+
+} // namespace
+
+SecurityTokenPinDialogHostPopupImpl::SecurityTokenPinDialogHostPopupImpl() =
+ default;
+
+SecurityTokenPinDialogHostPopupImpl::~SecurityTokenPinDialogHostPopupImpl() =
+ default;
+
+void SecurityTokenPinDialogHostPopupImpl::ShowSecurityTokenPinDialog(
+ const std::string& caller_extension_name,
+ security_token_pin::CodeType code_type,
+ bool enable_user_input,
+ security_token_pin::ErrorLabel error_label,
+ int attempts_left,
+ const absl::optional<AccountId>& /*authenticating_user_account_id*/,
+ SecurityTokenPinEnteredCallback pin_entered_callback,
+ SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) {
+ DCHECK(!caller_extension_name.empty());
+ DCHECK(!enable_user_input || attempts_left);
+ DCHECK_GE(attempts_left, -1);
+
+ pin_entered_callback_ = std::move(pin_entered_callback);
+ pin_dialog_closed_callback_ = std::move(pin_dialog_closed_callback);
+
+ if (active_pin_dialog_) {
+ active_pin_dialog_->SetDialogParameters(code_type, error_label,
+ attempts_left, enable_user_input);
+ active_pin_dialog_->SetExtensionName(caller_extension_name);
+ active_pin_dialog_->DialogModelChanged();
+ } else {
+ active_pin_dialog_ = new RequestPinView(
+ caller_extension_name, code_type, attempts_left,
+ base::BindRepeating(&SecurityTokenPinDialogHostPopupImpl::OnPinEntered,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&SecurityTokenPinDialogHostPopupImpl::OnViewDestroyed,
+ weak_ptr_factory_.GetWeakPtr()));
+ // If there is no parent, falls back to the root window for new windows.
+ active_window_ = views::DialogDelegate::CreateDialogWidget(
+ active_pin_dialog_, /*context=*/nullptr, GetBrowserParentWindow());
+ active_window_->Show();
+ }
+}
+
+void SecurityTokenPinDialogHostPopupImpl::CloseSecurityTokenPinDialog() {
+ if (!active_pin_dialog_)
+ return;
+ active_window_->Close();
+ // The view destruction may happen asynchronously, so clear our state and
+ // execute the callback immediately in order to follow our own API contract.
+ active_pin_dialog_ = nullptr;
+ active_window_ = nullptr;
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ if (pin_dialog_closed_callback_)
+ std::move(pin_dialog_closed_callback_).Run();
+}
+
+void SecurityTokenPinDialogHostPopupImpl::OnPinEntered(
+ const std::string& user_input) {
+ DCHECK(active_pin_dialog_);
+ DCHECK(active_window_);
+ std::move(pin_entered_callback_).Run(user_input);
+}
+
+void SecurityTokenPinDialogHostPopupImpl::OnViewDestroyed() {
+ DCHECK(active_pin_dialog_);
+ DCHECK(active_window_);
+
+ active_pin_dialog_ = nullptr;
+ active_window_ = nullptr;
+ if (pin_dialog_closed_callback_)
+ std::move(pin_dialog_closed_callback_).Run();
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h b/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h
new file mode 100644
index 0000000..272656c
--- /dev/null
+++ b/chrome/browser/certificate_provider/security_token_pin_dialog_host_popup_impl.h
@@ -0,0 +1,69 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/certificate_provider/security_token_pin_dialog_host.h"
+
+namespace views {
+class Widget;
+} // namespace views
+
+class RequestPinView;
+
+namespace chromeos {
+
+// The default implementation of the PIN dialog host. It renders the PIN dialog
+// as a popup with the RequestPinView view.
+class SecurityTokenPinDialogHostPopupImpl final
+ : public SecurityTokenPinDialogHost {
+ public:
+ SecurityTokenPinDialogHostPopupImpl();
+ SecurityTokenPinDialogHostPopupImpl(
+ const SecurityTokenPinDialogHostPopupImpl&) = delete;
+ SecurityTokenPinDialogHostPopupImpl& operator=(
+ const SecurityTokenPinDialogHostPopupImpl&) = delete;
+ ~SecurityTokenPinDialogHostPopupImpl() override;
+
+ // SecurityTokenPinDialogHost:
+ void ShowSecurityTokenPinDialog(
+ const std::string& caller_extension_name,
+ security_token_pin::CodeType code_type,
+ bool enable_user_input,
+ security_token_pin::ErrorLabel error_label,
+ int attempts_left,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ SecurityTokenPinEnteredCallback pin_entered_callback,
+ SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback) override;
+ void CloseSecurityTokenPinDialog() override;
+
+ RequestPinView* active_view_for_testing() { return active_pin_dialog_; }
+ views::Widget* active_window_for_testing() { return active_window_; }
+
+ private:
+ // Called every time the user submits some input.
+ void OnPinEntered(const std::string& user_input);
+ // Called when the |active_pin_dialog_| view is being destroyed.
+ void OnViewDestroyed();
+
+ SecurityTokenPinEnteredCallback pin_entered_callback_;
+ SecurityTokenPinDialogClosedCallback pin_dialog_closed_callback_;
+
+ // Owned by |active_window_|.
+ RequestPinView* active_pin_dialog_ = nullptr;
+ // Owned by the UI code (NativeWidget).
+ views::Widget* active_window_ = nullptr;
+
+ base::WeakPtrFactory<SecurityTokenPinDialogHostPopupImpl> weak_ptr_factory_{
+ this};
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_SECURITY_TOKEN_PIN_DIALOG_HOST_POPUP_IMPL_H_
diff --git a/chrome/browser/certificate_provider/sign_requests.cc b/chrome/browser/certificate_provider/sign_requests.cc
new file mode 100644
index 0000000..a98de3dc
--- /dev/null
+++ b/chrome/browser/certificate_provider/sign_requests.cc
@@ -0,0 +1,95 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/sign_requests.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+SignRequests::Request::Request(
+ const scoped_refptr<net::X509Certificate>& certificate,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback)
+ : certificate(certificate),
+ authenticating_user_account_id(authenticating_user_account_id),
+ callback(std::move(callback)) {}
+
+SignRequests::Request::Request(Request&& other) = default;
+
+SignRequests::Request::~Request() = default;
+
+SignRequests::Request& SignRequests::Request::operator=(Request&&) = default;
+
+SignRequests::RequestsState::RequestsState() {}
+
+SignRequests::RequestsState::RequestsState(RequestsState&& other) = default;
+
+SignRequests::RequestsState::~RequestsState() {}
+
+SignRequests::SignRequests() {}
+
+SignRequests::~SignRequests() {}
+
+int SignRequests::AddRequest(
+ const std::string& extension_id,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback) {
+ RequestsState& state = extension_to_requests_[extension_id];
+ const int request_id = state.next_free_id++;
+ state.pending_requests.emplace(
+ request_id, Request(certificate, authenticating_user_account_id,
+ std::move(callback)));
+ return request_id;
+}
+
+std::vector<SignRequests::ExtensionNameRequestIdPair>
+SignRequests::FindRequestsForAuthenticatingUser(
+ const AccountId& authenticating_user_account_id) const {
+ std::vector<ExtensionNameRequestIdPair> found_requests;
+ for (const auto& extension_entry : extension_to_requests_) {
+ const std::string& extension_id = extension_entry.first;
+ const RequestsState& extension_requests = extension_entry.second;
+ for (const auto& entry : extension_requests.pending_requests) {
+ const int request_id = entry.first;
+ const Request& request = entry.second;
+ if (request.authenticating_user_account_id ==
+ authenticating_user_account_id) {
+ found_requests.emplace_back(extension_id, request_id);
+ }
+ }
+ }
+ return found_requests;
+}
+
+bool SignRequests::RemoveRequest(
+ const std::string& extension_id,
+ int request_id,
+ scoped_refptr<net::X509Certificate>* certificate,
+ net::SSLPrivateKey::SignCallback* callback) {
+ RequestsState& state = extension_to_requests_[extension_id];
+ std::map<int, Request>& pending = state.pending_requests;
+ const auto it = pending.find(request_id);
+ if (it == pending.end())
+ return false;
+ Request& request = it->second;
+
+ *certificate = request.certificate;
+ *callback = std::move(request.callback);
+ pending.erase(it);
+ return true;
+}
+
+std::vector<net::SSLPrivateKey::SignCallback> SignRequests::RemoveAllRequests(
+ const std::string& extension_id) {
+ std::vector<net::SSLPrivateKey::SignCallback> callbacks;
+ for (auto& entry : extension_to_requests_[extension_id].pending_requests) {
+ callbacks.push_back(std::move(entry.second.callback));
+ }
+ extension_to_requests_.erase(extension_id);
+ return callbacks;
+}
+
+} // namespace certificate_provider
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/sign_requests.h b/chrome/browser/certificate_provider/sign_requests.h
new file mode 100644
index 0000000..ccb3d20
--- /dev/null
+++ b/chrome/browser/certificate_provider/sign_requests.h
@@ -0,0 +1,92 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_SIGN_REQUESTS_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_SIGN_REQUESTS_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "components/account_id/account_id.h"
+#include "net/cert/x509_certificate.h"
+#include "net/ssl/ssl_private_key.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+namespace certificate_provider {
+
+class SignRequests {
+ public:
+ using ExtensionNameRequestIdPair = std::pair<std::string, int>;
+
+ SignRequests();
+ ~SignRequests();
+
+ // Returns the id of the new request. The returned request id is specific to
+ // the given extension.
+ int AddRequest(
+ const std::string& extension_id,
+ const scoped_refptr<net::X509Certificate>& certificate,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback);
+
+ // Returns the list of requests that correspond to the authentication of the
+ // given user.
+ std::vector<ExtensionNameRequestIdPair> FindRequestsForAuthenticatingUser(
+ const AccountId& authenticating_user_account_id) const;
+
+ // Returns false if no request with the given id for |extension_id|
+ // could be found. Otherwise removes the request and sets |certificate| and
+ // |callback| to the values that were provided with AddRequest().
+ bool RemoveRequest(const std::string& extension_id,
+ int request_id,
+ scoped_refptr<net::X509Certificate>* certificate,
+ net::SSLPrivateKey::SignCallback* callback);
+
+ // Remove all pending requests for this extension and return their
+ // callbacks.
+ std::vector<net::SSLPrivateKey::SignCallback> RemoveAllRequests(
+ const std::string& extension_id);
+
+ private:
+ struct Request {
+ Request(const scoped_refptr<net::X509Certificate>& certificate,
+ const absl::optional<AccountId>& authenticating_user_account_id,
+ net::SSLPrivateKey::SignCallback callback);
+ Request(Request&& other);
+ Request& operator=(Request&&);
+ ~Request();
+
+ scoped_refptr<net::X509Certificate> certificate;
+ absl::optional<AccountId> authenticating_user_account_id;
+ net::SSLPrivateKey::SignCallback callback;
+ };
+
+ // Holds state of all sign requests to a single extension.
+ struct RequestsState {
+ RequestsState();
+ RequestsState(RequestsState&& other);
+ RequestsState& operator=(RequestsState&&);
+ ~RequestsState();
+
+ // Maps from request id to the request state.
+ std::map<int, Request> pending_requests;
+
+ // The request id that will be used for the next sign request to this
+ // extension.
+ int next_free_id = 0;
+ };
+
+ // Contains the state of all sign requests per extension.
+ std::map<std::string, RequestsState> extension_to_requests_;
+};
+
+} // namespace certificate_provider
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_SIGN_REQUESTS_H_
diff --git a/chrome/browser/certificate_provider/test_certificate_provider_extension.cc b/chrome/browser/certificate_provider/test_certificate_provider_extension.cc
new file mode 100644
index 0000000..9e43711
--- /dev/null
+++ b/chrome/browser/certificate_provider/test_certificate_provider_extension.cc
@@ -0,0 +1,360 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/test_certificate_provider_extension.h"
+
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/path_service.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/api/certificate_provider.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "crypto/rsa_private_key.h"
+#include "extensions/browser/api/test/test_api.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/api/test.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/boringssl/src/include/openssl/rsa.h"
+#include "third_party/boringssl/src/include/openssl/ssl.h"
+
+namespace ash {
+
+namespace {
+
+constexpr char kExtensionId[] = "ecmhnokcdiianioonpgakiooenfnonid";
+// Paths relative to |chrome::DIR_TEST_DATA|:
+constexpr base::FilePath::CharType kExtensionPath[] =
+ FILE_PATH_LITERAL("extensions/test_certificate_provider/extension/");
+constexpr base::FilePath::CharType kExtensionPemPath[] =
+ FILE_PATH_LITERAL("extensions/test_certificate_provider/extension.pem");
+
+// List of algorithms that the extension claims to support for the returned
+// certificates.
+constexpr extensions::api::certificate_provider::Algorithm
+ kSupportedAlgorithms[] = {extensions::api::certificate_provider::Algorithm::
+ ALGORITHM_RSASSA_PKCS1_V1_5_SHA256,
+ extensions::api::certificate_provider::Algorithm::
+ ALGORITHM_RSASSA_PKCS1_V1_5_SHA1};
+
+base::Value ConvertBytesToValue(base::span<const uint8_t> bytes) {
+ base::Value value(base::Value::Type::LIST);
+ for (auto byte : bytes)
+ value.Append(byte);
+ return value;
+}
+
+std::vector<uint8_t> ExtractBytesFromValue(const base::Value& value) {
+ std::vector<uint8_t> bytes;
+ for (const base::Value& item_value : value.GetListDeprecated())
+ bytes.push_back(base::checked_cast<uint8_t>(item_value.GetInt()));
+ return bytes;
+}
+
+base::span<const uint8_t> GetCertDer(const net::X509Certificate& certificate) {
+ return base::as_bytes(base::make_span(
+ net::x509_util::CryptoBufferAsStringPiece(certificate.cert_buffer())));
+}
+
+base::Value MakeClientCertificateInfoValue(
+ const net::X509Certificate& certificate) {
+ base::Value cert_info_value(base::Value::Type::DICTIONARY);
+ base::Value certificate_chain(base::Value::Type::LIST);
+ certificate_chain.Append(ConvertBytesToValue(GetCertDer(certificate)));
+ cert_info_value.SetKey("certificateChain", std::move(certificate_chain));
+ base::Value supported_algorithms_value(base::Value::Type::LIST);
+ for (auto supported_algorithm : kSupportedAlgorithms) {
+ supported_algorithms_value.Append(
+ extensions::api::certificate_provider::ToString(supported_algorithm));
+ }
+ cert_info_value.SetKey("supportedAlgorithms",
+ std::move(supported_algorithms_value));
+ return cert_info_value;
+}
+
+std::string ConvertValueToJson(const base::Value& value) {
+ std::string json;
+ CHECK(base::JSONWriter::Write(value, &json));
+ return json;
+}
+
+base::Value ParseJsonToValue(const std::string& json) {
+ absl::optional<base::Value> value = base::JSONReader::Read(json);
+ CHECK(value);
+ return std::move(*value);
+}
+
+bool RsaSignRawData(crypto::RSAPrivateKey* key,
+ uint16_t openssl_signature_algorithm,
+ const std::vector<uint8_t>& input,
+ 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;
+}
+
+void SendReplyToJs(ExtensionTestMessageListener* message_listener,
+ const base::Value& response) {
+ message_listener->Reply(ConvertValueToJson(response));
+ message_listener->Reset();
+}
+
+std::unique_ptr<crypto::RSAPrivateKey> LoadPrivateKeyFromFile(
+ const base::FilePath& path) {
+ std::string key_pk8;
+ {
+ base::ScopedAllowBlockingForTesting allow_io;
+ EXPECT_TRUE(base::ReadFileToString(path, &key_pk8));
+ }
+ return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(
+ base::as_bytes(base::make_span(key_pk8)));
+}
+
+} // namespace
+
+// static
+extensions::ExtensionId TestCertificateProviderExtension::extension_id() {
+ return kExtensionId;
+}
+
+// static
+base::FilePath TestCertificateProviderExtension::GetExtensionSourcePath() {
+ return base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
+ .Append(kExtensionPath);
+}
+
+// static
+base::FilePath TestCertificateProviderExtension::GetExtensionPemPath() {
+ return base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
+ .Append(kExtensionPemPath);
+}
+
+// static
+scoped_refptr<net::X509Certificate>
+TestCertificateProviderExtension::GetCertificate() {
+ return net::ImportCertFromFile(net::GetTestCertsDirectory(), "client_1.pem");
+}
+
+// static
+std::string TestCertificateProviderExtension::GetCertificateSpki() {
+ const scoped_refptr<net::X509Certificate> certificate = GetCertificate();
+ base::StringPiece spki_bytes;
+ if (!net::asn1::ExtractSPKIFromDERCert(
+ net::x509_util::CryptoBufferAsStringPiece(certificate->cert_buffer()),
+ &spki_bytes)) {
+ return {};
+ }
+ return std::string(spki_bytes);
+}
+
+TestCertificateProviderExtension::TestCertificateProviderExtension(
+ content::BrowserContext* browser_context)
+ : browser_context_(browser_context),
+ certificate_(GetCertificate()),
+ private_key_(LoadPrivateKeyFromFile(net::GetTestCertsDirectory().Append(
+ FILE_PATH_LITERAL("client_1.pk8")))),
+ message_listener_(ReplyBehavior::kWillReply) {
+ DCHECK(browser_context_);
+ CHECK(certificate_);
+ CHECK(private_key_);
+ // Ignore messages targeted to other extensions or browser contexts.
+ message_listener_.set_extension_id(kExtensionId);
+ message_listener_.set_browser_context(browser_context);
+ message_listener_.SetOnRepeatedlySatisfied(
+ base::BindRepeating(&TestCertificateProviderExtension::HandleMessage,
+ base::Unretained(this)));
+}
+
+TestCertificateProviderExtension::~TestCertificateProviderExtension() = default;
+
+void TestCertificateProviderExtension::TriggerSetCertificates() {
+ base::Value message_data(base::Value::Type::DICTIONARY);
+ message_data.SetStringKey("name", "setCertificates");
+ base::Value cert_info_values(base::Value::Type::LIST);
+ if (should_provide_certificates_)
+ cert_info_values.Append(MakeClientCertificateInfoValue(*certificate_));
+ message_data.SetKey("certificateInfoList", std::move(cert_info_values));
+
+ auto message = std::make_unique<base::Value>(base::Value::Type::LIST);
+ message->Append(std::move(message_data));
+ auto event = std::make_unique<extensions::Event>(
+ extensions::events::FOR_TEST,
+ extensions::api::test::OnMessage::kEventName,
+ std::move(*message).TakeListDeprecated(), browser_context_);
+ extensions::EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id(), std::move(event));
+}
+
+void TestCertificateProviderExtension::HandleMessage(
+ const std::string& message) {
+ // Handle the request and reply to it (possibly, asynchronously).
+ base::Value message_value = ParseJsonToValue(message);
+ CHECK(message_value.is_list());
+ CHECK(message_value.GetListDeprecated().size());
+ CHECK(message_value.GetListDeprecated()[0].is_string());
+ const std::string& request_type =
+ message_value.GetListDeprecated()[0].GetString();
+ ReplyToJsCallback send_reply_to_js_callback =
+ base::BindOnce(&SendReplyToJs, &message_listener_);
+ if (request_type == "getCertificates") {
+ CHECK_EQ(message_value.GetListDeprecated().size(), 1U);
+ HandleCertificatesRequest(std::move(send_reply_to_js_callback));
+ } else if (request_type == "onSignatureRequested") {
+ CHECK_EQ(message_value.GetListDeprecated().size(), 4U);
+ HandleSignatureRequest(
+ /*sign_request=*/message_value.GetListDeprecated()[1],
+ /*pin_status=*/message_value.GetListDeprecated()[2],
+ /*pin=*/message_value.GetListDeprecated()[3],
+ std::move(send_reply_to_js_callback));
+ } else {
+ LOG(FATAL) << "Unexpected JS message type: " << request_type;
+ }
+}
+
+void TestCertificateProviderExtension::HandleCertificatesRequest(
+ ReplyToJsCallback callback) {
+ ++certificate_request_count_;
+ base::Value cert_info_values(base::Value::Type::LIST);
+ if (should_provide_certificates_)
+ cert_info_values.Append(MakeClientCertificateInfoValue(*certificate_));
+ std::move(callback).Run(cert_info_values);
+}
+
+void TestCertificateProviderExtension::HandleSignatureRequest(
+ const base::Value& sign_request,
+ const base::Value& pin_status,
+ const base::Value& pin,
+ ReplyToJsCallback callback) {
+ CHECK_EQ(*sign_request.FindKey("certificate"),
+ ConvertBytesToValue(GetCertDer(*certificate_)));
+ const std::string pin_status_string = pin_status.GetString();
+ const std::string pin_string = pin.GetString();
+
+ const int sign_request_id = sign_request.FindKey("signRequestId")->GetInt();
+ const std::vector<uint8_t> input =
+ ExtractBytesFromValue(*sign_request.FindKey("input"));
+
+ const extensions::api::certificate_provider::Algorithm algorithm =
+ extensions::api::certificate_provider::ParseAlgorithm(
+ sign_request.FindKey("algorithm")->GetString());
+ int openssl_signature_algorithm = 0;
+ if (algorithm == extensions::api::certificate_provider::Algorithm::
+ ALGORITHM_RSASSA_PKCS1_V1_5_SHA256) {
+ openssl_signature_algorithm = SSL_SIGN_RSA_PKCS1_SHA256;
+ } else if (algorithm == extensions::api::certificate_provider::Algorithm::
+ ALGORITHM_RSASSA_PKCS1_V1_5_SHA1) {
+ openssl_signature_algorithm = SSL_SIGN_RSA_PKCS1_SHA1;
+ } else {
+ LOG(FATAL) << "Unexpected signature request algorithm: " << algorithm;
+ }
+
+ if (should_fail_sign_digest_requests_) {
+ // Simulate a failure.
+ std::move(callback).Run(/*response=*/base::Value());
+ return;
+ }
+
+ base::Value response(base::Value::Type::DICTIONARY);
+ if (required_pin_.has_value()) {
+ if (pin_status_string == "not_requested") {
+ // The PIN is required but not specified yet, so request it via the JS
+ // side before generating the signature.
+ base::Value pin_request_parameters(base::Value::Type::DICTIONARY);
+ pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
+ if (remaining_pin_attempts_ == 0) {
+ pin_request_parameters.SetStringKey("errorType",
+ "MAX_ATTEMPTS_EXCEEDED");
+ }
+ response.SetKey("requestPin", std::move(pin_request_parameters));
+ std::move(callback).Run(response);
+ return;
+ }
+ if (remaining_pin_attempts_ == 0) {
+ // The error about the lockout is already displayed, so fail immediately.
+ std::move(callback).Run(/*response=*/base::Value());
+ return;
+ }
+ if (pin_status_string == "canceled" ||
+ base::StartsWith(pin_status_string,
+ "failed:", base::CompareCase::SENSITIVE)) {
+ // The PIN request failed.
+ LOG(WARNING) << "PIN request failed: " << pin_status_string;
+ // Respond with a failure.
+ std::move(callback).Run(/*response=*/base::Value());
+ return;
+ }
+ DCHECK_EQ(pin_status_string, "ok");
+ if (pin_string != *required_pin_) {
+ // The entered PIN is wrong, so decrement the remaining attempt count, and
+ // update the PIN dialog with displaying an error.
+ if (remaining_pin_attempts_ > 0)
+ --remaining_pin_attempts_;
+ base::Value pin_request_parameters(base::Value::Type::DICTIONARY);
+ pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
+ pin_request_parameters.SetStringKey(
+ "errorType", remaining_pin_attempts_ == 0 ? "MAX_ATTEMPTS_EXCEEDED"
+ : "INVALID_PIN");
+ if (remaining_pin_attempts_ > 0) {
+ pin_request_parameters.SetIntKey("attemptsLeft",
+ remaining_pin_attempts_);
+ }
+ response.SetKey("requestPin", std::move(pin_request_parameters));
+ std::move(callback).Run(response);
+ return;
+ }
+ // The entered PIN is correct. Stop the PIN request and proceed to
+ // generating the signature.
+ base::Value stop_pin_request_parameters(base::Value::Type::DICTIONARY);
+ stop_pin_request_parameters.SetIntKey("signRequestId", sign_request_id);
+ response.SetKey("stopPinRequest", std::move(stop_pin_request_parameters));
+ }
+ // Generate and return a valid signature.
+ std::vector<uint8_t> signature;
+ CHECK(RsaSignRawData(private_key_.get(), openssl_signature_algorithm, input,
+ &signature));
+ response.SetKey("signature", ConvertBytesToValue(signature));
+ std::move(callback).Run(response);
+}
+
+} // namespace ash
diff --git a/chrome/browser/certificate_provider/test_certificate_provider_extension.h b/chrome/browser/certificate_provider/test_certificate_provider_extension.h
new file mode 100644
index 0000000..beaba4f
--- /dev/null
+++ b/chrome/browser/certificate_provider/test_certificate_provider_extension.h
@@ -0,0 +1,124 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/values.h"
+#include "extensions/common/extension_id.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/cert/x509_certificate.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
+
+namespace base {
+class FilePath;
+class Value;
+} // namespace base
+
+namespace content {
+class BrowserContext;
+}
+
+namespace crypto {
+class RSAPrivateKey;
+}
+
+namespace ash {
+
+// This class provides the C++ side of the test certificate provider extension's
+// implementation (the JavaScript side is in
+// chrome/test/data/extensions/test_certificate_provider).
+//
+// It subscribes itself for requests from the JavaScript side of the extension,
+// and implements the cryptographic operations using the "client_1" test
+// certificate and private key (see src/net/data/ssl/certificates). The
+// supported signature algorithms are currently hardcoded to PKCS #1 v1.5 with
+// SHA-1 and SHA-256.
+class TestCertificateProviderExtension final {
+ public:
+ static extensions::ExtensionId extension_id();
+ static base::FilePath GetExtensionSourcePath();
+ static base::FilePath GetExtensionPemPath();
+ // Returns the certificate provided by the extension.
+ static scoped_refptr<net::X509Certificate> GetCertificate();
+ static std::string GetCertificateSpki();
+
+ explicit TestCertificateProviderExtension(
+ content::BrowserContext* browser_context);
+
+ TestCertificateProviderExtension(const TestCertificateProviderExtension&) =
+ delete;
+ TestCertificateProviderExtension& operator=(
+ const TestCertificateProviderExtension&) = delete;
+
+ ~TestCertificateProviderExtension();
+
+ // Causes the extension to call chrome.certificateProvider.setCertificates,
+ // providing the certificates that are currently available.
+ void TriggerSetCertificates();
+
+ int certificate_request_count() const { return certificate_request_count_; }
+
+ // Sets the PIN that will be required when doing every signature request.
+ // (By default, no PIN is requested.)
+ void set_require_pin(const std::string& pin) { required_pin_ = pin; }
+
+ // Sets the number of remaining PIN attempts.
+ // Zero number means the lockout state, when no attempts are allowed anymore.
+ // A negative number denotes infinite number of attempts, which is the default
+ // behavior.
+ void set_remaining_pin_attempts(int remaining_pin_attempts) {
+ remaining_pin_attempts_ = remaining_pin_attempts;
+ }
+
+ // Sets whether the extension should return any certificates in response to a
+ // onCertificatesRequested request or a TriggerSetCertificates() call.
+ void set_should_provide_certificates(bool should_provide_certificates) {
+ should_provide_certificates_ = should_provide_certificates;
+ }
+
+ // Sets whether the extension should respond with a failure to the
+ // onSignDigestRequested requests.
+ void set_should_fail_sign_digest_requests(
+ bool should_fail_sign_digest_requests) {
+ should_fail_sign_digest_requests_ = should_fail_sign_digest_requests;
+ }
+
+ private:
+ using ReplyToJsCallback =
+ base::OnceCallback<void(const base::Value& response)>;
+
+ void HandleMessage(const std::string& message);
+
+ void HandleCertificatesRequest(ReplyToJsCallback callback);
+ void HandleSignatureRequest(const base::Value& sign_request,
+ const base::Value& pin_status,
+ const base::Value& pin,
+ ReplyToJsCallback callback);
+
+ content::BrowserContext* const browser_context_;
+ const scoped_refptr<net::X509Certificate> certificate_;
+ std::unique_ptr<crypto::RSAPrivateKey> private_key_;
+ int certificate_request_count_ = 0;
+ // When non-empty, contains the expected PIN; the implementation will request
+ // the PIN on every signature request in this case.
+ absl::optional<std::string> required_pin_;
+ // The number of remaining PIN attempts.
+ // When equal to zero, signature requests will be failed immediately; when is
+ // negative, infinite number of attempts is allowed.
+ int remaining_pin_attempts_ = -1;
+ bool should_provide_certificates_ = true;
+ bool should_fail_sign_digest_requests_ = false;
+ ExtensionTestMessageListener message_listener_;
+};
+
+} // namespace ash
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_H_
diff --git a/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.cc b/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.cc
new file mode 100644
index 0000000..e0babe8
--- /dev/null
+++ b/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.cc
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.h"
+
+#include <memory>
+
+#include "chrome/browser/certificate_provider/test_certificate_provider_extension.h"
+#include "chrome/browser/policy/extension_force_install_mixin.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/test/base/mixin_based_in_process_browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+TestCertificateProviderExtensionMixin::TestCertificateProviderExtensionMixin(
+ InProcessBrowserTestMixinHost* host,
+ ExtensionForceInstallMixin* extension_force_install_mixin)
+ : InProcessBrowserTestMixin(host),
+ extension_force_install_mixin_(extension_force_install_mixin) {}
+
+TestCertificateProviderExtensionMixin::
+ ~TestCertificateProviderExtensionMixin() = default;
+
+void TestCertificateProviderExtensionMixin::TearDownOnMainThread() {
+ certificate_provider_extension_.reset();
+}
+
+void TestCertificateProviderExtensionMixin::ForceInstall(
+ Profile* profile,
+ bool wait_on_extension_loaded,
+ bool immediately_provide_certificates) {
+ DCHECK(wait_on_extension_loaded || !immediately_provide_certificates)
+ << "When wait_on_extension_loaded is unset, "
+ "immediately_provide_certificates must also be unset!";
+ DCHECK(extension_force_install_mixin_->initialized())
+ << "ExtensionForceInstallMixin must be initialized before calling "
+ "ForceInstall!";
+ DCHECK(!certificate_provider_extension_) << "ForceInstall already called!";
+ certificate_provider_extension_ =
+ std::make_unique<TestCertificateProviderExtension>(profile);
+ ASSERT_TRUE(extension_force_install_mixin_->ForceInstallFromSourceDir(
+ TestCertificateProviderExtension::GetExtensionSourcePath(),
+ TestCertificateProviderExtension::GetExtensionPemPath(),
+ wait_on_extension_loaded
+ ? ExtensionForceInstallMixin::WaitMode::kBackgroundPageFirstLoad
+ : ExtensionForceInstallMixin::WaitMode::kPrefSet));
+ if (wait_on_extension_loaded && immediately_provide_certificates)
+ certificate_provider_extension_->TriggerSetCertificates();
+}
+
+} // namespace ash
diff --git a/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.h b/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.h
new file mode 100644
index 0000000..5a23347c
--- /dev/null
+++ b/chrome/browser/certificate_provider/test_certificate_provider_extension_mixin.h
@@ -0,0 +1,65 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_MIXIN_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_MIXIN_H_
+
+#include <memory>
+
+#include "chrome/test/base/mixin_based_in_process_browser_test.h"
+
+class ExtensionForceInstallMixin;
+class Profile;
+
+namespace ash {
+
+class TestCertificateProviderExtension;
+
+// Mixin to automatically set up a TestCertificateProviderExtension.
+class TestCertificateProviderExtensionMixin final
+ : public InProcessBrowserTestMixin {
+ public:
+ explicit TestCertificateProviderExtensionMixin(
+ InProcessBrowserTestMixinHost* host,
+ ExtensionForceInstallMixin* extension_force_install_mixin);
+ TestCertificateProviderExtensionMixin(
+ const TestCertificateProviderExtensionMixin&) = delete;
+ TestCertificateProviderExtensionMixin& operator=(
+ const TestCertificateProviderExtensionMixin&) = delete;
+ ~TestCertificateProviderExtensionMixin() override;
+
+ // InProcessBrowserTestMixin:
+ void TearDownOnMainThread() override;
+
+ // Sets up the extension.
+ // Must be called after the `extension_force_install_mixin_` is initialized.
+ // Should only be called once.
+ // This asserts that the extension is installed correctly. To stop execution
+ // of a test on a failed assertion use the ASSERT_NO_FATAL_FAILURE macro.
+ // `wait_on_extension_loaded`: Waits until the extension is ready to provide
+ // certificates.
+ // `immediately_provide_certificates`: Causes the extension to provide
+ // certificates once loaded. Applies only when `wait_on_extension_loaded` is
+ // set.
+ void ForceInstall(Profile* profile,
+ bool wait_on_extension_loaded = true,
+ bool immediately_provide_certificates = true);
+
+ TestCertificateProviderExtension* extension() {
+ return certificate_provider_extension_.get();
+ }
+
+ const TestCertificateProviderExtension* extension() const {
+ return certificate_provider_extension_.get();
+ }
+
+ private:
+ ExtensionForceInstallMixin* const extension_force_install_mixin_;
+ std::unique_ptr<TestCertificateProviderExtension>
+ certificate_provider_extension_;
+};
+
+} // namespace ash
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_TEST_CERTIFICATE_PROVIDER_EXTENSION_MIXIN_H_
diff --git a/chrome/browser/certificate_provider/thread_safe_certificate_map.cc b/chrome/browser/certificate_provider/thread_safe_certificate_map.cc
new file mode 100644
index 0000000..a3c716f
--- /dev/null
+++ b/chrome/browser/certificate_provider/thread_safe_certificate_map.cc
@@ -0,0 +1,141 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/certificate_provider/thread_safe_certificate_map.h"
+
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "chrome/browser/certificate_provider/certificate_info.h"
+#include "net/base/hash_value.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+
+namespace chromeos {
+namespace certificate_provider {
+namespace {
+
+std::string GetSubjectPublicKeyInfo(const net::X509Certificate& certificate) {
+ base::StringPiece spki_bytes;
+ if (!net::asn1::ExtractSPKIFromDERCert(
+ net::x509_util::CryptoBufferAsStringPiece(certificate.cert_buffer()),
+ &spki_bytes)) {
+ return {};
+ }
+ return std::string(spki_bytes);
+}
+
+} // namespace
+
+ThreadSafeCertificateMap::ThreadSafeCertificateMap() {}
+
+ThreadSafeCertificateMap::~ThreadSafeCertificateMap() {}
+
+void ThreadSafeCertificateMap::UpdateCertificatesForExtension(
+ const std::string& extension_id,
+ const CertificateInfoList& certificates) {
+ base::AutoLock auto_lock(lock_);
+ RemoveCertificatesProvidedByExtension(extension_id);
+
+ for (const CertificateInfo& cert_info : certificates) {
+ const net::SHA256HashValue fingerprint =
+ net::X509Certificate::CalculateFingerprint256(
+ cert_info.certificate->cert_buffer());
+ fingerprint_to_extension_and_cert_[fingerprint][extension_id] = cert_info;
+
+ const std::string spki = GetSubjectPublicKeyInfo(*cert_info.certificate);
+ spki_to_extension_and_cert_[spki][extension_id] = cert_info;
+ }
+}
+
+std::vector<scoped_refptr<net::X509Certificate>>
+ThreadSafeCertificateMap::GetCertificates() {
+ base::AutoLock auto_lock(lock_);
+ std::vector<scoped_refptr<net::X509Certificate>> certificates;
+ for (const auto& fingerprint_entry : fingerprint_to_extension_and_cert_) {
+ const ExtensionToCertificateMap* extension_to_certificate_map =
+ &fingerprint_entry.second;
+ if (!extension_to_certificate_map->empty()) {
+ // If there are multiple entries with the same fingerprint, they are the
+ // same certificate as SHA256 should not have collisions.
+ // Since we need each certificate only once, we can return any entry.
+ certificates.push_back(
+ extension_to_certificate_map->begin()->second.certificate);
+ }
+ }
+ return certificates;
+}
+
+bool ThreadSafeCertificateMap::LookUpCertificate(
+ const net::X509Certificate& cert,
+ bool* is_currently_provided,
+ CertificateInfo* info,
+ std::string* extension_id) {
+ *is_currently_provided = false;
+ const net::SHA256HashValue fingerprint =
+ net::X509Certificate::CalculateFingerprint256(cert.cert_buffer());
+
+ base::AutoLock auto_lock(lock_);
+ const auto it = fingerprint_to_extension_and_cert_.find(fingerprint);
+ if (it == fingerprint_to_extension_and_cert_.end())
+ return false;
+
+ ExtensionToCertificateMap* const map = &it->second;
+ if (!map->empty()) {
+ // If multiple entries are found, it is unspecified which is returned.
+ const auto map_entry = map->begin();
+ *is_currently_provided = true;
+ *info = map_entry->second;
+ *extension_id = map_entry->first;
+ }
+ return true;
+}
+
+bool ThreadSafeCertificateMap::LookUpCertificateBySpki(
+ const std::string& subject_public_key_info,
+ bool* is_currently_provided,
+ CertificateInfo* info,
+ std::string* extension_id) {
+ *is_currently_provided = false;
+ base::AutoLock auto_lock(lock_);
+ const auto it = spki_to_extension_and_cert_.find(subject_public_key_info);
+ if (it == spki_to_extension_and_cert_.end())
+ return false;
+
+ ExtensionToCertificateMap* const map = &it->second;
+ if (!map->empty()) {
+ // If multiple entries are found, it is unspecified which is returned.
+ const auto map_entry = map->begin();
+ *is_currently_provided = true;
+ *info = map_entry->second;
+ *extension_id = map_entry->first;
+ }
+ return true;
+}
+
+void ThreadSafeCertificateMap::RemoveExtension(
+ const std::string& extension_id) {
+ base::AutoLock auto_lock(lock_);
+ RemoveCertificatesProvidedByExtension(extension_id);
+}
+
+void ThreadSafeCertificateMap::RemoveCertificatesProvidedByExtension(
+ const std::string& extension_id) {
+ for (auto& entry : fingerprint_to_extension_and_cert_) {
+ ExtensionToCertificateMap* map = &entry.second;
+ map->erase(extension_id);
+ }
+
+ for (auto& entry : spki_to_extension_and_cert_) {
+ ExtensionToCertificateMap* map = &entry.second;
+ map->erase(extension_id);
+ }
+}
+
+} // namespace certificate_provider
+} // namespace chromeos
diff --git a/chrome/browser/certificate_provider/thread_safe_certificate_map.h b/chrome/browser/certificate_provider/thread_safe_certificate_map.h
new file mode 100644
index 0000000..14b848fe
--- /dev/null
+++ b/chrome/browser/certificate_provider/thread_safe_certificate_map.h
@@ -0,0 +1,88 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CERTIFICATE_PROVIDER_THREAD_SAFE_CERTIFICATE_MAP_H_
+#define CHROME_BROWSER_CERTIFICATE_PROVIDER_THREAD_SAFE_CERTIFICATE_MAP_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/synchronization/lock.h"
+#include "chrome/browser/certificate_provider/certificate_info.h"
+
+namespace net {
+class X509Certificate;
+struct SHA256HashValue;
+} // namespace net
+
+namespace chromeos {
+namespace certificate_provider {
+
+class ThreadSafeCertificateMap {
+ public:
+ ThreadSafeCertificateMap();
+ ThreadSafeCertificateMap(const ThreadSafeCertificateMap&) = delete;
+ ThreadSafeCertificateMap& operator=(const ThreadSafeCertificateMap&) = delete;
+ ~ThreadSafeCertificateMap();
+
+ // Updates the certificates provided by extension |extension_id| to be
+ // |certificates|.
+ void UpdateCertificatesForExtension(const std::string& extension_id,
+ const CertificateInfoList& certificates);
+
+ // Returns all currently provided certificates.
+ std::vector<scoped_refptr<net::X509Certificate>> GetCertificates();
+
+ // Looks up the given certificate. If the certificate was added by any
+ // previous Update() call, returns true.
+ // If this certificate was provided in the most recent Update() call,
+ // |is_currently_provided| will be set to true, |extension_id| be set to that
+ // extension's id and |info| will be set to the stored info. Otherwise, if
+ // this certificate was not provided in the the most recent Update() call,
+ // sets |is_currently_provided| to false and doesn't modify |info| and
+ // |extension_id|.
+ bool LookUpCertificate(const net::X509Certificate& cert,
+ bool* is_currently_provided,
+ CertificateInfo* info,
+ std::string* extension_id);
+
+ // Looks up for certificate and extension_id based on
+ // |subject_public_key_info|, which is a DER-encoded X.509 Subject Public Key
+ // Info. If the certificate was added by previous Update() call, returns true.
+ // If this certificate was provided in the most recent Update() call,
+ // |is_currently_provided| will be set to true and |info| and |extension_id|
+ // will be populated according to the data that have been mapped to this
+ // |subject_public_key_info|. Otherwise, if this certificate was not provided
+ // in the most recent Update() call, sets |is_currently_provided| to false and
+ // doesn't modify |info| and |extension_id|. If multiple entries are found, it
+ // is unspecified which one will be returned.
+ bool LookUpCertificateBySpki(const std::string& subject_public_key_info,
+ bool* is_currently_provided,
+ CertificateInfo* info,
+ std::string* extension_id);
+
+ // Remove every association of stored certificates to the given extension.
+ // The certificates themselves will be remembered.
+ void RemoveExtension(const std::string& extension_id);
+
+ private:
+ void RemoveCertificatesProvidedByExtension(const std::string& extension_id);
+
+ base::Lock lock_;
+ using ExtensionToCertificateMap =
+ base::flat_map<std::string, CertificateInfo>;
+ // A map that has the certificates' fingerprints as keys.
+ base::flat_map<net::SHA256HashValue, ExtensionToCertificateMap>
+ fingerprint_to_extension_and_cert_;
+ // A map that has a DER-encoded X.509 Subject Public Key Info as keys.
+ base::flat_map<std::string, ExtensionToCertificateMap>
+ spki_to_extension_and_cert_;
+};
+
+} // namespace certificate_provider
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CERTIFICATE_PROVIDER_THREAD_SAFE_CERTIFICATE_MAP_H_