summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/platform_keys/OWNERS1
-rw-r--r--chrome/browser/chromeos/platform_keys/platform_keys.h110
-rw-r--r--chrome/browser/chromeos/platform_keys/platform_keys_nss.cc563
-rw-r--r--chrome/browser/extensions/api/enterprise_platform_keys/OWNERS1
-rw-r--r--chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc237
-rw-r--r--chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h107
-rw-r--r--chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc222
-rw-r--r--chrome/chrome_browser_chromeos.gypi2
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_renderer.gypi1
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/extensions/api/_api_features.json9
-rw-r--r--chrome/common/extensions/api/_permission_features.json6
-rw-r--r--chrome/common/extensions/api/api.gyp2
-rw-r--r--chrome/common/extensions/api/enterprise_platform_keys.idl78
-rw-r--r--chrome/common/extensions/api/enterprise_platform_keys_internal.idl50
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc1
-rw-r--r--chrome/common/extensions/permissions/permission_set_unittest.cc4
-rw-r--r--chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc14
-rw-r--r--chrome/renderer/resources/extensions/OWNERS1
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/OWNERS1
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/internal_api.js11
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/key.js66
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/key_pair.js35
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/subtle_crypto.js144
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/token.js19
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys/utils.js16
-rw-r--r--chrome/renderer/resources/extensions/enterprise_platform_keys_custom_bindings.js32
-rw-r--r--chrome/renderer/resources/renderer_resources.grd7
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys.crxbin0 -> 6002 bytes
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys.pem28
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys/OWNERS1
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.html6
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.js394
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys/manifest.json10
-rw-r--r--chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml12
-rw-r--r--extensions/browser/extension_function_histogram_value.h6
-rw-r--r--extensions/common/permissions/api_permission.h1
-rw-r--r--net/cert/nss_cert_database.cc107
-rw-r--r--net/cert/nss_cert_database.h41
-rw-r--r--net/cert/nss_cert_database_chromeos.cc2
-rw-r--r--tools/metrics/histograms/histograms.xml6
42 files changed, 2325 insertions, 33 deletions
diff --git a/chrome/browser/chromeos/platform_keys/OWNERS b/chrome/browser/chromeos/platform_keys/OWNERS
new file mode 100644
index 0000000..713045b
--- /dev/null
+++ b/chrome/browser/chromeos/platform_keys/OWNERS
@@ -0,0 +1 @@
+pneubeck@chromium.org
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys.h b/chrome/browser/chromeos/platform_keys/platform_keys.h
new file mode 100644
index 0000000..de27fb4
--- /dev/null
+++ b/chrome/browser/chromeos/platform_keys/platform_keys.h
@@ -0,0 +1,110 @@
+// Copyright 2014 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_PLATFORM_KEYS_PLATFORM_KEYS_H_
+#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_PLATFORM_KEYS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+
+class Profile;
+
+namespace net {
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+}
+
+namespace chromeos {
+
+namespace platform_keys {
+
+// If the generation was successful, |public_key_spki_der| will contain the DER
+// encoding of the SubjectPublicKeyInfo of the generated key and |error_message|
+// will be empty. If it failed, |public_key_spki_der| will be empty and
+// |error_message| contain an error message.
+typedef base::Callback<void(const std::string& public_key_spki_der,
+ const std::string& error_message)>
+ GenerateKeyCallback;
+
+// Generates a RSA key with |modulus_length|. |token_id| is currently ignored,
+// instead the user token associated with |profile| is always used. |callback|
+// will be invoked with the resulting public key or an error.
+void GenerateRSAKey(const std::string& token_id,
+ unsigned int modulus_length,
+ const GenerateKeyCallback& callback,
+ Profile* profile);
+
+// If signing was successful, |signature| will be contain the signature and
+// |error_message| will be empty. If it failed, |signature| will be empty and
+// |error_message| contain an error message.
+typedef base::Callback<void(const std::string& signature,
+ const std::string& error_message)> SignCallback;
+
+// Signs |data| with the private key matching |public_key|, if that key is
+// stored in the given token. |token_id| is currently ignored, instead the user
+// token associated with |profile| is always used. |public_key| must be the DER
+// encoding of a SubjectPublicKeyInfo. |callback| will be invoked with the
+// signature or an error message.
+// Currently supports RSA keys only.
+void Sign(const std::string& token_id,
+ const std::string& public_key,
+ const std::string& data,
+ const SignCallback& callback,
+ Profile* profile);
+
+// If the list of certificates could be successfully retrieved, |certs| will
+// contain the list of available certificates (maybe empty) and |error_message|
+// will be empty. If an error occurred, |certs| will be empty and
+// |error_message| contain an error message.
+typedef base::Callback<void(scoped_ptr<net::CertificateList> certs,
+ const std::string& error_message)>
+ GetCertificatesCallback;
+
+// Returns the list of all certificates with stored private key available from
+// the given token. |token_id| is currently ignored, instead the user token
+// associated with |profile| is always used. |callback| will be invoked with
+// the list of available certificates or an error message.
+void GetCertificates(const std::string& token_id,
+ const GetCertificatesCallback& callback,
+ Profile* profile);
+
+// If an error occurred during import, |error_message| will be set to an error
+// message.
+typedef base::Callback<void(const std::string& error_message)>
+ ImportCertificateCallback;
+
+// Imports |certificate| to the given token if the certified key is already
+// stored in this token. Any intermediate of |certificate| will be ignored.
+// |token_id| is currently ignored, instead the user token associated with
+// |profile| is always used. |callback| will be invoked when the import is
+// finished, possibly with an error message.
+void ImportCertificate(const std::string& token_id,
+ scoped_refptr<net::X509Certificate> certificate,
+ const ImportCertificateCallback& callback,
+ Profile* profile);
+
+// If an error occurred during removal, |error_message| will be set to an error
+// message.
+typedef base::Callback<void(const std::string& error_message)>
+ RemoveCertificateCallback;
+
+// Removes |certificate| from the given token if present. Any intermediate of
+// |certificate| will be ignored. |token_id| is currently ignored, instead the
+// user token associated with |profile| is always used. |callback| will be
+// invoked when the removal is finished, possibly with an error message.
+void RemoveCertificate(const std::string& token_id,
+ scoped_refptr<net::X509Certificate> certificate,
+ const RemoveCertificateCallback& callback,
+ Profile* profile);
+
+} // namespace platform_keys
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_PLATFORM_KEYS_H_
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_nss.cc b/chrome/browser/chromeos/platform_keys/platform_keys_nss.cc
new file mode 100644
index 0000000..5493e18
--- /dev/null
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_nss.cc
@@ -0,0 +1,563 @@
+// Copyright 2014 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/platform_keys/platform_keys.h"
+
+#include <cryptohi.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/worker_pool.h"
+#include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
+#include "chrome/browser/net/nss_context.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/crypto_module.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_database.h"
+#include "net/cert/nss_cert_database.h"
+#include "net/cert/x509_certificate.h"
+
+using content::BrowserThread;
+
+namespace {
+const char kErrorInternal[] = "Internal Error.";
+const char kErrorKeyNotFound[] = "Key not found.";
+const char kErrorCertificateNotFound[] = "Certificate could not be found.";
+const char kErrorAlgorithmNotSupported[] = "Algorithm not supported.";
+
+// The current maximal RSA modulus length that ChromeOS's TPM supports for key
+// generation.
+const unsigned int kMaxRSAModulusLength = 2048;
+}
+
+namespace chromeos {
+
+namespace platform_keys {
+
+namespace {
+
+// Base class to store state that is common to all NSS database operations and
+// to provide convenience methods to call back.
+// Keeps track of the originating task runner.
+class NSSOperationState {
+ public:
+ NSSOperationState();
+ virtual ~NSSOperationState() {}
+
+ // Called if an error occurred during the execution of the NSS operation
+ // described by this object.
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) = 0;
+
+ crypto::ScopedPK11Slot slot_;
+
+ // The task runner on which the NSS operation was called. Any reply must be
+ // posted to this runner.
+ scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NSSOperationState);
+};
+
+typedef base::Callback<void(net::NSSCertDatabase* cert_db)> GetCertDBCallback;
+
+// Called back with the NSSCertDatabase associated to the given |token_id|.
+// Calls |callback| if the database was successfully retrieved. Used by
+// GetCertDatabaseOnIOThread.
+void DidGetCertDBOnIOThread(const GetCertDBCallback& callback,
+ NSSOperationState* state,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ if (!cert_db) {
+ LOG(ERROR) << "Couldn't get NSSCertDatabase.";
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+
+ state->slot_ = cert_db->GetPrivateSlot();
+ if (!state->slot_) {
+ LOG(ERROR) << "No private slot";
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+
+ callback.Run(cert_db);
+}
+
+// Retrieves the NSSCertDatabase from |context|. Must be called on the IO
+// thread.
+void GetCertDatabaseOnIOThread(content::ResourceContext* context,
+ const GetCertDBCallback& callback,
+ NSSOperationState* state) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
+ context, base::Bind(&DidGetCertDBOnIOThread, callback, state));
+
+ if (cert_db)
+ DidGetCertDBOnIOThread(callback, state, cert_db);
+}
+
+// Asynchronously fetches the NSSCertDatabase and PK11Slot for |token_id|.
+// Stores the slot in |state| and passes the database to |callback|. Will run
+// |callback| on the IO thread.
+void GetCertDatabase(const std::string& token_id,
+ const GetCertDBCallback& callback,
+ Profile* profile,
+ NSSOperationState* state) {
+ // TODO(pneubeck): Decide which DB to retrieve depending on |token_id|.
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&GetCertDatabaseOnIOThread,
+ profile->GetResourceContext(),
+ callback,
+ state));
+}
+
+class GenerateRSAKeyState : public NSSOperationState {
+ public:
+ GenerateRSAKeyState(unsigned int modulus_length,
+ const GenerateKeyCallback& callback);
+ virtual ~GenerateRSAKeyState() {}
+
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) OVERRIDE {
+ CallBack(from, std::string() /* no public key */, error_message);
+ }
+
+ void CallBack(const tracked_objects::Location& from,
+ const std::string& public_key_spki_der,
+ const std::string& error_message) {
+ origin_task_runner_->PostTask(
+ from, base::Bind(callback_, public_key_spki_der, error_message));
+ }
+
+ unsigned int modulus_length_;
+
+ private:
+ // Must be called on origin thread, use CallBack() therefore.
+ GenerateKeyCallback callback_;
+};
+
+class SignState : public NSSOperationState {
+ public:
+ SignState(const std::string& public_key,
+ const std::string& data,
+ const SignCallback& callback);
+ virtual ~SignState() {}
+
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) OVERRIDE {
+ CallBack(from, std::string() /* no signature */, error_message);
+ }
+
+ void CallBack(const tracked_objects::Location& from,
+ const std::string& signature,
+ const std::string& error_message) {
+ origin_task_runner_->PostTask(
+ from, base::Bind(callback_, signature, error_message));
+ }
+
+ std::string public_key_;
+ std::string data_;
+
+ private:
+ // Must be called on origin thread, use CallBack() therefore.
+ SignCallback callback_;
+};
+
+class GetCertificatesState : public NSSOperationState {
+ public:
+ explicit GetCertificatesState(const GetCertificatesCallback& callback);
+ virtual ~GetCertificatesState() {}
+
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) OVERRIDE {
+ CallBack(from,
+ scoped_ptr<net::CertificateList>() /* no certificates */,
+ error_message);
+ }
+
+ void CallBack(const tracked_objects::Location& from,
+ scoped_ptr<net::CertificateList> certs,
+ const std::string& error_message) {
+ origin_task_runner_->PostTask(
+ from, base::Bind(callback_, base::Passed(&certs), error_message));
+ }
+
+ scoped_ptr<net::CertificateList> certs_;
+
+ private:
+ // Must be called on origin thread, use CallBack() therefore.
+ GetCertificatesCallback callback_;
+};
+
+class ImportCertificateState : public NSSOperationState {
+ public:
+ ImportCertificateState(scoped_refptr<net::X509Certificate> certificate,
+ const ImportCertificateCallback& callback);
+ virtual ~ImportCertificateState() {}
+
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) OVERRIDE {
+ CallBack(from, error_message);
+ }
+
+ void CallBack(const tracked_objects::Location& from,
+ const std::string& error_message) {
+ origin_task_runner_->PostTask(from, base::Bind(callback_, error_message));
+ }
+
+ scoped_refptr<net::X509Certificate> certificate_;
+
+ private:
+ // Must be called on origin thread, use CallBack() therefore.
+ ImportCertificateCallback callback_;
+};
+
+class RemoveCertificateState : public NSSOperationState {
+ public:
+ RemoveCertificateState(scoped_refptr<net::X509Certificate> certificate,
+ const RemoveCertificateCallback& callback);
+ virtual ~RemoveCertificateState() {}
+
+ virtual void OnError(const tracked_objects::Location& from,
+ const std::string& error_message) OVERRIDE {
+ CallBack(from, error_message);
+ }
+
+ void CallBack(const tracked_objects::Location& from,
+ const std::string& error_message) {
+ origin_task_runner_->PostTask(from, base::Bind(callback_, error_message));
+ }
+
+ scoped_refptr<net::X509Certificate> certificate_;
+
+ private:
+ // Must be called on origin thread, use CallBack() therefore.
+ RemoveCertificateCallback callback_;
+};
+
+NSSOperationState::NSSOperationState()
+ : origin_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+}
+
+GenerateRSAKeyState::GenerateRSAKeyState(unsigned int modulus_length,
+ const GenerateKeyCallback& callback)
+ : modulus_length_(modulus_length), callback_(callback) {
+}
+
+SignState::SignState(const std::string& public_key,
+ const std::string& data,
+ const SignCallback& callback)
+ : public_key_(public_key), data_(data), callback_(callback) {
+}
+
+GetCertificatesState::GetCertificatesState(
+ const GetCertificatesCallback& callback)
+ : callback_(callback) {
+}
+
+ImportCertificateState::ImportCertificateState(
+ scoped_refptr<net::X509Certificate> certificate,
+ const ImportCertificateCallback& callback)
+ : certificate_(certificate), callback_(callback) {
+}
+
+RemoveCertificateState::RemoveCertificateState(
+ scoped_refptr<net::X509Certificate> certificate,
+ const RemoveCertificateCallback& callback)
+ : certificate_(certificate), callback_(callback) {
+}
+
+// Does the actual key generation on a worker thread. Used by
+// GenerateRSAKeyWithDB().
+void GenerateRSAKeyOnWorkerThread(scoped_ptr<GenerateRSAKeyState> state) {
+ scoped_ptr<crypto::RSAPrivateKey> rsa_key(
+ crypto::RSAPrivateKey::CreateSensitive(state->slot_.get(),
+ state->modulus_length_));
+ if (!rsa_key) {
+ LOG(ERROR) << "Couldn't create key.";
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+
+ std::vector<uint8> public_key_spki_der;
+ if (!rsa_key->ExportPublicKey(&public_key_spki_der)) {
+ // TODO(pneubeck): Remove rsa_key from storage.
+ LOG(ERROR) << "Couldn't export public key.";
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+ state->CallBack(
+ FROM_HERE,
+ std::string(public_key_spki_der.begin(), public_key_spki_der.end()),
+ std::string() /* no error */);
+}
+
+// Continues generating a RSA key with the obtained NSSCertDatabase. Used by
+// GenerateRSAKey().
+void GenerateRSAKeyWithDB(scoped_ptr<GenerateRSAKeyState> state,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&GenerateRSAKeyOnWorkerThread, base::Passed(&state)),
+ true /*task is slow*/);
+}
+
+// Does the actual signing on a worker thread. Used by RSASignWithDB().
+void RSASignOnWorkerThread(scoped_ptr<SignState> state) {
+ const uint8* public_key_uint8 =
+ reinterpret_cast<const uint8*>(state->public_key_.data());
+ std::vector<uint8> public_key_vector(
+ public_key_uint8, public_key_uint8 + state->public_key_.size());
+
+ // TODO(pneubeck): This searches all slots. Change to look only at |slot_|.
+ scoped_ptr<crypto::RSAPrivateKey> rsa_key(
+ crypto::RSAPrivateKey::FindFromPublicKeyInfo(public_key_vector));
+ if (!rsa_key || rsa_key->key()->pkcs11Slot != state->slot_) {
+ state->OnError(FROM_HERE, kErrorKeyNotFound);
+ return;
+ }
+
+ SECItem sign_result = {siBuffer, NULL, 0};
+ if (SEC_SignData(&sign_result,
+ reinterpret_cast<const unsigned char*>(state->data_.data()),
+ state->data_.size(),
+ rsa_key->key(),
+ SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION) != SECSuccess) {
+ LOG(ERROR) << "Couldn't sign.";
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+
+ std::string signature(reinterpret_cast<const char*>(sign_result.data),
+ sign_result.len);
+ state->CallBack(FROM_HERE, signature, std::string() /* no error */);
+}
+
+// Continues signing with the obtained NSSCertDatabase. Used by Sign().
+void RSASignWithDB(scoped_ptr<SignState> state, net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&RSASignOnWorkerThread, base::Passed(&state)),
+ true /*task is slow*/);
+}
+
+// Filters the obtained certificates on a worker thread. Used by
+// DidGetCertificates().
+void FilterCertificatesOnWorkerThread(scoped_ptr<GetCertificatesState> state) {
+ scoped_ptr<net::CertificateList> client_certs(new net::CertificateList);
+ for (net::CertificateList::const_iterator it = state->certs_->begin();
+ it != state->certs_->end();
+ ++it) {
+ net::X509Certificate::OSCertHandle cert_handle = (*it)->os_cert_handle();
+ crypto::ScopedPK11Slot cert_slot(PK11_KeyForCertExists(cert_handle,
+ NULL, // keyPtr
+ NULL)); // wincx
+
+ // Keep only user certificates, i.e. certs for which the private key is
+ // present and stored in the queried slot.
+ if (cert_slot != state->slot_)
+ continue;
+
+ client_certs->push_back(*it);
+ }
+
+ state->CallBack(FROM_HERE, client_certs.Pass(), std::string() /* no error */);
+}
+
+// Passes the obtained certificates to the worker thread for filtering. Used by
+// GetCertificatesWithDB().
+void DidGetCertificates(scoped_ptr<GetCertificatesState> state,
+ scoped_ptr<net::CertificateList> all_certs) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ state->certs_ = all_certs.Pass();
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&FilterCertificatesOnWorkerThread, base::Passed(&state)),
+ true /*task is slow*/);
+}
+
+// Continues getting certificates with the obtained NSSCertDatabase. Used by
+// GetCertificates().
+void GetCertificatesWithDB(scoped_ptr<GetCertificatesState> state,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Get the pointer to slot before base::Passed releases |state|.
+ PK11SlotInfo* slot = state->slot_.get();
+ cert_db->ListCertsInSlot(
+ base::Bind(&DidGetCertificates, base::Passed(&state)), slot);
+}
+
+// Does the actual certificate importing on the IO thread. Used by
+// ImportCertificate().
+void ImportCertificateWithDB(scoped_ptr<ImportCertificateState> state,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // TODO(pneubeck): Use |state->slot_| to verify that we're really importing to
+ // the correct token.
+ // |cert_db| is not required, ignore it.
+ net::CertDatabase* db = net::CertDatabase::GetInstance();
+
+ const net::Error cert_status =
+ static_cast<net::Error>(db->CheckUserCert(state->certificate_));
+ if (cert_status == net::ERR_NO_PRIVATE_KEY_FOR_CERT) {
+ state->OnError(FROM_HERE, kErrorKeyNotFound);
+ return;
+ } else if (cert_status != net::OK) {
+ state->OnError(FROM_HERE, net::ErrorToString(cert_status));
+ return;
+ }
+
+ const net::Error import_status =
+ static_cast<net::Error>(db->AddUserCert(state->certificate_.get()));
+ if (import_status != net::OK) {
+ LOG(ERROR) << "Could not import certificate.";
+ state->OnError(FROM_HERE, net::ErrorToString(import_status));
+ return;
+ }
+
+ state->CallBack(FROM_HERE, std::string() /* no error */);
+}
+
+// Called on IO thread after the certificate removal is finished.
+void DidRemoveCertificate(scoped_ptr<RemoveCertificateState> state,
+ bool certificate_found,
+ bool success) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // CertificateNotFound error has precedence over an internal error.
+ if (!certificate_found) {
+ state->OnError(FROM_HERE, kErrorCertificateNotFound);
+ return;
+ }
+ if (!success) {
+ state->OnError(FROM_HERE, kErrorInternal);
+ return;
+ }
+
+ state->CallBack(FROM_HERE, std::string() /* no error */);
+}
+
+// Does the actual certificate removal on the IO thread. Used by
+// RemoveCertificate().
+void RemoveCertificateWithDB(scoped_ptr<RemoveCertificateState> state,
+ net::NSSCertDatabase* cert_db) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Get the pointer before base::Passed clears |state|.
+ scoped_refptr<net::X509Certificate> certificate = state->certificate_;
+ bool certificate_found = certificate->os_cert_handle()->isperm;
+ cert_db->DeleteCertAndKeyAsync(
+ certificate,
+ base::Bind(
+ &DidRemoveCertificate, base::Passed(&state), certificate_found));
+}
+
+} // namespace
+
+void GenerateRSAKey(const std::string& token_id,
+ unsigned int modulus_length,
+ const GenerateKeyCallback& callback,
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<GenerateRSAKeyState> state(
+ new GenerateRSAKeyState(modulus_length, callback));
+
+ if (modulus_length > kMaxRSAModulusLength) {
+ state->OnError(FROM_HERE, kErrorAlgorithmNotSupported);
+ return;
+ }
+
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+ GetCertDatabase(token_id,
+ base::Bind(&GenerateRSAKeyWithDB, base::Passed(&state)),
+ profile,
+ state_ptr);
+}
+
+void Sign(const std::string& token_id,
+ const std::string& public_key,
+ const std::string& data,
+ const SignCallback& callback,
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<SignState> state(new SignState(public_key, data, callback));
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+
+ // The NSSCertDatabase object is not required. But in case it's not available
+ // we would get more informative error messages and we can double check that
+ // we use a key of the correct token.
+ GetCertDatabase(token_id,
+ base::Bind(&RSASignWithDB, base::Passed(&state)),
+ profile,
+ state_ptr);
+}
+
+void GetCertificates(const std::string& token_id,
+ const GetCertificatesCallback& callback,
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<GetCertificatesState> state(new GetCertificatesState(callback));
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+ GetCertDatabase(token_id,
+ base::Bind(&GetCertificatesWithDB, base::Passed(&state)),
+ profile,
+ state_ptr);
+}
+
+void ImportCertificate(const std::string& token_id,
+ scoped_refptr<net::X509Certificate> certificate,
+ const ImportCertificateCallback& callback,
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<ImportCertificateState> state(
+ new ImportCertificateState(certificate, callback));
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+
+ // The NSSCertDatabase object is not required. But in case it's not available
+ // we would get more informative error messages and we can double check that
+ // we use a key of the correct token.
+ GetCertDatabase(token_id,
+ base::Bind(&ImportCertificateWithDB, base::Passed(&state)),
+ profile,
+ state_ptr);
+}
+
+void RemoveCertificate(const std::string& token_id,
+ scoped_refptr<net::X509Certificate> certificate,
+ const RemoveCertificateCallback& callback,
+ Profile* profile) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ scoped_ptr<RemoveCertificateState> state(
+ new RemoveCertificateState(certificate, callback));
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+
+ // The NSSCertDatabase object is not required. But in case it's not available
+ // we would get more informative error messages.
+ GetCertDatabase(token_id,
+ base::Bind(&RemoveCertificateWithDB, base::Passed(&state)),
+ profile,
+ state_ptr);
+}
+
+} // namespace platform_keys
+
+} // namespace chromeos
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/OWNERS b/chrome/browser/extensions/api/enterprise_platform_keys/OWNERS
new file mode 100644
index 0000000..713045b
--- /dev/null
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/OWNERS
@@ -0,0 +1 @@
+pneubeck@chromium.org
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc
new file mode 100644
index 0000000..cdf779c
--- /dev/null
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc
@@ -0,0 +1,237 @@
+// Copyright 2014 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/enterprise_platform_keys/enterprise_platform_keys_api.h"
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
+#include "chrome/common/extensions/api/enterprise_platform_keys.h"
+#include "chrome/common/extensions/api/enterprise_platform_keys_internal.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/cert/x509_certificate.h"
+
+namespace extensions {
+
+namespace {
+
+namespace api_epk = api::enterprise_platform_keys;
+namespace api_epki = api::enterprise_platform_keys_internal;
+
+// This error will occur if a token is removed and will be exposed to the
+// extension. Keep this in sync with the custom binding in Javascript.
+const char kErrorInvalidToken[] = "The token is not valid.";
+
+const char kErrorInvalidX509Cert[] =
+ "Certificate is not a valid X.509 certificate.";
+const char kTokenIdUser[] = "user";
+
+// Returns whether |token_id| references a known Token.
+bool ValidateToken(const std::string& token_id) {
+ // For now, the user token is the only valid one.
+ return token_id == kTokenIdUser;
+}
+
+} // namespace
+
+EnterprisePlatformKeysInternalGenerateKeyFunction::
+ ~EnterprisePlatformKeysInternalGenerateKeyFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysInternalGenerateKeyFunction::Run() {
+ scoped_ptr<api_epki::GenerateKey::Params> params(
+ api_epki::GenerateKey::Params::Create(*args_));
+ // TODO(pneubeck): Add support for unsigned integers to IDL.
+ EXTENSION_FUNCTION_VALIDATE(params && params->modulus_length >= 0);
+ if (!ValidateToken(params->token_id))
+ return RespondNow(Error(kErrorInvalidToken));
+
+ chromeos::platform_keys::GenerateRSAKey(
+ params->token_id,
+ params->modulus_length,
+ base::Bind(
+ &EnterprisePlatformKeysInternalGenerateKeyFunction::OnGeneratedKey,
+ this),
+ GetProfile());
+ return RespondLater();
+}
+
+void EnterprisePlatformKeysInternalGenerateKeyFunction::OnGeneratedKey(
+ const std::string& public_key_der,
+ const std::string& error_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (error_message.empty()) {
+ Respond(
+ ArgumentList(api_epki::GenerateKey::Results::Create(public_key_der)));
+ } else {
+ Respond(Error(error_message));
+ }
+}
+
+EnterprisePlatformKeysInternalSignFunction::
+ ~EnterprisePlatformKeysInternalSignFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysInternalSignFunction::Run() {
+ scoped_ptr<api_epki::Sign::Params> params(
+ api_epki::Sign::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!ValidateToken(params->token_id))
+ return RespondNow(Error(kErrorInvalidToken));
+
+ chromeos::platform_keys::Sign(
+ params->token_id,
+ params->public_key,
+ params->data,
+ base::Bind(&EnterprisePlatformKeysInternalSignFunction::OnSigned, this),
+ GetProfile());
+ return RespondLater();
+}
+
+void EnterprisePlatformKeysInternalSignFunction::OnSigned(
+ const std::string& signature,
+ const std::string& error_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (error_message.empty())
+ Respond(ArgumentList(api_epki::Sign::Results::Create(signature)));
+ else
+ Respond(Error(error_message));
+}
+
+EnterprisePlatformKeysGetCertificatesFunction::
+ ~EnterprisePlatformKeysGetCertificatesFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysGetCertificatesFunction::Run() {
+ scoped_ptr<api_epk::GetCertificates::Params> params(
+ api_epk::GetCertificates::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!ValidateToken(params->token_id))
+ return RespondNow(Error(kErrorInvalidToken));
+
+ chromeos::platform_keys::GetCertificates(
+ params->token_id,
+ base::Bind(
+ &EnterprisePlatformKeysGetCertificatesFunction::OnGotCertificates,
+ this),
+ GetProfile());
+ return RespondLater();
+}
+
+void EnterprisePlatformKeysGetCertificatesFunction::OnGotCertificates(
+ scoped_ptr<net::CertificateList> certs,
+ const std::string& error_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (!error_message.empty()) {
+ Respond(Error(error_message));
+ return;
+ }
+
+ scoped_ptr<base::ListValue> client_certs(new base::ListValue());
+ for (net::CertificateList::const_iterator it = certs->begin();
+ it != certs->end();
+ ++it) {
+ std::string der_encoding;
+ net::X509Certificate::GetDEREncoded((*it)->os_cert_handle(), &der_encoding);
+ client_certs->Append(base::BinaryValue::CreateWithCopiedBuffer(
+ der_encoding.data(), der_encoding.size()));
+ }
+
+ scoped_ptr<base::ListValue> results(new base::ListValue());
+ results->Append(client_certs.release());
+ Respond(ArgumentList(results.Pass()));
+}
+
+EnterprisePlatformKeysImportCertificateFunction::
+ ~EnterprisePlatformKeysImportCertificateFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysImportCertificateFunction::Run() {
+ scoped_ptr<api_epk::ImportCertificate::Params> params(
+ api_epk::ImportCertificate::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!ValidateToken(params->token_id))
+ return RespondNow(Error(kErrorInvalidToken));
+
+ const std::string& cert_der = params->certificate;
+ scoped_refptr<net::X509Certificate> cert_x509 =
+ net::X509Certificate::CreateFromBytes(cert_der.data(), cert_der.size());
+ if (!cert_x509)
+ return RespondNow(Error(kErrorInvalidX509Cert));
+
+ chromeos::platform_keys::ImportCertificate(
+ params->token_id,
+ cert_x509,
+ base::Bind(&EnterprisePlatformKeysImportCertificateFunction::
+ OnImportedCertificate,
+ this),
+ GetProfile());
+ return RespondLater();
+}
+
+void EnterprisePlatformKeysImportCertificateFunction::OnImportedCertificate(
+ const std::string& error_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (error_message.empty())
+ Respond(NoArguments());
+ else
+ Respond(Error(error_message));
+}
+
+EnterprisePlatformKeysRemoveCertificateFunction::
+ ~EnterprisePlatformKeysRemoveCertificateFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysRemoveCertificateFunction::Run() {
+ scoped_ptr<api_epk::RemoveCertificate::Params> params(
+ api_epk::RemoveCertificate::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ if (!ValidateToken(params->token_id))
+ return RespondNow(Error(kErrorInvalidToken));
+
+ const std::string& cert_der = params->certificate;
+ scoped_refptr<net::X509Certificate> cert_x509 =
+ net::X509Certificate::CreateFromBytes(cert_der.data(), cert_der.size());
+ if (!cert_x509)
+ return RespondNow(Error(kErrorInvalidX509Cert));
+
+ chromeos::platform_keys::RemoveCertificate(
+ params->token_id,
+ cert_x509,
+ base::Bind(&EnterprisePlatformKeysRemoveCertificateFunction::
+ OnRemovedCertificate,
+ this),
+ GetProfile());
+ return RespondLater();
+}
+
+void EnterprisePlatformKeysRemoveCertificateFunction::OnRemovedCertificate(
+ const std::string& error_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (error_message.empty())
+ Respond(NoArguments());
+ else
+ Respond(Error(error_message));
+}
+
+EnterprisePlatformKeysInternalGetTokensFunction::
+ ~EnterprisePlatformKeysInternalGetTokensFunction() {
+}
+
+ExtensionFunction::ResponseAction
+EnterprisePlatformKeysInternalGetTokensFunction::Run() {
+ EXTENSION_FUNCTION_VALIDATE(args_->empty());
+
+ std::vector<std::string> token_ids;
+ token_ids.push_back(kTokenIdUser);
+ return RespondNow(
+ ArgumentList(api_epki::GetTokens::Results::Create(token_ids)));
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h
new file mode 100644
index 0000000..4cfd43f
--- /dev/null
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h
@@ -0,0 +1,107 @@
+// Copyright 2014 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_ENTERPRISE_PLATFORM_KEYS_ENTERPRISE_PLATFORM_KEYS_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_ENTERPRISE_PLATFORM_KEYS_API_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/chrome_extension_function.h"
+
+namespace net {
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+}
+
+namespace extensions {
+
+class EnterprisePlatformKeysInternalGenerateKeyFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysInternalGenerateKeyFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ // Called when the key was generated. If an error occurred, |public_key_der|
+ // will be empty and instead |error_message| be set.
+ void OnGeneratedKey(const std::string& public_key_der,
+ const std::string& error_message);
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeysInternal.generateKey",
+ ENTERPRISE_PLATFORMKEYSINTERNAL_GENERATEKEY);
+};
+
+class EnterprisePlatformKeysInternalSignFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysInternalSignFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ // Called when the signature was generated. If an error occurred,
+ // |signature| will be empty and instead |error_message| be set.
+ void OnSigned(const std::string& signature, const std::string& error_message);
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeysInternal.sign",
+ ENTERPRISE_PLATFORMKEYSINTERNAL_SIGN);
+};
+
+class EnterprisePlatformKeysGetCertificatesFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysGetCertificatesFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ // Called when the list of certificates was determined. If an error occurred,
+ // |certs| will be NULL and instead |error_message| be set.
+ void OnGotCertificates(scoped_ptr<net::CertificateList> certs,
+ const std::string& error_message);
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.getCertificates",
+ ENTERPRISE_PLATFORMKEYS_GETCERTIFICATES);
+};
+
+class EnterprisePlatformKeysImportCertificateFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysImportCertificateFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ // Called when the certificate was imported. Only if an error occurred,
+ // |error_message| will be set.
+ void OnImportedCertificate(const std::string& error_message);
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.importCertificate",
+ ENTERPRISE_PLATFORMKEYS_IMPORTCERTIFICATE);
+};
+
+class EnterprisePlatformKeysRemoveCertificateFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysRemoveCertificateFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ // Called when the certificate was removed. Only if an error occurred,
+ // |error_message| will be set.
+ void OnRemovedCertificate(const std::string& error_message);
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.removeCertificate",
+ ENTERPRISE_PLATFORMKEYS_REMOVECERTIFICATE);
+};
+
+class EnterprisePlatformKeysInternalGetTokensFunction
+ : public ChromeUIThreadExtensionFunction {
+ private:
+ virtual ~EnterprisePlatformKeysInternalGetTokensFunction();
+ virtual ResponseAction Run() OVERRIDE;
+
+ DECLARE_EXTENSION_FUNCTION("enterprise.platformKeysInternal.getTokens",
+ ENTERPRISE_PLATFORMKEYSINTERNAL_GETTOKENS);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_ENTERPRISE_PLATFORM_KEYS_API_H_
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc
new file mode 100644
index 0000000..d37b977
--- /dev/null
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 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 <cryptohi.h>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/policy/user_network_configuration_updater.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/net/nss_context.h"
+#include "chrome/browser/net/url_request_mock_util.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 "content/public/browser/notification_service.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/test/net/url_request_mock_http_job.h"
+#include "crypto/nss_util.h"
+#include "net/base/net_errors.h"
+#include "net/cert/nss_cert_database.h"
+#include "policy/policy_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+// The test extension has a certificate referencing this private key.
+//
+// openssl genrsa > privkey.pem
+// openssl pkcs8 -inform pem -in privkey.pem -topk8
+// -outform der -out privkey8.der -nocrypt
+// xxd -i privkey8.der
+const unsigned char privateKeyPkcs8[] = {
+ 0x30, 0x82, 0x01, 0x55, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
+ 0x01, 0x3f, 0x30, 0x82, 0x01, 0x3b, 0x02, 0x01, 0x00, 0x02, 0x41, 0x00,
+ 0xc7, 0xc1, 0x4d, 0xd5, 0xdc, 0x3a, 0x2e, 0x1f, 0x42, 0x30, 0x3d, 0x21,
+ 0x1e, 0xa2, 0x1f, 0x60, 0xcb, 0x71, 0x11, 0x53, 0xb0, 0x75, 0xa0, 0x62,
+ 0xfe, 0x5e, 0x0a, 0xde, 0xb0, 0x0f, 0x48, 0x97, 0x5e, 0x42, 0xa7, 0x3a,
+ 0xd1, 0xca, 0x4c, 0xe3, 0xdb, 0x5f, 0x31, 0xc2, 0x99, 0x08, 0x89, 0xcd,
+ 0x6d, 0x20, 0xaa, 0x75, 0xe6, 0x2b, 0x98, 0xd2, 0xf3, 0x7b, 0x4b, 0xe5,
+ 0x9b, 0xfe, 0xe2, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x40, 0x4a,
+ 0xf5, 0x76, 0x10, 0xe7, 0xb8, 0x89, 0x70, 0x3f, 0x75, 0x3c, 0xab, 0x3e,
+ 0x04, 0x96, 0x83, 0xcb, 0x34, 0x1d, 0xcd, 0x6a, 0xed, 0x69, 0x07, 0x5c,
+ 0xee, 0xcb, 0x63, 0x6f, 0x6b, 0xfc, 0xcf, 0xee, 0xa2, 0xc4, 0x67, 0x05,
+ 0x68, 0x4d, 0x21, 0x7e, 0x3e, 0xde, 0x74, 0x72, 0xf8, 0x04, 0x35, 0x66,
+ 0x1e, 0x6b, 0x1d, 0xef, 0x77, 0xf7, 0x33, 0xf0, 0x35, 0xcf, 0x35, 0x6e,
+ 0x53, 0x3f, 0x9d, 0x02, 0x21, 0x00, 0xee, 0x48, 0x67, 0x1b, 0x24, 0x6e,
+ 0x3d, 0x7b, 0xa0, 0xc3, 0xee, 0x8a, 0x2e, 0xc7, 0xd0, 0xa1, 0xdb, 0x25,
+ 0x31, 0x12, 0x99, 0x43, 0x06, 0x3c, 0xb0, 0x80, 0x35, 0x2b, 0xf4, 0xc5,
+ 0xa2, 0xd3, 0x02, 0x21, 0x00, 0xd6, 0x9b, 0x8b, 0x75, 0x91, 0x52, 0xd4,
+ 0xf0, 0x76, 0xcf, 0xa2, 0xbe, 0xa6, 0xaf, 0x72, 0x6c, 0x52, 0xf9, 0xc9,
+ 0x0e, 0xea, 0x4a, 0x4c, 0xd2, 0xdf, 0x25, 0x70, 0xc6, 0x66, 0x35, 0x9d,
+ 0xbf, 0x02, 0x21, 0x00, 0xe8, 0x9e, 0x40, 0x21, 0xcc, 0x37, 0xde, 0xc7,
+ 0xd1, 0x13, 0x55, 0xcd, 0x0a, 0x8c, 0x40, 0xcd, 0xb1, 0xed, 0xa5, 0xf1,
+ 0x7d, 0x33, 0x64, 0x64, 0x5c, 0xfe, 0x5c, 0x6a, 0x34, 0x03, 0xb8, 0xc7,
+ 0x02, 0x20, 0x17, 0xe1, 0xb5, 0x52, 0x3e, 0xfa, 0xc5, 0xc1, 0x80, 0xa7,
+ 0x38, 0x88, 0x18, 0xca, 0x7b, 0x64, 0x3c, 0x93, 0x99, 0x61, 0x34, 0x87,
+ 0x52, 0x27, 0x41, 0x37, 0xcc, 0x65, 0xf7, 0xa7, 0xcd, 0xc7, 0x02, 0x21,
+ 0x00, 0x8a, 0x17, 0x7f, 0xf9, 0x45, 0xf3, 0xfd, 0xf7, 0x96, 0x62, 0xf3,
+ 0x7a, 0x09, 0xfb, 0xe9, 0x9e, 0xc7, 0x7a, 0x1f, 0x53, 0x1a, 0xb8, 0xd5,
+ 0x88, 0x9d, 0xd4, 0x79, 0x57, 0x88, 0x68, 0x72, 0x6f};
+
+const base::FilePath::CharType kTestExtensionDir[] =
+ FILE_PATH_LITERAL("extensions/api_test/enterprise_platform_keys");
+const base::FilePath::CharType kUpdateManifestFileName[] =
+ FILE_PATH_LITERAL("update_manifest.xml");
+
+// The managed_storage extension has a key defined in its manifest, so that
+// its extension ID is well-known and the policy system can push policies for
+// the extension.
+const char kTestExtensionID[] = "aecpbnckhoppanpmefllkdkohionpmig";
+
+void DidGetCertDatabase(base::RunLoop* loop, net::NSSCertDatabase* cert_db) {
+ // In order to use a prepared certificate, import a private key at first for
+ // which the Javscript test will import the certificate.
+ SECItem pki_der = {
+ siBuffer,
+ // NSS requires non-const data even though it is just for input.
+ const_cast<unsigned char*>((const unsigned char*)privateKeyPkcs8),
+ arraysize(privateKeyPkcs8)};
+
+ crypto::ScopedPK11Slot private_slot(cert_db->GetPrivateSlot());
+ SECKEYPrivateKey* seckey_private_key = NULL;
+ ASSERT_EQ(SECSuccess,
+ PK11_ImportDERPrivateKeyInfoAndReturnKey(private_slot.get(),
+ &pki_der,
+ NULL, // nickname
+ NULL, // publicValue
+ true, // isPerm
+ true, // isPrivate
+ KU_ALL, // usage
+ &seckey_private_key,
+ NULL));
+ loop->Quit();
+}
+
+class EnterprisePlatformKeysTest : public ExtensionApiTest {
+ public:
+ virtual void SetUp() {
+ policy::UserNetworkConfigurationUpdater::
+ SetSkipCertificateImporterCreationForTest(true /*skip*/);
+ ExtensionApiTest::SetUp();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+
+ // Enable the WebCrypto API.
+ command_line->AppendSwitch(
+ switches::kEnableExperimentalWebPlatformFeatures);
+ }
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+
+ EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+ .WillRepeatedly(testing::Return(true));
+ policy_provider_.SetAutoRefresh();
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+ &policy_provider_);
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionApiTest::SetUpOnMainThread();
+
+ // Enable the URLRequestMock, which is required for force-installing the
+ // test extension through policy.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(chrome_browser_net::SetUrlRequestMocksEnabled, true));
+
+ {
+ base::RunLoop loop;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&EnterprisePlatformKeysTest::SetupTestNSSDBOnIOThread,
+ base::Unretained(this),
+ &loop));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ GetNSSCertDatabaseForProfile(browser()->profile(),
+ base::Bind(&DidGetCertDatabase, &loop));
+ loop.Run();
+ }
+
+ SetPolicy();
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ base::RunLoop loop;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&EnterprisePlatformKeysTest::DeleteTestNSSDBOnIOThread,
+ base::Unretained(this),
+ &loop));
+ loop.Run();
+ ExtensionApiTest::CleanUpOnMainThread();
+ }
+
+ private:
+ void SetupTestNSSDBOnIOThread(base::RunLoop* loop) {
+ test_nssdb_.reset(new crypto::ScopedTestNSSDB);
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE, loop->QuitClosure());
+ }
+
+ void DeleteTestNSSDBOnIOThread(base::RunLoop* loop) {
+ test_nssdb_.reset();
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE, loop->QuitClosure());
+ }
+
+ void SetPolicy() {
+ // Extensions that are force-installed come from an update URL, which
+ // defaults to the webstore. Use a mock URL for this test with an update
+ // manifest that includes the crx file of the test extension.
+ base::FilePath update_manifest_path =
+ base::FilePath(kTestExtensionDir).Append(kUpdateManifestFileName);
+ GURL update_manifest_url(
+ content::URLRequestMockHTTPJob::GetMockUrl(update_manifest_path));
+
+ scoped_ptr<base::ListValue> forcelist(new base::ListValue);
+ forcelist->AppendString(base::StringPrintf(
+ "%s;%s", kTestExtensionID, update_manifest_url.spec().c_str()));
+
+ policy::PolicyMap policy;
+ policy.Set(policy::key::kExtensionInstallForcelist,
+ policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_MACHINE,
+ forcelist.release(),
+ NULL);
+
+ // Set the policy and wait until the extension is installed.
+ content::WindowedNotificationObserver observer(
+ chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
+ content::NotificationService::AllSources());
+ policy_provider_.UpdateChromePolicy(policy);
+ observer.Wait();
+ }
+
+ scoped_ptr<crypto::ScopedTestNSSDB> test_nssdb_;
+ policy::MockConfigurationPolicyProvider policy_provider_;
+};
+
+} // namespace
+
+IN_PROC_BROWSER_TEST_F(EnterprisePlatformKeysTest, Basic) {
+ ASSERT_TRUE(RunExtensionSubtest(
+ "",
+ base::StringPrintf("chrome-extension://%s/basic.html", kTestExtensionID)))
+ << message_;
+}
diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi
index 49aa3fc..96de3da 100644
--- a/chrome/chrome_browser_chromeos.gypi
+++ b/chrome/chrome_browser_chromeos.gypi
@@ -752,6 +752,8 @@
'browser/chromeos/ownership/owner_settings_service.h',
'browser/chromeos/ownership/owner_settings_service_factory.cc',
'browser/chromeos/ownership/owner_settings_service_factory.h',
+ 'browser/chromeos/platform_keys/platform_keys.h',
+ 'browser/chromeos/platform_keys/platform_keys_nss.cc',
'browser/chromeos/policy/app_pack_updater.cc',
'browser/chromeos/policy/app_pack_updater.h',
'browser/chromeos/policy/auto_enrollment_client.cc',
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 9a364d9..20b5ff0 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -951,6 +951,8 @@
'browser/extensions/api/diagnostics/diagnostics_api.cc',
'browser/extensions/api/diagnostics/diagnostics_api.h',
'browser/extensions/api/diagnostics/diagnostics_api_chromeos.cc',
+ 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc',
+ 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h',
'browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc',
'browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h',
'browser/extensions/api/input_ime/input_ime_api.cc',
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index 78fbdf5..955bbe49 100644
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -148,6 +148,7 @@
'renderer/resources/extensions/context_menus_custom_bindings.js',
'renderer/resources/extensions/declarative_content_custom_bindings.js',
'renderer/resources/extensions/declarative_webrequest_custom_bindings.js',
+ 'renderer/resources/extensions/enterprise_platform_keys_custom_bindings.js',
'renderer/resources/extensions/event.js',
'renderer/resources/extensions/extension_custom_bindings.js',
'renderer/resources/extensions/feedback_private_custom_bindings.js',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 959f84f..a1c91e8 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1074,6 +1074,7 @@
'browser/extensions/api/dns/mock_host_resolver_creator.cc',
'browser/extensions/api/dns/mock_host_resolver_creator.h',
'browser/extensions/api/downloads/downloads_api_browsertest.cc',
+ 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc',
'browser/extensions/api/extension_action/browser_action_apitest.cc',
'browser/extensions/api/extension_action/browser_action_browsertest.cc',
'browser/extensions/api/extension_action/page_action_apitest.cc',
@@ -1734,6 +1735,7 @@
['exclude', '^browser/ui/webui/options/chromeos/'],
],
'sources!': [
+ 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_apitest_nss.cc',
'browser/extensions/api/terminal/terminal_private_apitest.cc',
'browser/invalidation/invalidation_service_factory_browsertest.cc',
'browser/net/nss_context_chromeos_browsertest.cc',
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index b6cf8ba..22af2c48 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -305,6 +305,15 @@
"dependencies": ["permission:echoPrivate"],
"contexts": ["blessed_extension"]
},
+ "enterprise.platformKeys": {
+ "dependencies": ["permission:enterprise.platformKeys"],
+ "contexts": ["blessed_extension"]
+ },
+ "enterprise.platformKeysInternal": {
+ "dependencies": ["permission:enterprise.platformKeys"],
+ "internal": true,
+ "contexts": ["blessed_extension"]
+ },
"enterprise.platformKeysPrivate": {
"platforms": ["chromeos"],
"dependencies": ["permission:enterprise.platformKeysPrivate"],
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 855f6d7..c800a26 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -376,6 +376,12 @@
"16CA7A47AAE4BE49B1E75A6B960C3875E945B264" // Google Cast Stable
]
},
+ "enterprise.platformKeys": {
+ "channel": "dev",
+ "platforms": ["chromeos"],
+ "extension_types": ["extension", "legacy_packaged_app"],
+ "location": "policy"
+ },
"enterprise.platformKeysPrivate": {
"channel": "stable",
"extension_types": ["extension", "legacy_packaged_app"],
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
index cefd3fa..36c4f58 100644
--- a/chrome/common/extensions/api/api.gyp
+++ b/chrome/common/extensions/api/api.gyp
@@ -161,6 +161,8 @@
'schema_files': [
'accessibility_features.json',
'diagnostics.idl',
+ 'enterprise_platform_keys.idl',
+ 'enterprise_platform_keys_internal.idl',
'file_browser_handler_internal.json',
'first_run_private.json',
'log_private.idl',
diff --git a/chrome/common/extensions/api/enterprise_platform_keys.idl b/chrome/common/extensions/api/enterprise_platform_keys.idl
new file mode 100644
index 0000000..aac6e62
--- /dev/null
+++ b/chrome/common/extensions/api/enterprise_platform_keys.idl
@@ -0,0 +1,78 @@
+// Copyright 2014 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 the <code>chrome.enterprise.platformKeys</code> API to generate
+// hardware-backed keys and to install certificates for these keys. The
+// certificates will be available to the platform and can, for example, be used
+// for TLS authentication and network access.
+[platforms = ("chromeos")]
+namespace enterprise.platformKeys {
+ [nocompile] dictionary Token {
+ // Uniquely identifies this Token. Static IDs are 'user' and 'device',
+ // referring to the platform's user-specific and the device-wide hardware
+ // token, respectively. Any other tokens (with other identifiers) might be
+ // returned by getTokens.
+ DOMString id;
+
+ // Implements the WebCrypto's <code>SubtleCrypto</code> interface. The
+ // crypto operations are hardware-backed.
+ [instanceOf = SubtleCrypto] object subtleCrypto;
+ };
+
+ // Invoked by <code>getTokens</code> with the list of available Tokens.
+ callback GetTokensCallback = void(Token[] tokens);
+
+ // Callback to which the certificates are passed.
+ // |certificates| The list of certificates, each in DER encoding of a X.509
+ // certificate.
+ callback GetCertificatesCallback = void(ArrayBuffer[] certificates);
+
+ // Invoked by importCertificate or removeCertificate when the respective
+ // operation is finished.
+ callback DoneCallback = void();
+
+ interface Functions {
+ // Returns the available Tokens. In a regular user's session the list will
+ // always contain the user's token with id 'user'. If a device-wide TPM
+ // token is available it will also contain the device-wide token with id
+ // 'device'. The device-wide token will be the same for all sessions on this
+ // device (device in the sense of e.g. a Chromebook).
+ [nocompile] static void getTokens(GetTokensCallback callback);
+
+ // Returns the list of all client certificates available from the given
+ // token. Can be used to check for the existence and expiration of client
+ // certificates that are usable for a certain authentication.
+ // |tokenId| The id of a Token returned by <code>getTokens</code>.
+ // |callback| Called back with the list of the available certificates.
+ static void getCertificates(DOMString tokenId,
+ GetCertificatesCallback callback);
+
+ // Imports |certificate| to the given token if the certified key is already
+ // stored in this token.
+ // After a successful certification request, this function should be used to
+ // store the obtained certificate and to make it available to the operating
+ // system and browser for authentication.
+ // TODO: Instead of ArrayBuffer should be (ArrayBuffer or ArrayBufferView),
+ // or at least (ArrayBuffer or Uint8Array).
+ // |tokenId| The id of a Token returned by <code>getTokens</code>.
+ // |certificate| The DER encoding of a X.509 certificate.
+ // |callback| Called back when this operation is finished.
+ static void importCertificate(DOMString tokenId,
+ ArrayBuffer certificate,
+ optional DoneCallback callback);
+
+ // Removes |certificate| from the given token if present.
+ // Should be used to remove obsolete certificates so that they are not
+ // considered during authentication and do not clutter the certificate
+ // choice. Should be used to free storage in the certificate store.
+ // TODO: Instead of ArrayBuffer should be (ArrayBuffer or ArrayBufferView),
+ // or at least (ArrayBuffer or Uint8Array).
+ // |tokenId| The id of a Token returned by <code>getTokens</code>.
+ // |certificate| The DER encoding of a X.509 certificate.
+ // |callback| Called back when this operation is finished.
+ static void removeCertificate(DOMString tokenId,
+ ArrayBuffer certificate,
+ optional DoneCallback callback);
+ };
+};
diff --git a/chrome/common/extensions/api/enterprise_platform_keys_internal.idl b/chrome/common/extensions/api/enterprise_platform_keys_internal.idl
new file mode 100644
index 0000000..b6d8f06
--- /dev/null
+++ b/chrome/common/extensions/api/enterprise_platform_keys_internal.idl
@@ -0,0 +1,50 @@
+// Copyright 2014 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 for platform keys and certificate management.
+[ platforms = ("chromeos"),
+ implemented_in = "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h" ]
+namespace enterprise.platformKeysInternal {
+ // Invoked by <code>getTokens</code>.
+ // |tokenIds| The list of IDs of the avialable Tokens.
+ callback GetTokensCallback = void(DOMString[] tokenIds);
+
+ // Invoked by <code>generateKey</code>.
+ // |publicKey| The Subject Public Key Info (see X.509) of the generated key
+ // in DER encoding.
+ callback GenerateKeyCallback = void(ArrayBuffer publicKey);
+
+ // Invoked by <code>sign</code>.
+ // |signature| The signature, a octet string.
+ callback SignCallback = void(ArrayBuffer signature);
+
+ interface Functions {
+ // Internal version of entrprise.platformKeys.getTokens. Returns a list of
+ // token IDs instead of token objects.
+ static void getTokens(GetTokensCallback callback);
+
+ // Internal version of Token.generateKey, currently supporting only
+ // RSASSA-PKCS1-v1_5.
+ // |tokenId| The id of a Token returned by |getTokens|.
+ // |modulusLength| The length, in bits, of the RSA modulus.
+ // |callback| Called back with the Subject Public Key Info of the generated
+ // key.
+ static void generateKey(DOMString tokenId,
+ long modulusLength,
+ GenerateKeyCallback callback);
+
+ // Internal version of Token.sign.
+ // |tokenId| The id of a Token returned by |getTokens|.
+ // |publicKey| The Subject Public Key Info of a key previously generated by
+ // |generateKey| in DER encoding.
+ // |data| The data to sign.
+ // |callback| Called back with the signature of |data|.
+ // TODO: Instead of ArrayBuffer should be (ArrayBuffer or ArrayBufferView),
+ // or at least (ArrayBuffer or Uint8Array).
+ static void sign(DOMString tokenId,
+ ArrayBuffer publicKey,
+ ArrayBuffer data,
+ SignCallback callback);
+ };
+};
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index 2b5b4c3..330ee12 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -100,6 +100,7 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions()
PermissionMessage::kContentSettings},
{APIPermission::kContextMenus, "contextMenus"},
{APIPermission::kCookie, "cookies"},
+ {APIPermission::kEnterprisePlatformKeys, "enterprise.platformKeys"},
{APIPermission::kFileBrowserHandler, "fileBrowserHandler",
APIPermissionInfo::kFlagCannotBeOptional},
{APIPermission::kFontSettings, "fontSettings",
diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc
index 9a027a8..60dc5ba 100644
--- a/chrome/common/extensions/permissions/permission_set_unittest.cc
+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc
@@ -675,6 +675,10 @@ TEST(PermissionsTest, PermissionMessages) {
skip.insert(APIPermission::kWebView);
skip.insert(APIPermission::kWindowShape);
+ // These permissions are restricted to extensions force-installed by policy
+ // and don't require a prompt, i.e. they're restricted to location 'policy'.
+ skip.insert(APIPermission::kEnterprisePlatformKeys);
+
// TODO(erikkay) add a string for this permission.
skip.insert(APIPermission::kBackground);
diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
index d5f1836..4105d74 100644
--- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
+++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -166,6 +166,20 @@ void ChromeExtensionsDispatcherDelegate::PopulateSourceMap(
source_map->RegisterSource("developerPrivate",
IDR_DEVELOPER_PRIVATE_CUSTOM_BINDINGS_JS);
source_map->RegisterSource("downloads", IDR_DOWNLOADS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("enterprise.platformKeys",
+ IDR_ENTERPRISE_PLATFORM_KEYS_CUSTOM_BINDINGS_JS);
+ source_map->RegisterSource("enterprise.platformKeys.internalAPI",
+ IDR_ENTERPRISE_PLATFORM_KEYS_INTERNAL_API_JS);
+ source_map->RegisterSource("enterprise.platformKeys.Key",
+ IDR_ENTERPRISE_PLATFORM_KEYS_KEY_JS);
+ source_map->RegisterSource("enterprise.platformKeys.KeyPair",
+ IDR_ENTERPRISE_PLATFORM_KEYS_KEY_PAIR_JS);
+ source_map->RegisterSource("enterprise.platformKeys.SubtleCrypto",
+ IDR_ENTERPRISE_PLATFORM_KEYS_SUBTLE_CRYPTO_JS);
+ source_map->RegisterSource("enterprise.platformKeys.Token",
+ IDR_ENTERPRISE_PLATFORM_KEYS_TOKEN_JS);
+ source_map->RegisterSource("enterprise.platformKeys.utils",
+ IDR_ENTERPRISE_PLATFORM_KEYS_UTILS_JS);
source_map->RegisterSource("feedbackPrivate",
IDR_FEEDBACK_PRIVATE_CUSTOM_BINDINGS_JS);
source_map->RegisterSource("fileBrowserHandler",
diff --git a/chrome/renderer/resources/extensions/OWNERS b/chrome/renderer/resources/extensions/OWNERS
index fc47b41..1c80546 100644
--- a/chrome/renderer/resources/extensions/OWNERS
+++ b/chrome/renderer/resources/extensions/OWNERS
@@ -19,3 +19,4 @@ per-file web_view*.js=fsamuel@chromium.org
per-file web_view*.js=lazyboy@chromium.org
per-file ad_view*.js=fsamuel@chromium.org
per-file ad_view*.js=lazyboy@chromium.org
+per-file enterprise_platform_keys*=pneubeck@chromium.org
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/OWNERS b/chrome/renderer/resources/extensions/enterprise_platform_keys/OWNERS
new file mode 100644
index 0000000..713045b
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/OWNERS
@@ -0,0 +1 @@
+pneubeck@chromium.org
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/internal_api.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/internal_api.js
new file mode 100644
index 0000000..97ff660
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/internal_api.js
@@ -0,0 +1,11 @@
+// Copyright 2014 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('enterprise.platformKeysInternal')
+ .generate();
+
+exports.getTokens = binding.getTokens;
+exports.generateKey = binding.generateKey;
+exports.sign = binding.sign;
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/key.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/key.js
new file mode 100644
index 0000000..7d146cc
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/key.js
@@ -0,0 +1,66 @@
+// Copyright 2014 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 utils = require('utils');
+
+/**
+ * Enum of possible key types (subset of WebCrypto.KeyType).
+ * @enum {string}
+ */
+var KeyType = {
+ public: 'public',
+ private: 'private'
+};
+
+/**
+ * Enum of possible key usages (subset of WebCrypto.KeyUsage).
+ * @enum {string}
+ */
+var KeyUsage = {
+ sign: 'sign',
+ verify: 'verify'
+};
+
+/**
+ * Implementation of WebCrypto.Key used in enterprise.platformKeys.
+ * @param {KeyType} type The type of the new key.
+ * @param {ArrayBuffer} publicKeySpki The Subject Public Key Info in DER
+ * encoding.
+ * @param {KeyAlgorithm} algorithm The algorithm identifier.
+ * @param {KeyUsage[]} usages The allowed key usages.
+ * @param {boolean} extractable Whether the key is extractable.
+ * @constructor
+ */
+var KeyImpl = function(type, publicKeySpki, algorithm, usages, extractable) {
+ this.type = type;
+ this.spki = publicKeySpki;
+ this.algorithm = algorithm;
+ this.usages = usages;
+ this.extractable = extractable;
+};
+
+var Key =
+ utils.expose('Key',
+ KeyImpl,
+ {readonly:['extractable', 'type', 'algorithm', 'usages']});
+
+/**
+ * Returns |key|'s Subject Public Key Info. Throws an exception if |key| is not
+ * a valid Key object.
+ * @param {Key} key
+ * @return {ArrayBuffer} The Subject Public Key Info in DER encoding of |key|.
+ */
+function getSpki(key) {
+ if (!privates(key))
+ throw new Error('Invalid key object.');
+ var keyImpl = privates(key).impl;
+ if (!keyImpl || !keyImpl.spki)
+ throw new Error('Invalid key object.');
+ return keyImpl.spki;
+}
+
+exports.Key = Key;
+exports.KeyType = KeyType;
+exports.KeyUsage = KeyUsage;
+exports.getSpki = getSpki;
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/key_pair.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/key_pair.js
new file mode 100644
index 0000000..581a2e8
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/key_pair.js
@@ -0,0 +1,35 @@
+// Copyright 2014 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 utils = require('utils');
+var intersect = require('enterprise.platformKeys.utils').intersect;
+var keyModule = require('enterprise.platformKeys.Key');
+var Key = keyModule.Key;
+var KeyType = keyModule.KeyType;
+var KeyUsage = keyModule.KeyUsage;
+
+/**
+ * Implementation of WebCrypto.KeyPair used in enterprise.platformKeys.
+ * @param {ArrayBuffer} publicKeySpki The Subject Public Key Info in DER
+ * encoding.
+ * @param {KeyAlgorithm} algorithm The algorithm identifier.
+ * @param {KeyUsage[]} usages The allowed key usages.
+ * @constructor
+ */
+var KeyPairImpl = function(publicKeySpki, algorithm, usages) {
+ this.publicKey = new Key(KeyType.public,
+ publicKeySpki,
+ algorithm,
+ intersect([KeyUsage.verify], usages),
+ true /* extractable */);
+ this.privateKey = new Key(KeyType.private,
+ publicKeySpki,
+ algorithm,
+ intersect([KeyUsage.sign], usages),
+ false /* not extractable */);
+};
+
+exports.KeyPair = utils.expose('KeyPair',
+ KeyPairImpl,
+ {readonly:['publicKey', 'privateKey']});
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/subtle_crypto.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/subtle_crypto.js
new file mode 100644
index 0000000..d94722c
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/subtle_crypto.js
@@ -0,0 +1,144 @@
+// Copyright 2014 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 utils = require('utils');
+var internalAPI = require('enterprise.platformKeys.internalAPI');
+var intersect = require('enterprise.platformKeys.utils').intersect;
+var KeyPair = require('enterprise.platformKeys.KeyPair').KeyPair;
+var keyModule = require('enterprise.platformKeys.Key');
+var getSpki = keyModule.getSpki;
+var KeyUsage = keyModule.KeyUsage;
+
+// This error is thrown by the internal and public API's token functions and
+// must be rethrown by this custom binding. Keep this in sync with the C++ part
+// of this API.
+var errorInvalidToken = "The token is not valid.";
+
+// The following errors are specified in WebCrypto.
+// TODO(pneubeck): These should be DOMExceptions.
+function CreateNotSupportedError() {
+ return new Error('The algorithm is not supported');
+}
+
+function CreateInvalidAccessError() {
+ return new Error('The requested operation is not valid for the provided key');
+}
+
+function CreateDataError() {
+ return new Error('Data provided to an operation does not meet requirements');
+}
+
+function CreateSyntaxError() {
+ return new Error('A required parameter was missing our out-of-range');
+}
+
+function CreateOperationError() {
+ return new Error('The operation failed for an operation-specific reason');
+}
+
+// Catches an |internalErrorInvalidToken|. If so, forwards it to |reject| and
+// returns true.
+function catchInvalidTokenError(reject) {
+ if (chrome.runtime.lastError &&
+ chrome.runtime.lastError.message == errorInvalidToken) {
+ reject(chrome.runtime.lastError);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Implementation of WebCrypto.SubtleCrypto used in enterprise.platformKeys.
+ * @param {string} tokenId The id of the backing Token.
+ * @constructor
+ */
+var SubtleCryptoImpl = function(tokenId) {
+ this.tokenId = tokenId;
+};
+
+SubtleCryptoImpl.prototype.generateKey =
+ function(algorithm, extractable, keyUsages) {
+ var subtleCrypto = this;
+ return new Promise(function(resolve, reject) {
+ // TODO(pneubeck): Apply the algorithm normalization of the WebCrypto
+ // implementation.
+
+ if (extractable) {
+ // Note: This deviates from WebCrypto.SubtleCrypto.
+ throw CreateNotSupportedError();
+ }
+ if (intersect(keyUsages, [KeyUsage.sign, KeyUsage.verify]).length !=
+ keyUsages.length) {
+ throw CreateDataError();
+ }
+ if (!algorithm.name) {
+ // TODO(pneubeck): It's not clear from the WebCrypto spec which error to
+ // throw here.
+ throw CreateSyntaxError();
+ }
+
+ if (algorithm.name.toUpperCase() !== 'RSASSA-PKCS1-V1_5') {
+ // Note: This deviates from WebCrypto.SubtleCrypto.
+ throw CreateNotSupportedError();
+ }
+ if (!algorithm.modulusLength || !algorithm.publicExponent)
+ throw CreateSyntaxError();
+
+ internalAPI.generateKey(
+ subtleCrypto.tokenId, algorithm.modulusLength, function(spki) {
+ if (catchInvalidTokenError(reject))
+ return;
+ if (chrome.runtime.lastError) {
+ reject(CreateOperationError());
+ return;
+ }
+ resolve(new KeyPair(spki, algorithm, keyUsages));
+ });
+ });
+};
+
+SubtleCryptoImpl.prototype.sign = function(algorithm, key, dataView) {
+ var subtleCrypto = this;
+ return new Promise(function(resolve, reject) {
+ if (key.type != 'private' || key.usages.indexOf(KeyUsage.sign) == -1)
+ throw CreateInvalidAccessError();
+
+ // Create an ArrayBuffer that equals the dataView. Note that dataView.buffer
+ // might contain more data than dataView.
+ var data = dataView.buffer.slice(dataView.byteOffset,
+ dataView.byteOffset + dataView.byteLength);
+ internalAPI.sign(
+ subtleCrypto.tokenId, getSpki(key), data, function(signature) {
+ if (catchInvalidTokenError(reject))
+ return;
+ if (chrome.runtime.lastError) {
+ reject(CreateOperationError());
+ return;
+ }
+ resolve(signature);
+ });
+ });
+};
+
+SubtleCryptoImpl.prototype.exportKey = function(format, key) {
+ return new Promise(function(resolve, reject) {
+ if (format == 'pkcs8') {
+ // Either key.type is not 'private' or the key is not extractable. In both
+ // cases the error is the same.
+ throw CreateInvalidAccessError();
+ } else if (format == 'spki') {
+ if (key.type != 'public')
+ throw CreateInvalidAccessError();
+ resolve(getSpki(key));
+ } else {
+ // TODO(pneubeck): It should be possible to export to format 'jwk'.
+ throw CreateNotSupportedError();
+ }
+ });
+};
+
+exports.SubtleCrypto =
+ utils.expose('SubtleCrypto',
+ SubtleCryptoImpl,
+ {functions:['generateKey', 'sign', 'exportKey']});
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/token.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/token.js
new file mode 100644
index 0000000..dee7952
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/token.js
@@ -0,0 +1,19 @@
+// Copyright 2014 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 utils = require('utils');
+var SubtleCrypto = require('enterprise.platformKeys.SubtleCrypto').SubtleCrypto;
+
+/**
+ * Implementation of enterprise.platformKeys.Token.
+ * @param {string} id The id of the new Token.
+ * @constructor
+ */
+var TokenImpl = function(id) {
+ this.id = id;
+ this.subtleCrypto = new SubtleCrypto(id);
+};
+
+exports.Token =
+ utils.expose('Token', TokenImpl, {readonly:['id', 'subtleCrypto']});
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys/utils.js b/chrome/renderer/resources/extensions/enterprise_platform_keys/utils.js
new file mode 100644
index 0000000..7bfbbf5
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys/utils.js
@@ -0,0 +1,16 @@
+// Copyright 2014 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.
+
+// Returns the intersection of the arrays |a| and |b|, which do not have to be
+// sorted.
+function intersect(a, b) {
+ var result = [];
+ for (var i = 0; i < a.length; i++) {
+ if (b.indexOf(a[i]) >= 0)
+ result.push(a[i]);
+ }
+ return result;
+};
+
+exports.intersect = intersect;
diff --git a/chrome/renderer/resources/extensions/enterprise_platform_keys_custom_bindings.js b/chrome/renderer/resources/extensions/enterprise_platform_keys_custom_bindings.js
new file mode 100644
index 0000000..a81c944
--- /dev/null
+++ b/chrome/renderer/resources/extensions/enterprise_platform_keys_custom_bindings.js
@@ -0,0 +1,32 @@
+// Copyright 2014 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.
+
+// Custom binding for the enterprise.platformKeys API.
+
+// The platformKeys API consists of two major parts:
+// - the certificate management.
+// - the key generation and crypto operations and
+// The former is implemented without custom binding as static functions.
+// The latter is exposed by implementing WebCrypto's SubtleCrypto interface.
+// The internal API provides the key and crypto operations through static
+// functions expecting token IDs and this custom binding adds the SubtleCrypto
+// wrapper.
+// The Token object holds the token id and the SubtleCrypto member.
+
+var binding = require('binding').Binding.create('enterprise.platformKeys');
+var Token = require('enterprise.platformKeys.Token').Token;
+var internalAPI = require('enterprise.platformKeys.internalAPI');
+
+binding.registerCustomHook(function(api) {
+ var apiFunctions = api.apiFunctions;
+
+ var ret = apiFunctions.setHandleRequest('getTokens', function(callback) {
+ internalAPI.getTokens(function(tokenIds) {
+ callback($Array.map(tokenIds,
+ function(tokenId) { return new Token(tokenId); }));
+ });
+ });
+});
+
+exports.binding = binding.generate();
diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd
index b810508..8d545cb 100644
--- a/chrome/renderer/resources/renderer_resources.grd
+++ b/chrome/renderer/resources/renderer_resources.grd
@@ -56,6 +56,13 @@ without changes to the corresponding grd file. fb9 -->
<include name="IDR_DESKTOP_CAPTURE_CUSTOM_BINDINGS_JS" file="extensions\desktop_capture_custom_bindings.js" type="BINDATA" />
<include name="IDR_DEVELOPER_PRIVATE_CUSTOM_BINDINGS_JS" file="extensions\developer_private_custom_bindings.js" type="BINDATA" />
<include name="IDR_DOWNLOADS_CUSTOM_BINDINGS_JS" file="extensions\downloads_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_CUSTOM_BINDINGS_JS" file="extensions\enterprise_platform_keys_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_INTERNAL_API_JS" file="extensions\enterprise_platform_keys\internal_api.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_KEY_JS" file="extensions\enterprise_platform_keys\key.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_KEY_PAIR_JS" file="extensions\enterprise_platform_keys\key_pair.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_SUBTLE_CRYPTO_JS" file="extensions\enterprise_platform_keys\subtle_crypto.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_TOKEN_JS" file="extensions\enterprise_platform_keys\token.js" type="BINDATA" />
+ <include name="IDR_ENTERPRISE_PLATFORM_KEYS_UTILS_JS" file="extensions\enterprise_platform_keys\utils.js" type="BINDATA" />
<include name="IDR_ENTRY_ID_MANAGER" file="extensions\entry_id_manager.js" type="BINDATA" />
<include name="IDR_EVENT_BINDINGS_JS" file="extensions\event.js" type="BINDATA" />
<include name="IDR_EXTENSION_CUSTOM_BINDINGS_JS" file="extensions\extension_custom_bindings.js" type="BINDATA" />
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys.crx b/chrome/test/data/extensions/api_test/enterprise_platform_keys.crx
new file mode 100644
index 0000000..449b588
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys.crx
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys.pem b/chrome/test/data/extensions/api_test/enterprise_platform_keys.pem
new file mode 100644
index 0000000..3caf483
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2cCpUm9Sh/e5v
+xdAqcDxdRbGNy+eaxAPTPwki4/ONeloXTsDWvnYZP47ybVv/wtLWLP03yWqNPzy1
+JRU+OschpFW3USfZvA5Sc2ogLEYI9K/5MbOpk6faaiak0qecpHhv//EENNzCAn9C
+RVaHWXmJVfHaMlxmZeJB+4dFyaFP+XJ+vgUP2m6F+e3aX44QlYFUwmC96wNMXRY8
+DPB2Gw+td6Z+/UUVtYg2SuK5YUJ5jHTfHHmzM3zfGwnEp/vFDmF+Hg9yxJID9Dyh
+oQ5cfr9A4WgM12piuOal/imuiKVG+GgYSB5P5dKsgStGHrLVlRq2pv1WkwpFzD2s
+4DV6nEDTAgMBAAECggEAMxWfVMe5t45yKIwcaPW6gWeVex4AgT6nz4wUE9f+h7wd
+4+7iYn88dH9umIV0wF4eSWiZkvhsRJbTeemjVCSDQ/FPKyCnZvmYjQ/SRKYBClrx
+t3ZqV26IDsmwfnCaDGulab8iDsp6kjy+w6DExOuR85nEOhPl+apHu7AhxbHa4JiA
+DTPumuUna+oPTMoMB8lU9J96zPzdgqpzgVjkLyT6sA6stSYMx6i7p0g2ds7HqEfF
+Rmhc4GK0afzlNhMYawV6CBO1DQsewCA7XwU7Sr/cXwwUu5hp2MxUh+Vk9o9M5H+L
+vb/Qj8zt5t4U7YaR3ZHlrXl1/+TvVqPF4HHMcNEsAQKBgQDutwed7Qsg1FWeZ2/W
+Ww+ozgKAXQaohxTdP1wYdomtT4+3AtCTE6Eo8MD8LaGi+6j7Mr2rdsP9Co+DYRgQ
+YC8EXQbLZKyg22hbxOBgLjZSJnxUiWnKzpxHd8KaY4wATH9eSS7dQlmZzkSS2Ujy
+g/PW9ZCF521Qlnmbn7c96yZy0wKBgQDDpfQJmjLq2xhYVlyPPew62RPWN1pdwzQ+
+WMU+1rEKIZ9Fr455JrSkPb4MyHhmmh4n78hgcAd5vQG44AWDRKYnPvKsNe7R2ciq
+l7DXsmOdCYlkQvntoH9CT53TrWpwqwGioRVJtRi/IKrxvWBebaxS3v67C7mp9Hjr
+SXF+5YY6AQKBgBCRyciPfJdBxdOh8GRbm8Gm0wuf6WdnQO5+iGD2UWYjQKF+Y4Xh
+9ezGkU6vyCls+HS/N+Uf8EivI/kFk0sHshvGbUNO8KCVui+sgbqLWp/nJfgkNaCr
+KLPZPzvCLySSKsEbqVIYWenQJHhg9qxGtnU0RFzXDIJsf3Jq6qg0m02bAoGAXuiy
+abYT3TpLpQ6/9/ziC+oiZViUzcdUIKqPhNxBJdkJmpR/WOYd207YUvss7Rj2aAk5
++w5dkgDORRDnm7ZBo+g1cLT5FXPAlIDhmMFA8X0e2TRkaZHiKhjVR0poyB8su+fI
+m+juCEmgo4Rqq2QopEro53PYrUmbHwiRti1B4AECgYEAjnovOiTPqEebrhaQzRZE
+/uP/Nn3y4xEdirZN36Z6ZBV/KjSoe33wFp1cENNW/USlAUnIfzqKuwHkDByH+TAo
+UfpvhV1EVgaqAUGDm9CYm+wFblLuckkDr4UbybqqMH3HvmeeeE20/q/LhjPGxMR9
+CeFGN7ngBTNX7OXd3YGDzjA=
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/OWNERS b/chrome/test/data/extensions/api_test/enterprise_platform_keys/OWNERS
new file mode 100644
index 0000000..713045b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/OWNERS
@@ -0,0 +1 @@
+pneubeck@chromium.org
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.html b/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.html
new file mode 100644
index 0000000..569a748
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.html
@@ -0,0 +1,6 @@
+<!--
+ * Copyright 2014 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.
+-->
+<script src="basic.js"></script>
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.js b/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.js
new file mode 100644
index 0000000..557ae68
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/basic.js
@@ -0,0 +1,394 @@
+// Copyright 2014 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.
+
+// Must be packed to ../enterprise_platform_keys.crx using the private key
+// ../enterprise_platform_keys.pem .
+
+var assertEq = chrome.test.assertEq;
+var assertTrue = chrome.test.assertTrue;
+var assertThrows = chrome.test.assertThrows;
+var fail = chrome.test.fail;
+var succeed = chrome.test.succeed;
+var callbackPass = chrome.test.callbackPass;
+var callbackFail= chrome.test.callbackFail;
+
+// openssl req -new -x509 -key privkey.pem \
+// -outform der -out cert.der -days 36500
+// xxd -i cert.der
+// based on privateKeyPkcs8
+var cert1a = new Uint8Array([
+ 0x30, 0x82, 0x01, 0xd5, 0x30, 0x82, 0x01, 0x7f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x09, 0x00, 0xd2, 0xcc, 0x76, 0xeb, 0x19, 0xb9, 0x3a, 0x33,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
+ 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
+ 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c,
+ 0x74, 0x64, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x34, 0x31, 0x35,
+ 0x31, 0x34, 0x35, 0x32, 0x30, 0x33, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x31,
+ 0x34, 0x30, 0x33, 0x32, 0x32, 0x31, 0x34, 0x35, 0x32, 0x30, 0x33, 0x5a,
+ 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
+ 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
+ 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30,
+ 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00,
+ 0xc7, 0xc1, 0x4d, 0xd5, 0xdc, 0x3a, 0x2e, 0x1f, 0x42, 0x30, 0x3d, 0x21,
+ 0x1e, 0xa2, 0x1f, 0x60, 0xcb, 0x71, 0x11, 0x53, 0xb0, 0x75, 0xa0, 0x62,
+ 0xfe, 0x5e, 0x0a, 0xde, 0xb0, 0x0f, 0x48, 0x97, 0x5e, 0x42, 0xa7, 0x3a,
+ 0xd1, 0xca, 0x4c, 0xe3, 0xdb, 0x5f, 0x31, 0xc2, 0x99, 0x08, 0x89, 0xcd,
+ 0x6d, 0x20, 0xaa, 0x75, 0xe6, 0x2b, 0x98, 0xd2, 0xf3, 0x7b, 0x4b, 0xe5,
+ 0x9b, 0xfe, 0xe2, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x50, 0x30,
+ 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0xbd, 0x85, 0x6b, 0xdd, 0x84, 0xd1, 0x54, 0x2e, 0xad, 0xb4, 0x5e, 0xdd,
+ 0x24, 0x7e, 0x16, 0x9c, 0x84, 0x1e, 0x19, 0xf0, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xbd, 0x85, 0x6b,
+ 0xdd, 0x84, 0xd1, 0x54, 0x2e, 0xad, 0xb4, 0x5e, 0xdd, 0x24, 0x7e, 0x16,
+ 0x9c, 0x84, 0x1e, 0x19, 0xf0, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41,
+ 0x00, 0x37, 0x23, 0x2f, 0x81, 0x24, 0xfc, 0xec, 0x2d, 0x0b, 0xd1, 0xa0,
+ 0x74, 0xdf, 0x2e, 0x34, 0x9a, 0x92, 0x33, 0xae, 0x75, 0xd6, 0x60, 0xfc,
+ 0x44, 0x1d, 0x65, 0x8c, 0xb7, 0xd9, 0x60, 0x3b, 0xc7, 0x20, 0x30, 0xdf,
+ 0x17, 0x07, 0xd1, 0x87, 0xda, 0x2b, 0x7f, 0x84, 0xf3, 0xfc, 0xb0, 0x31,
+ 0x42, 0x08, 0x17, 0x96, 0xd2, 0x1b, 0xdc, 0x28, 0xae, 0xf8, 0xbd, 0xf9,
+ 0x4e, 0x78, 0xc3, 0xe8, 0x80
+]);
+
+// based on privateKeyPkcs8, different from cert1a
+var cert1b = new Uint8Array([
+ 0x30, 0x82, 0x01, 0xd5, 0x30, 0x82, 0x01, 0x7f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x09, 0x00, 0xe7, 0x1e, 0x6e, 0xb0, 0x12, 0x87, 0xf5, 0x09,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
+ 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
+ 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c,
+ 0x74, 0x64, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x34, 0x31, 0x35,
+ 0x31, 0x35, 0x31, 0x39, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x31,
+ 0x34, 0x30, 0x33, 0x32, 0x32, 0x31, 0x35, 0x31, 0x39, 0x30, 0x30, 0x5a,
+ 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
+ 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
+ 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30,
+ 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00,
+ 0xc7, 0xc1, 0x4d, 0xd5, 0xdc, 0x3a, 0x2e, 0x1f, 0x42, 0x30, 0x3d, 0x21,
+ 0x1e, 0xa2, 0x1f, 0x60, 0xcb, 0x71, 0x11, 0x53, 0xb0, 0x75, 0xa0, 0x62,
+ 0xfe, 0x5e, 0x0a, 0xde, 0xb0, 0x0f, 0x48, 0x97, 0x5e, 0x42, 0xa7, 0x3a,
+ 0xd1, 0xca, 0x4c, 0xe3, 0xdb, 0x5f, 0x31, 0xc2, 0x99, 0x08, 0x89, 0xcd,
+ 0x6d, 0x20, 0xaa, 0x75, 0xe6, 0x2b, 0x98, 0xd2, 0xf3, 0x7b, 0x4b, 0xe5,
+ 0x9b, 0xfe, 0xe2, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x50, 0x30,
+ 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0xbd, 0x85, 0x6b, 0xdd, 0x84, 0xd1, 0x54, 0x2e, 0xad, 0xb4, 0x5e, 0xdd,
+ 0x24, 0x7e, 0x16, 0x9c, 0x84, 0x1e, 0x19, 0xf0, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xbd, 0x85, 0x6b,
+ 0xdd, 0x84, 0xd1, 0x54, 0x2e, 0xad, 0xb4, 0x5e, 0xdd, 0x24, 0x7e, 0x16,
+ 0x9c, 0x84, 0x1e, 0x19, 0xf0, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41,
+ 0x00, 0x82, 0x95, 0xa7, 0x08, 0x6c, 0xbd, 0x49, 0xe6, 0x1e, 0xc1, 0xd9,
+ 0x58, 0x54, 0x11, 0x11, 0x84, 0x77, 0x1e, 0xad, 0xe9, 0x73, 0x69, 0x1c,
+ 0x5c, 0xaa, 0x26, 0x3e, 0x5f, 0x1d, 0x89, 0x20, 0xc3, 0x90, 0xa4, 0x67,
+ 0xfa, 0x26, 0x20, 0xd7, 0x1f, 0xae, 0x42, 0x89, 0x30, 0x61, 0x43, 0x8a,
+ 0x8c, 0xbe, 0xd4, 0x32, 0xf7, 0x96, 0x71, 0x2a, 0xcd, 0xeb, 0x26, 0xf6,
+ 0xdb, 0x54, 0x95, 0xca, 0x5a
+]);
+
+// based on a private key different than privateKeyPkcs8
+var cert2 = new Uint8Array([
+ 0x30, 0x82, 0x01, 0xd5, 0x30, 0x82, 0x01, 0x7f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x09, 0x00, 0x9e, 0x11, 0x7e, 0xff, 0x43, 0x84, 0xd4, 0xe6,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
+ 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
+ 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c,
+ 0x74, 0x64, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x34, 0x30, 0x37,
+ 0x31, 0x35, 0x35, 0x30, 0x30, 0x38, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x31,
+ 0x34, 0x30, 0x33, 0x31, 0x34, 0x31, 0x35, 0x35, 0x30, 0x30, 0x38, 0x5a,
+ 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49,
+ 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67,
+ 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30,
+ 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00,
+ 0xac, 0x6c, 0x72, 0x46, 0xa2, 0xde, 0x88, 0x30, 0x54, 0x06, 0xad, 0xc7,
+ 0x2d, 0x64, 0x6e, 0xf6, 0x0f, 0x72, 0x3e, 0x92, 0x31, 0xcc, 0x0b, 0xa0,
+ 0x18, 0x20, 0xb0, 0xdb, 0x86, 0xab, 0x11, 0xc6, 0xa5, 0x78, 0xea, 0x64,
+ 0xe8, 0xeb, 0xa5, 0xb3, 0x78, 0x5d, 0xbb, 0x10, 0x57, 0xe6, 0x12, 0x23,
+ 0x89, 0x92, 0x1d, 0xa0, 0xe5, 0x1e, 0xd1, 0xc9, 0x0e, 0x62, 0xcb, 0xc9,
+ 0xaf, 0xde, 0x4e, 0x83, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x50, 0x30,
+ 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x75, 0x6c, 0x61, 0xfb, 0xb0, 0x6e, 0x37, 0x32, 0x41, 0x62, 0x3b, 0x55,
+ 0xbd, 0x5f, 0x6b, 0xe0, 0xdb, 0xb9, 0xc7, 0xec, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x75, 0x6c, 0x61,
+ 0xfb, 0xb0, 0x6e, 0x37, 0x32, 0x41, 0x62, 0x3b, 0x55, 0xbd, 0x5f, 0x6b,
+ 0xe0, 0xdb, 0xb9, 0xc7, 0xec, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41,
+ 0x00, 0xa5, 0xe8, 0x9d, 0x3d, 0xc4, 0x1a, 0x6e, 0xd2, 0x92, 0x42, 0x37,
+ 0xb9, 0x3a, 0xb3, 0x8e, 0x2f, 0x55, 0xb5, 0xf2, 0xe4, 0x6e, 0x39, 0x0d,
+ 0xa8, 0xba, 0x10, 0x43, 0x57, 0xdd, 0x4e, 0x4e, 0x52, 0xc6, 0xbe, 0x07,
+ 0xdb, 0x83, 0x05, 0x97, 0x97, 0xc1, 0x7b, 0xd5, 0x5c, 0x50, 0x64, 0x0f,
+ 0x96, 0xff, 0x3d, 0x83, 0x37, 0x8f, 0x3a, 0x85, 0x08, 0x62, 0x5c, 0xb1,
+ 0x2f, 0x68, 0xb2, 0x4a, 0x4a
+]);
+
+/**
+ * Runs an array of asynchronous functions [f1, f2, ...] of the form
+ * function(callback) {}
+ * by chaining, i.e. f1(f2(...)). Additionally, each callback is wrapped with
+ * callbackPass.
+ */
+function runAsyncSequence(funcs) {
+ if (funcs.length == 0)
+ return;
+ function go(i) {
+ var current = funcs[i];
+ console.log('#' + (i + 1) + ' of ' + funcs.length);
+ if (i == funcs.length - 1) {
+ current(callbackPass());
+ } else {
+ current(callbackPass(go.bind(undefined, i + 1)));
+ }
+ };
+ go(0);
+}
+
+// Some array comparison. Note: not lexicographical!
+function compareArrays(array1, array2) {
+ if (array1.length < array2.length)
+ return -1;
+ if (array1.length > array2.length)
+ return 1;
+ for (var i = 0; i < array1.length; i++) {
+ if (array1[i] < array2[i])
+ return -1;
+ if (array1[i] > array2[i])
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * @param {ArrayBufferView[]} certs
+ * @return {ArrayBufferView[]} |certs| sorted in some order.
+ */
+function sortCerts(certs) {
+ return certs.sort(compareArrays);
+}
+
+/**
+ * Checks whether the certificates currently stored in |token| match
+ * |expectedCerts| by comparing to the result of platformKeys.getCertificates.
+ * The order of |expectedCerts| is ignored. Afterwards calls |callback|.
+ */
+function assertCertsStored(token, expectedCerts, callback) {
+ chrome.enterprise.platformKeys.getCertificates(
+ token.id,
+ callbackPass(function(actualCerts) {
+ assertEq(expectedCerts.length,
+ actualCerts.length,
+ 'Number of stored certs not as expected');
+ if (expectedCerts.length == actualCerts.length) {
+ actualCerts = actualCerts.map(
+ function(buffer) { return new Uint8Array(buffer); });
+ actualCerts = sortCerts(actualCerts);
+ expectedCerts = sortCerts(expectedCerts);
+ for (var i = 0; i < expectedCerts.length; i++) {
+ assertTrue(compareArrays(expectedCerts[i], actualCerts[i]) == 0,
+ 'Certs at index ' + i + ' differ');
+ }
+ }
+ if (callback)
+ callback();
+ }));
+}
+
+/**
+ * Fetches all available tokens using platformKeys.getTokens and calls
+ * |callback| with the user token if available or with undefined otherwise.
+ */
+function getUserToken(callback) {
+ chrome.enterprise.platformKeys.getTokens(function(tokens) {
+ for (var i = 0; i < tokens.length; i++) {
+ if (tokens[i].id == 'user') {
+ callback(tokens[i]);
+ return;
+ }
+ }
+ callback(undefined);
+ });
+}
+
+/**
+ * Runs preparations before the actual tests. Calls |callback| with |userToken|.
+ */
+function beforeTests(callback) {
+ assertTrue(!!chrome.enterprise, "No enterprise namespace.");
+ assertTrue(!!chrome.enterprise.platformKeys, "No platformKeys namespace.");
+ assertTrue(!!chrome.enterprise.platformKeys.getTokens,
+ "No getTokens function.");
+ assertTrue(!!chrome.enterprise.platformKeys.importCertificate,
+ "No importCertificate function.");
+ assertTrue(!!chrome.enterprise.platformKeys.removeCertificate,
+ "No removeCertificate function.");
+
+ getUserToken(function(userToken) {
+ if (!userToken)
+ chrome.test.fail('no user token');
+ if (userToken.id != 'user')
+ chrome.test.fail('token is not named "user".');
+
+ callback(userToken);
+ });
+}
+
+function runTests(userToken) {
+ chrome.test.runTests([
+ function hasSubtleCryptoMethods() {
+ assertTrue(!!userToken.subtleCrypto.generateKey,
+ "user token has no generateKey method");
+ assertTrue(!!userToken.subtleCrypto.sign,
+ "user token has no sign method");
+ assertTrue(!!userToken.subtleCrypto.exportKey,
+ "user token has no exportKey method");
+ succeed();
+ },
+ function initiallyNoCerts() { assertCertsStored(userToken, []); },
+
+ // Generates a key and sign some data with it. Verifies the signature using
+ // WebCrypto.
+ function generateKeyAndSign() {
+ var algorithm = {
+ name: "RSASSA-PKCS1-v1_5",
+ // RsaHashedKeyGenParams
+ modulusLength: 512,
+ publicExponent:
+ new Uint8Array([0x01, 0x00, 0x01]), // Equivalent to 65537
+ hash: {
+ name: "SHA-1",
+ }
+ };
+ // Some random data to sign.
+ var data = new Uint8Array([0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6]);
+ var cachedKeyPair;
+ var cachedSpki;
+ var cachedSignature;
+ userToken.subtleCrypto.generateKey(algorithm, false, ["sign"])
+ .then(callbackPass(function(keyPair) {
+ assertTrue(!!keyPair, "No key pair.");
+ cachedKeyPair = keyPair;
+ return userToken.subtleCrypto.exportKey('spki',
+ keyPair.publicKey);
+ }),
+ function(error) {
+ assertTrue(false, "GenerateKey failed: " + error);
+ })
+ .then(callbackPass(function(publicKeySpki) {
+ cachedSpki = publicKeySpki;
+ return userToken.subtleCrypto.sign(
+ {}, cachedKeyPair.privateKey, data);
+ }),
+ function(error) {
+ assertTrue(false, "Export failed: " + error);
+ })
+ .then(callbackPass(function(signature) {
+ assertTrue(!!signature, "No signature.");
+ assertTrue(signature.length != 0, "Signature is empty.");
+ cachedSignature = signature;
+ return window.crypto.subtle.importKey(
+ "spki", cachedSpki, algorithm, false, ["verify"]);
+ }),
+ function(error) { assertTrue(false, "Sign failed: " + error); })
+ .then(callbackPass(function(webCryptoPublicKey) {
+ assertTrue(!!webCryptoPublicKey);
+ assertEq(algorithm.modulusLength,
+ webCryptoPublicKey.algorithm.modulusLength);
+ return window.crypto.subtle.verify(
+ algorithm, webCryptoPublicKey, cachedSignature, data);
+ }),
+ function(error) {
+ assertTrue(false, "Import failed: " + error);
+ })
+ .then(callbackPass(function(success) {
+ assertEq(true, success, "Signature invalid.");
+ }),
+ function(error) {
+ assertTrue(false, "Verification failed: " + error);
+ });
+ },
+
+ // Imports and removes certificates for privateKeyPkcs8, which was imported
+ // by on C++'s side.
+ // Note: After this test, privateKeyPkcs8 is not stored anymore!
+ function importAndRemoveCerts() {
+ runAsyncSequence([
+ chrome.enterprise.platformKeys.importCertificate.bind(
+ null, userToken.id, cert1a.buffer),
+ assertCertsStored.bind(null, userToken, [cert1a]),
+ // Importing the same cert again shouldn't change anything.
+ chrome.enterprise.platformKeys.importCertificate.bind(
+ null, userToken.id, cert1a.buffer),
+ assertCertsStored.bind(null, userToken, [cert1a]),
+ // Importing another certificate should succeed.
+ chrome.enterprise.platformKeys.importCertificate.bind(
+ null, userToken.id, cert1b.buffer),
+ assertCertsStored.bind(null, userToken, [cert1a, cert1b]),
+ chrome.enterprise.platformKeys.removeCertificate.bind(
+ null, userToken.id, cert1a.buffer),
+ assertCertsStored.bind(null, userToken, [cert1b]),
+ chrome.enterprise.platformKeys.removeCertificate.bind(
+ null, userToken.id, cert1b.buffer),
+ assertCertsStored.bind(null, userToken, [])
+ ]);
+ },
+
+ // Imports a certificate for which now private key was imported/generated
+ // before.
+ function missingPrivateKey() {
+ chrome.enterprise.platformKeys.importCertificate(
+ userToken.id, cert2.buffer, callbackFail('Key not found.'));
+ },
+ function importInvalidCert() {
+ var invalidCert = new ArrayBuffer(16);
+ chrome.enterprise.platformKeys.importCertificate(
+ userToken.id,
+ invalidCert,
+ callbackFail('Certificate is not a valid X.509 certificate.'));
+ },
+ function removeUnknownCert() {
+ chrome.enterprise.platformKeys.removeCertificate(
+ userToken.id,
+ cert2.buffer,
+ callbackFail('Certificate could not be found.'));
+ },
+ function removeInvalidCert() {
+ var invalidCert = new ArrayBuffer(16);
+ chrome.enterprise.platformKeys.removeCertificate(
+ userToken.id,
+ invalidCert,
+ callbackFail('Certificate is not a valid X.509 certificate.'));
+ },
+ function getCertsInvalidToken() {
+ chrome.enterprise.platformKeys.getCertificates(
+ 'invalid token id', callbackFail('The token is not valid.'));
+ }
+ ]);
+}
+
+beforeTests(runTests);
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/manifest.json b/chrome/test/data/extensions/api_test/enterprise_platform_keys/manifest.json
new file mode 100644
index 0000000..a302425
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/manifest.json
@@ -0,0 +1,10 @@
+{
+ // chrome-extension://aecpbnckhoppanpmefllkdkohionpmig
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnAqVJvUof3ub8XQKnA8XUWxjcvnmsQD0z8JIuPzjXpaF07A1r52GT+O8m1b/8LS1iz9N8lqjT88tSUVPjrHIaRVt1En2bwOUnNqICxGCPSv+TGzqZOn2mompNKnnKR4b//xBDTcwgJ/QkVWh1l5iVXx2jJcZmXiQfuHRcmhT/lyfr4FD9puhfnt2l+OEJWBVMJgvesDTF0WPAzwdhsPrXemfv1FFbWINkriuWFCeYx03xx5szN83xsJxKf7xQ5hfh4PcsSSA/Q8oaEOXH6/QOFoDNdqYrjmpf4proilRvhoGEgeT+XSrIErRh6y1ZUatqb9VpMKRcw9rOA1epxA0wIDAQAB",
+ "name": "Basic tests",
+ "version": "0.1",
+ "manifest_version": 2,
+ "permissions": [
+ "enterprise.platformKeys"
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml b/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml
new file mode 100644
index 0000000..9487a30
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys/update_manifest.xml
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+ This update manifest points to the enterprise_platform_keys.crx file. It is
+ meant to be used with a URLRequestMockHTTPJob.
+-->
+<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
+ <app appid='aecpbnckhoppanpmefllkdkohionpmig'>
+ <updatecheck
+ codebase='http://mock.http/extensions/api_test/enterprise_platform_keys.crx'
+ version='1.0.0.0' />
+ </app>
+</gupdate>
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 58fee8b..95b8b75 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -834,6 +834,12 @@ enum HistogramValue {
FILESYSTEMPROVIDERINTERNAL_READFILEREQUESTEDERROR,
NETWORKINGPRIVATE_GETNETWORKS,
WEBVIEW_SETNAME,
+ ENTERPRISE_PLATFORMKEYSINTERNAL_GENERATEKEY,
+ ENTERPRISE_PLATFORMKEYSINTERNAL_SIGN,
+ ENTERPRISE_PLATFORMKEYSINTERNAL_GETTOKENS,
+ ENTERPRISE_PLATFORMKEYS_GETCERTIFICATES,
+ ENTERPRISE_PLATFORMKEYS_IMPORTCERTIFICATE,
+ ENTERPRISE_PLATFORMKEYS_REMOVECERTIFICATE,
// Last entry: Add new entries above and ensure to update
// tools/metrics/histograms/histograms/histograms.xml.
ENUM_BOUNDARY
diff --git a/extensions/common/permissions/api_permission.h b/extensions/common/permissions/api_permission.h
index b38a057..ab9e72a 100644
--- a/extensions/common/permissions/api_permission.h
+++ b/extensions/common/permissions/api_permission.h
@@ -78,6 +78,7 @@ class APIPermission {
kDownloadsOpen,
kDownloadsShelf,
kEchoPrivate,
+ kEnterprisePlatformKeys,
kEnterprisePlatformKeysPrivate,
kExperimental,
kFeedbackPrivate,
diff --git a/net/cert/nss_cert_database.cc b/net/cert/nss_cert_database.cc
index ed861b2..c1fc4f9 100644
--- a/net/cert/nss_cert_database.cc
+++ b/net/cert/nss_cert_database.cc
@@ -17,6 +17,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/observer_list_threadsafe.h"
#include "base/task_runner.h"
+#include "base/task_runner_util.h"
#include "base/threading/worker_pool.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
@@ -65,7 +66,8 @@ NSSCertDatabase* NSSCertDatabase::GetInstance() {
}
NSSCertDatabase::NSSCertDatabase()
- : observer_list_(new ObserverListThreadSafe<Observer>) {
+ : observer_list_(new ObserverListThreadSafe<Observer>),
+ weak_factory_(this) {
// This also makes sure that NSS has been initialized.
CertDatabase::GetInstance()->ObserveNSSCertDatabase(this);
@@ -75,18 +77,34 @@ NSSCertDatabase::NSSCertDatabase()
NSSCertDatabase::~NSSCertDatabase() {}
void NSSCertDatabase::ListCertsSync(CertificateList* certs) {
- ListCertsImpl(certs);
+ ListCertsImpl(crypto::ScopedPK11Slot(), certs);
}
void NSSCertDatabase::ListCerts(
const base::Callback<void(scoped_ptr<CertificateList> certs)>& callback) {
scoped_ptr<CertificateList> certs(new CertificateList());
- // base::Pased will NULL out |certs|, so cache the underlying pointer here.
+ // base::Passed will NULL out |certs|, so cache the underlying pointer here.
CertificateList* raw_certs = certs.get();
GetSlowTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&NSSCertDatabase::ListCertsImpl,
+ base::Passed(crypto::ScopedPK11Slot()),
+ base::Unretained(raw_certs)),
+ base::Bind(callback, base::Passed(&certs)));
+}
+
+void NSSCertDatabase::ListCertsInSlot(const ListCertsCallback& callback,
+ PK11SlotInfo* slot) {
+ DCHECK(slot);
+ scoped_ptr<CertificateList> certs(new CertificateList());
+
+ // base::Passed will NULL out |certs|, so cache the underlying pointer here.
+ CertificateList* raw_certs = certs.get();
+ GetSlowTaskRunner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&NSSCertDatabase::ListCertsImpl,
+ base::Passed(crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot))),
base::Unretained(raw_certs)),
base::Bind(callback, base::Passed(&certs)));
}
@@ -313,31 +331,26 @@ bool NSSCertDatabase::SetCertTrust(const X509Certificate* cert,
return success;
}
-bool NSSCertDatabase::DeleteCertAndKey(const X509Certificate* cert) {
- // For some reason, PK11_DeleteTokenCertAndKey only calls
- // SEC_DeletePermCertificate if the private key is found. So, we check
- // whether a private key exists before deciding which function to call to
- // delete the cert.
- SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert(cert->os_cert_handle(),
- NULL);
- if (privKey) {
- SECKEY_DestroyPrivateKey(privKey);
- if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) {
- LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError();
- return false;
- }
- } else {
- if (SEC_DeletePermCertificate(cert->os_cert_handle())) {
- LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError();
- return false;
- }
- }
-
+bool NSSCertDatabase::DeleteCertAndKey(X509Certificate* cert) {
+ if (!DeleteCertAndKeyImpl(cert))
+ return false;
NotifyObserversOfCertRemoved(cert);
-
return true;
}
+void NSSCertDatabase::DeleteCertAndKeyAsync(
+ const scoped_refptr<X509Certificate>& cert,
+ const DeleteCertCallback& callback) {
+ base::PostTaskAndReplyWithResult(
+ GetSlowTaskRunner().get(),
+ FROM_HERE,
+ base::Bind(&NSSCertDatabase::DeleteCertAndKeyImpl, cert),
+ base::Bind(&NSSCertDatabase::NotifyCertRemovalAndCallBack,
+ weak_factory_.GetWeakPtr(),
+ cert,
+ callback));
+}
+
bool NSSCertDatabase::IsReadOnly(const X509Certificate* cert) const {
PK11SlotInfo* slot = cert->os_cert_handle()->slot;
return slot && PK11_IsReadOnly(slot);
@@ -362,13 +375,18 @@ void NSSCertDatabase::SetSlowTaskRunnerForTest(
}
// static
-void NSSCertDatabase::ListCertsImpl(CertificateList* certs) {
+void NSSCertDatabase::ListCertsImpl(crypto::ScopedPK11Slot slot,
+ CertificateList* certs) {
certs->clear();
- CERTCertList* cert_list = PK11_ListCerts(PK11CertListUnique, NULL);
+ CERTCertList* cert_list = NULL;
+ if (slot)
+ cert_list = PK11_ListCertsInSlot(slot.get());
+ else
+ cert_list = PK11_ListCerts(PK11CertListUnique, NULL);
+
CERTCertListNode* node;
- for (node = CERT_LIST_HEAD(cert_list);
- !CERT_LIST_END(node, cert_list);
+ for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list);
node = CERT_LIST_NEXT(node)) {
certs->push_back(X509Certificate::CreateFromHandle(
node->cert, X509Certificate::OSCertHandles()));
@@ -382,6 +400,15 @@ scoped_refptr<base::TaskRunner> NSSCertDatabase::GetSlowTaskRunner() const {
return base::WorkerPool::GetTaskRunner(true /*task is slow*/);
}
+void NSSCertDatabase::NotifyCertRemovalAndCallBack(
+ scoped_refptr<X509Certificate> cert,
+ const DeleteCertCallback& callback,
+ bool success) {
+ if (success)
+ NotifyObserversOfCertRemoved(cert);
+ callback.Run(success);
+}
+
void NSSCertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) {
observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert));
}
@@ -397,4 +424,28 @@ void NSSCertDatabase::NotifyObserversOfCACertChanged(
&Observer::OnCACertChanged, make_scoped_refptr(cert));
}
+// static
+bool NSSCertDatabase::DeleteCertAndKeyImpl(
+ scoped_refptr<X509Certificate> cert) {
+ // For some reason, PK11_DeleteTokenCertAndKey only calls
+ // SEC_DeletePermCertificate if the private key is found. So, we check
+ // whether a private key exists before deciding which function to call to
+ // delete the cert.
+ SECKEYPrivateKey* privKey =
+ PK11_FindKeyByAnyCert(cert->os_cert_handle(), NULL);
+ if (privKey) {
+ SECKEY_DestroyPrivateKey(privKey);
+ if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) {
+ LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError();
+ return false;
+ }
+ } else {
+ if (SEC_DeletePermCertificate(cert->os_cert_handle())) {
+ LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError();
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace net
diff --git a/net/cert/nss_cert_database.h b/net/cert/nss_cert_database.h
index df52e58..1c4daf8 100644
--- a/net/cert/nss_cert_database.h
+++ b/net/cert/nss_cert_database.h
@@ -11,8 +11,10 @@
#include "base/basictypes.h"
#include "base/callback_forward.h"
#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "crypto/scoped_nss_types.h"
+#include "net/base/net_errors.h"
#include "net/base/net_export.h"
#include "net/cert/cert_type.h"
#include "net/cert/x509_certificate.h"
@@ -96,6 +98,8 @@ class NET_EXPORT NSSCertDatabase {
typedef base::Callback<void(scoped_ptr<CertificateList> certs)>
ListCertsCallback;
+ typedef base::Callback<void(bool)> DeleteCertCallback;
+
// DEPRECATED: See http://crbug.com/329735.
static NSSCertDatabase* GetInstance();
@@ -109,6 +113,14 @@ class NET_EXPORT NSSCertDatabase {
// run even after the database is deleted.
virtual void ListCerts(const ListCertsCallback& callback);
+ // Get a list of certificates in the certificate database of the given slot.
+ // Note that the callback may be run even after the database is deleted.
+ // Must be called on the IO thread and it calls |callback| on the IO thread.
+ // This does not block by retrieving the certs asynchronously on a worker
+ // thread. Never calls |callback| synchronously.
+ virtual void ListCertsInSlot(const ListCertsCallback& callback,
+ PK11SlotInfo* slot);
+
// Get the default slot for public key data.
virtual crypto::ScopedPK11Slot GetPublicSlot() const;
@@ -199,7 +211,13 @@ class NET_EXPORT NSSCertDatabase {
// Delete certificate and associated private key (if one exists).
// |cert| is still valid when this function returns. Returns true on
// success.
- bool DeleteCertAndKey(const X509Certificate* cert);
+ bool DeleteCertAndKey(X509Certificate* cert);
+
+ // Like DeleteCertAndKey but does not block by running the removal on a worker
+ // thread. This must be called on IO thread and it will run |callback| on IO
+ // thread. Never calls |callback| synchronously.
+ void DeleteCertAndKeyAsync(const scoped_refptr<X509Certificate>& cert,
+ const DeleteCertCallback& callback);
// Check whether cert is stored in a readonly slot.
bool IsReadOnly(const X509Certificate* cert) const;
@@ -228,9 +246,12 @@ class NET_EXPORT NSSCertDatabase {
NSSCertDatabase();
virtual ~NSSCertDatabase();
- // Certificate listing implementation used by |ListCerts| and |ListCertsSync|.
- // Static so it may safely be used on the worker thread.
- static void ListCertsImpl(CertificateList* certs);
+ // Certificate listing implementation used by |ListCerts*| and
+ // |ListCertsSync|. Static so it may safely be used on the worker thread.
+ // If |slot| is NULL, obtains the certs of all slots, otherwise only of
+ // |slot|.
+ static void ListCertsImpl(crypto::ScopedPK11Slot slot,
+ CertificateList* certs);
// Gets task runner that should be used for slow tasks like certificate
// listing. Defaults to a base::WorkerPool runner, but may be overriden
@@ -240,16 +261,28 @@ class NET_EXPORT NSSCertDatabase {
private:
friend struct base::DefaultLazyInstanceTraits<NSSCertDatabase>;
+ // Notifies observers of the removal of |cert| and calls |callback| with
+ // |success| as argument.
+ void NotifyCertRemovalAndCallBack(scoped_refptr<X509Certificate> cert,
+ const DeleteCertCallback& callback,
+ bool success);
+
// Broadcasts notifications to all registered observers.
void NotifyObserversOfCertAdded(const X509Certificate* cert);
void NotifyObserversOfCertRemoved(const X509Certificate* cert);
void NotifyObserversOfCACertChanged(const X509Certificate* cert);
+ // Certificate removal implementation used by |DeleteCertAndKey*|. Static so
+ // it may safely be used on the worker thread.
+ static bool DeleteCertAndKeyImpl(scoped_refptr<X509Certificate> cert);
+
// Task runner that should be used in tests if set.
scoped_refptr<base::TaskRunner> slow_task_runner_for_test_;
const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_;
+ base::WeakPtrFactory<NSSCertDatabase> weak_factory_;
+
DISALLOW_COPY_AND_ASSIGN(NSSCertDatabase);
};
diff --git a/net/cert/nss_cert_database_chromeos.cc b/net/cert/nss_cert_database_chromeos.cc
index 79e5781..41852a7 100644
--- a/net/cert/nss_cert_database_chromeos.cc
+++ b/net/cert/nss_cert_database_chromeos.cc
@@ -75,7 +75,7 @@ void NSSCertDatabaseChromeOS::ListModules(CryptoModuleList* modules,
void NSSCertDatabaseChromeOS::ListCertsImpl(
const NSSProfileFilterChromeOS& profile_filter,
CertificateList* certs) {
- NSSCertDatabase::ListCertsImpl(certs);
+ NSSCertDatabase::ListCertsImpl(crypto::ScopedPK11Slot(), certs);
size_t pre_size = certs->size();
certs->erase(std::remove_if(
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index cf1145c..8cb3db1 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -34845,6 +34845,12 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="773" label="FILESYSTEMPROVIDERINTERNAL_READFILEREQUESTEDERROR"/>
<int value="774" label="NETWORKINGPRIVATE_GETNETWORKS"/>
<int value="775" label="WEBVIEW_SETNAME"/>
+ <int value="776" label="ENTERPRISE_PLATFORMKEYSINTERNAL_GENERATEKEY"/>
+ <int value="777" label="ENTERPRISE_PLATFORMKEYSINTERNAL_SIGN"/>
+ <int value="778" label="ENTERPRISE_PLATFORMKEYSINTERNAL_GETTOKENS"/>
+ <int value="779" label="ENTERPRISE_PLATFORMKEYS_GETCERTIFICATES"/>
+ <int value="780" label="ENTERPRISE_PLATFORMKEYS_IMPORTCERTIFICATE"/>
+ <int value="781" label="ENTERPRISE_PLATFORMKEYS_REMOVECERTIFICATE"/>
</enum>
<enum name="ExtensionInstallCause" type="int">