blob: d841b25f394449e58398d5d5d5a3b0a0eac6bfc5 [file] [log] [blame]
Shawn Willden128ffe02014-08-06 12:31:33 -06001/*
2 * Copyright 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18#include <string.h>
19
Shawn Willden4db3fbd2014-08-08 22:13:44 -060020#include <cstddef>
21
Shawn Willden128ffe02014-08-06 12:31:33 -060022#include <openssl/err.h>
Shawn Willden3879f862014-08-06 14:40:48 -060023#include <openssl/evp.h>
24#include <openssl/rsa.h>
Shawn Willden1615f2e2014-08-13 10:37:40 -060025#include <openssl/rand.h>
Shawn Willden128ffe02014-08-06 12:31:33 -060026
27#include <UniquePtr.h>
28
Shawn Willden3879f862014-08-06 14:40:48 -060029#include "ae.h"
Shawn Willden128ffe02014-08-06 12:31:33 -060030#include "google_keymaster.h"
31#include "google_keymaster_utils.h"
Shawn Willden4db3fbd2014-08-08 22:13:44 -060032#include "key_blob.h"
Shawn Willden1615f2e2014-08-13 10:37:40 -060033#include "rsa_operation.h"
Shawn Willden128ffe02014-08-06 12:31:33 -060034
35namespace keymaster {
36
Shawn Willden1615f2e2014-08-13 10:37:40 -060037GoogleKeymaster::GoogleKeymaster(size_t operation_table_size)
38 : operation_table_(new OpTableEntry[operation_table_size]),
39 operation_table_size_(operation_table_size) {
40 if (operation_table_.get() == NULL)
41 operation_table_size_ = 0;
Shawn Willden39b970b2014-08-11 09:11:21 -060042}
43GoogleKeymaster::~GoogleKeymaster() {
Shawn Willden1615f2e2014-08-13 10:37:40 -060044 for (size_t i = 0; i < operation_table_size_; ++i)
45 if (operation_table_[i].operation != NULL)
46 delete operation_table_[i].operation;
Shawn Willden39b970b2014-08-11 09:11:21 -060047}
Shawn Willden128ffe02014-08-06 12:31:33 -060048
49const int RSA_DEFAULT_KEY_SIZE = 2048;
50const int RSA_DEFAULT_EXPONENT = 65537;
51
Shawn Willden128ffe02014-08-06 12:31:33 -060052struct BIGNUM_Delete {
Shawn Willden39b970b2014-08-11 09:11:21 -060053 void operator()(BIGNUM* p) const {
54 BN_free(p);
55 }
Shawn Willden128ffe02014-08-06 12:31:33 -060056};
57typedef UniquePtr<BIGNUM, BIGNUM_Delete> Unique_BIGNUM;
58
59struct RSA_Delete {
Shawn Willden39b970b2014-08-11 09:11:21 -060060 void operator()(RSA* p) const {
61 RSA_free(p);
62 }
Shawn Willden128ffe02014-08-06 12:31:33 -060063};
64typedef UniquePtr<RSA, RSA_Delete> Unique_RSA;
65
66struct EVP_PKEY_Delete {
Shawn Willden39b970b2014-08-11 09:11:21 -060067 void operator()(EVP_PKEY* p) const {
68 EVP_PKEY_free(p);
69 }
Shawn Willden128ffe02014-08-06 12:31:33 -060070};
71typedef UniquePtr<EVP_PKEY, EVP_PKEY_Delete> Unique_EVP_PKEY;
72
73struct AE_CTX_Delete {
Shawn Willden39b970b2014-08-11 09:11:21 -060074 void operator()(ae_ctx* ctx) const {
75 ae_free(ctx);
76 }
Shawn Willden128ffe02014-08-06 12:31:33 -060077};
78typedef UniquePtr<ae_ctx, AE_CTX_Delete> Unique_ae_ctx;
79
80struct ByteArray_Delete {
Shawn Willden39b970b2014-08-11 09:11:21 -060081 void operator()(void* p) const {
82 delete[] reinterpret_cast<uint8_t*>(p);
83 }
Shawn Willden128ffe02014-08-06 12:31:33 -060084};
85
Shawn Willden128ffe02014-08-06 12:31:33 -060086/**
87 * Many OpenSSL APIs take ownership of an argument on success but don't free the argument on
88 * failure. This means we need to tell our scoped pointers when we've transferred ownership, without
89 * triggering a warning by not using the result of release().
90 */
91template <typename T, typename Delete_T>
92inline void release_because_ownership_transferred(UniquePtr<T, Delete_T>& p) {
93 T* val __attribute__((unused)) = p.release();
94}
95
96keymaster_algorithm_t supported_algorithms[] = {
97 KM_ALGORITHM_RSA,
98};
99
100template <typename T>
101bool check_supported(keymaster_algorithm_t algorithm, SupportedResponse<T>* response) {
102 if (!array_contains(supported_algorithms, algorithm)) {
103 response->error = KM_ERROR_UNSUPPORTED_ALGORITHM;
104 return false;
105 }
106 return true;
107}
108
109void
110GoogleKeymaster::SupportedAlgorithms(SupportedResponse<keymaster_algorithm_t>* response) const {
111 if (response == NULL)
112 return;
113 response->SetResults(supported_algorithms);
114}
115
116void
117GoogleKeymaster::SupportedBlockModes(keymaster_algorithm_t algorithm,
118 SupportedResponse<keymaster_block_mode_t>* response) const {
119 if (response == NULL || !check_supported(algorithm, response))
120 return;
121 response->error = KM_ERROR_OK;
122}
123
124keymaster_padding_t rsa_supported_padding[] = {KM_PAD_NONE};
125
126void
127GoogleKeymaster::SupportedPaddingModes(keymaster_algorithm_t algorithm,
128 SupportedResponse<keymaster_padding_t>* response) const {
129 if (response == NULL || !check_supported(algorithm, response))
130 return;
131
132 response->error = KM_ERROR_OK;
133 switch (algorithm) {
134 case KM_ALGORITHM_RSA:
135 response->SetResults(rsa_supported_padding);
136 break;
137 default:
138 response->results_length = 0;
139 break;
140 }
141}
142
143keymaster_digest_t rsa_supported_digests[] = {KM_DIGEST_NONE};
144void GoogleKeymaster::SupportedDigests(keymaster_algorithm_t algorithm,
145 SupportedResponse<keymaster_digest_t>* response) const {
146 if (response == NULL || !check_supported(algorithm, response))
147 return;
148
149 response->error = KM_ERROR_OK;
150 switch (algorithm) {
151 case KM_ALGORITHM_RSA:
152 response->SetResults(rsa_supported_digests);
153 break;
154 default:
155 response->results_length = 0;
156 break;
157 }
158}
159
160keymaster_key_format_t rsa_supported_import_formats[] = {KM_KEY_FORMAT_PKCS8};
161void
162GoogleKeymaster::SupportedImportFormats(keymaster_algorithm_t algorithm,
163 SupportedResponse<keymaster_key_format_t>* response) const {
164 if (response == NULL || !check_supported(algorithm, response))
165 return;
166
167 response->error = KM_ERROR_OK;
168 switch (algorithm) {
169 case KM_ALGORITHM_RSA:
170 response->SetResults(rsa_supported_import_formats);
171 break;
172 default:
173 response->results_length = 0;
174 break;
175 }
176}
177
178keymaster_key_format_t rsa_supported_export_formats[] = {KM_KEY_FORMAT_X509};
179void
180GoogleKeymaster::SupportedExportFormats(keymaster_algorithm_t algorithm,
181 SupportedResponse<keymaster_key_format_t>* response) const {
182 if (response == NULL || !check_supported(algorithm, response))
183 return;
184
185 response->error = KM_ERROR_OK;
186 switch (algorithm) {
187 case KM_ALGORITHM_RSA:
188 response->SetResults(rsa_supported_export_formats);
189 break;
190 default:
191 response->results_length = 0;
192 break;
193 }
194}
195
196template <typename Message>
197void store_bignum(Message* message, void (Message::*set)(const void* value, size_t size),
198 BIGNUM* bignum) {
199 size_t bufsize = BN_num_bytes(bignum);
200 UniquePtr<uint8_t[]> buf(new uint8_t[bufsize]);
201 int bytes_written = BN_bn2bin(bignum, buf.get());
202 (message->*set)(buf.get(), bytes_written);
203}
204
Shawn Willden128ffe02014-08-06 12:31:33 -0600205void GoogleKeymaster::GenerateKey(const GenerateKeyRequest& request,
206 GenerateKeyResponse* response) {
207 if (response == NULL)
208 return;
Shawn Willden1615f2e2014-08-13 10:37:40 -0600209 response->error = KM_ERROR_UNKNOWN_ERROR;
Shawn Willden128ffe02014-08-06 12:31:33 -0600210
Shawn Willden76364712014-08-11 17:48:04 -0600211 if (!CopyAuthorizations(request.key_description, response))
212 return;
213
Shawn Willden39b970b2014-08-11 09:11:21 -0600214 AuthorizationSet hidden_auths;
Shawn Willden76364712014-08-11 17:48:04 -0600215 response->error = BuildHiddenAuthorizations(request.key_description, &hidden_auths);
216 if (response->error != KM_ERROR_OK)
Shawn Willden128ffe02014-08-06 12:31:33 -0600217 return;
218
219 keymaster_algorithm_t algorithm;
220 if (!request.key_description.GetTagValue(TAG_ALGORITHM, &algorithm)) {
221 response->error = KM_ERROR_UNSUPPORTED_ALGORITHM;
222 return;
223 }
224 switch (algorithm) {
225 case KM_ALGORITHM_RSA:
Shawn Willden39b970b2014-08-11 09:11:21 -0600226 if (!GenerateRsa(request.key_description, response, &hidden_auths))
Shawn Willden128ffe02014-08-06 12:31:33 -0600227 return;
228 break;
229 default:
230 response->error = KM_ERROR_UNSUPPORTED_ALGORITHM;
231 return;
232 }
Shawn Willden1615f2e2014-08-13 10:37:40 -0600233
234 response->error = KM_ERROR_OK;
Shawn Willden128ffe02014-08-06 12:31:33 -0600235}
236
Shawn Willden76364712014-08-11 17:48:04 -0600237void GoogleKeymaster::GetKeyCharacteristics(const GetKeyCharacteristicsRequest& request,
238 GetKeyCharacteristicsResponse* response) {
Shawn Willden1615f2e2014-08-13 10:37:40 -0600239 if (response == NULL)
240 return;
241 response->error = KM_ERROR_UNKNOWN_ERROR;
Shawn Willden76364712014-08-11 17:48:04 -0600242
Shawn Willden1615f2e2014-08-13 10:37:40 -0600243 UniquePtr<KeyBlob> blob(
244 LoadKeyBlob(request.key_blob, request.additional_params, &(response->error)));
245 if (blob.get() == NULL)
246 return;
247
248 response->enforced.Reinitialize(blob->enforced());
249 response->unenforced.Reinitialize(blob->unenforced());
250 response->error = KM_ERROR_OK;
251}
252
253void GoogleKeymaster::BeginOperation(const BeginOperationRequest& request,
254 BeginOperationResponse* response) {
255 if (response == NULL)
256 return;
257 response->error = KM_ERROR_UNKNOWN_ERROR;
258 response->op_handle = 0;
259
260 UniquePtr<KeyBlob> key(
261 LoadKeyBlob(request.key_blob, request.additional_params, &response->error));
262 if (key.get() == NULL)
263 return;
264
265 UniquePtr<Operation> operation;
266 switch (key->algorithm()) {
267 case KM_ALGORITHM_RSA:
268 operation.reset(new RsaOperation(request.purpose, *key));
269 break;
270 default:
271 response->error = KM_ERROR_UNSUPPORTED_ALGORITHM;
272 break;
273 }
274
275 if (operation.get() == NULL) {
Shawn Willden76364712014-08-11 17:48:04 -0600276 return;
277 }
Shawn Willden1615f2e2014-08-13 10:37:40 -0600278
279 response->error = operation->Begin();
280 if (response->error != KM_ERROR_OK)
281 return;
282
283 response->error = AddOperation(operation.release(), &response->op_handle);
284}
285
286void GoogleKeymaster::UpdateOperation(const UpdateOperationRequest& request,
287 UpdateOperationResponse* response) {
288 OpTableEntry* entry = FindOperation(request.op_handle);
289 if (entry == NULL) {
290 response->error = KM_ERROR_INVALID_OPERATION_HANDLE;
291 return;
292 }
293
294 response->error = entry->operation->Update(request.input, &response->output);
295 if (response->error != KM_ERROR_OK) {
296 // Any error invalidates the operation.
297 DeleteOperation(entry);
298 }
299}
300
Shawn Willden43e999e2014-08-13 13:29:50 -0600301void GoogleKeymaster::FinishOperation(const FinishOperationRequest& request,
Shawn Willden1615f2e2014-08-13 10:37:40 -0600302 FinishOperationResponse* response) {
Shawn Willden43e999e2014-08-13 13:29:50 -0600303 OpTableEntry* entry = FindOperation(request.op_handle);
Shawn Willden1615f2e2014-08-13 10:37:40 -0600304 if (entry == NULL) {
305 response->error = KM_ERROR_INVALID_OPERATION_HANDLE;
306 return;
307 }
308
Shawn Willden43e999e2014-08-13 13:29:50 -0600309 response->error = entry->operation->Finish(request.signature, &response->output);
Shawn Willden1615f2e2014-08-13 10:37:40 -0600310 DeleteOperation(entry);
311}
312
313keymaster_error_t GoogleKeymaster::AbortOperation(const keymaster_operation_handle_t op_handle) {
314 OpTableEntry* entry = FindOperation(op_handle);
315 if (entry == NULL)
316 return KM_ERROR_INVALID_OPERATION_HANDLE;
317 DeleteOperation(entry);
318 return KM_ERROR_OK;
Shawn Willden76364712014-08-11 17:48:04 -0600319}
320
Shawn Willden39b970b2014-08-11 09:11:21 -0600321bool GoogleKeymaster::GenerateRsa(const AuthorizationSet& key_auths, GenerateKeyResponse* response,
322 AuthorizationSet* hidden_auths) {
Shawn Willden128ffe02014-08-06 12:31:33 -0600323 uint64_t public_exponent = RSA_DEFAULT_EXPONENT;
324 if (!key_auths.GetTagValue(TAG_RSA_PUBLIC_EXPONENT, &public_exponent))
Shawn Willden76364712014-08-11 17:48:04 -0600325 AddAuthorization(Authorization(TAG_RSA_PUBLIC_EXPONENT, public_exponent), response);
Shawn Willden128ffe02014-08-06 12:31:33 -0600326
327 uint32_t key_size = RSA_DEFAULT_KEY_SIZE;
328 if (!key_auths.GetTagValue(TAG_KEY_SIZE, &key_size))
Shawn Willden76364712014-08-11 17:48:04 -0600329 AddAuthorization(Authorization(TAG_KEY_SIZE, key_size), response);
Shawn Willden128ffe02014-08-06 12:31:33 -0600330
331 Unique_BIGNUM exponent(BN_new());
332 Unique_RSA rsa_key(RSA_new());
333 Unique_EVP_PKEY pkey(EVP_PKEY_new());
334 if (rsa_key.get() == NULL || pkey.get() == NULL) {
335 response->error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
336 return false;
337 }
338
339 if (!BN_set_word(exponent.get(), public_exponent) ||
340 !RSA_generate_key_ex(rsa_key.get(), key_size, exponent.get(), NULL /* callback */)) {
341 response->error = KM_ERROR_UNKNOWN_ERROR;
342 return false;
343 }
344
345 if (!EVP_PKEY_assign_RSA(pkey.get(), rsa_key.get())) {
346 response->error = KM_ERROR_UNKNOWN_ERROR;
347 return false;
348 } else {
349 release_because_ownership_transferred(rsa_key);
350 }
351
352 int der_length = i2d_PrivateKey(pkey.get(), NULL);
353 if (der_length <= 0) {
354 response->error = KM_ERROR_UNKNOWN_ERROR;
355 return false;
356 }
357 UniquePtr<uint8_t[]> der_data(new uint8_t[der_length]);
358 if (der_data.get() == NULL) {
359 response->error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
360 return false;
361 }
362
363 uint8_t* tmp = der_data.get();
364 i2d_PrivateKey(pkey.get(), &tmp);
365
Shawn Willden39b970b2014-08-11 09:11:21 -0600366 return CreateKeyBlob(response, *hidden_auths, der_data.get(), der_length);
367}
368
369bool GoogleKeymaster::CreateKeyBlob(GenerateKeyResponse* response,
370 const AuthorizationSet& hidden_auths, uint8_t* key_bytes,
371 size_t key_length) {
372 uint8_t nonce[KeyBlob::NONCE_LENGTH];
373 GenerateNonce(nonce, array_size(nonce));
374
375 keymaster_key_blob_t key_data = {key_bytes, key_length};
376 UniquePtr<KeyBlob> blob(new KeyBlob(response->enforced, response->unenforced, hidden_auths,
377 key_data, MasterKey(), nonce));
378 if (blob.get() == NULL) {
379 response->error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
380 return false;
381 }
382
383 if (blob->error() != KM_ERROR_OK) {
384 return blob->error();
385 return false;
386 }
387
388 size_t size = blob->SerializedSize();
389 UniquePtr<uint8_t[]> blob_bytes(new uint8_t[size]);
390 if (blob_bytes.get() == NULL) {
391 response->error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
392 return false;
393 }
394 blob->Serialize(blob_bytes.get(), blob_bytes.get() + size);
395 response->key_blob.key_material_size = size;
396 response->key_blob.key_material = blob_bytes.release();
397 return true;
Shawn Willden128ffe02014-08-06 12:31:33 -0600398}
399
Shawn Willden1615f2e2014-08-13 10:37:40 -0600400KeyBlob* GoogleKeymaster::LoadKeyBlob(const keymaster_key_blob_t& key,
401 const AuthorizationSet& client_params,
402 keymaster_error_t* error) {
403 AuthorizationSet hidden;
404 BuildHiddenAuthorizations(client_params, &hidden);
405 UniquePtr<KeyBlob> blob(new KeyBlob(key, hidden, MasterKey()));
406 if (blob.get() == NULL) {
407 *error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
408 return NULL;
409 } else if (blob->error() != KM_ERROR_OK) {
410 *error = blob->error();
411 return NULL;
412 }
413 return blob.release();
414}
415
Shawn Willden128ffe02014-08-06 12:31:33 -0600416static keymaster_error_t CheckAuthorizationSet(const AuthorizationSet& set) {
417 switch (set.is_valid()) {
Shawn Willden58e1a542014-08-08 21:58:29 -0600418 case AuthorizationSet::OK:
Shawn Willden128ffe02014-08-06 12:31:33 -0600419 return KM_ERROR_OK;
420 case AuthorizationSet::ALLOCATION_FAILURE:
421 return KM_ERROR_MEMORY_ALLOCATION_FAILED;
Shawn Willden128ffe02014-08-06 12:31:33 -0600422 case AuthorizationSet::MALFORMED_DATA:
423 return KM_ERROR_UNKNOWN_ERROR;
424 }
425 return KM_ERROR_OK;
426}
427
428bool GoogleKeymaster::CopyAuthorizations(const AuthorizationSet& key_description,
Shawn Willden76364712014-08-11 17:48:04 -0600429 GenerateKeyResponse* response) {
Shawn Willden128ffe02014-08-06 12:31:33 -0600430 for (size_t i = 0; i < key_description.size(); ++i) {
431 switch (key_description[i].tag) {
432 case KM_TAG_ROOT_OF_TRUST:
433 case KM_TAG_CREATION_DATETIME:
434 case KM_TAG_ORIGIN:
435 response->error = KM_ERROR_INVALID_TAG;
436 return false;
437 case KM_TAG_ROLLBACK_RESISTANT:
438 response->error = KM_ERROR_UNSUPPORTED_TAG;
439 return false;
440 default:
Shawn Willden76364712014-08-11 17:48:04 -0600441 AddAuthorization(key_description[i], response);
Shawn Willden128ffe02014-08-06 12:31:33 -0600442 break;
443 }
444 }
445
Shawn Willden76364712014-08-11 17:48:04 -0600446 AddAuthorization(Authorization(TAG_CREATION_DATETIME, java_time(time(NULL))), response);
447 AddAuthorization(Authorization(TAG_ORIGIN, origin()), response);
Shawn Willden128ffe02014-08-06 12:31:33 -0600448
449 response->error = CheckAuthorizationSet(response->enforced);
450 if (response->error != KM_ERROR_OK)
451 return false;
452 response->error = CheckAuthorizationSet(response->unenforced);
453 if (response->error != KM_ERROR_OK)
454 return false;
455
456 return true;
457}
458
Shawn Willden76364712014-08-11 17:48:04 -0600459keymaster_error_t GoogleKeymaster::BuildHiddenAuthorizations(const AuthorizationSet& input_set,
Shawn Willden1615f2e2014-08-13 10:37:40 -0600460 AuthorizationSet* hidden) {
Shawn Willden76364712014-08-11 17:48:04 -0600461 keymaster_blob_t entry;
462 if (input_set.GetTagValue(TAG_APPLICATION_ID, &entry))
463 hidden->push_back(TAG_APPLICATION_ID, entry.data, entry.data_length);
464 if (input_set.GetTagValue(TAG_APPLICATION_DATA, &entry))
465 hidden->push_back(TAG_APPLICATION_DATA, entry.data, entry.data_length);
466 hidden->push_back(RootOfTrustTag());
467
468 return CheckAuthorizationSet(*hidden);
469}
470
Shawn Willden128ffe02014-08-06 12:31:33 -0600471void GoogleKeymaster::AddAuthorization(const keymaster_key_param_t& auth,
Shawn Willden76364712014-08-11 17:48:04 -0600472 GenerateKeyResponse* response) {
Shawn Willden39b970b2014-08-11 09:11:21 -0600473 switch (auth.tag) {
474 case KM_TAG_ROOT_OF_TRUST:
475 case KM_TAG_APPLICATION_ID:
476 case KM_TAG_APPLICATION_DATA:
Shawn Willden76364712014-08-11 17:48:04 -0600477 // Skip. We handle these tags separately.
Shawn Willden39b970b2014-08-11 09:11:21 -0600478 break;
479 default:
480 if (is_enforced(auth.tag))
481 response->enforced.push_back(auth);
482 else
483 response->unenforced.push_back(auth);
484 break;
485 }
Shawn Willden128ffe02014-08-06 12:31:33 -0600486}
487
Shawn Willden1615f2e2014-08-13 10:37:40 -0600488keymaster_error_t GoogleKeymaster::AddOperation(Operation* operation,
489 keymaster_operation_handle_t* op_handle) {
490 UniquePtr<Operation> op(operation);
491 if (RAND_bytes(reinterpret_cast<uint8_t*>(op_handle), sizeof(*op_handle)) == 0)
492 return KM_ERROR_UNKNOWN_ERROR;
493 if (*op_handle == 0){
494 // Statistically this is vanishingly unlikely, which means if it ever happens in practice,
495 // it indicates a broken RNG.
496 return KM_ERROR_UNKNOWN_ERROR;
497 }
498 for (size_t i = 0; i < operation_table_size_; ++i) {
499 if (operation_table_[i].operation == NULL) {
500 operation_table_[i].operation = op.release();
501 operation_table_[i].handle = *op_handle;
502 return KM_ERROR_OK;
503 }
504 }
505 return KM_ERROR_TOO_MANY_OPERATIONS;
506}
507
508GoogleKeymaster::OpTableEntry*
509GoogleKeymaster::FindOperation(keymaster_operation_handle_t op_handle) {
510 if (op_handle == 0)
511 return NULL;
512
513 for (size_t i = 0; i < operation_table_size_; ++i) {
514 if (operation_table_[i].handle == op_handle)
515 return operation_table_.get() + i;
516 }
517 return NULL;
518}
519
520void GoogleKeymaster::DeleteOperation(OpTableEntry* entry) {
521 delete entry->operation;
522 entry->operation = NULL;
523 entry->handle = 0;
524}
525
Shawn Willden128ffe02014-08-06 12:31:33 -0600526} // namespace keymaster