Chromium Code Reviews| 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 |