1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/child/webcrypto/webcrypto_impl.h"
8 #include "base/lazy_instance.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/stl_util.h"
14 #include "base/task_runner.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/sequenced_worker_pool.h"
17 #include "base/threading/worker_pool.h"
18 #include "content/child/webcrypto/algorithm_dispatch.h"
19 #include "content/child/webcrypto/crypto_data.h"
20 #include "content/child/webcrypto/generate_key_result.h"
21 #include "content/child/webcrypto/status.h"
22 #include "content/child/webcrypto/structured_clone.h"
23 #include "content/child/webcrypto/webcrypto_util.h"
24 #include "content/child/worker_thread_task_runner.h"
25 #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"
26 #include "third_party/WebKit/public/platform/WebString.h"
30 using webcrypto::Status;
34 // ---------------------
36 // ---------------------
38 // WebCrypto operations can be slow. For instance generating an RSA key can
39 // take hundreds of milliseconds to several seconds.
41 // Moreover the underlying crypto libraries are not threadsafe when operating
44 // The strategy used here is to run a sequenced worker pool for all WebCrypto
45 // operations. This pool (of 1 threads) is also used by requests started from
48 // A few notes to keep in mind:
50 // * PostTaskAndReply() cannot be used for two reasons:
52 // (1) Blink web worker threads do not have an associated message loop so
53 // construction of the reply callback will crash.
55 // (2) PostTaskAndReply() handles failure posting the reply by leaking the
56 // callback, rather than destroying it. In the case of Web Workers this
57 // condition is reachable via normal execution, since Web Workers can
58 // be stopped before the WebCrypto operation has finished. A policy of
59 // leaking would therefore be problematic.
61 // * blink::WebArrayBuffer is NOT threadsafe, and should therefore be allocated
62 // on the target Blink thread.
64 // TODO(eroman): Is there any way around this? Copying the result between
67 // * WebCryptoAlgorithm and WebCryptoKey are threadsafe (however the key's
68 // handle(), which wraps an NSS/OpenSSL type, may not be and should only be
69 // used from the webcrypto thread).
71 // * blink::WebCryptoResult is not threadsafe and should only be operated on
72 // the target Blink thread. HOWEVER, it is safe to delete it from any thread.
73 // This can happen if by the time the operation has completed in the crypto
74 // worker pool, the Blink worker thread that initiated the request is gone.
75 // Posting back to the origin thread will fail, and the WebCryptoResult will
76 // be deleted while running in the crypto worker pool.
77 class CryptoThreadPool {
80 : worker_pool_(new base::SequencedWorkerPool(1, "WebCrypto")),
81 task_runner_(worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
82 worker_pool_->GetSequenceToken(),
83 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)) {}
85 static bool PostTask(const tracked_objects::Location& from_here,
86 const base::Closure& task);
89 scoped_refptr<base::SequencedWorkerPool> worker_pool_;
90 scoped_refptr<base::SequencedTaskRunner> task_runner_;
93 base::LazyInstance<CryptoThreadPool>::Leaky crypto_thread_pool =
94 LAZY_INSTANCE_INITIALIZER;
96 bool CryptoThreadPool::PostTask(const tracked_objects::Location& from_here,
97 const base::Closure& task) {
98 return crypto_thread_pool.Get().task_runner_->PostTask(from_here, task);
101 void CompleteWithThreadPoolError(blink::WebCryptoResult* result) {
102 result->completeWithError(blink::WebCryptoErrorTypeOperation,
103 "Failed posting to crypto worker pool");
106 void CompleteWithError(const Status& status, blink::WebCryptoResult* result) {
107 DCHECK(status.IsError());
109 result->completeWithError(status.error_type(),
110 blink::WebString::fromUTF8(status.error_details()));
113 void CompleteWithBufferOrError(const Status& status,
114 const std::vector<uint8_t>& buffer,
115 blink::WebCryptoResult* result) {
116 if (status.IsError()) {
117 CompleteWithError(status, result);
119 if (buffer.size() > UINT_MAX) {
120 // WebArrayBuffers have a smaller range than std::vector<>, so
121 // theoretically this could overflow.
122 CompleteWithError(Status::ErrorUnexpected(), result);
124 result->completeWithBuffer(vector_as_array(&buffer), buffer.size());
129 void CompleteWithKeyOrError(const Status& status,
130 const blink::WebCryptoKey& key,
131 blink::WebCryptoResult* result) {
132 if (status.IsError()) {
133 CompleteWithError(status, result);
135 result->completeWithKey(key);
139 // Gets a task runner for the current thread. The current thread is either:
141 // * The main Blink thread
142 // * A Blink web worker thread
144 // A different mechanism is needed for posting to these threads. The main
145 // thread has an associated message loop and can simply use
146 // base::ThreadTaskRunnerHandle. Whereas the web worker threads are managed by
147 // Blink and need to be indirected through WorkerThreadTaskRunner.
148 scoped_refptr<base::TaskRunner> GetCurrentBlinkThread() {
149 if (base::ThreadTaskRunnerHandle::IsSet())
150 return base::ThreadTaskRunnerHandle::Get();
151 return WorkerThreadTaskRunner::current();
154 // --------------------------------------------------------------------
156 // --------------------------------------------------------------------
158 // Explicit state classes are used rather than base::Bind(). This is done
159 // both for clarity, but also to avoid extraneous allocations for things
160 // like passing buffers and result objects between threads.
162 // BaseState is the base class common to all of the async operations, and
163 // keeps track of the thread to complete on, the error state, and the
164 // callback into Blink.
166 // Ownership of the State object is passed between the crypto thread and the
167 // Blink thread. Under normal completion it is destroyed on the Blink thread.
168 // However it may also be destroyed on the crypto thread if the Blink thread
169 // has vanished (which can happen for Blink web worker threads).
172 explicit BaseState(const blink::WebCryptoResult& result)
173 : origin_thread(GetCurrentBlinkThread()), result(result) {}
176 return result.cancelled();
179 scoped_refptr<base::TaskRunner> origin_thread;
181 webcrypto::Status status;
182 blink::WebCryptoResult result;
185 // Since there is no virtual destructor, must not delete directly as a
190 struct EncryptState : public BaseState {
191 EncryptState(const blink::WebCryptoAlgorithm& algorithm,
192 const blink::WebCryptoKey& key,
193 const unsigned char* data,
194 unsigned int data_size,
195 const blink::WebCryptoResult& result)
197 algorithm(algorithm),
199 data(data, data + data_size) {}
201 const blink::WebCryptoAlgorithm algorithm;
202 const blink::WebCryptoKey key;
203 const std::vector<uint8_t> data;
205 std::vector<uint8_t> buffer;
208 typedef EncryptState DecryptState;
209 typedef EncryptState DigestState;
211 struct GenerateKeyState : public BaseState {
212 GenerateKeyState(const blink::WebCryptoAlgorithm& algorithm,
214 blink::WebCryptoKeyUsageMask usages,
215 const blink::WebCryptoResult& result)
217 algorithm(algorithm),
218 extractable(extractable),
221 const blink::WebCryptoAlgorithm algorithm;
222 const bool extractable;
223 const blink::WebCryptoKeyUsageMask usages;
225 webcrypto::GenerateKeyResult generate_key_result;
228 struct ImportKeyState : public BaseState {
229 ImportKeyState(blink::WebCryptoKeyFormat format,
230 const unsigned char* key_data,
231 unsigned int key_data_size,
232 const blink::WebCryptoAlgorithm& algorithm,
234 blink::WebCryptoKeyUsageMask usages,
235 const blink::WebCryptoResult& result)
238 key_data(key_data, key_data + key_data_size),
239 algorithm(algorithm),
240 extractable(extractable),
243 const blink::WebCryptoKeyFormat format;
244 const std::vector<uint8_t> key_data;
245 const blink::WebCryptoAlgorithm algorithm;
246 const bool extractable;
247 const blink::WebCryptoKeyUsageMask usages;
249 blink::WebCryptoKey key;
252 struct ExportKeyState : public BaseState {
253 ExportKeyState(blink::WebCryptoKeyFormat format,
254 const blink::WebCryptoKey& key,
255 const blink::WebCryptoResult& result)
256 : BaseState(result), format(format), key(key) {}
258 const blink::WebCryptoKeyFormat format;
259 const blink::WebCryptoKey key;
261 std::vector<uint8_t> buffer;
264 typedef EncryptState SignState;
266 struct VerifySignatureState : public BaseState {
267 VerifySignatureState(const blink::WebCryptoAlgorithm& algorithm,
268 const blink::WebCryptoKey& key,
269 const unsigned char* signature,
270 unsigned int signature_size,
271 const unsigned char* data,
272 unsigned int data_size,
273 const blink::WebCryptoResult& result)
275 algorithm(algorithm),
277 signature(signature, signature + signature_size),
278 data(data, data + data_size),
279 verify_result(false) {}
281 const blink::WebCryptoAlgorithm algorithm;
282 const blink::WebCryptoKey key;
283 const std::vector<uint8_t> signature;
284 const std::vector<uint8_t> data;
289 struct WrapKeyState : public BaseState {
290 WrapKeyState(blink::WebCryptoKeyFormat format,
291 const blink::WebCryptoKey& key,
292 const blink::WebCryptoKey& wrapping_key,
293 const blink::WebCryptoAlgorithm& wrap_algorithm,
294 const blink::WebCryptoResult& result)
298 wrapping_key(wrapping_key),
299 wrap_algorithm(wrap_algorithm) {}
301 const blink::WebCryptoKeyFormat format;
302 const blink::WebCryptoKey key;
303 const blink::WebCryptoKey wrapping_key;
304 const blink::WebCryptoAlgorithm wrap_algorithm;
306 std::vector<uint8_t> buffer;
309 struct UnwrapKeyState : public BaseState {
310 UnwrapKeyState(blink::WebCryptoKeyFormat format,
311 const unsigned char* wrapped_key,
312 unsigned wrapped_key_size,
313 const blink::WebCryptoKey& wrapping_key,
314 const blink::WebCryptoAlgorithm& unwrap_algorithm,
315 const blink::WebCryptoAlgorithm& unwrapped_key_algorithm,
317 blink::WebCryptoKeyUsageMask usages,
318 const blink::WebCryptoResult& result)
321 wrapped_key(wrapped_key, wrapped_key + wrapped_key_size),
322 wrapping_key(wrapping_key),
323 unwrap_algorithm(unwrap_algorithm),
324 unwrapped_key_algorithm(unwrapped_key_algorithm),
325 extractable(extractable),
328 const blink::WebCryptoKeyFormat format;
329 const std::vector<uint8_t> wrapped_key;
330 const blink::WebCryptoKey wrapping_key;
331 const blink::WebCryptoAlgorithm unwrap_algorithm;
332 const blink::WebCryptoAlgorithm unwrapped_key_algorithm;
333 const bool extractable;
334 const blink::WebCryptoKeyUsageMask usages;
336 blink::WebCryptoKey unwrapped_key;
339 // --------------------------------------------------------------------
341 // --------------------------------------------------------------------
343 // * The methods named Do*() run on the crypto thread.
344 // * The methods named Do*Reply() run on the target Blink thread
346 void DoEncryptReply(scoped_ptr<EncryptState> state) {
347 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
350 void DoEncrypt(scoped_ptr<EncryptState> passed_state) {
351 EncryptState* state = passed_state.get();
352 if (state->cancelled())
354 state->status = webcrypto::Encrypt(state->algorithm,
356 webcrypto::CryptoData(state->data),
358 state->origin_thread->PostTask(
359 FROM_HERE, base::Bind(DoEncryptReply, Passed(&passed_state)));
362 void DoDecryptReply(scoped_ptr<DecryptState> state) {
363 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
366 void DoDecrypt(scoped_ptr<DecryptState> passed_state) {
367 DecryptState* state = passed_state.get();
368 if (state->cancelled())
370 state->status = webcrypto::Decrypt(state->algorithm,
372 webcrypto::CryptoData(state->data),
374 state->origin_thread->PostTask(
375 FROM_HERE, base::Bind(DoDecryptReply, Passed(&passed_state)));
378 void DoDigestReply(scoped_ptr<DigestState> state) {
379 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
382 void DoDigest(scoped_ptr<DigestState> passed_state) {
383 DigestState* state = passed_state.get();
384 if (state->cancelled())
386 state->status = webcrypto::Digest(
387 state->algorithm, webcrypto::CryptoData(state->data), &state->buffer);
388 state->origin_thread->PostTask(
389 FROM_HERE, base::Bind(DoDigestReply, Passed(&passed_state)));
392 void DoGenerateKeyReply(scoped_ptr<GenerateKeyState> state) {
393 if (state->status.IsError()) {
394 CompleteWithError(state->status, &state->result);
396 state->generate_key_result.Complete(&state->result);
400 void DoGenerateKey(scoped_ptr<GenerateKeyState> passed_state) {
401 GenerateKeyState* state = passed_state.get();
402 if (state->cancelled())
404 state->status = webcrypto::GenerateKey(state->algorithm,
407 &state->generate_key_result);
408 state->origin_thread->PostTask(
409 FROM_HERE, base::Bind(DoGenerateKeyReply, Passed(&passed_state)));
412 void DoImportKeyReply(scoped_ptr<ImportKeyState> state) {
413 CompleteWithKeyOrError(state->status, state->key, &state->result);
416 void DoImportKey(scoped_ptr<ImportKeyState> passed_state) {
417 ImportKeyState* state = passed_state.get();
418 if (state->cancelled())
420 state->status = webcrypto::ImportKey(state->format,
421 webcrypto::CryptoData(state->key_data),
426 if (state->status.IsSuccess()) {
427 DCHECK(state->key.handle());
428 DCHECK(!state->key.algorithm().isNull());
429 DCHECK_EQ(state->extractable, state->key.extractable());
432 state->origin_thread->PostTask(
433 FROM_HERE, base::Bind(DoImportKeyReply, Passed(&passed_state)));
436 void DoExportKeyReply(scoped_ptr<ExportKeyState> state) {
437 if (state->format != blink::WebCryptoKeyFormatJwk) {
438 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
442 if (state->status.IsError()) {
443 CompleteWithError(state->status, &state->result);
445 state->result.completeWithJson(
446 reinterpret_cast<const char*>(vector_as_array(&state->buffer)),
447 state->buffer.size());
451 void DoExportKey(scoped_ptr<ExportKeyState> passed_state) {
452 ExportKeyState* state = passed_state.get();
453 if (state->cancelled())
456 webcrypto::ExportKey(state->format, state->key, &state->buffer);
457 state->origin_thread->PostTask(
458 FROM_HERE, base::Bind(DoExportKeyReply, Passed(&passed_state)));
461 void DoSignReply(scoped_ptr<SignState> state) {
462 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
465 void DoSign(scoped_ptr<SignState> passed_state) {
466 SignState* state = passed_state.get();
467 if (state->cancelled())
469 state->status = webcrypto::Sign(state->algorithm,
471 webcrypto::CryptoData(state->data),
474 state->origin_thread->PostTask(
475 FROM_HERE, base::Bind(DoSignReply, Passed(&passed_state)));
478 void DoVerifyReply(scoped_ptr<VerifySignatureState> state) {
479 if (state->status.IsError()) {
480 CompleteWithError(state->status, &state->result);
482 state->result.completeWithBoolean(state->verify_result);
486 void DoVerify(scoped_ptr<VerifySignatureState> passed_state) {
487 VerifySignatureState* state = passed_state.get();
488 if (state->cancelled())
490 state->status = webcrypto::Verify(state->algorithm,
492 webcrypto::CryptoData(state->signature),
493 webcrypto::CryptoData(state->data),
494 &state->verify_result);
496 state->origin_thread->PostTask(
497 FROM_HERE, base::Bind(DoVerifyReply, Passed(&passed_state)));
500 void DoWrapKeyReply(scoped_ptr<WrapKeyState> state) {
501 CompleteWithBufferOrError(state->status, state->buffer, &state->result);
504 void DoWrapKey(scoped_ptr<WrapKeyState> passed_state) {
505 WrapKeyState* state = passed_state.get();
506 if (state->cancelled())
508 state->status = webcrypto::WrapKey(state->format,
511 state->wrap_algorithm,
514 state->origin_thread->PostTask(
515 FROM_HERE, base::Bind(DoWrapKeyReply, Passed(&passed_state)));
518 void DoUnwrapKeyReply(scoped_ptr<UnwrapKeyState> state) {
519 CompleteWithKeyOrError(state->status, state->unwrapped_key, &state->result);
522 void DoUnwrapKey(scoped_ptr<UnwrapKeyState> passed_state) {
523 UnwrapKeyState* state = passed_state.get();
524 if (state->cancelled())
527 webcrypto::UnwrapKey(state->format,
528 webcrypto::CryptoData(state->wrapped_key),
530 state->unwrap_algorithm,
531 state->unwrapped_key_algorithm,
534 &state->unwrapped_key);
536 state->origin_thread->PostTask(
537 FROM_HERE, base::Bind(DoUnwrapKeyReply, Passed(&passed_state)));
542 WebCryptoImpl::WebCryptoImpl() {
545 WebCryptoImpl::~WebCryptoImpl() {
548 void WebCryptoImpl::encrypt(const blink::WebCryptoAlgorithm& algorithm,
549 const blink::WebCryptoKey& key,
550 const unsigned char* data,
551 unsigned int data_size,
552 blink::WebCryptoResult result) {
553 DCHECK(!algorithm.isNull());
555 scoped_ptr<EncryptState> state(
556 new EncryptState(algorithm, key, data, data_size, result));
557 if (!CryptoThreadPool::PostTask(FROM_HERE,
558 base::Bind(DoEncrypt, Passed(&state)))) {
559 CompleteWithThreadPoolError(&result);
563 void WebCryptoImpl::decrypt(const blink::WebCryptoAlgorithm& algorithm,
564 const blink::WebCryptoKey& key,
565 const unsigned char* data,
566 unsigned int data_size,
567 blink::WebCryptoResult result) {
568 DCHECK(!algorithm.isNull());
570 scoped_ptr<DecryptState> state(
571 new DecryptState(algorithm, key, data, data_size, result));
572 if (!CryptoThreadPool::PostTask(FROM_HERE,
573 base::Bind(DoDecrypt, Passed(&state)))) {
574 CompleteWithThreadPoolError(&result);
578 void WebCryptoImpl::digest(const blink::WebCryptoAlgorithm& algorithm,
579 const unsigned char* data,
580 unsigned int data_size,
581 blink::WebCryptoResult result) {
582 DCHECK(!algorithm.isNull());
584 scoped_ptr<DigestState> state(new DigestState(
585 algorithm, blink::WebCryptoKey::createNull(), data, data_size, result));
586 if (!CryptoThreadPool::PostTask(FROM_HERE,
587 base::Bind(DoDigest, Passed(&state)))) {
588 CompleteWithThreadPoolError(&result);
592 void WebCryptoImpl::generateKey(const blink::WebCryptoAlgorithm& algorithm,
594 blink::WebCryptoKeyUsageMask usages,
595 blink::WebCryptoResult result) {
596 DCHECK(!algorithm.isNull());
598 scoped_ptr<GenerateKeyState> state(
599 new GenerateKeyState(algorithm, extractable, usages, result));
600 if (!CryptoThreadPool::PostTask(FROM_HERE,
601 base::Bind(DoGenerateKey, Passed(&state)))) {
602 CompleteWithThreadPoolError(&result);
606 void WebCryptoImpl::importKey(blink::WebCryptoKeyFormat format,
607 const unsigned char* key_data,
608 unsigned int key_data_size,
609 const blink::WebCryptoAlgorithm& algorithm,
611 blink::WebCryptoKeyUsageMask usages,
612 blink::WebCryptoResult result) {
613 scoped_ptr<ImportKeyState> state(new ImportKeyState(
614 format, key_data, key_data_size, algorithm, extractable, usages, result));
615 if (!CryptoThreadPool::PostTask(FROM_HERE,
616 base::Bind(DoImportKey, Passed(&state)))) {
617 CompleteWithThreadPoolError(&result);
621 void WebCryptoImpl::exportKey(blink::WebCryptoKeyFormat format,
622 const blink::WebCryptoKey& key,
623 blink::WebCryptoResult result) {
624 scoped_ptr<ExportKeyState> state(new ExportKeyState(format, key, result));
625 if (!CryptoThreadPool::PostTask(FROM_HERE,
626 base::Bind(DoExportKey, Passed(&state)))) {
627 CompleteWithThreadPoolError(&result);
631 void WebCryptoImpl::sign(const blink::WebCryptoAlgorithm& algorithm,
632 const blink::WebCryptoKey& key,
633 const unsigned char* data,
634 unsigned int data_size,
635 blink::WebCryptoResult result) {
636 scoped_ptr<SignState> state(
637 new SignState(algorithm, key, data, data_size, result));
638 if (!CryptoThreadPool::PostTask(FROM_HERE,
639 base::Bind(DoSign, Passed(&state)))) {
640 CompleteWithThreadPoolError(&result);
644 void WebCryptoImpl::verifySignature(const blink::WebCryptoAlgorithm& algorithm,
645 const blink::WebCryptoKey& key,
646 const unsigned char* signature,
647 unsigned int signature_size,
648 const unsigned char* data,
649 unsigned int data_size,
650 blink::WebCryptoResult result) {
651 scoped_ptr<VerifySignatureState> state(new VerifySignatureState(
652 algorithm, key, signature, signature_size, data, data_size, result));
653 if (!CryptoThreadPool::PostTask(FROM_HERE,
654 base::Bind(DoVerify, Passed(&state)))) {
655 CompleteWithThreadPoolError(&result);
659 void WebCryptoImpl::wrapKey(blink::WebCryptoKeyFormat format,
660 const blink::WebCryptoKey& key,
661 const blink::WebCryptoKey& wrapping_key,
662 const blink::WebCryptoAlgorithm& wrap_algorithm,
663 blink::WebCryptoResult result) {
664 scoped_ptr<WrapKeyState> state(
665 new WrapKeyState(format, key, wrapping_key, wrap_algorithm, result));
666 if (!CryptoThreadPool::PostTask(FROM_HERE,
667 base::Bind(DoWrapKey, Passed(&state)))) {
668 CompleteWithThreadPoolError(&result);
672 void WebCryptoImpl::unwrapKey(
673 blink::WebCryptoKeyFormat format,
674 const unsigned char* wrapped_key,
675 unsigned wrapped_key_size,
676 const blink::WebCryptoKey& wrapping_key,
677 const blink::WebCryptoAlgorithm& unwrap_algorithm,
678 const blink::WebCryptoAlgorithm& unwrapped_key_algorithm,
680 blink::WebCryptoKeyUsageMask usages,
681 blink::WebCryptoResult result) {
682 scoped_ptr<UnwrapKeyState> state(new UnwrapKeyState(format,
687 unwrapped_key_algorithm,
691 if (!CryptoThreadPool::PostTask(FROM_HERE,
692 base::Bind(DoUnwrapKey, Passed(&state)))) {
693 CompleteWithThreadPoolError(&result);
697 blink::WebCryptoDigestor* WebCryptoImpl::createDigestor(
698 blink::WebCryptoAlgorithmId algorithm_id) {
699 return webcrypto::CreateDigestor(algorithm_id).release();
702 bool WebCryptoImpl::deserializeKeyForClone(
703 const blink::WebCryptoKeyAlgorithm& algorithm,
704 blink::WebCryptoKeyType type,
706 blink::WebCryptoKeyUsageMask usages,
707 const unsigned char* key_data,
708 unsigned key_data_size,
709 blink::WebCryptoKey& key) {
710 // TODO(eroman): Rather than do the import immediately on the current thread,
711 // it could defer to the crypto thread.
712 return webcrypto::DeserializeKeyForClone(
717 webcrypto::CryptoData(key_data, key_data_size),
721 bool WebCryptoImpl::serializeKeyForClone(
722 const blink::WebCryptoKey& key,
723 blink::WebVector<unsigned char>& key_data) {
724 return webcrypto::SerializeKeyForClone(key, &key_data);
727 } // namespace content