Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(188)

Unified Diff: chrome/browser/chromeos/platform_keys/platform_keys_nss.cc

Issue 214863002: Extension API enterprise.platformKeys. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Asynchronous calls revisited. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/chromeos/platform_keys/platform_keys_nss.cc
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 0000000000000000000000000000000000000000..b8e311bf7ea4a39b8a600b5287bb32472efb4ffc
--- /dev/null
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_nss.cc
@@ -0,0 +1,577 @@
+// 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:
+ explicit NSSOperationState(Profile* profile);
+ 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;
+
+ Profile* profile_;
+ 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 for |token_id| and passes it to
+// |callback|. Will run |callback| on the IO thread.
+void GetCertDatabase(const std::string& token_id,
+ const GetCertDBCallback& callback,
+ NSSOperationState* state) {
+ // TODO(pneubeck): Decide which DB to retrieve depending on |token_id|.
+ BrowserThread::PostTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&GetCertDatabaseOnIOThread,
+ state->profile_->GetResourceContext(),
+ callback,
+ state));
+}
+
+class GenerateRSAKeyState : public NSSOperationState {
+ public:
+ GenerateRSAKeyState(unsigned int modulus_length,
+ const GenerateKeyCallback& callback,
+ Profile* profile);
+ 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,
+ Profile* profile);
+ 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:
+ GetCertificatesState(const GetCertificatesCallback& callback,
+ Profile* profile);
+ 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,
+ Profile* profile);
+ 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,
+ Profile* profile);
+ 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(Profile* profile)
+ : profile_(profile),
+ origin_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+}
+
+GenerateRSAKeyState::GenerateRSAKeyState(unsigned int modulus_length,
+ const GenerateKeyCallback& callback,
+ Profile* profile)
+ : NSSOperationState(profile),
+ modulus_length_(modulus_length),
+ callback_(callback) {
+}
+
+SignState::SignState(const std::string& public_key,
+ const std::string& data,
+ const SignCallback& callback,
+ Profile* profile)
+ : NSSOperationState(profile),
+ public_key_(public_key),
+ data_(data),
+ callback_(callback) {
+}
+
+GetCertificatesState::GetCertificatesState(
+ const GetCertificatesCallback& callback,
+ Profile* profile)
+ : NSSOperationState(profile), callback_(callback) {
+}
+
+ImportCertificateState::ImportCertificateState(
+ scoped_refptr<net::X509Certificate> certificate,
+ const ImportCertificateCallback& callback,
+ Profile* profile)
eroman 2014/05/19 23:27:45 The ownership model here seems ill defined. There
pneubeck (no reviews) 2014/05/20 09:29:21 I fixed Profile*, by passing it explicitly. It's n
+ : NSSOperationState(profile),
+ certificate_(certificate),
+ callback_(callback) {
+}
+
+RemoveCertificateState::RemoveCertificateState(
+ scoped_refptr<net::X509Certificate> certificate,
+ const RemoveCertificateCallback& callback,
+ Profile* profile)
+ : NSSOperationState(profile),
+ 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 = 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 = 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, profile));
+
+ 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)),
+ 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, profile));
+ // 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)), 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, profile));
+ // Get the pointer to |state| before base::Passed releases |state|.
+ NSSOperationState* state_ptr = state.get();
+ GetCertDatabase(token_id,
+ base::Bind(&GetCertificatesWithDB, base::Passed(&state)),
+ 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, profile));
+ // 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)),
+ 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, profile));
+ // 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)),
+ state_ptr);
+}
+
+} // namespace platform_keys
+
+} // namespace chromeos

Powered by Google App Engine
This is Rietveld 408576698