From 9cac4acf0e0a0898e14ac0c187a4872eef41b820 Mon Sep 17 00:00:00 2001 From: pneubeck Date: Wed, 9 Sep 2015 05:53:23 -0700 Subject: [PATCH] Add new certificateProvider extension API. BUG=514575 Review URL: https://codereview.chromium.org/1232553003 Cr-Commit-Position: refs/heads/master@{#347916} --- chrome/app/generated_resources.grd | 3 + .../certificate_provider_service.cc | 2 +- .../certificate_provider_service.h | 10 +- .../certificate_provider_service_factory.cc | 207 ++++++++++++++++ .../certificate_provider_service_factory.h | 48 ++++ .../certificate_provider_service_unittest.cc | 10 +- .../certificate_provider_api.cc | 177 ++++++++++++++ .../certificate_provider_api.h | 50 ++++ .../certificate_provider_apitest.cc | 265 +++++++++++++++++++++ chrome/browser/profiles/profile_io_data.cc | 14 +- chrome/browser/profiles/profile_io_data.h | 6 + chrome/chrome_browser_chromeos.gypi | 2 + chrome/chrome_browser_extensions.gypi | 2 + chrome/chrome_renderer.gypi | 1 + chrome/chrome_tests.gypi | 7 + chrome/common/extensions/api/_api_features.json | 9 + .../extensions/api/_permission_features.json | 5 + .../common/extensions/api/certificate_provider.idl | 157 ++++++------ .../api/certificate_provider_internal.idl | 34 +++ chrome/common/extensions/api/schemas.gypi | 2 + .../permissions/chrome_api_permissions.cc | 1 + .../permissions/chrome_permission_message_rules.cc | 3 + .../chrome_extensions_dispatcher_delegate.cc | 2 + .../certificate_provider_custom_bindings.js | 81 +++++++ chrome/renderer/resources/renderer_resources.grd | 1 + .../api_test/certificate_provider/OWNERS | 2 + .../api_test/certificate_provider/basic.html | 6 + .../api_test/certificate_provider/basic.js | 110 +++++++++ .../api_test/certificate_provider/ca.cnf | 57 +++++ .../certificate_provider/create_test_certs.sh | 80 +++++++ .../api_test/certificate_provider/l1_leaf.der | Bin 0 -> 783 bytes .../api_test/certificate_provider/l1_leaf.pk8 | Bin 0 -> 1219 bytes .../api_test/certificate_provider/manifest.json | 8 + .../api_test/certificate_provider/root.pem | 19 ++ .../browser/extension_event_histogram_value.h | 2 + .../browser/extension_function_histogram_value.h | 2 + extensions/common/permissions/api_permission.h | 1 + tools/metrics/histograms/histograms.xml | 4 + 38 files changed, 1302 insertions(+), 88 deletions(-) create mode 100644 chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.cc create mode 100644 chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h create mode 100644 chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc create mode 100644 chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h create mode 100644 chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc rewrite chrome/common/extensions/api/certificate_provider.idl (62%) create mode 100644 chrome/common/extensions/api/certificate_provider_internal.idl create mode 100644 chrome/renderer/resources/extensions/certificate_provider_custom_bindings.js create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/OWNERS create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/basic.html create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/basic.js create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/ca.cnf create mode 100755 chrome/test/data/extensions/api_test/certificate_provider/create_test_certs.sh create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.der create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.pk8 create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/manifest.json create mode 100644 chrome/test/data/extensions/api_test/certificate_provider/root.pem diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 690c54f549de..cee92fa4cda9 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4187,6 +4187,9 @@ Even if you have downloaded files from this website before, the website might ha Use your client certificates + + Provide certificates for authentication + Read and change anything you type including task switching keys like CMD+TAB diff --git a/chrome/browser/chromeos/certificate_provider/certificate_provider_service.cc b/chrome/browser/chromeos/certificate_provider/certificate_provider_service.cc index f6e6cd75a47b..da19c82e8540 100644 --- a/chrome/browser/chromeos/certificate_provider/certificate_provider_service.cc +++ b/chrome/browser/chromeos/certificate_provider/certificate_provider_service.cc @@ -444,7 +444,7 @@ void CertificateProviderService::RequestSignatureFromExtension( const int sign_request_id = sign_requests_.AddRequest(extension_id, callback); if (!delegate_->DispatchSignRequestToExtension(extension_id, sign_request_id, - hash, digest)) { + hash, certificate, digest)) { sign_requests_.RemoveRequest(extension_id, sign_request_id, nullptr /* callback */); callback.Run(net::ERR_FAILED, std::vector()); diff --git a/chrome/browser/chromeos/certificate_provider/certificate_provider_service.h b/chrome/browser/chromeos/certificate_provider/certificate_provider_service.h index 1b9808219afe..5fc84ea0f77c 100644 --- a/chrome/browser/chromeos/certificate_provider/certificate_provider_service.h +++ b/chrome/browser/chromeos/certificate_provider/certificate_provider_service.h @@ -80,10 +80,12 @@ class CertificateProviderService : public KeyedService { // Dispatches a sign request with the given arguments to the extension with // id |extension_id|. Returns whether that extension is actually a listener // for that event. - virtual bool DispatchSignRequestToExtension(const std::string& extension_id, - int sign_request_id, - net::SSLPrivateKey::Hash hash, - const std::string& digest) = 0; + virtual bool DispatchSignRequestToExtension( + const std::string& extension_id, + int sign_request_id, + net::SSLPrivateKey::Hash hash, + const scoped_refptr& certificate, + const std::string& digest) = 0; private: DISALLOW_COPY_AND_ASSIGN(Delegate); diff --git a/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.cc b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.cc new file mode 100644 index 000000000000..d61468b43ccd --- /dev/null +++ b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.cc @@ -0,0 +1,207 @@ +// 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/chromeos/certificate_provider/certificate_provider_service_factory.h" + +#include +#include + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/values.h" +#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h" +#include "chrome/browser/chromeos/profiles/profile_helper.h" +#include "chrome/browser/profiles/incognito_helpers.h" +#include "chrome/browser/profiles/profile.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/ssl/ssl_private_key.h" + +namespace chromeos { + +namespace { + +namespace api_cp = extensions::api::certificate_provider; + +class DefaultDelegate : public CertificateProviderService::Delegate, + public extensions::ExtensionRegistryObserver { + public: + DefaultDelegate(CertificateProviderService* service, + extensions::ExtensionRegistry* registry, + extensions::EventRouter* event_router); + ~DefaultDelegate() override; + + // CertificateProviderService::Delegate: + std::vector CertificateProviderExtensions() override; + void BroadcastCertificateRequest(int request_id) override; + bool DispatchSignRequestToExtension( + const std::string& extension_id, + int request_id, + net::SSLPrivateKey::Hash hash, + const scoped_refptr& certificate, + const std::string& digest) override; + + // extensions::ExtensionRegistryObserver: + void OnExtensionUnloaded( + content::BrowserContext* browser_context, + const extensions::Extension* extension, + extensions::UnloadedExtensionInfo::Reason reason) override; + + private: + CertificateProviderService* const service_; + extensions::ExtensionRegistry* const registry_; + extensions::EventRouter* const event_router_; + + DISALLOW_COPY_AND_ASSIGN(DefaultDelegate); +}; + +DefaultDelegate::DefaultDelegate(CertificateProviderService* service, + extensions::ExtensionRegistry* registry, + extensions::EventRouter* event_router) + : service_(service), registry_(registry), event_router_(event_router) { + DCHECK(service_); + DCHECK(event_router_); + registry_->AddObserver(this); +} + +DefaultDelegate::~DefaultDelegate() { + registry_->RemoveObserver(this); +} + +std::vector DefaultDelegate::CertificateProviderExtensions() { + const std::string event_name(api_cp::OnCertificatesRequested::kEventName); + std::vector ids; + for (const auto& listener : + event_router_->listeners().GetEventListenersByName(event_name)) { + ids.push_back(listener->extension_id()); + } + return ids; +} + +void DefaultDelegate::BroadcastCertificateRequest(int request_id) { + const std::string event_name(api_cp::OnCertificatesRequested::kEventName); + scoped_ptr internal_args(new base::ListValue); + internal_args->AppendInteger(request_id); + scoped_ptr event(new extensions::Event( + extensions::events::CERTIFICATEPROVIDER_ON_CERTIFICATES_REQUESTED, + event_name, internal_args.Pass())); + event_router_->BroadcastEvent(event.Pass()); +} + +bool DefaultDelegate::DispatchSignRequestToExtension( + const std::string& extension_id, + int request_id, + net::SSLPrivateKey::Hash hash, + const scoped_refptr& certificate, + const std::string& digest) { + const std::string event_name(api_cp::OnSignDigestRequested::kEventName); + if (!event_router_->ExtensionHasEventListener(extension_id, event_name)) + return false; + + api_cp::SignRequest request; + switch (hash) { + case net::SSLPrivateKey::Hash::MD5_SHA1: + request.hash = api_cp::HASH_MD5_SHA1; + break; + case net::SSLPrivateKey::Hash::SHA1: + request.hash = api_cp::HASH_SHA1; + break; + case net::SSLPrivateKey::Hash::SHA256: + request.hash = api_cp::HASH_SHA256; + break; + case net::SSLPrivateKey::Hash::SHA384: + request.hash = api_cp::HASH_SHA384; + break; + case net::SSLPrivateKey::Hash::SHA512: + request.hash = api_cp::HASH_SHA512; + break; + } + request.digest.assign(digest.begin(), digest.end()); + std::string cert_der; + if (!net::X509Certificate::GetDEREncoded(certificate->os_cert_handle(), + &cert_der)) { + LOG(ERROR) << "Could not DER encode the certificate."; + return false; // Behave as if the extension wasn't registered anymore. + } + request.certificate.assign(cert_der.begin(), cert_der.end()); + + scoped_ptr internal_args(new base::ListValue); + internal_args->AppendInteger(request_id); + internal_args->Append(request.ToValue().Pass()); + + event_router_->DispatchEventToExtension( + extension_id, + make_scoped_ptr(new extensions::Event( + extensions::events::CERTIFICATEPROVIDER_ON_SIGN_DIGEST_REQUESTED, + event_name, internal_args.Pass()))); + return true; +} + +void DefaultDelegate::OnExtensionUnloaded( + content::BrowserContext* browser_context, + const extensions::Extension* extension, + extensions::UnloadedExtensionInfo::Reason reason) { + service_->OnExtensionUnloaded(extension->id()); +} + +} // namespace + +// static +CertificateProviderService* +CertificateProviderServiceFactory::GetForBrowserContext( + content::BrowserContext* context) { + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +CertificateProviderServiceFactory* +CertificateProviderServiceFactory::GetInstance() { + return Singleton::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 { + if (chromeos::ProfileHelper::IsSigninProfile( + Profile::FromBrowserContext(context))) { + return nullptr; + } + CertificateProviderService* const service = new CertificateProviderService(); + service->SetDelegate(make_scoped_ptr(new DefaultDelegate( + service, + extensions::ExtensionRegistryFactory::GetForBrowserContext(context), + extensions::EventRouterFactory::GetForBrowserContext(context)))); + return service; +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h new file mode 100644 index 000000000000..57707c428bb1 --- /dev/null +++ b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h @@ -0,0 +1,48 @@ +// 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_CHROMEOS_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_ +#define CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_ + +#include "base/macros.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +template +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(); + + private: + friend struct DefaultSingletonTraits; + + CertificateProviderServiceFactory(); + + // BrowserContextKeyedServiceFactory: + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; + bool ServiceIsNULLWhileTesting() const override; + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + + DISALLOW_COPY_AND_ASSIGN(CertificateProviderServiceFactory); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_SERVICE_FACTORY_H_ diff --git a/chrome/browser/chromeos/certificate_provider/certificate_provider_service_unittest.cc b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_unittest.cc index 857a41a8847e..a15061dd03d3 100644 --- a/chrome/browser/chromeos/certificate_provider/certificate_provider_service_unittest.cc +++ b/chrome/browser/chromeos/certificate_provider/certificate_provider_service_unittest.cc @@ -93,10 +93,12 @@ class TestDelegate : public CertificateProviderService::Delegate { expected_request_type_ = RequestType::NONE; } - bool DispatchSignRequestToExtension(const std::string& extension_id, - int sign_request_id, - net::SSLPrivateKey::Hash hash, - const std::string& input) override { + bool DispatchSignRequestToExtension( + const std::string& extension_id, + int sign_request_id, + net::SSLPrivateKey::Hash hash, + const scoped_refptr& certificate, + const std::string& input) override { EXPECT_EQ(expected_request_type_, RequestType::SIGN); last_sign_request_id_ = sign_request_id; last_extension_id_ = extension_id; diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc new file mode 100644 index 000000000000..9e212645ce04 --- /dev/null +++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.cc @@ -0,0 +1,177 @@ +// 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/extensions/api/certificate_provider/certificate_provider_api.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h" +#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h" +#include "chrome/common/extensions/api/certificate_provider.h" +#include "chrome/common/extensions/api/certificate_provider_internal.h" +#include "content/public/common/console_message_level.h" +#include "net/cert/x509_certificate.h" +#include "net/ssl/ssl_private_key.h" + +namespace extensions { + +namespace api_cp = api::certificate_provider; +namespace api_cpi = api::certificate_provider_internal; + +namespace { + +const char kErrorInvalidX509Cert[] = + "Certificate is not a valid X.509 certificate."; +const char kErrorECDSANotSupported[] = "Key type ECDSA not supported."; +const char kErrorUnknownKeyType[] = "Key type unknown."; +const char kErrorAborted[] = "Request was aborted."; +const char kErrorTimeout[] = "Request timed out, reply rejected."; + +} // namespace + +CertificateProviderInternalReportCertificatesFunction:: + ~CertificateProviderInternalReportCertificatesFunction() {} + +ExtensionFunction::ResponseAction +CertificateProviderInternalReportCertificatesFunction::Run() { + scoped_ptr params( + api_cpi::ReportCertificates::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + + chromeos::CertificateProviderService* const service = + chromeos::CertificateProviderServiceFactory::GetForBrowserContext( + browser_context()); + DCHECK(service); + + if (!params->certificates) { + // In the public API, the certificates parameter is mandatory. We only run + // into this case, if the custom binding rejected the reply by the + // extension. + return RespondNow(Error(kErrorAborted)); + } + + chromeos::certificate_provider::CertificateInfoList cert_infos; + std::vector> rejected_certificates; + for (linked_ptr input_cert_info : + *params->certificates) { + chromeos::certificate_provider::CertificateInfo parsed_cert_info; + + if (ParseCertificateInfo(*input_cert_info, &parsed_cert_info)) + cert_infos.push_back(parsed_cert_info); + else + rejected_certificates.push_back(input_cert_info->certificate); + } + + if (service->SetCertificatesProvidedByExtension( + extension_id(), params->request_id, cert_infos)) { + return RespondNow(ArgumentList( + api_cpi::ReportCertificates::Results::Create(rejected_certificates))); + } else { + // The custom binding already checks for multiple reports to the same + // request. The only remaining case, why this reply can fail is that the + // request timed out. + return RespondNow(Error(kErrorTimeout)); + } +} + +bool CertificateProviderInternalReportCertificatesFunction:: + ParseCertificateInfo( + const api_cp::CertificateInfo& info, + chromeos::certificate_provider::CertificateInfo* out_info) { + const std::vector& cert_der = info.certificate; + if (cert_der.empty()) { + WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_ERROR, kErrorInvalidX509Cert); + return false; + } + + out_info->certificate = net::X509Certificate::CreateFromBytes( + vector_as_array(&cert_der), cert_der.size()); + if (!out_info->certificate) { + WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_ERROR, kErrorInvalidX509Cert); + return false; + } + + size_t public_key_length_in_bits = 0; + net::X509Certificate::PublicKeyType type = + net::X509Certificate::kPublicKeyTypeUnknown; + net::X509Certificate::GetPublicKeyInfo( + out_info->certificate->os_cert_handle(), &public_key_length_in_bits, + &type); + + switch (type) { + case net::X509Certificate::kPublicKeyTypeRSA: + DCHECK(public_key_length_in_bits); + + // Convert bits to bytes. + out_info->max_signature_length_in_bytes = + (public_key_length_in_bits + 7) / 8; + out_info->type = net::SSLPrivateKey::Type::RSA; + break; + case net::X509Certificate::kPublicKeyTypeECDSA: + WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_ERROR, + kErrorECDSANotSupported); + return false; + default: + WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_ERROR, + kErrorUnknownKeyType); + return false; + } + + for (const api_cp::Hash hash : info.supported_hashes) { + switch (hash) { + case api_cp::HASH_MD5_SHA1: + out_info->supported_hashes.push_back( + net::SSLPrivateKey::Hash::MD5_SHA1); + break; + case api_cp::HASH_SHA1: + out_info->supported_hashes.push_back(net::SSLPrivateKey::Hash::SHA1); + break; + case api_cp::HASH_SHA256: + out_info->supported_hashes.push_back(net::SSLPrivateKey::Hash::SHA256); + break; + case api_cp::HASH_SHA384: + out_info->supported_hashes.push_back(net::SSLPrivateKey::Hash::SHA384); + break; + case api_cp::HASH_SHA512: + out_info->supported_hashes.push_back(net::SSLPrivateKey::Hash::SHA512); + break; + case api_cp::HASH_NONE: + NOTREACHED(); + return false; + } + } + return true; +} + +CertificateProviderInternalReportSignatureFunction:: + ~CertificateProviderInternalReportSignatureFunction() {} + +ExtensionFunction::ResponseAction +CertificateProviderInternalReportSignatureFunction::Run() { + scoped_ptr params( + api_cpi::ReportSignature::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + + chromeos::CertificateProviderService* const service = + chromeos::CertificateProviderServiceFactory::GetForBrowserContext( + browser_context()); + DCHECK(service); + + std::vector signature; + // If an error occurred, |signature| will not be set. + if (params->signature) + signature.assign(params->signature->begin(), params->signature->end()); + + service->ReplyToSignRequest(extension_id(), params->request_id, signature); + return RespondNow(NoArguments()); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h new file mode 100644 index 000000000000..bc4ec19752bd --- /dev/null +++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h @@ -0,0 +1,50 @@ +// 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_EXTENSIONS_API_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_API_H_ + +#include "extensions/browser/extension_function.h" + +namespace chromeos { +namespace certificate_provider { +struct CertificateInfo; +} +} + +namespace extensions { + +namespace api { +namespace certificate_provider { +struct CertificateInfo; +} +} + +class CertificateProviderInternalReportCertificatesFunction + : public UIThreadExtensionFunction { + private: + ~CertificateProviderInternalReportCertificatesFunction() override; + ResponseAction Run() override; + + bool ParseCertificateInfo( + const api::certificate_provider::CertificateInfo& info, + chromeos::certificate_provider::CertificateInfo* out_info); + + DECLARE_EXTENSION_FUNCTION("certificateProviderInternal.reportCertificates", + CERTIFICATEPROVIDERINTERNAL_REPORTCERTIFICATES); +}; + +class CertificateProviderInternalReportSignatureFunction + : public UIThreadExtensionFunction { + private: + ~CertificateProviderInternalReportSignatureFunction() override; + ResponseAction Run() override; + + DECLARE_EXTENSION_FUNCTION("certificateProviderInternal.reportSignature", + CERTIFICATEPROVIDERINTERNAL_REPORTSIGNATURE); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_CERTIFICATE_PROVIDER_CERTIFICATE_PROVIDER_API_H_ diff --git a/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc new file mode 100644 index 000000000000..d550b73cd707 --- /dev/null +++ b/chrome/browser/extensions/api/certificate_provider/certificate_provider_apitest.cc @@ -0,0 +1,265 @@ +// 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 +#include +#include +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/policy/core/browser/browser_policy_connector.h" +#include "components/policy/core/common/mock_configuration_policy_provider.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/core/common/policy_types.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/test_navigation_observer.h" +#include "content/public/test/test_utils.h" +#include "crypto/rsa_private_key.h" +#include "crypto/scoped_openssl_types.h" +#include "extensions/common/extension.h" +#include "extensions/test/result_catcher.h" +#include "net/test/spawned_test_server/spawned_test_server.h" +#include "policy/policy_constants.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::Return; +using testing::_; + +namespace { + +void IgnoreResult(const base::Closure& callback, const base::Value* value) { + callback.Run(); +} + +void StoreBool(bool* result, + const base::Closure& callback, + const base::Value* value) { + value->GetAsBoolean(result); + callback.Run(); +} + +void StoreString(std::string* result, + const base::Closure& callback, + const base::Value* value) { + value->GetAsString(result); + callback.Run(); +} + +void StoreDigest(std::vector* digest, + const base::Closure& callback, + const base::Value* value) { + const base::BinaryValue* binary = nullptr; + value->GetAsBinary(&binary); + const uint8_t* const binary_begin = + reinterpret_cast(binary->GetBuffer()); + digest->assign(binary_begin, binary_begin + binary->GetSize()); + + callback.Run(); +} + +// See net::SSLPrivateKey::SignDigest for the expected padding and DigestInfo +// prefixing. +bool RsaSign(const std::vector& digest, + crypto::RSAPrivateKey* key, + std::vector* signature) { + crypto::ScopedRSA rsa_key(EVP_PKEY_get1_RSA(key->key())); + if (!rsa_key) + return false; + + uint8_t* prefixed_digest = nullptr; + size_t prefixed_digest_len = 0; + int is_alloced = 0; + if (!RSA_add_pkcs1_prefix(&prefixed_digest, &prefixed_digest_len, &is_alloced, + NID_sha1, vector_as_array(&digest), + digest.size())) { + return false; + } + size_t len = 0; + signature->resize(RSA_size(rsa_key.get())); + const int rv = RSA_sign_raw(rsa_key.get(), &len, vector_as_array(signature), + signature->size(), prefixed_digest, + prefixed_digest_len, RSA_PKCS1_PADDING); + if (is_alloced) + free(prefixed_digest); + + if (rv) { + signature->resize(len); + return true; + } else { + signature->clear(); + return false; + } +} + +// Create a string that if evaluated in JavaScript returns a Uint8Array with +// |bytes| as content. +std::string JsUint8Array(const std::vector& bytes) { + std::string res = "new Uint8Array(["; + for (const uint8_t byte : bytes) { + res += base::UintToString(byte); + res += ", "; + } + res += "])"; + return res; +} + +class CertificateProviderApiTest : public ExtensionApiTest { + public: + CertificateProviderApiTest() {} + + void SetUpInProcessBrowserTestFixture() override { + EXPECT_CALL(provider_, IsInitializationComplete(_)) + .WillRepeatedly(Return(true)); + policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_); + + ExtensionApiTest::SetUpInProcessBrowserTestFixture(); + } + + void SetUpOnMainThread() override { + // Set up the AutoSelectCertificateForUrls policy to avoid the client + // certificate selection dialog. + const std::string autoselect_pattern = + "{\"pattern\": \"*\", \"filter\": {\"ISSUER\": {\"CN\": \"root\"}}}"; + + scoped_ptr autoselect_policy(new base::ListValue); + autoselect_policy->AppendString(autoselect_pattern); + + policy::PolicyMap policy; + policy.Set(policy::key::kAutoSelectCertificateForUrls, + policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER, + autoselect_policy.release(), nullptr); + provider_.UpdateChromePolicy(policy); + + content::RunAllPendingInMessageLoop(); + } + + protected: + policy::MockConfigurationPolicyProvider provider_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(CertificateProviderApiTest, Basic) { + // Start an HTTPS test server that requests a client certificate. + net::SpawnedTestServer::SSLOptions ssl_options; + ssl_options.request_client_certificate = true; + net::SpawnedTestServer https_server( + net::SpawnedTestServer::TYPE_HTTPS, ssl_options, + base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(https_server.Start()); + + extensions::ResultCatcher catcher; + + const base::FilePath extension_path = + test_data_dir_.AppendASCII("certificate_provider"); + const extensions::Extension* const extension = LoadExtension(extension_path); + ui_test_utils::NavigateToURL(browser(), + extension->GetResourceURL("basic.html")); + + ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); + VLOG(1) << "Extension registered. Navigate to the test https page."; + + content::WebContents* const extension_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + content::TestNavigationObserver navigation_observer( + nullptr /* no WebContents */); + navigation_observer.StartWatchingNewWebContents(); + ui_test_utils::NavigateToURLWithDisposition( + browser(), https_server.GetURL("client-cert"), NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_NONE); + + content::WebContents* const https_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + VLOG(1) << "Wait for the extension to receive the sign request."; + ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); + + VLOG(1) << "Fetch the digest from the sign request."; + std::vector request_digest; + { + base::RunLoop run_loop; + extension_contents->GetMainFrame()->ExecuteJavaScriptForTests( + base::ASCIIToUTF16("signDigestRequest.digest;"), + base::Bind(&StoreDigest, &request_digest, run_loop.QuitClosure())); + run_loop.Run(); + } + + VLOG(1) << "Sign the digest using the private key."; + std::string key_pk8; + base::ReadFileToString(extension_path.AppendASCII("l1_leaf.pk8"), &key_pk8); + + const uint8_t* const key_pk8_begin = + reinterpret_cast(key_pk8.data()); + scoped_ptr key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo( + std::vector(key_pk8_begin, key_pk8_begin + key_pk8.size()))); + ASSERT_TRUE(key); + + std::vector signature; + EXPECT_TRUE(RsaSign(request_digest, key.get(), &signature)); + + VLOG(1) << "Inject the signature back to the extension and let it reply."; + { + base::RunLoop run_loop; + const std::string code = + "replyWithSignature(" + JsUint8Array(signature) + ");"; + extension_contents->GetMainFrame()->ExecuteJavaScriptForTests( + base::ASCIIToUTF16(code), + base::Bind(&IgnoreResult, run_loop.QuitClosure())); + run_loop.Run(); + } + + VLOG(1) << "Wait for the https navigation to finish."; + navigation_observer.Wait(); + + VLOG(1) << "Check whether the server acknowledged that a client certificate " + "was presented."; + { + base::RunLoop run_loop; + std::string https_reply; + https_contents->GetMainFrame()->ExecuteJavaScriptForTests( + base::ASCIIToUTF16("document.body.textContent;"), + base::Bind(&StoreString, &https_reply, run_loop.QuitClosure())); + run_loop.Run(); + // Expect the server to return the fingerprint of the client cert that we + // presented, which should be the fingerprint of 'l1_leaf.der'. + // The fingerprint can be calculated independently using: + // openssl x509 -inform DER -noout -fingerprint -in \ + // chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.der + ASSERT_EQ( + "got client cert with fingerprint: " + "2ab3f55e06eb8b36a741fe285a769da45edb2695", + https_reply); + } + + // Replying to the same signature request a second time must fail. + { + base::RunLoop run_loop; + const std::string code = "replyWithSignatureSecondTime();"; + bool result = false; + extension_contents->GetMainFrame()->ExecuteJavaScriptForTests( + base::ASCIIToUTF16(code), + base::Bind(&StoreBool, &result, run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_TRUE(result); + } +} diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc index 36c26e9b0275..85815a836785 100644 --- a/chrome/browser/profiles/profile_io_data.cc +++ b/chrome/browser/profiles/profile_io_data.cc @@ -117,6 +117,8 @@ #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/certificate_provider/certificate_provider.h" +#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service.h" +#include "chrome/browser/chromeos/certificate_provider/certificate_provider_service_factory.h" #include "chrome/browser/chromeos/fileapi/external_file_protocol_handler.h" #include "chrome/browser/chromeos/login/startup_utils.h" #include "chrome/browser/chromeos/net/cert_verify_proc_chromeos.h" @@ -453,6 +455,14 @@ void ProfileIOData::InitializeOnUIThread(Profile* profile) { policy::USER_AFFILIATION_MANAGED; } } + + chromeos::CertificateProviderService* cert_provider_service = + chromeos::CertificateProviderServiceFactory::GetForBrowserContext( + profile); + if (cert_provider_service) { + params->certificate_provider = + cert_provider_service->CreateCertificateProvider(); + } #endif params->profile = profile; @@ -940,7 +950,7 @@ ProfileIOData::ResourceContext::CreateClientCertStore() { return io_data_->client_cert_store_factory_.Run(); #if defined(OS_CHROMEOS) return scoped_ptr(new chromeos::ClientCertStoreChromeOS( - nullptr, // no additional provider + io_data_->certificate_provider_->Copy(), make_scoped_ptr(new chromeos::ClientCertFilterChromeOS( io_data_->use_system_key_slot(), io_data_->username_hash())), base::Bind(&CreateCryptoModuleBlockingPasswordDelegate, @@ -1106,6 +1116,8 @@ void ProfileIOData::Init( use_system_key_slot_ = profile_params_->use_system_key_slot; if (use_system_key_slot_) EnableNSSSystemKeySlotForResourceContext(resource_context_.get()); + + certificate_provider_ = profile_params_->certificate_provider.Pass(); #endif if (g_cert_verifier_for_testing) { diff --git a/chrome/browser/profiles/profile_io_data.h b/chrome/browser/profiles/profile_io_data.h index 7870c4b351d5..d90f71e09438 100644 --- a/chrome/browser/profiles/profile_io_data.h +++ b/chrome/browser/profiles/profile_io_data.h @@ -40,6 +40,10 @@ class MediaDeviceIDSalt; class ProtocolHandlerRegistry; class SupervisedUserURLFilter; +namespace chromeos { +class CertificateProvider; +} + namespace chrome_browser_net { class ResourcePrefetchPredictorObserver; } @@ -313,6 +317,7 @@ class ProfileIOData { #if defined(OS_CHROMEOS) std::string username_hash; bool use_system_key_slot; + scoped_ptr certificate_provider; #endif // The profile this struct was populated from. It's passed as a void* to @@ -550,6 +555,7 @@ class ProfileIOData { mutable policy::PolicyCertVerifier* policy_cert_verifier_; mutable std::string username_hash_; mutable bool use_system_key_slot_; + mutable scoped_ptr certificate_provider_; #endif mutable scoped_ptr diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index 1af13590c927..151d02981181 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -73,6 +73,8 @@ 'browser/chromeos/certificate_provider/certificate_info.h', 'browser/chromeos/certificate_provider/certificate_provider_service.cc', 'browser/chromeos/certificate_provider/certificate_provider_service.h', + 'browser/chromeos/certificate_provider/certificate_provider_service_factory.cc', + 'browser/chromeos/certificate_provider/certificate_provider_service_factory.h', 'browser/chromeos/certificate_provider/certificate_requests.cc', 'browser/chromeos/certificate_provider/certificate_requests.h', 'browser/chromeos/certificate_provider/sign_requests.cc', diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 5cd2a908f45b..822162ab41fc 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -7,6 +7,8 @@ 'chrome_browser_extensions_chromeos_sources': [ 'browser/extensions/api/cast_devices_private/cast_devices_private_api.cc', 'browser/extensions/api/cast_devices_private/cast_devices_private_api.h', + 'browser/extensions/api/certificate_provider/certificate_provider_api.cc', + 'browser/extensions/api/certificate_provider/certificate_provider_api.h', 'browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.cc', 'browser/extensions/api/enterprise_device_attributes/enterprise_device_attributes_api.h', 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc', diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 1774a9df41fc..998ad8240558 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -130,6 +130,7 @@ 'renderer/resources/extensions/app_custom_bindings.js', 'renderer/resources/extensions/automation_custom_bindings.js', 'renderer/resources/extensions/browser_action_custom_bindings.js', + 'renderer/resources/extensions/certificate_provider_custom_bindings.js', 'renderer/resources/extensions/chrome_direct_setting.js', 'renderer/resources/extensions/chrome_setting.js', 'renderer/resources/extensions/content_setting.js', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 74636a1e8ec1..c60307d2d005 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -802,6 +802,7 @@ 'browser/chromeos/system/tray_accessibility_browsertest.cc', 'browser/download/notification/download_notification_browsertest.cc', 'browser/drive/drive_notification_manager_factory_browsertest.cc', + 'browser/extensions/api/certificate_provider/certificate_provider_apitest.cc', 'browser/extensions/api/vpn_provider/vpn_provider_apitest.cc', 'browser/ui/webui/options/chromeos/accounts_options_browsertest.cc', 'browser/ui/webui/options/chromeos/guest_mode_options_ui_browsertest.cc', @@ -2156,6 +2157,12 @@ }, }, 'conditions': [ + ['chromeos==1 and use_openssl==1', { + 'dependencies': [ + '../third_party/boringssl/boringssl.gyp:boringssl', + ] + } + ], [ 'cld_version==2', { 'dependencies': [ # Because the browser_tests use translate, they need CLD data. diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 6f8656f05127..ad7b36c89f0b 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -193,6 +193,15 @@ "dependencies": ["permission:cast.streaming"], "contexts": ["blessed_extension"] }, + "certificateProvider": { + "dependencies": ["permission:certificateProvider"], + "contexts": ["blessed_extension"] + }, + "certificateProviderInternal": { + "internal": true, + "dependencies": ["permission:certificateProvider"], + "contexts": ["blessed_extension"] + }, "chromeosInfoPrivate": { "dependencies": ["permission:chromeosInfoPrivate"], "contexts": ["blessed_extension"] diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index a4f441a145ea..73e179d431cd 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json @@ -125,6 +125,11 @@ "EF2AB692559EA97C3BBDEA018A8C45F92457BD4E" // http://crbug.com/457908 ] }], + "certificateProvider": { + "channel": "stable", + "platforms": ["chromeos"], + "extension_types": ["extension", "platform_app"] + }, "chromePrivate": { "channel": "stable", "extension_types": ["extension", "legacy_packaged_app"], diff --git a/chrome/common/extensions/api/certificate_provider.idl b/chrome/common/extensions/api/certificate_provider.idl dissimilarity index 62% index 631026e2206c..1fa9dc834422 100644 --- a/chrome/common/extensions/api/certificate_provider.idl +++ b/chrome/common/extensions/api/certificate_provider.idl @@ -1,78 +1,79 @@ -// 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. - -// Use this API to expose certificates to the platform which can use these -// certificates for TLS authentications. -namespace certificateProvider { - enum Hash { - MD5_SHA1, - SHA1, - SHA256, - SHA384, - SHA512 - }; - - dictionary CertificateInfo { - // Must be the DER encoding of a X.509 client certificate. Currently, only - // certificates of RSA keys are supported. - ArrayBuffer certificate; - - // Must be set to all hashes supported for this certificate. This extension - // will only be asked for signatures of digests calculated with one of these - // hash algorithms. - Hash[] supportedHashes; - }; - - dictionary SignRequest { - // The digest that must be signed. - ArrayBuffer digest; - - // Refers to the hash algorithm that was used to create |digest|. - Hash hash; - - // The DER encoding of a X.509 client certificate. The extension must sign - // |digest| using the associated private key. - ArrayBuffer certificate; - }; - - // Either |error| or |signature| and not both must be set. - dictionary SignatureDetails { - // If the signature of the digest could not be calculated, this field must - // be set. - DOMString? error; - - // If no error occurred, this field must be set to the signature of the - // digest using the private the of the requested client certificate. - // For an RSA key, the signature must be a PKCS#1 signature. The extension - // is responsible for prepending the DigestInfo prefix and adding PKCS#1 - // padding. If an MD5_SHA1 hash must be signed, the extension must not - // prepend a DigestInfo prefix but only add PKCS#1 padding. - ArrayBuffer? signature; - }; - - callback DoneCallback = void (); - callback SignCallback = void(SignatureDetails reply, DoneCallback callback); - - // Notifies Chrome that this extension is capable of responding to signing - // requests for the certificates listed in |certificates|. The list must - // only contain certificates for which the extension can sign data - // using the associated private key. - callback CertificatesCallback = - void(CertificateInfo[] certificates, DoneCallback callback); - - interface Events { - // This event fires every time the browser requests the current list of - // certificates provided by this extension. The extension must call - // |callback| exactly once with the current list of certificates. - static void onClientCertificatesRequested(CertificatesCallback callback); - - // This event fires every time the browser needs to sign a message using a - // certificate provided by this extension using |publishClientCertificates|. - // The extension must sign the data in |request| using the appropriate - // algorithm and private key and return it by calling |callback|. |callback| - // must be called exactly once. - static void onSignDigestRequested(SignRequest request, - SignCallback callback); - }; -}; +// 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. + +// Use this API to expose certificates to the platform which can use these +// certificates for TLS authentications. +namespace certificateProvider { + enum Hash { + MD5_SHA1, + SHA1, + SHA256, + SHA384, + SHA512 + }; + + [noinline_doc] dictionary CertificateInfo { + // Must be the DER encoding of a X.509 certificate. Currently, only + // certificates of RSA keys are supported. + ArrayBuffer certificate; + + // Must be set to all hashes supported for this certificate. This extension + // will only be asked for signatures of digests calculated with one of these + // hash algorithms. + Hash[] supportedHashes; + }; + + [noinline_doc] dictionary SignRequest { + // The digest that must be signed. + ArrayBuffer digest; + + // Refers to the hash algorithm that was used to create digest. + Hash hash; + + // The DER encoding of a X.509 certificate. The extension must sign + // digest using the associated private key. + ArrayBuffer certificate; + }; + + // The callback provided by the extension that Chrome uses to report back + // rejected certificates. See CertificatesCallback. + callback ResultCallback = void (ArrayBuffer[] rejectedCertificates); + + // If no error occurred, this function must be called with the signature of + // the digest using the private key of the requested certificate. + // For an RSA key, the signature must be a PKCS#1 signature. The extension + // is responsible for prepending the DigestInfo prefix and adding PKCS#1 + // padding. If an MD5_SHA1 hash is to be signed, the extension + // must not prepend a DigestInfo prefix but only add PKCS#1 padding. + // If an error occurred, this callback should be called without signature. + callback SignCallback = void (optional ArrayBuffer signature); + + // Call this exactly once with the list of certificates that this extension is + // providing. The list must only contain certificates for which the extension + // can sign data using the associated private key. If the list contains + // invalid certificates, these will be ignored. All valid certificates are + // still registered for the extension. Chrome will call back with the list of + // rejected certificates, which might be empty. + callback CertificatesCallback = + void (CertificateInfo[] certificates, ResultCallback callback); + + interface Events { + // This event fires every time the browser requests the current list of + // certificates provided by this extension. The extension must call + // reportCallback exactly once with the current list of + // certificates. + static void onCertificatesRequested(CertificatesCallback reportCallback); + + // This event fires every time the browser needs to sign a message using a + // certificate provided by this extension in reply to an + // $(ref:onCertificatesRequested) event. + // The extension must sign the data in request using the + // appropriate algorithm and private key and return it by calling + // reportCallback. reportCallback must be called + // exactly once. + // |request|: Contains the details about the sign request. + static void onSignDigestRequested(SignRequest request, + SignCallback reportCallback); + }; +}; diff --git a/chrome/common/extensions/api/certificate_provider_internal.idl b/chrome/common/extensions/api/certificate_provider_internal.idl new file mode 100644 index 000000000000..aa179d269709 --- /dev/null +++ b/chrome/common/extensions/api/certificate_provider_internal.idl @@ -0,0 +1,34 @@ +// 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. + +// Internal API backing the chrome.certificateProvider API events. +// The internal API associates events with replies to these events using request +// IDs. A custom binding is used to hide these IDs from the public API. +// Before an event hits the extension, the request ID is removed and instead a +// callback is added to the event arguments. On the way back, when the extension +// runs the callback to report its results, the callback magically prepends the +// request ID to the results and calls the respective internal report function +// (reportSignature or reportCertificates). +[implemented_in = "chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h"] +namespace certificateProviderInternal { + callback DoneCallback = void (); + callback ResultCallback = void (ArrayBuffer[] rejectedCertificates); + + interface Functions { + // Matches certificateProvider.SignCallback. Must be called without the + // signature to report an error. + static void reportSignature( + long requestId, + optional ArrayBuffer signature, + optional DoneCallback callback); + + // Matches certificateProvider.CertificatesCallback. Must be called without + // the certificates argument to report an error. + static void reportCertificates( + long requestId, + optional certificateProvider.CertificateInfo[] certificates, + optional ResultCallback callback); + }; +}; + diff --git a/chrome/common/extensions/api/schemas.gypi b/chrome/common/extensions/api/schemas.gypi index 9defa05c8572..346cee65f26b 100644 --- a/chrome/common/extensions/api/schemas.gypi +++ b/chrome/common/extensions/api/schemas.gypi @@ -108,6 +108,8 @@ # ChromeOS-specific schemas. 'chromeos_schema_files': [ 'cast_devices_private.idl', + 'certificate_provider.idl', + 'certificate_provider_internal.idl', 'echo_private.json', 'enterprise_device_attributes.idl', 'enterprise_platform_keys.idl', diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc index 0c8648fa3491..729b1e2e08f3 100644 --- a/chrome/common/extensions/permissions/chrome_api_permissions.cc +++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc @@ -64,6 +64,7 @@ std::vector ChromeAPIPermissions::GetAllPermissions() {APIPermission::kBrailleDisplayPrivate, "brailleDisplayPrivate", APIPermissionInfo::kFlagCannotBeOptional}, {APIPermission::kBrowsingData, "browsingData"}, + {APIPermission::kCertificateProvider, "certificateProvider"}, {APIPermission::kContentSettings, "contentSettings"}, {APIPermission::kContextMenus, "contextMenus"}, {APIPermission::kCookie, "cookies"}, diff --git a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc index 6caae310e1c6..2e050a27f256 100644 --- a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc +++ b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc @@ -610,6 +610,9 @@ ChromePermissionMessageRule::GetAllRules() { {IDS_EXTENSION_PROMPT_WARNING_PLATFORMKEYS, {APIPermission::kPlatformKeys}, {}}, + {IDS_EXTENSION_PROMPT_WARNING_CERTIFICATEPROVIDER, + {APIPermission::kCertificateProvider}, + {}}, {IDS_EXTENSION_PROMPT_WARNING_SCREENLOCK_PRIVATE, {APIPermission::kScreenlockPrivate}, diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc index a7758857adc5..2b5a5dd19112 100644 --- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc +++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc @@ -131,6 +131,8 @@ void ChromeExtensionsDispatcherDelegate::PopulateSourceMap( source_map->RegisterSource("automationNode", IDR_AUTOMATION_NODE_JS); source_map->RegisterSource("browserAction", IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS); + source_map->RegisterSource("certificateProvider", + IDR_CERTIFICATE_PROVIDER_CUSTOM_BINDINGS_JS); source_map->RegisterSource("declarativeContent", IDR_DECLARATIVE_CONTENT_CUSTOM_BINDINGS_JS); source_map->RegisterSource("desktopCapture", diff --git a/chrome/renderer/resources/extensions/certificate_provider_custom_bindings.js b/chrome/renderer/resources/extensions/certificate_provider_custom_bindings.js new file mode 100644 index 000000000000..6089677b2a8f --- /dev/null +++ b/chrome/renderer/resources/extensions/certificate_provider_custom_bindings.js @@ -0,0 +1,81 @@ +// 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. + +var binding = require('binding').Binding.create('certificateProvider'); +var certificateProviderInternal = require('binding').Binding.create( + 'certificateProviderInternal').generate(); +var eventBindings = require('event_bindings'); + +var certificateProviderSchema = + requireNative('schema_registry').GetSchema('certificateProvider') +var utils = require('utils'); +var validate = require('schemaUtils').validate; + +// Custom bindings for chrome.certificateProvider API. +// The bindings are used to implement callbacks for the API events. Internally +// each event is passed a requestId argument used to identify the callback +// associated with the event. This argument is massaged out from the event +// arguments before dispatching the event to consumers. A callback is appended +// to the event arguments. The callback wraps an appropriate +// chrome.certificateProviderInternal API function that is used to report the +// event result from the extension. The function is passed the requestId and +// values provided by the extension. It validates that the values provided by +// the extension match chrome.certificateProvider event callback schemas. It +// also ensures that a callback is run at most once. In case there is an +// exception during event dispatching, the chrome.certificateProviderInternal +// function is called with a default error value. + +// Handles a chrome.certificateProvider event as described in the file comment. +// |eventName|: The event name. The first argument of the event must be a +// request id. +// |internalReportFunc|: The function that should be called to report results in +// reply to an event. The first argument of the function must be the request +// id that was received with the event. +function handleEvent(eventName, internalReportFunc) { + var eventSchema = + utils.lookup(certificateProviderSchema.events, 'name', eventName); + var callbackSchema = utils.lookup(eventSchema.parameters, 'type', 'function'); + + eventBindings.registerArgumentMassager( + 'certificateProvider.' + eventName, + function(args, dispatch) { + var responded = false; + + // Function provided to the extension as the event callback argument. + // The extension calls this to report results in reply to the event. + // It throws an exception if called more than once and if the provided + // results don't match the callback schema. + var reportFunc = function(reportArg1, reportArg2) { + if (responded) { + throw new Error( + 'Event callback must not be called more than once.'); + } + + var reportArgs = [reportArg1]; + if (reportArg2 !== undefined) + reportArgs.push(reportArg2); + var finalArgs = []; + try { + // Validates that the results reported by the extension matche the + // callback schema of the event. Throws an exception in case of an + // error. + validate(reportArgs, callbackSchema.parameters); + finalArgs = reportArgs; + } finally { + responded = true; + internalReportFunc.apply( + null, [args[0] /* requestId */].concat(finalArgs)); + } + }; + dispatch(args.slice(1).concat(reportFunc)); + }); +} + +handleEvent('onCertificatesRequested', + certificateProviderInternal.reportCertificates); + +handleEvent('onSignDigestRequested', + certificateProviderInternal.reportSignature); + +exports.binding = binding.generate(); diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd index 78ffdc612521..c07a6d4b0585 100644 --- a/chrome/renderer/resources/renderer_resources.grd +++ b/chrome/renderer/resources/renderer_resources.grd @@ -39,6 +39,7 @@ + diff --git a/chrome/test/data/extensions/api_test/certificate_provider/OWNERS b/chrome/test/data/extensions/api_test/certificate_provider/OWNERS new file mode 100644 index 000000000000..cb0d4a6547f1 --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/OWNERS @@ -0,0 +1,2 @@ +pneubeck@chromium.org +emaxx@chromium.org diff --git a/chrome/test/data/extensions/api_test/certificate_provider/basic.html b/chrome/test/data/extensions/api_test/certificate_provider/basic.html new file mode 100644 index 000000000000..957ba1c293c2 --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/basic.html @@ -0,0 +1,6 @@ + + diff --git a/chrome/test/data/extensions/api_test/certificate_provider/basic.js b/chrome/test/data/extensions/api_test/certificate_provider/basic.js new file mode 100644 index 000000000000..454a0ee0e497 --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/basic.js @@ -0,0 +1,110 @@ +// 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. + +'use strict'; + +var assertEq = chrome.test.assertEq; +var assertTrue = chrome.test.assertTrue; +var callbackPass = chrome.test.callbackPass; +var succeed = chrome.test.succeed; + +// X.509 certificate in DER encoding issued by 'root.pem' which is set to be +// trusted by the test setup. +// Read from 'l1_leaf.der', generated by create_test_certs.sh . +var l1_leaf_cert = null; + +// Reads the binary file at |path| and passes it as a Uint8Array to |callback|. +function readFile(path, callback) { + var oReq = new XMLHttpRequest(); + oReq.responseType = "arraybuffer"; + oReq.open("GET", path, true /* asynchronous */); + oReq.onload = function() { + var arrayBuffer = oReq.response; + if (arrayBuffer) { + callback(new Uint8Array(arrayBuffer)); + } else { + callback(null); + } + }; + oReq.send(null); +} + +function compareBuffers(a, b) { + if (a.length != b.length) + return false; + for (var i = 0; i < a.length; i++) { + if (a[i] != b[i]) + return false; + } + return true; +} + +var signDigestRequest; +var signCallback; + +function register() { + assertTrue(!!chrome.certificateProvider); + assertTrue(!!chrome.certificateProvider.onCertificatesRequested); + assertTrue(!!chrome.certificateProvider.onSignDigestRequested); + + var validCertInfo = { + certificate: l1_leaf_cert.buffer, + supportedHashes: ['SHA1'] + }; + var invalidCert = new Uint8Array([1, 2, 3, 4, 5]); + var invalidCertInfo = { + certificate: invalidCert.buffer, + supportedHashes: ['SHA256'] + }; + + function checkResult(rejectedCerts) { + assertEq(1, rejectedCerts.length); + assertTrue(compareBuffers(invalidCert, new Uint8Array(rejectedCerts[0]))); + } + + function reportCertificates(reportCallback) { + reportCallback([validCertInfo, invalidCertInfo], callbackPass(checkResult)); + } + + chrome.certificateProvider.onCertificatesRequested.addListener( + callbackPass(reportCertificates)); + + chrome.certificateProvider.onSignDigestRequested.addListener(function( + request, callback) { + assertTrue( + compareBuffers(l1_leaf_cert, new Uint8Array(request.certificate))); + // The sign request must refer to the only hash that was declared to be + // supported. + assertEq(1, validCertInfo.supportedHashes.length); + assertEq(validCertInfo.supportedHashes[0], request.hash); + signCallback = callback; + signDigestRequest = request; + succeed(); + }); + + succeed(); +} + +function replyWithSignature(signature) { + signCallback(signature.buffer); +} + +function replyWithSignatureSecondTime() { + var signature = new Uint8Array([1,2,3]); + try { + signCallback(signature.buffer); + } catch (e) { + return true; + } + return false; +} + +function runTest() { + chrome.test.runTests([register]); +} + +readFile('l1_leaf.der', function(cert) { + l1_leaf_cert = cert; + runTest(); +}); diff --git a/chrome/test/data/extensions/api_test/certificate_provider/ca.cnf b/chrome/test/data/extensions/api_test/certificate_provider/ca.cnf new file mode 100644 index 000000000000..ddac80378488 --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/ca.cnf @@ -0,0 +1,57 @@ +[ca] +default_ca = CA_root +preserve = yes + +# The default test root, used to generate certificates and CRLs. +[CA_root] +dir = out +key_size = 2048 +algo = sha256 +cert_type = root +database = $dir/${ENV::CA_ID}-index.txt +new_certs_dir = $dir +serial = $dir/${ENV::CA_ID}-serial +certificate = $dir/${ENV::CA_ID}.pem +private_key = $dir/${ENV::CA_ID}.key +RANDFILE = $dir/.rand +default_days = 3650 +default_crl_days = 30 +default_md = sha256 +policy = policy_anything +unique_subject = no +copy_extensions = copy + +[leaf_cert] +# Extensions to add when signing a request for an leaf cert +basicConstraints = critical, CA:false +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +extendedKeyUsage = serverAuth, clientAuth + +[ca_cert] +# Extensions to add when signing a request for an intermediate/CA cert +basicConstraints = critical, CA:true +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +keyUsage = critical, keyCertSign, cRLSign + +[policy_anything] +# Default signing policy +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = optional +emailAddress = optional + +[req] +default_bits = 2048 +default_md = sha256 +string_mask = utf8only +prompt = no +encrypt_key = no +distinguished_name = dn + +[dn] +CN = $ENV::CN diff --git a/chrome/test/data/extensions/api_test/certificate_provider/create_test_certs.sh b/chrome/test/data/extensions/api_test/certificate_provider/create_test_certs.sh new file mode 100755 index 000000000000..ecd1772b225d --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/create_test_certs.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# 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. + +# Generates the following tree of certificates: +# root (self-signed root) +# \ +# \--> l1_leaf (end-entity) + +try() { + "$@" || { + e=$? + echo "*** ERROR $e *** $@ " > /dev/stderr + exit $e + } +} + +# Create a self-signed CA cert with CommonName CN and store it at $1.pem . +root_cert() { + try /bin/sh -c "echo 01 > out/${1}-serial" + try touch out/${1}-index.txt + try openssl genrsa -out out/${1}.key 2048 + + CA_ID=$1 \ + try openssl req \ + -new \ + -key out/${1}.key \ + -out out/${1}.req \ + -config ca.cnf + + CA_ID=$1 \ + try openssl x509 \ + -req -days 3650 \ + -in out/${1}.req \ + -signkey out/${1}.key \ + -extfile ca.cnf \ + -extensions ca_cert > out/${1}.pem + + try cp out/${1}.pem ${1}.pem +} + +# Create a cert with CommonName CN signed by CA_ID and store it at $1.der . +# $2 must either be "leaf_cert" (for a server/user cert) or "ca_cert" (for a +# intermediate CA). +# Stores the private key at $1.pk8 . +issue_cert() { + if [[ "$2" == "ca_cert" ]] + then + try /bin/sh -c "echo 01 > out/${1}-serial" + try touch out/${1}-index.txt + fi + try openssl req \ + -new \ + -keyout out/${1}.key \ + -out out/${1}.req \ + -config ca.cnf + + try openssl ca \ + -batch \ + -extensions $2 \ + -in out/${1}.req \ + -out out/${1}.pem \ + -config ca.cnf + + try openssl pkcs8 -topk8 -in out/${1}.key -out ${1}.pk8 -outform DER -nocrypt + + try openssl x509 -in out/${1}.pem -outform DER -out out/${1}.der + try cp out/${1}.der ${1}.der +} + +try rm -rf out +try mkdir out + +CN=root \ + try root_cert root + +CA_ID=root CN=l1_leaf \ + try issue_cert l1_leaf leaf_cert diff --git a/chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.der b/chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.der new file mode 100644 index 0000000000000000000000000000000000000000..038c8454c0c87e2d8434fac1fabdc4e861734e68 GIT binary patch literal 783 zcwRGTV&*nzV*I>-nTe5!iILHOmyJ`a&7QA+G^98*?ZNGY?BqetwC8 zoH(zcse!qHrJS2pI|(@If@N=NQK4q$Z{T%~wJ;mywl$xrvdV z0VvMJ)Wpch@VKth?RwG6-P;cKee+1LV>{`{{A88y7k4X#lpTB1I6vM9?NQT7&D_p+ zMfW(@>U!B+LDzGC)~2x^+a|&Oi@Eag`4x^6+$8upZYz|S*GSHeTe)$2=f^+qoWNTWFEr;zrzvJjn=-K zXr6aLxYn^DMr%9{qeg`9pfwrj2v2pW~nMnu+7_qg9!QtX$4??sZ!H?d%lx z>q$j756^t5X8An&sJ%nI*Um~NW=00a#rX!g20Xx!lNDxU{LjKK(&PA{X2W$w^+w?WMK&z2bjS@y1;V#?`N`obxocW*@nm_&BLC>d=y&4fR|<4G$H(UsLMj#!&Lf_-3Jo<^hhZ zi{9RoQj6btq!m2Be`?;GH${_ks~I-#hm6=2hEV^s9T;N9gl>nm)hJQQ=El^Nu`#h+ny-1&orQyG*21qM9X literal 0 HcwPel00001 diff --git a/chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.pk8 b/chrome/test/data/extensions/api_test/certificate_provider/l1_leaf.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..5cade788112c2c1b9d28d267424f965056dfcdd1 GIT binary patch literal 1219 zcwPa+1U&mNf&{+;0RS)!1_>&LNQUrsW5^Br2+u}0)hbn0ONjnM%Qxb zySBlO_DEnp2FXDK-&6xV>fx@z_<2CM;!Xw+_@T#|o=|9c>Xs&it)r2gbG- z2l@kfm;fEhj zuMF56)#P+hBc23z*I=3<+aRg6t9inX5Jr|lFUmDgyLe*^HF2cd-hZ4qd-V3A6J*LK z8`uIJ(|}gx|6(0wDD5I+3^8y{!&sLxt?rpOa_S9^DeDt_zt5gH_l3pt$7Aqkin6ht z*Uk@=>jDVl;;L!FIz-Hgy+Wh*&6Ee%VshETndv4u=Uc@;Kz~ZPc>)6g009Dm0RaH1 zo0yfYd73kReD1bIouh#9xeQANf!Y@2ic~GT-+-8tzN{am$MQNY&ly%FX;}L@UcXu+ z-mi=*rG{f94n=7QU>u2&2+ZWbVu0fT9wJRZT_i@YXz@X5dpxDt9~8)n)iCcC?q}mt zphlZtUxUms+DaW-7cz@Tdva0SPVL`@g*fsbc_z8Br6*Ee>DN_Pc>Y1APo*Ex6Ulo6 zlI>FVdKyh0lN#p?%I4!xH7yk#)4t*2n&&poV&`$o#sR zOli&(ZyiC+lJ}1^V@0J?O(2tbpa7vnXnlKUT0$iP2QGuA7xx}1!~aJI6A&c=fq?+{ z-+LYFAw;CXjkQ>F=TfvcS2K=)Td!uld9)R%sh7$VVYoU(Im-8^~;>l&|*z;klhjP=&uDw z`g;$1BNRyiEFNy5rBtzYRC3)G^C1@kfq?+(0z%sZcU6)kX!){9*3k{5R2Q}2rEDMb zrVs+CNgA>ZZ1et+{L2x;Rs6`#`D4j{=wYN%IV-p>Da*+ST0V~R%~D%??zw_hK+%7F z>zpg3_RB2Q z?6P^{BPP}&rljmf)+8KP<_)TcY{0#Ek%}djEMuvUy1MO32Gs3t+v}ig>Y0NrW^0wm zO4_-MVTyA;E>K`4B4y{Y4+iJJja=B3WZlOCfq?*_qP8-8HnH=Ei^x!szr5MCN%fh2J4L)rV!X zC3cUkmX?vfgY4$~-IwmH!+@?604V5DRs0luC=N%2e+?y5toa3*o%=D#`6_YFjU-5; ztf3hKfq-n(BOK~No(rZ*Fur$+JyQxy!U^&07l`q+`jZm}lPWW~pO-(y=R57=FcMww zRCGyR{+GJ+d|t2&x&@WhyMYcz?lZo;!E;4X{5aL5LU!N32cj#4NR^pQ2jF_vTBHSU hpcP3HcZU>RHthVr!#oBmE{mf`=1oLQ|K&xN0SO|gN%{Z) literal 0 HcwPel00001 diff --git a/chrome/test/data/extensions/api_test/certificate_provider/manifest.json b/chrome/test/data/extensions/api_test/certificate_provider/manifest.json new file mode 100644 index 000000000000..d89d25646d4e --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "Basic tests", + "version": "0.1", + "manifest_version": 2, + "permissions": [ + "certificateProvider" + ] +} diff --git a/chrome/test/data/extensions/api_test/certificate_provider/root.pem b/chrome/test/data/extensions/api_test/certificate_provider/root.pem new file mode 100644 index 000000000000..0219f78bc1bb --- /dev/null +++ b/chrome/test/data/extensions/api_test/certificate_provider/root.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIJANx0yuz7bYMgMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNV +BAMMBHJvb3QwHhcNMTUwNzA5MTEwOTI4WhcNMjUwNzA2MTEwOTI4WjAPMQ0wCwYD +VQQDDARyb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx+NuzMk9 +RJi6pIeILIce1EAuS4wkSY9K3DK4/g/8mSYT00m8Kh7srrgDQcH91g2Asz/DSxga +9kyDSxNW/Jsn4xvvsfg/Rfg7ob363k9m50e76Ks4o+ulFYJfHNDyM1dAu9gTxPCE +X0UWkCcqAXUcvzPfTGGWszE2dJgRW+hj0rU59ICEqkLtMgyOwgpymfMlsYgcdVbr +7Q8Jd0sLWRFk4Ttpf19qbI54ShGXjzzBHwyLqgUQ46cGIa/p/rzokAVN7iiJLRB+ +EUV44/kEtReW0+OL2Gix1MJLlVcNgp0VpG4DHe598nwWDp2DRpsPqopk0zHlORzn +prFe8kb4je0ZRwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSo +hrlZIUre/ixsj5WghP4FtDjyozAfBgNVHSMEGDAWgBSohrlZIUre/ixsj5WghP4F +tDjyozAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAIjjljyV7tuj +E0sWaWDcvGjSNJdBaD4Wxu1+clS9u1KOBRXp3+4HV+zZ42Q43G5Zn0bObmsRa87C +TMlNYknXOmzqAxbF4OabgeS6jI/UiLmmGa01UzW7+7QmLHjNgFI/AjdEFbZ600i6 +/L9Owyj95+uzlRudnSDi8x0dEQ/23+rkWFzHXBR0U1hBI6mRDCrakH7CLXCG1f/+ +wIgu1RYFuWRyOAhQlgb87Gixs5ipzpF3FbUJ4wDh6SNjzTC6kzZuJgN1FmUHQ/CK +w+6Do9wDaG/0VH5nxLzlN+1et3vGHRz68xizL3bvouGwDsQCBN7j6hAdNH80fgUx +/utKrkUKauI= +-----END CERTIFICATE----- diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h index c3df48854e08..d6f6fffef3af 100644 --- a/extensions/browser/extension_event_histogram_value.h +++ b/extensions/browser/extension_event_histogram_value.h @@ -401,6 +401,8 @@ enum HistogramValue { CAST_DEVICES_PRIVATE_ON_UPDATE_DEVICES_REQUESTED, CAST_DEVICES_PRIVATE_ON_START_CAST, CAST_DEVICES_PRIVATE_ON_STOP_CAST, + CERTIFICATEPROVIDER_ON_CERTIFICATES_REQUESTED, + CERTIFICATEPROVIDER_ON_SIGN_DIGEST_REQUESTED, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 5982ed3e9f55..32dab82b6120 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1138,6 +1138,8 @@ enum HistogramValue { BROWSINGDATA_REMOVECACHESTORAGE, VIRTUALKEYBOARDPRIVATE_SETKEYBOARDSTATE, VIRTUALKEYBOARDPRIVATE_SETHOTRODKEYBOARD, + CERTIFICATEPROVIDERINTERNAL_REPORTSIGNATURE, + CERTIFICATEPROVIDERINTERNAL_REPORTCERTIFICATES, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h index e49a1531363c..a316cdecc243 100644 --- a/extensions/common/permissions/api_permission.h +++ b/extensions/common/permissions/api_permission.h @@ -241,6 +241,7 @@ class APIPermission { kPasswordsPrivate, kLanguageSettingsPrivate, kEnterpriseDeviceAttributes, + kCertificateProvider, // Last entry: Add new entries above and ensure to update the // "ExtensionPermission3" enum in tools/metrics/histograms/histograms.xml // (by running update_extension_permission.py). diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 896959cd4845..e9b82d75dbb1 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -57851,6 +57851,8 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. + + @@ -58974,6 +58976,8 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. + + -- 2.11.4.GIT