// 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 "content/child/webcrypto/webcrypto_impl.h" #include "base/bind.h" #include "base/lazy_instance.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" #include "content/child/webcrypto/algorithm_dispatch.h" #include "content/child/webcrypto/crypto_data.h" #include "content/child/webcrypto/status.h" #include "content/child/webcrypto/structured_clone.h" #include "content/child/webcrypto/webcrypto_util.h" #include "content/child/worker_thread_task_runner.h" #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" #include "third_party/WebKit/public/platform/WebString.h" namespace content { using webcrypto::Status; namespace { // --------------------- // Threading // --------------------- // // WebCrypto operations can be slow. For instance generating an RSA key can // take hundreds of milliseconds to several seconds. // // Moreover the underlying crypto libraries are not threadsafe when operating // on the same key. // // The strategy used here is to run a sequenced worker pool for all WebCrypto // operations. This pool (of 1 threads) is also used by requests started from // Blink Web Workers. // // A few notes to keep in mind: // // * PostTaskAndReply() cannot be used for two reasons: // // (1) Blink web worker threads do not have an associated message loop so // construction of the reply callback will crash. // // (2) PostTaskAndReply() handles failure posting the reply by leaking the // callback, rather than destroying it. In the case of Web Workers this // condition is reachable via normal execution, since Web Workers can // be stopped before the WebCrypto operation has finished. A policy of // leaking would therefore be problematic. // // * blink::WebArrayBuffer is NOT threadsafe, and should therefore be allocated // on the target Blink thread. // // TODO(eroman): Is there any way around this? Copying the result between // threads is silly. // // * WebCryptoAlgorithm and WebCryptoKey are threadsafe (however the key's // handle(), which wraps an NSS/OpenSSL type, may not be and should only be // used from the webcrypto thread). // // * blink::WebCryptoResult is not threadsafe and should only be operated on // the target Blink thread. HOWEVER, it is safe to delete it from any thread. // This can happen if by the time the operation has completed in the crypto // worker pool, the Blink worker thread that initiated the request is gone. // Posting back to the origin thread will fail, and the WebCryptoResult will // be deleted while running in the crypto worker pool. class CryptoThreadPool { public: CryptoThreadPool() : worker_pool_(new base::SequencedWorkerPool(1, "WebCrypto")), task_runner_(worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior( worker_pool_->GetSequenceToken(), base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)) {} static bool PostTask(const tracked_objects::Location& from_here, const base::Closure& task); private: scoped_refptr<base::SequencedWorkerPool> worker_pool_; scoped_refptr<base::SequencedTaskRunner> task_runner_; }; base::LazyInstance<CryptoThreadPool>::Leaky crypto_thread_pool = LAZY_INSTANCE_INITIALIZER; bool CryptoThreadPool::PostTask(const tracked_objects::Location& from_here, const base::Closure& task) { return crypto_thread_pool.Get().task_runner_->PostTask(from_here, task); } void CompleteWithThreadPoolError(blink::WebCryptoResult* result) { result->completeWithError(blink::WebCryptoErrorTypeOperation, "Failed posting to crypto worker pool"); } void CompleteWithError(const Status& status, blink::WebCryptoResult* result) { DCHECK(status.IsError()); result->completeWithError(status.error_type(), blink::WebString::fromUTF8(status.error_details())); } void CompleteWithBufferOrError(const Status& status, const std::vector<uint8_t>& buffer, blink::WebCryptoResult* result) { if (status.IsError()) { CompleteWithError(status, result); } else { if (buffer.size() > UINT_MAX) { // WebArrayBuffers have a smaller range than std::vector<>, so // theoretically this could overflow. CompleteWithError(Status::ErrorUnexpected(), result); } else { result->completeWithBuffer(vector_as_array(&buffer), buffer.size()); } } } void CompleteWithKeyOrError(const Status& status, const blink::WebCryptoKey& key, blink::WebCryptoResult* result) { if (status.IsError()) { CompleteWithError(status, result); } else { result->completeWithKey(key); } } // Gets a task runner for the current thread. The current thread is either: // // * The main Blink thread // * A Blink web worker thread // // A different mechanism is needed for posting to these threads. The main // thread has an associated message loop and can simply use // base::ThreadTaskRunnerHandle. Whereas the web worker threads are managed by // Blink and need to be indirected through WorkerThreadTaskRunner. scoped_refptr<base::TaskRunner> GetCurrentBlinkThread() { if (base::ThreadTaskRunnerHandle::IsSet()) return base::ThreadTaskRunnerHandle::Get(); return WorkerThreadTaskRunner::current(); } // -------------------------------------------------------------------- // State // -------------------------------------------------------------------- // // Explicit state classes are used rather than base::Bind(). This is done // both for clarity, but also to avoid extraneous allocations for things // like passing buffers and result objects between threads. // // BaseState is the base class common to all of the async operations, and // keeps track of the thread to complete on, the error state, and the // callback into Blink. // // Ownership of the State object is passed between the crypto thread and the // Blink thread. Under normal completion it is destroyed on the Blink thread. // However it may also be destroyed on the crypto thread if the Blink thread // has vanished (which can happen for Blink web worker threads). struct BaseState { explicit BaseState(const blink::WebCryptoResult& result) : origin_thread(GetCurrentBlinkThread()), result(result) {} bool cancelled() { return result.cancelled(); } scoped_refptr<base::TaskRunner> origin_thread; webcrypto::Status status; blink::WebCryptoResult result; protected: // Since there is no virtual destructor, must not delete directly as a // BaseState. ~BaseState() {} }; struct EncryptState : public BaseState { EncryptState(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* data, unsigned int data_size, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), key(key), data(data, data + data_size) {} const blink::WebCryptoAlgorithm algorithm; const blink::WebCryptoKey key; const std::vector<uint8_t> data; std::vector<uint8_t> buffer; }; typedef EncryptState DecryptState; typedef EncryptState DigestState; struct GenerateKeyState : public BaseState { GenerateKeyState(const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usage_mask, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), extractable(extractable), usage_mask(usage_mask), public_key(blink::WebCryptoKey::createNull()), private_key(blink::WebCryptoKey::createNull()), is_asymmetric(false) {} const blink::WebCryptoAlgorithm algorithm; const bool extractable; const blink::WebCryptoKeyUsageMask usage_mask; // If |is_asymmetric| is false, then |public_key| is understood to mean the // symmetric key, and |private_key| is unused. blink::WebCryptoKey public_key; blink::WebCryptoKey private_key; bool is_asymmetric; }; struct ImportKeyState : public BaseState { ImportKeyState(blink::WebCryptoKeyFormat format, const unsigned char* key_data, unsigned int key_data_size, const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usage_mask, const blink::WebCryptoResult& result) : BaseState(result), format(format), key_data(key_data, key_data + key_data_size), algorithm(algorithm), extractable(extractable), usage_mask(usage_mask), key(blink::WebCryptoKey::createNull()) {} const blink::WebCryptoKeyFormat format; const std::vector<uint8_t> key_data; const blink::WebCryptoAlgorithm algorithm; const bool extractable; const blink::WebCryptoKeyUsageMask usage_mask; blink::WebCryptoKey key; }; struct ExportKeyState : public BaseState { ExportKeyState(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& key, const blink::WebCryptoResult& result) : BaseState(result), format(format), key(key) {} const blink::WebCryptoKeyFormat format; const blink::WebCryptoKey key; std::vector<uint8_t> buffer; }; typedef EncryptState SignState; struct VerifySignatureState : public BaseState { VerifySignatureState(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* signature, unsigned int signature_size, const unsigned char* data, unsigned int data_size, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), key(key), signature(signature, signature + signature_size), data(data, data + data_size), verify_result(false) {} const blink::WebCryptoAlgorithm algorithm; const blink::WebCryptoKey key; const std::vector<uint8_t> signature; const std::vector<uint8_t> data; bool verify_result; }; struct WrapKeyState : public BaseState { WrapKeyState(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& key, const blink::WebCryptoKey& wrapping_key, const blink::WebCryptoAlgorithm& wrap_algorithm, const blink::WebCryptoResult& result) : BaseState(result), format(format), key(key), wrapping_key(wrapping_key), wrap_algorithm(wrap_algorithm) {} const blink::WebCryptoKeyFormat format; const blink::WebCryptoKey key; const blink::WebCryptoKey wrapping_key; const blink::WebCryptoAlgorithm wrap_algorithm; std::vector<uint8_t> buffer; }; struct UnwrapKeyState : public BaseState { UnwrapKeyState(blink::WebCryptoKeyFormat format, const unsigned char* wrapped_key, unsigned wrapped_key_size, const blink::WebCryptoKey& wrapping_key, const blink::WebCryptoAlgorithm& unwrap_algorithm, const blink::WebCryptoAlgorithm& unwrapped_key_algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, const blink::WebCryptoResult& result) : BaseState(result), format(format), wrapped_key(wrapped_key, wrapped_key + wrapped_key_size), wrapping_key(wrapping_key), unwrap_algorithm(unwrap_algorithm), unwrapped_key_algorithm(unwrapped_key_algorithm), extractable(extractable), usages(usages), unwrapped_key(blink::WebCryptoKey::createNull()) {} const blink::WebCryptoKeyFormat format; const std::vector<uint8_t> wrapped_key; const blink::WebCryptoKey wrapping_key; const blink::WebCryptoAlgorithm unwrap_algorithm; const blink::WebCryptoAlgorithm unwrapped_key_algorithm; const bool extractable; const blink::WebCryptoKeyUsageMask usages; blink::WebCryptoKey unwrapped_key; }; // -------------------------------------------------------------------- // Wrapper functions // -------------------------------------------------------------------- // // * The methods named Do*() run on the crypto thread. // * The methods named Do*Reply() run on the target Blink thread void DoEncryptReply(scoped_ptr<EncryptState> state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoEncrypt(scoped_ptr<EncryptState> passed_state) { EncryptState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::Encrypt(state->algorithm, state->key, webcrypto::CryptoData(state->data), &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoEncryptReply, Passed(&passed_state))); } void DoDecryptReply(scoped_ptr<DecryptState> state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoDecrypt(scoped_ptr<DecryptState> passed_state) { DecryptState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::Decrypt(state->algorithm, state->key, webcrypto::CryptoData(state->data), &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoDecryptReply, Passed(&passed_state))); } void DoDigestReply(scoped_ptr<DigestState> state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoDigest(scoped_ptr<DigestState> passed_state) { DigestState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::Digest( state->algorithm, webcrypto::CryptoData(state->data), &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoDigestReply, Passed(&passed_state))); } void DoGenerateKeyReply(scoped_ptr<GenerateKeyState> state) { if (state->status.IsError()) { CompleteWithError(state->status, &state->result); } else { if (state->is_asymmetric) state->result.completeWithKeyPair(state->public_key, state->private_key); else state->result.completeWithKey(state->public_key); } } void DoGenerateKey(scoped_ptr<GenerateKeyState> passed_state) { GenerateKeyState* state = passed_state.get(); if (state->cancelled()) return; state->is_asymmetric = webcrypto::IsAlgorithmAsymmetric(state->algorithm.id()); if (state->is_asymmetric) { state->status = webcrypto::GenerateKeyPair(state->algorithm, state->extractable, state->usage_mask, &state->public_key, &state->private_key); if (state->status.IsSuccess()) { DCHECK(state->public_key.handle()); DCHECK(state->private_key.handle()); DCHECK_EQ(state->algorithm.id(), state->public_key.algorithm().id()); DCHECK_EQ(state->algorithm.id(), state->private_key.algorithm().id()); DCHECK_EQ(true, state->public_key.extractable()); DCHECK_EQ(state->extractable, state->private_key.extractable()); } } else { blink::WebCryptoKey* key = &state->public_key; state->status = webcrypto::GenerateSecretKey( state->algorithm, state->extractable, state->usage_mask, key); if (state->status.IsSuccess()) { DCHECK(key->handle()); DCHECK_EQ(state->algorithm.id(), key->algorithm().id()); DCHECK_EQ(state->extractable, key->extractable()); DCHECK_EQ(state->usage_mask, key->usages()); } } state->origin_thread->PostTask( FROM_HERE, base::Bind(DoGenerateKeyReply, Passed(&passed_state))); } void DoImportKeyReply(scoped_ptr<ImportKeyState> state) { CompleteWithKeyOrError(state->status, state->key, &state->result); } void DoImportKey(scoped_ptr<ImportKeyState> passed_state) { ImportKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::ImportKey(state->format, webcrypto::CryptoData(state->key_data), state->algorithm, state->extractable, state->usage_mask, &state->key); if (state->status.IsSuccess()) { DCHECK(state->key.handle()); DCHECK(!state->key.algorithm().isNull()); DCHECK_EQ(state->extractable, state->key.extractable()); } state->origin_thread->PostTask( FROM_HERE, base::Bind(DoImportKeyReply, Passed(&passed_state))); } void DoExportKeyReply(scoped_ptr<ExportKeyState> state) { if (state->format != blink::WebCryptoKeyFormatJwk) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); return; } if (state->status.IsError()) { CompleteWithError(state->status, &state->result); } else { state->result.completeWithJson( reinterpret_cast<const char*>(vector_as_array(&state->buffer)), state->buffer.size()); } } void DoExportKey(scoped_ptr<ExportKeyState> passed_state) { ExportKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::ExportKey(state->format, state->key, &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoExportKeyReply, Passed(&passed_state))); } void DoSignReply(scoped_ptr<SignState> state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoSign(scoped_ptr<SignState> passed_state) { SignState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::Sign(state->algorithm, state->key, webcrypto::CryptoData(state->data), &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoSignReply, Passed(&passed_state))); } void DoVerifyReply(scoped_ptr<VerifySignatureState> state) { if (state->status.IsError()) { CompleteWithError(state->status, &state->result); } else { state->result.completeWithBoolean(state->verify_result); } } void DoVerify(scoped_ptr<VerifySignatureState> passed_state) { VerifySignatureState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::Verify(state->algorithm, state->key, webcrypto::CryptoData(state->signature), webcrypto::CryptoData(state->data), &state->verify_result); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoVerifyReply, Passed(&passed_state))); } void DoWrapKeyReply(scoped_ptr<WrapKeyState> state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoWrapKey(scoped_ptr<WrapKeyState> passed_state) { WrapKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::WrapKey(state->format, state->key, state->wrapping_key, state->wrap_algorithm, &state->buffer); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoWrapKeyReply, Passed(&passed_state))); } void DoUnwrapKeyReply(scoped_ptr<UnwrapKeyState> state) { CompleteWithKeyOrError(state->status, state->unwrapped_key, &state->result); } void DoUnwrapKey(scoped_ptr<UnwrapKeyState> passed_state) { UnwrapKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::UnwrapKey(state->format, webcrypto::CryptoData(state->wrapped_key), state->wrapping_key, state->unwrap_algorithm, state->unwrapped_key_algorithm, state->extractable, state->usages, &state->unwrapped_key); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoUnwrapKeyReply, Passed(&passed_state))); } } // namespace WebCryptoImpl::WebCryptoImpl() { } WebCryptoImpl::~WebCryptoImpl() { } void WebCryptoImpl::encrypt(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* data, unsigned int data_size, blink::WebCryptoResult result) { DCHECK(!algorithm.isNull()); scoped_ptr<EncryptState> state( new EncryptState(algorithm, key, data, data_size, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoEncrypt, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::decrypt(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* data, unsigned int data_size, blink::WebCryptoResult result) { DCHECK(!algorithm.isNull()); scoped_ptr<DecryptState> state( new DecryptState(algorithm, key, data, data_size, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoDecrypt, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::digest(const blink::WebCryptoAlgorithm& algorithm, const unsigned char* data, unsigned int data_size, blink::WebCryptoResult result) { DCHECK(!algorithm.isNull()); scoped_ptr<DigestState> state(new DigestState( algorithm, blink::WebCryptoKey::createNull(), data, data_size, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoDigest, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::generateKey(const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usage_mask, blink::WebCryptoResult result) { DCHECK(!algorithm.isNull()); scoped_ptr<GenerateKeyState> state( new GenerateKeyState(algorithm, extractable, usage_mask, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoGenerateKey, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::importKey(blink::WebCryptoKeyFormat format, const unsigned char* key_data, unsigned int key_data_size, const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usage_mask, blink::WebCryptoResult result) { scoped_ptr<ImportKeyState> state(new ImportKeyState(format, key_data, key_data_size, algorithm, extractable, usage_mask, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoImportKey, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::exportKey(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& key, blink::WebCryptoResult result) { scoped_ptr<ExportKeyState> state(new ExportKeyState(format, key, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoExportKey, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::sign(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* data, unsigned int data_size, blink::WebCryptoResult result) { scoped_ptr<SignState> state( new SignState(algorithm, key, data, data_size, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoSign, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::verifySignature(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& key, const unsigned char* signature, unsigned int signature_size, const unsigned char* data, unsigned int data_size, blink::WebCryptoResult result) { scoped_ptr<VerifySignatureState> state(new VerifySignatureState( algorithm, key, signature, signature_size, data, data_size, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoVerify, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::wrapKey(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& key, const blink::WebCryptoKey& wrapping_key, const blink::WebCryptoAlgorithm& wrap_algorithm, blink::WebCryptoResult result) { scoped_ptr<WrapKeyState> state( new WrapKeyState(format, key, wrapping_key, wrap_algorithm, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoWrapKey, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::unwrapKey( blink::WebCryptoKeyFormat format, const unsigned char* wrapped_key, unsigned wrapped_key_size, const blink::WebCryptoKey& wrapping_key, const blink::WebCryptoAlgorithm& unwrap_algorithm, const blink::WebCryptoAlgorithm& unwrapped_key_algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, blink::WebCryptoResult result) { scoped_ptr<UnwrapKeyState> state(new UnwrapKeyState(format, wrapped_key, wrapped_key_size, wrapping_key, unwrap_algorithm, unwrapped_key_algorithm, extractable, usages, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoUnwrapKey, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } blink::WebCryptoDigestor* WebCryptoImpl::createDigestor( blink::WebCryptoAlgorithmId algorithm_id) { return webcrypto::CreateDigestor(algorithm_id).release(); } bool WebCryptoImpl::deserializeKeyForClone( const blink::WebCryptoKeyAlgorithm& algorithm, blink::WebCryptoKeyType type, bool extractable, blink::WebCryptoKeyUsageMask usages, const unsigned char* key_data, unsigned key_data_size, blink::WebCryptoKey& key) { // TODO(eroman): Rather than do the import immediately on the current thread, // it could defer to the crypto thread. return webcrypto::DeserializeKeyForClone( algorithm, type, extractable, usages, webcrypto::CryptoData(key_data, key_data_size), &key); } bool WebCryptoImpl::serializeKeyForClone( const blink::WebCryptoKey& key, blink::WebVector<unsigned char>& key_data) { return webcrypto::SerializeKeyForClone(key, &key_data); } } // namespace content