From 21924fdd22b19efc25827a250237deb7928e6f11 Mon Sep 17 00:00:00 2001 From: pneubeck Date: Sat, 23 May 2015 05:13:20 -0700 Subject: Add an extension API to verify server certificate trust. BUG=476464 Review URL: https://codereview.chromium.org/1066383002 Cr-Commit-Position: refs/heads/master@{#331236} --- .../api/platform_keys/platform_keys_api.cc | 50 ++++- .../api/platform_keys/platform_keys_api.h | 15 +- .../api/platform_keys/platform_keys_apitest_nss.cc | 16 +- .../api/platform_keys/verify_trust_api.cc | 223 +++++++++++++++++++++ .../api/platform_keys/verify_trust_api.h | 111 ++++++++++ .../chrome_browser_main_extra_parts_profiles.cc | 7 + chrome/chrome_browser_extensions.gypi | 2 + chrome/common/extensions/api/platform_keys.idl | 32 +++ .../extensions/api_test/platform_keys/basic.js | 58 ++++++ .../data/extensions/api_test/platform_keys/ca.cnf | 57 ++++++ .../api_test/platform_keys/create_test_certs.sh | 86 ++++++++ .../api_test/platform_keys/l1_interm.der | Bin 0 -> 773 bytes .../extensions/api_test/platform_keys/l1_leaf.der | Bin 0 -> 783 bytes .../extensions/api_test/platform_keys/l2_leaf.der | Bin 0 -> 788 bytes .../extensions/api_test/platform_keys/root.pem | 19 ++ .../browser/extension_function_histogram_value.h | 1 + tools/metrics/histograms/histograms.xml | 1 + 17 files changed, 672 insertions(+), 6 deletions(-) create mode 100644 chrome/browser/extensions/api/platform_keys/verify_trust_api.cc create mode 100644 chrome/browser/extensions/api/platform_keys/verify_trust_api.h create mode 100644 chrome/test/data/extensions/api_test/platform_keys/ca.cnf create mode 100755 chrome/test/data/extensions/api_test/platform_keys/create_test_certs.sh create mode 100644 chrome/test/data/extensions/api_test/platform_keys/l1_interm.der create mode 100644 chrome/test/data/extensions/api_test/platform_keys/l1_leaf.der create mode 100644 chrome/test/data/extensions/api_test/platform_keys/l2_leaf.der create mode 100644 chrome/test/data/extensions/api_test/platform_keys/root.pem diff --git a/chrome/browser/extensions/api/platform_keys/platform_keys_api.cc b/chrome/browser/extensions/api/platform_keys/platform_keys_api.cc index c6301e9..29cce26 100644 --- a/chrome/browser/extensions/api/platform_keys/platform_keys_api.cc +++ b/chrome/browser/extensions/api/platform_keys/platform_keys_api.cc @@ -12,9 +12,11 @@ #include "chrome/browser/chromeos/platform_keys/platform_keys.h" #include "chrome/browser/chromeos/platform_keys/platform_keys_service.h" #include "chrome/browser/chromeos/platform_keys/platform_keys_service_factory.h" +#include "chrome/browser/extensions/api/platform_keys/verify_trust_api.h" #include "chrome/common/extensions/api/platform_keys_internal.h" #include "components/web_modal/popup_manager.h" #include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" #include "net/cert/x509_certificate.h" namespace extensions { @@ -27,8 +29,6 @@ namespace { const char kErrorAlgorithmNotSupported[] = "Algorithm not supported."; const char kErrorAlgorithmNotPermittedByCertificate[] = "The requested Algorithm is not permitted by the certificate."; -const char kErrorInvalidX509Cert[] = - "Certificate is not a valid X.509 certificate."; const char kErrorInteractiveCallFromBackground[] = "Interactive calls must happen in the context of a browser tab or a " "window."; @@ -72,6 +72,8 @@ void BuildWebCryptoRSAAlgorithmDictionary(const PublicKeyInfo& key_info, namespace platform_keys { const char kErrorInvalidToken[] = "The token is not valid."; +const char kErrorInvalidX509Cert[] = + "Certificate is not a valid X.509 certificate."; const char kTokenIdUser[] = "user"; const char kTokenIdSystem[] = "system"; @@ -114,12 +116,12 @@ PlatformKeysInternalGetPublicKeyFunction::Run() { const std::vector& cert_der = params->certificate; if (cert_der.empty()) - return RespondNow(Error(kErrorInvalidX509Cert)); + return RespondNow(Error(platform_keys::kErrorInvalidX509Cert)); scoped_refptr cert_x509 = net::X509Certificate::CreateFromBytes(vector_as_array(&cert_der), cert_der.size()); if (!cert_x509) - return RespondNow(Error(kErrorInvalidX509Cert)); + return RespondNow(Error(platform_keys::kErrorInvalidX509Cert)); PublicKeyInfo key_info; key_info.public_key_spki_der = @@ -208,6 +210,7 @@ void PlatformKeysInternalSelectClientCertificatesFunction:: OnSelectedCertificates(scoped_ptr matches, const std::string& error_message) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (!error_message.empty()) { Respond(Error(error_message)); return; @@ -297,6 +300,7 @@ void PlatformKeysInternalSignFunction::OnSigned( const std::string& signature, const std::string& error_message) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (error_message.empty()) Respond(ArgumentList(api_pki::Sign::Results::Create( std::vector(signature.begin(), signature.end())))); @@ -304,4 +308,42 @@ void PlatformKeysInternalSignFunction::OnSigned( Respond(Error(error_message)); } +PlatformKeysVerifyTLSServerCertificateFunction:: + ~PlatformKeysVerifyTLSServerCertificateFunction() { +} + +ExtensionFunction::ResponseAction +PlatformKeysVerifyTLSServerCertificateFunction::Run() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + scoped_ptr params( + api_pk::VerifyTLSServerCertificate::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + VerifyTrustAPI::GetFactoryInstance() + ->Get(browser_context()) + ->Verify(params.Pass(), extension_id(), + base::Bind(&PlatformKeysVerifyTLSServerCertificateFunction:: + FinishedVerification, + this)); + + return RespondLater(); +} + +void PlatformKeysVerifyTLSServerCertificateFunction::FinishedVerification( + const std::string& error, + int verify_result_code) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!error.empty()) { + Respond(Error(error)); + return; + } + + api_pk::VerificationResult result; + result.trusted = verify_result_code == net::OK; + Respond(ArgumentList( + api_pk::VerifyTLSServerCertificate::Results::Create(result))); +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/platform_keys/platform_keys_api.h b/chrome/browser/extensions/api/platform_keys/platform_keys_api.h index 7a4b9ea..aa0fb59 100644 --- a/chrome/browser/extensions/api/platform_keys/platform_keys_api.h +++ b/chrome/browser/extensions/api/platform_keys/platform_keys_api.h @@ -13,12 +13,13 @@ namespace net { class X509Certificate; typedef std::vector> CertificateList; -} +} // net namespace extensions { namespace platform_keys { extern const char kErrorInvalidToken[]; +extern const char kErrorInvalidX509Cert[]; // Returns whether |token_id| references a known Token. bool ValidateToken(const std::string& token_id, @@ -70,6 +71,18 @@ class PlatformKeysInternalSignFunction PLATFORMKEYSINTERNAL_SIGN); }; +class PlatformKeysVerifyTLSServerCertificateFunction + : public UIThreadExtensionFunction { + private: + ~PlatformKeysVerifyTLSServerCertificateFunction() override; + ResponseAction Run() override; + + void FinishedVerification(const std::string& error, int result); + + DECLARE_EXTENSION_FUNCTION("platformKeys.verifyTLSServerCertificate", + PLATFORMKEYS_VERIFYTLSSERVERCERTIFICATE); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_PLATFORM_KEYS_PLATFORM_KEYS_API_H_ diff --git a/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc b/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc index 01ff012..8930640 100644 --- a/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc +++ b/chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc @@ -25,6 +25,7 @@ #include "net/base/net_errors.h" #include "net/base/test_data_directory.h" #include "net/cert/nss_cert_database.h" +#include "net/cert/test_root_certs.h" #include "net/test/cert_test_util.h" #include "net/test/url_request/url_request_mock_http_job.h" #include "policy/policy_constants.h" @@ -145,6 +146,12 @@ class PlatformKeysTest : public ExtensionApiTest, private: void SetupTestCerts(const base::Closure& done_callback, net::NSSCertDatabase* cert_db) { + SetupTestClientCerts(cert_db); + SetupTestCACerts(); + done_callback.Run(); + } + + void SetupTestClientCerts(net::NSSCertDatabase* cert_db) { client_cert1_ = net::ImportClientCertAndKeyFromFile( net::GetTestCertsDirectory(), "client_1.pem", "client_1.pk8", cert_db->GetPrivateSlot().get()); @@ -156,8 +163,15 @@ class PlatformKeysTest : public ExtensionApiTest, net::GetTestCertsDirectory(), "client_2.pem", "client_2.pk8", test_system_slot_->slot()); ASSERT_TRUE(client_cert2_.get()); + } - done_callback.Run(); + void SetupTestCACerts() { + net::TestRootCerts* root_certs = net::TestRootCerts::GetInstance(); + // "root_ca_cert.pem" is the issuer of "ok_cert.pem" which is loaded on the + // JS side. Generated by create_test_certs.sh . + base::FilePath extension_path = test_data_dir_.AppendASCII("platform_keys"); + root_certs->AddFromFile( + test_data_dir_.AppendASCII("platform_keys").AppendASCII("root.pem")); } void SetUpTestSystemSlotOnIO(content::ResourceContext* context, diff --git a/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc new file mode 100644 index 0000000..26bb8f3 --- /dev/null +++ b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc @@ -0,0 +1,223 @@ +// 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/platform_keys/verify_trust_api.h" + +#include + +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/extensions/api/platform_keys/platform_keys_api.h" +#include "chrome/common/extensions/api/platform_keys_internal.h" +#include "extensions/browser/extension_registry_factory.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" +#include "net/log/net_log.h" +#include "net/ssl/ssl_config_service.h" + +namespace extensions { + +namespace { + +base::LazyInstance>::Leaky + g_factory = LAZY_INSTANCE_INITIALIZER; + +const char kErrorEmptyCertificateChain[] = + "Server certificate chain must not be empty."; + +} // namespace + +// This class bundles IO data and functions of the VerifyTrustAPI that are to be +// used on the IO thread only. +// It is created on the UI thread and afterwards lives on the IO thread. +class VerifyTrustAPI::IOPart { + public: + ~IOPart(); + + // Verifies the certificate as stated by |params| and calls back |callback| + // with the result (see the declaration of VerifyCallback). + // Will not call back after this object is destructed or the verifier for this + // extension is deleted (see OnExtensionUnloaded). + void Verify(scoped_ptr params, + const std::string& extension_id, + const VerifyCallback& callback); + + // Must be called when the extension with id |extension_id| is unloaded. + // Deletes the verifier for |extension_id| and cancels all pending + // verifications of this verifier. + void OnExtensionUnloaded(const std::string& extension_id); + + private: + struct RequestState { + RequestState() {} + + scoped_ptr request; + + private: + DISALLOW_COPY_AND_ASSIGN(RequestState); + }; + + // Calls back |callback| with the result and no error. + void CallBackWithResult(const VerifyCallback& callback, + scoped_ptr verify_result, + RequestState* request_state, + int result); + + // One CertVerifier per extension to verify trust. Each verifier is created on + // first usage and deleted when this IOPart is destructed or the respective + // extension is unloaded. + std::map> extension_to_verifier_; +}; + +// static +BrowserContextKeyedAPIFactory* +VerifyTrustAPI::GetFactoryInstance() { + return g_factory.Pointer(); +} + +template <> +void BrowserContextKeyedAPIFactory< + VerifyTrustAPI>::DeclareFactoryDependencies() { + DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); + DependsOn(ExtensionRegistryFactory::GetInstance()); +} + +VerifyTrustAPI::VerifyTrustAPI(content::BrowserContext* context) + : io_part_(new IOPart), registry_observer_(this), weak_factory_(this) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + registry_observer_.Add(ExtensionRegistry::Get(context)); +} + +VerifyTrustAPI::~VerifyTrustAPI() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +} + +void VerifyTrustAPI::Verify(scoped_ptr params, + const std::string& extension_id, + const VerifyCallback& ui_callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // Call back through the VerifyTrustAPI object on the UIThread. Because of the + // WeakPtr usage, this will ensure that |ui_callback| is not called after the + // API is destroyed. + VerifyCallback finish_callback(base::Bind( + &CallBackOnUI, base::Bind(&VerifyTrustAPI::FinishedVerificationOnUI, + weak_factory_.GetWeakPtr(), ui_callback))); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&IOPart::Verify, base::Unretained(io_part_.get()), + base::Passed(¶ms), extension_id, finish_callback)); +} + +void VerifyTrustAPI::OnExtensionUnloaded( + content::BrowserContext* browser_context, + const Extension* extension, + UnloadedExtensionInfo::Reason reason) { + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&IOPart::OnExtensionUnloaded, base::Unretained(io_part_.get()), + extension->id())); +} + +void VerifyTrustAPI::FinishedVerificationOnUI(const VerifyCallback& ui_callback, + const std::string& error, + int result) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + ui_callback.Run(error, result); +} + +// static +void VerifyTrustAPI::CallBackOnUI(const VerifyCallback& ui_callback, + const std::string& error, + int result) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(ui_callback, error, result)); +} + +VerifyTrustAPI::IOPart::~IOPart() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); +} + +void VerifyTrustAPI::IOPart::Verify(scoped_ptr params, + const std::string& extension_id, + const VerifyCallback& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + const api::platform_keys::VerificationDetails& details = params->details; + + if (details.server_certificate_chain.empty()) { + callback.Run(kErrorEmptyCertificateChain, 0); + return; + } + + std::vector der_cert_chain; + for (const std::vector& cert_der : details.server_certificate_chain) { + if (cert_der.empty()) { + callback.Run(platform_keys::kErrorInvalidX509Cert, 0); + return; + } + der_cert_chain.push_back(base::StringPiece( + reinterpret_cast(vector_as_array(&cert_der)), + cert_der.size())); + } + scoped_refptr cert_chain( + net::X509Certificate::CreateFromDERCertChain(der_cert_chain)); + if (!cert_chain) { + callback.Run(platform_keys::kErrorInvalidX509Cert, 0); + return; + } + + net::CertVerifier* verifier = nullptr; + if (ContainsKey(extension_to_verifier_, extension_id)) { + verifier = extension_to_verifier_[extension_id].get(); + } else { + verifier = net::CertVerifier::CreateDefault(); + extension_to_verifier_[extension_id] = make_linked_ptr(verifier); + } + + scoped_ptr verify_result(new net::CertVerifyResult); + scoped_ptr net_log(new net::BoundNetLog); + const int flags = 0; + + std::string ocsp_response; + net::CertVerifyResult* const verify_result_ptr = verify_result.get(); + + RequestState* request_state = new RequestState(); + base::Callback bound_callback( + base::Bind(&IOPart::CallBackWithResult, base::Unretained(this), callback, + base::Passed(&verify_result), base::Owned(request_state))); + + const int result = verifier->Verify( + cert_chain.get(), details.hostname, ocsp_response, flags, + net::SSLConfigService::GetCRLSet().get(), verify_result_ptr, + bound_callback, &request_state->request, *net_log); + + if (result != net::ERR_IO_PENDING) { + bound_callback.Run(result); + return; + } +} + +void VerifyTrustAPI::IOPart::OnExtensionUnloaded( + const std::string& extension_id) { + extension_to_verifier_.erase(extension_id); +} + +void VerifyTrustAPI::IOPart::CallBackWithResult( + const VerifyCallback& callback, + scoped_ptr verify_result, + RequestState* request_state, + int result) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + callback.Run(std::string() /* no error message */, result); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/platform_keys/verify_trust_api.h b/chrome/browser/extensions/api/platform_keys/verify_trust_api.h new file mode 100644 index 0000000..ac27bad --- /dev/null +++ b/chrome/browser/extensions/api/platform_keys/verify_trust_api.h @@ -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. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_PLATFORM_KEYS_VERIFY_TRUST_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_PLATFORM_KEYS_VERIFY_TRUST_API_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observer.h" +#include "content/public/browser/browser_thread.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_registry_observer.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace extensions { + +namespace api { +namespace platform_keys { +namespace VerifyTLSServerCertificate { +struct Params; +} // namespace VerifyTLSServerCertificate +} // namespace platform_keys +} // namespace api + +// This keyed service is used by the platformKeys.verifyTLSServerCertificate for +// caching and to reuse objects between multiple API calls (e.g. the +// net::CertVerifier). +class VerifyTrustAPI : public BrowserContextKeyedAPI, + public ExtensionRegistryObserver { + public: + // Will be called with |result| set to the verification result (net::OK if the + // certificate is trusted or a net error code) or if an error occured during + // processing the parameters, |error| is set to an english error message and + // |result| must be ignored. + using VerifyCallback = + base::Callback; + using Params = api::platform_keys::VerifyTLSServerCertificate::Params; + + // Consumers should use the factory instead of this constructor. + explicit VerifyTrustAPI(content::BrowserContext* context); + ~VerifyTrustAPI() override; + + // Verifies the server certificate as described by |params| for the + // extension with id |extension_id|. When verification is complete + // (successful or not), the result will be passed to |callback|. + // + // Note: It is safe to delete this object while there are still + // outstanding operations. However, if this happens, |callback| + // will NOT be called. + void Verify(scoped_ptr params, + const std::string& extension_id, + const VerifyCallback& callback); + + // ExtensionRegistryObserver: + void OnExtensionUnloaded(content::BrowserContext* browser_context, + const Extension* extension, + UnloadedExtensionInfo::Reason reason) override; + + // BrowserContextKeyedAPI: + static BrowserContextKeyedAPIFactory* GetFactoryInstance(); + + protected: + static const bool kServiceRedirectedInIncognito = true; + static const bool kServiceIsCreatedWithBrowserContext = false; + static const bool kServiceIsNULLWhileTesting = true; + + private: + class IOPart; + friend class BrowserContextKeyedAPIFactory; + + // Calls |ui_callback| with the given parameters. + void FinishedVerificationOnUI(const VerifyCallback& ui_callback, + const std::string& error, + int result); + + // Calls |ui_callback| on the UIThread with the given arguments. + static void CallBackOnUI(const VerifyCallback& ui_callback, + const std::string& error, + int result); + + // BrowserContextKeyedAPI implementation. + static const char* service_name() { return "VerifyTrustAPI"; } + + // Created on the UIThread but must be used and destroyed only on the + // IOThread. + scoped_ptr io_part_; + + ScopedObserver + registry_observer_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(VerifyTrustAPI); +}; + +template <> +void BrowserContextKeyedAPIFactory< + VerifyTrustAPI>::DeclareFactoryDependencies(); + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_PLATFORM_KEYS_VERIFY_TRUST_API_H_ diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc index 8868b38..49869cb 100644 --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc @@ -116,6 +116,10 @@ #include "chrome/browser/signin/cross_device_promo_factory.h" #endif +#if defined(OS_CHROMEOS) +#include "chrome/browser/extensions/api/platform_keys/verify_trust_api.h" +#endif + #if !defined(OS_ANDROID) #include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h" #endif @@ -203,6 +207,9 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() { EasyUnlockServiceFactory::GetInstance(); EnhancedBookmarkKeyServiceFactory::GetInstance(); #endif +#if defined(OS_CHROMEOS) + extensions::VerifyTrustAPI::GetFactoryInstance(); +#endif FaviconServiceFactory::GetInstance(); FindBarStateFactory::GetInstance(); GAIAInfoUpdateServiceFactory::GetInstance(); diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 8721e77..b5e81fc 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -28,6 +28,8 @@ 'browser/extensions/api/networking_private/crypto_verify_impl.h', 'browser/extensions/api/platform_keys/platform_keys_api.cc', 'browser/extensions/api/platform_keys/platform_keys_api.h', + 'browser/extensions/api/platform_keys/verify_trust_api.cc', + 'browser/extensions/api/platform_keys/verify_trust_api.h', 'browser/extensions/api/terminal/terminal_extension_helper.cc', 'browser/extensions/api/terminal/terminal_extension_helper.h', 'browser/extensions/api/terminal/terminal_private_api.cc', diff --git a/chrome/common/extensions/api/platform_keys.idl b/chrome/common/extensions/api/platform_keys.idl index 8627556..3b83923 100644 --- a/chrome/common/extensions/api/platform_keys.idl +++ b/chrome/common/extensions/api/platform_keys.idl @@ -59,6 +59,24 @@ namespace platformKeys { boolean interactive; }; + dictionary VerificationDetails { + // Each chain entry must be the DER encoding of a X.509 certificate, the + // first entry must be the server certificate and each entry must certify + // the entry preceding it. + ArrayBuffer[] serverCertificateChain; + + // The hostname of the server to verify the certificate for, e.g. the server + // that presented the serverCertificateChain. + DOMString hostname; + }; + + dictionary VerificationResult { + // The result of the trust verificaiton: true if trust for the given + // verification details could be established and false if trust is rejected + // for any reason. + boolean trusted; + }; + // |matches|: The list of certificates that match the request, that the // extension has permission for and, if interactive is true, that // were selected by the user. @@ -73,6 +91,8 @@ namespace platformKeys { callback GetKeyPairCallback = void (object publicKey, optional object privateKey); + callback VerificationCallback = void (VerificationResult result); + interface Functions { // This function filters from a list of client certificates the ones that // are known to the platform, match request and for which the @@ -108,6 +128,18 @@ namespace platformKeys { // that allows crypto operations on keys of client certificates that are // available to this extension. [nocompile] static object subtleCrypto(); + + // Checks whether details.serverCertificateChain can be trusted + // for details.serverHostname according to the trust + // settings of the platform. + // Note: The actual behavior of the trust verification is not fully + // specified and might change in the future. + // The API implementation verifies certificate expiration, validates the + // certification path and checks trust by a known CA. + // The implementation is supposed to respect the EKU serverAuth and to + // support subject alternative names. + static void verifyTLSServerCertificate(VerificationDetails details, + VerificationCallback callback); }; }; diff --git a/chrome/test/data/extensions/api_test/platform_keys/basic.js b/chrome/test/data/extensions/api_test/platform_keys/basic.js index e874671..3790e42 100644 --- a/chrome/test/data/extensions/api_test/platform_keys/basic.js +++ b/chrome/test/data/extensions/api_test/platform_keys/basic.js @@ -11,6 +11,7 @@ console.log('[SELECTED TEST SUITE] ' + selectedTestSuite + var assertEq = chrome.test.assertEq; var assertTrue = chrome.test.assertTrue; +var assertFalse = chrome.test.assertFalse; var fail = chrome.test.fail; var succeed = chrome.test.succeed; var callbackPass = chrome.test.callbackPass; @@ -19,6 +20,20 @@ var callbackFail= chrome.test.callbackFail; // Each value is the path to a file in this extension's folder that will be // loaded and replaced by a Uint8Array in the setUp() function below. var data = { + // X.509 certificate in DER encoding issued by 'root.pem' which is set to be + // trusted by the test setup. + // Generated by create_test_certs.sh . + trusted_l1_leaf_cert: 'l1_leaf.der', + + // X.509 intermediate CA certificate in DER encoding issued by 'root.pem' + // which is set to be trusted by the test setup. + // Generated by create_test_certs.sh . + trusted_l1_interm_cert: 'l1_interm.der', + + // X.509 certificate in DER encoding issued by 'l1_interm'. + // Generated by create_test_certs.sh . + trusted_l2_leaf_cert: 'l2_leaf.der', + // X.509 client certificate in DER encoding. // Algorithm in SPKI: rsaEncryption. // openssl x509 -in net/data/ssl/certificates/client_1.pem -outform DER -out @@ -453,6 +468,45 @@ function testBackgroundInteractiveSelect() { })); } +function testVerifyTrusted() { + var details = { + serverCertificateChain: [data.trusted_l1_leaf_cert.buffer], + hostname: "l1_leaf" + }; + chrome.platformKeys.verifyTLSServerCertificate( + details, callbackPass(function(result) { assertTrue(result.trusted); })); +} + +function testVerifyTrustedChain() { + var details = { + serverCertificateChain: + [data.trusted_l2_leaf_cert.buffer, data.trusted_l1_interm_cert.buffer], + hostname: "l2_leaf" + }; + chrome.platformKeys.verifyTLSServerCertificate( + details, callbackPass(function(result) { assertTrue(result.trusted); })); +} + +function testVerifyCommonNameInvalid() { + var details = { + serverCertificateChain: + [data.trusted_l2_leaf_cert.buffer, data.trusted_l1_interm_cert.buffer], + // Use any hostname not matching the common name 'l2_leaf' of the cert. + hostname: "abc.example" + }; + chrome.platformKeys.verifyTLSServerCertificate( + details, callbackPass(function(result) { assertFalse(result.trusted); })); +} + +function testVerifyUntrusted() { + var details = { + serverCertificateChain: [data.client_1.buffer], + hostname: "127.0.0.1" + }; + chrome.platformKeys.verifyTLSServerCertificate( + details, callbackPass(function(result) { assertFalse(result.trusted); })); +} + var testSuites = { // These tests assume already granted permissions for client_1 and client_2. // On interactive selectClientCertificates calls, the simulated user does not @@ -473,6 +527,10 @@ var testSuites = { testGetKeyPair, testSignNoHash, testSignSha1Client1, + testVerifyTrusted, + testVerifyTrustedChain, + testVerifyCommonNameInvalid, + testVerifyUntrusted, ]; chrome.test.runTests(tests); diff --git a/chrome/test/data/extensions/api_test/platform_keys/ca.cnf b/chrome/test/data/extensions/api_test/platform_keys/ca.cnf new file mode 100644 index 0000000..ddac803 --- /dev/null +++ b/chrome/test/data/extensions/api_test/platform_keys/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/platform_keys/create_test_certs.sh b/chrome/test/data/extensions/api_test/platform_keys/create_test_certs.sh new file mode 100755 index 0000000..85c67f4 --- /dev/null +++ b/chrome/test/data/extensions/api_test/platform_keys/create_test_certs.sh @@ -0,0 +1,86 @@ +#!/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) +# \ +# \----> l1_interm --> l2_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). +issue_cert() { + if [[ "$2" == "ca_cert" ]] + then + try /bin/sh -c "echo 01 > out/${1}-serial" + try touch out/${1}-index.txt + try openssl genrsa -out out/${1}.key 2048 + 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 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 + +CA_ID=root CN=l1_interm \ + try issue_cert l1_interm ca_cert + +CA_ID=l1_interm CN=l2_leaf \ + try issue_cert l2_leaf leaf_cert diff --git a/chrome/test/data/extensions/api_test/platform_keys/l1_interm.der b/chrome/test/data/extensions/api_test/platform_keys/l1_interm.der new file mode 100644 index 0000000..f56ea48 Binary files /dev/null and b/chrome/test/data/extensions/api_test/platform_keys/l1_interm.der differ diff --git a/chrome/test/data/extensions/api_test/platform_keys/l1_leaf.der b/chrome/test/data/extensions/api_test/platform_keys/l1_leaf.der new file mode 100644 index 0000000..358380c Binary files /dev/null and b/chrome/test/data/extensions/api_test/platform_keys/l1_leaf.der differ diff --git a/chrome/test/data/extensions/api_test/platform_keys/l2_leaf.der b/chrome/test/data/extensions/api_test/platform_keys/l2_leaf.der new file mode 100644 index 0000000..0477436 Binary files /dev/null and b/chrome/test/data/extensions/api_test/platform_keys/l2_leaf.der differ diff --git a/chrome/test/data/extensions/api_test/platform_keys/root.pem b/chrome/test/data/extensions/api_test/platform_keys/root.pem new file mode 100644 index 0000000..8681990 --- /dev/null +++ b/chrome/test/data/extensions/api_test/platform_keys/root.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIJAPzptz/6abHIMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNV +BAMMBHJvb3QwHhcNMTUwNTE5MDkzMTQwWhcNMjUwNTE2MDkzMTQwWjAPMQ0wCwYD +VQQDDARyb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqFm0ruJH +FcsiLRY+j1LaW2StdCyKSG0LVqb4RYrzuVi1rPbAC9+L9QP1zQL8iwQSGplpvUR6 +lZW2ykL9OgUroje6BQYl9RkUyHF3a3iG5wqHB1ileUmu69+fcCl1ohUfwgWW3QWf +Kglp1wyThOOaiOclV2K9e/JxEoq8Ng6BvBE4coGr7tHQxajbcjoDkyMC+yXneghn +yyeNS1qZ+kY3Aw3KQVc+wJ5EyuErM3ARDaT1fVOfiyS2kbpRCqFlhYGdCFp8ebuk +PY7FJLFvmPp8A41bEqLXgJ4l9ynGHqyYmf+n7PKg6HChRnWGIrha2SsPaIfDX8Sh +Cf8vaxR3KPAFMwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT +t0RZWqsOg9uMi/BtiODSFRcV2DAfBgNVHSMEGDAWgBRTt0RZWqsOg9uMi/BtiODS +FRcV2DAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFX09PBL4l+T +Wp1GjA/oVYtF8D6ZJ0w9+5bcTk8QlwBbNjKa55b6q6vzyx02kXsldJzcQjMoWWLg +YQ+c1tngd6UoMEdQzGnc93WR2DDNXEZgEGbFfydxRx6PHNtGWN5vRSwANScCG6NA +PEcIDOT3gZ9sXaogYqzwXwpc0bv3r6Ema9ZPO7QzCVnqJVFyDXHdGDSSZ55jDAep +yqTSzGLalPWTfkqCULIzRtOT6aAtYUtbNNp3XbxOtw8EMxfBvmD5Rniy0yw43KMI +raG5YyEDaV72xTKpYs/UDImvT7LgS0XFRP7jH5agqjKkCLy1gBYnue0BAI7qusWA +3CqAWWi3btw= +-----END CERTIFICATE----- diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 8a7081c..e183baf 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1099,6 +1099,7 @@ enum HistogramValue { PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTION, PASSWORDSPRIVATE_GETPLAINTEXTPASSWORD, LAUNCHERPAGE_HIDE, + PLATFORMKEYS_VERIFYTLSSERVERCERTIFICATE, // Last entry: Add new entries above and ensure to update // tools/metrics/histograms/histograms.xml. ENUM_BOUNDARY diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index be22885..fcbafcc 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -52891,6 +52891,7 @@ Therefore, the affected-histogram name has to have at least one dot in it. + -- cgit v1.1