summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpneubeck <pneubeck@chromium.org>2015-05-23 05:13:20 -0700
committerCommit bot <commit-bot@chromium.org>2015-05-23 12:13:51 +0000
commit21924fdd22b19efc25827a250237deb7928e6f11 (patch)
tree539d0bdfbb08d4e59da0beeebd98e28dbd93939f
parent7f68f8735355c1c73557c3cfb294b441901fc31d (diff)
downloadchromium_src-21924fdd22b19efc25827a250237deb7928e6f11.zip
chromium_src-21924fdd22b19efc25827a250237deb7928e6f11.tar.gz
chromium_src-21924fdd22b19efc25827a250237deb7928e6f11.tar.bz2
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}
-rw-r--r--chrome/browser/extensions/api/platform_keys/platform_keys_api.cc50
-rw-r--r--chrome/browser/extensions/api/platform_keys/platform_keys_api.h15
-rw-r--r--chrome/browser/extensions/api/platform_keys/platform_keys_apitest_nss.cc16
-rw-r--r--chrome/browser/extensions/api/platform_keys/verify_trust_api.cc223
-rw-r--r--chrome/browser/extensions/api/platform_keys/verify_trust_api.h111
-rw-r--r--chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc7
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/common/extensions/api/platform_keys.idl32
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/basic.js58
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/ca.cnf57
-rwxr-xr-xchrome/test/data/extensions/api_test/platform_keys/create_test_certs.sh86
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/l1_interm.derbin0 -> 773 bytes
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/l1_leaf.derbin0 -> 783 bytes
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/l2_leaf.derbin0 -> 788 bytes
-rw-r--r--chrome/test/data/extensions/api_test/platform_keys/root.pem19
-rw-r--r--extensions/browser/extension_function_histogram_value.h1
-rw-r--r--tools/metrics/histograms/histograms.xml1
17 files changed, 672 insertions, 6 deletions
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<char>& cert_der = params->certificate;
if (cert_der.empty())
- return RespondNow(Error(kErrorInvalidX509Cert));
+ return RespondNow(Error(platform_keys::kErrorInvalidX509Cert));
scoped_refptr<net::X509Certificate> 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<net::CertificateList> 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<char>(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<api_pk::VerifyTLSServerCertificate::Params> 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<scoped_refptr<X509Certificate>> 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 <algorithm>
+
+#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<BrowserContextKeyedAPIFactory<VerifyTrustAPI>>::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> 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<net::CertVerifier::Request> request;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RequestState);
+ };
+
+ // Calls back |callback| with the result and no error.
+ void CallBackWithResult(const VerifyCallback& callback,
+ scoped_ptr<net::CertVerifyResult> 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<std::string, linked_ptr<net::CertVerifier>> extension_to_verifier_;
+};
+
+// static
+BrowserContextKeyedAPIFactory<VerifyTrustAPI>*
+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> 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(&params), 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> 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<base::StringPiece> der_cert_chain;
+ for (const std::vector<char>& 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<const char*>(vector_as_array(&cert_der)),
+ cert_der.size()));
+ }
+ scoped_refptr<net::X509Certificate> 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<net::CertVerifyResult> verify_result(new net::CertVerifyResult);
+ scoped_ptr<net::BoundNetLog> 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<void(int)> 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<net::CertVerifyResult> 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 <string>
+
+#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<void(const std::string& error, int result)>;
+ 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> 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<VerifyTrustAPI>* GetFactoryInstance();
+
+ protected:
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsCreatedWithBrowserContext = false;
+ static const bool kServiceIsNULLWhileTesting = true;
+
+ private:
+ class IOPart;
+ friend class BrowserContextKeyedAPIFactory<VerifyTrustAPI>;
+
+ // 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<IOPart, content::BrowserThread::DeleteOnIOThread> io_part_;
+
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ registry_observer_;
+
+ base::WeakPtrFactory<VerifyTrustAPI> 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 <code>serverCertificateChain</code>.
+ 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 <code>interactive</code> 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 <code>request</code> 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 <code>details.serverCertificateChain</code> can be trusted
+ // for <code>details.serverHostname</code> 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
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/platform_keys/l1_interm.der
Binary files 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
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/platform_keys/l1_leaf.der
Binary files 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
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/platform_keys/l2_leaf.der
Binary files 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.
<int value="1038" label="PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTION"/>
<int value="1039" label="PASSWORDSPRIVATE_GETPLAINTEXTPASSWORD"/>
<int value="1040" label="LAUNCHERPAGE_HIDE"/>
+ <int value="1041" label="PLATFORMKEYS_VERIFYTLSSERVERCERTIFICATE"/>
</enum>
<enum name="ExtensionInstallCause" type="int">