diff options
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 Binary files differnew file mode 100644 index 0000000..449b588 --- /dev/null +++ b/chrome/test/data/extensions/api_test/enterprise_platform_keys.crx 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"> |