// 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 "components/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 "components/webcrypto/algorithm_dispatch.h" #include "components/webcrypto/crypto_data.h" #include "components/webcrypto/generate_key_result.h" #include "components/webcrypto/status.h" #include "components/webcrypto/webcrypto_util.h" #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" #include "third_party/WebKit/public/platform/WebString.h" namespace webcrypto { using webcrypto::Status; namespace { // --------------------- // Threading // --------------------- // // WebCrypto operations can be slow. For instance generating an RSA key can // 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 (except structured cloning). This same pool 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 worker_pool_; scoped_refptr task_runner_; }; base::LazyInstance::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& 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. scoped_refptr GetCurrentBlinkThread() { DCHECK(base::ThreadTaskRunnerHandle::IsSet()); return base::ThreadTaskRunnerHandle::Get(); } // -------------------------------------------------------------------- // 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 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 data; std::vector buffer; }; typedef EncryptState DecryptState; typedef EncryptState DigestState; struct GenerateKeyState : public BaseState { GenerateKeyState(const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), extractable(extractable), usages(usages) {} const blink::WebCryptoAlgorithm algorithm; const bool extractable; const blink::WebCryptoKeyUsageMask usages; webcrypto::GenerateKeyResult generate_key_result; }; 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 usages, const blink::WebCryptoResult& result) : BaseState(result), format(format), key_data(key_data, key_data + key_data_size), algorithm(algorithm), extractable(extractable), usages(usages) {} const blink::WebCryptoKeyFormat format; const std::vector key_data; const blink::WebCryptoAlgorithm algorithm; const bool extractable; const blink::WebCryptoKeyUsageMask usages; 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 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 signature; const std::vector 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 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) {} const blink::WebCryptoKeyFormat format; const std::vector 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; }; struct DeriveBitsState : public BaseState { DeriveBitsState(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& base_key, unsigned int length_bits, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), base_key(base_key), length_bits(length_bits) {} const blink::WebCryptoAlgorithm algorithm; const blink::WebCryptoKey base_key; const unsigned int length_bits; std::vector derived_bytes; }; struct DeriveKeyState : public BaseState { DeriveKeyState(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& base_key, const blink::WebCryptoAlgorithm& import_algorithm, const blink::WebCryptoAlgorithm& key_length_algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, const blink::WebCryptoResult& result) : BaseState(result), algorithm(algorithm), base_key(base_key), import_algorithm(import_algorithm), key_length_algorithm(key_length_algorithm), extractable(extractable), usages(usages) {} const blink::WebCryptoAlgorithm algorithm; const blink::WebCryptoKey base_key; const blink::WebCryptoAlgorithm import_algorithm; const blink::WebCryptoAlgorithm key_length_algorithm; bool extractable; blink::WebCryptoKeyUsageMask usages; blink::WebCryptoKey derived_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 state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoEncrypt(scoped_ptr 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 state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoDecrypt(scoped_ptr 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 state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoDigest(scoped_ptr 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 state) { if (state->status.IsError()) { CompleteWithError(state->status, &state->result); } else { state->generate_key_result.Complete(&state->result); } } void DoGenerateKey(scoped_ptr passed_state) { GenerateKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::GenerateKey(state->algorithm, state->extractable, state->usages, &state->generate_key_result); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoGenerateKeyReply, Passed(&passed_state))); } void DoImportKeyReply(scoped_ptr state) { CompleteWithKeyOrError(state->status, state->key, &state->result); } void DoImportKey(scoped_ptr 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->usages, &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 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(vector_as_array(&state->buffer)), state->buffer.size()); } } void DoExportKey(scoped_ptr 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 state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoSign(scoped_ptr 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 state) { if (state->status.IsError()) { CompleteWithError(state->status, &state->result); } else { state->result.completeWithBoolean(state->verify_result); } } void DoVerify(scoped_ptr 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 state) { CompleteWithBufferOrError(state->status, state->buffer, &state->result); } void DoWrapKey(scoped_ptr 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 state) { CompleteWithKeyOrError(state->status, state->unwrapped_key, &state->result); } void DoUnwrapKey(scoped_ptr 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))); } void DoDeriveBitsReply(scoped_ptr state) { CompleteWithBufferOrError(state->status, state->derived_bytes, &state->result); } void DoDeriveBits(scoped_ptr passed_state) { DeriveBitsState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::DeriveBits(state->algorithm, state->base_key, state->length_bits, &state->derived_bytes); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoDeriveBitsReply, Passed(&passed_state))); } void DoDeriveKeyReply(scoped_ptr state) { CompleteWithKeyOrError(state->status, state->derived_key, &state->result); } void DoDeriveKey(scoped_ptr passed_state) { DeriveKeyState* state = passed_state.get(); if (state->cancelled()) return; state->status = webcrypto::DeriveKey( state->algorithm, state->base_key, state->import_algorithm, state->key_length_algorithm, state->extractable, state->usages, &state->derived_key); state->origin_thread->PostTask( FROM_HERE, base::Bind(DoDeriveKeyReply, 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 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 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 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 usages, blink::WebCryptoResult result) { DCHECK(!algorithm.isNull()); scoped_ptr state( new GenerateKeyState(algorithm, extractable, usages, 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 usages, blink::WebCryptoResult result) { scoped_ptr state(new ImportKeyState( format, key_data, key_data_size, algorithm, extractable, usages, 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 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 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 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 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 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); } } void WebCryptoImpl::deriveBits(const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& base_key, unsigned int length_bits, blink::WebCryptoResult result) { scoped_ptr state( new DeriveBitsState(algorithm, base_key, length_bits, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoDeriveBits, Passed(&state)))) { CompleteWithThreadPoolError(&result); } } void WebCryptoImpl::deriveKey( const blink::WebCryptoAlgorithm& algorithm, const blink::WebCryptoKey& base_key, const blink::WebCryptoAlgorithm& import_algorithm, const blink::WebCryptoAlgorithm& key_length_algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, blink::WebCryptoResult result) { scoped_ptr state( new DeriveKeyState(algorithm, base_key, import_algorithm, key_length_algorithm, extractable, usages, result)); if (!CryptoThreadPool::PostTask(FROM_HERE, base::Bind(DoDeriveKey, 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) { return webcrypto::DeserializeKeyForClone( algorithm, type, extractable, usages, webcrypto::CryptoData(key_data, key_data_size), &key); } bool WebCryptoImpl::serializeKeyForClone( const blink::WebCryptoKey& key, blink::WebVector& key_data) { return webcrypto::SerializeKeyForClone(key, &key_data); } } // namespace webcrypto