blob: 9801ea4e50303b07114794bda3f0f6bbfdc3a4ae [file] [log] [blame]
Shawn Willden9e149572017-10-30 16:08:21 -06001/*
2 **
3 ** Copyright 2017, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18#define LOG_TAG "android.hardware.keymaster@4.0-impl"
19#include <log/log.h>
20
Shawn Willdenefd06732017-11-30 19:34:16 -070021#include "include/AndroidKeymaster4Device.h"
Shawn Willden9e149572017-10-30 16:08:21 -060022
23#include <keymasterV4_0/authorization_set.h>
24
25#include <keymaster/android_keymaster.h>
26#include <keymaster/android_keymaster_messages.h>
27#include <keymaster/contexts/pure_soft_keymaster_context.h>
28#include <keymaster/contexts/soft_keymaster_context.h>
29#include <keymaster/keymaster_configuration.h>
30#include <keymaster/keymaster_enforcement.h>
31#include <keymaster/km_openssl/soft_keymaster_enforcement.h>
32
Shawn Willden9e149572017-10-30 16:08:21 -060033namespace keymaster {
34namespace V4_0 {
35namespace ng {
36
37namespace {
38
39constexpr size_t kOperationTableSize = 16;
40
41inline keymaster_tag_t legacy_enum_conversion(const Tag value) {
42 return keymaster_tag_t(value);
43}
44inline Tag legacy_enum_conversion(const keymaster_tag_t value) {
45 return Tag(value);
46}
47inline keymaster_purpose_t legacy_enum_conversion(const KeyPurpose value) {
48 return keymaster_purpose_t(value);
49}
50inline keymaster_key_format_t legacy_enum_conversion(const KeyFormat value) {
51 return keymaster_key_format_t(value);
52}
53inline ErrorCode legacy_enum_conversion(const keymaster_error_t value) {
54 return ErrorCode(value);
55}
56
57inline keymaster_tag_type_t typeFromTag(const keymaster_tag_t tag) {
58 return keymaster_tag_get_type(tag);
59}
60
61class KmParamSet : public keymaster_key_param_set_t {
62 public:
63 KmParamSet(const hidl_vec<KeyParameter>& keyParams) {
64 params = new keymaster_key_param_t[keyParams.size()];
65 length = keyParams.size();
66 for (size_t i = 0; i < keyParams.size(); ++i) {
67 auto tag = legacy_enum_conversion(keyParams[i].tag);
68 switch (typeFromTag(tag)) {
69 case KM_ENUM:
70 case KM_ENUM_REP:
71 params[i] = keymaster_param_enum(tag, keyParams[i].f.integer);
72 break;
73 case KM_UINT:
74 case KM_UINT_REP:
75 params[i] = keymaster_param_int(tag, keyParams[i].f.integer);
76 break;
77 case KM_ULONG:
78 case KM_ULONG_REP:
79 params[i] = keymaster_param_long(tag, keyParams[i].f.longInteger);
80 break;
81 case KM_DATE:
82 params[i] = keymaster_param_date(tag, keyParams[i].f.dateTime);
83 break;
84 case KM_BOOL:
85 if (keyParams[i].f.boolValue)
86 params[i] = keymaster_param_bool(tag);
87 else
88 params[i].tag = KM_TAG_INVALID;
89 break;
90 case KM_BIGNUM:
91 case KM_BYTES:
92 params[i] =
93 keymaster_param_blob(tag, &keyParams[i].blob[0], keyParams[i].blob.size());
94 break;
95 case KM_INVALID:
96 default:
97 params[i].tag = KM_TAG_INVALID;
98 /* just skip */
99 break;
100 }
101 }
102 }
103 KmParamSet(KmParamSet&& other) : keymaster_key_param_set_t{other.params, other.length} {
104 other.length = 0;
105 other.params = nullptr;
106 }
107 KmParamSet(const KmParamSet&) = delete;
108 ~KmParamSet() { delete[] params; }
109};
110
111inline hidl_vec<uint8_t> kmBlob2hidlVec(const keymaster_key_blob_t& blob) {
112 hidl_vec<uint8_t> result;
113 result.setToExternal(const_cast<unsigned char*>(blob.key_material), blob.key_material_size);
114 return result;
115}
116
117inline hidl_vec<uint8_t> kmBlob2hidlVec(const keymaster_blob_t& blob) {
118 hidl_vec<uint8_t> result;
119 result.setToExternal(const_cast<unsigned char*>(blob.data), blob.data_length);
120 return result;
121}
122
123inline hidl_vec<uint8_t> kmBuffer2hidlVec(const ::keymaster::Buffer& buf) {
124 hidl_vec<uint8_t> result;
125 result.setToExternal(const_cast<unsigned char*>(buf.peek_read()), buf.available_read());
126 return result;
127}
128
129inline static hidl_vec<hidl_vec<uint8_t>>
130kmCertChain2Hidl(const keymaster_cert_chain_t& cert_chain) {
131 hidl_vec<hidl_vec<uint8_t>> result;
132 if (!cert_chain.entry_count || !cert_chain.entries)
133 return result;
134
135 result.resize(cert_chain.entry_count);
136 for (size_t i = 0; i < cert_chain.entry_count; ++i) {
137 result[i] = kmBlob2hidlVec(cert_chain.entries[i]);
138 }
139
140 return result;
141}
142
143static inline hidl_vec<KeyParameter> kmParamSet2Hidl(const keymaster_key_param_set_t& set) {
144 hidl_vec<KeyParameter> result;
145 if (set.length == 0 || set.params == nullptr)
146 return result;
147
148 result.resize(set.length);
149 keymaster_key_param_t* params = set.params;
150 for (size_t i = 0; i < set.length; ++i) {
151 auto tag = params[i].tag;
152 result[i].tag = legacy_enum_conversion(tag);
153 switch (typeFromTag(tag)) {
154 case KM_ENUM:
155 case KM_ENUM_REP:
156 result[i].f.integer = params[i].enumerated;
157 break;
158 case KM_UINT:
159 case KM_UINT_REP:
160 result[i].f.integer = params[i].integer;
161 break;
162 case KM_ULONG:
163 case KM_ULONG_REP:
164 result[i].f.longInteger = params[i].long_integer;
165 break;
166 case KM_DATE:
167 result[i].f.dateTime = params[i].date_time;
168 break;
169 case KM_BOOL:
170 result[i].f.boolValue = params[i].boolean;
171 break;
172 case KM_BIGNUM:
173 case KM_BYTES:
174 result[i].blob.setToExternal(const_cast<unsigned char*>(params[i].blob.data),
175 params[i].blob.data_length);
176 break;
177 case KM_INVALID:
178 default:
179 params[i].tag = KM_TAG_INVALID;
180 /* just skip */
181 break;
182 }
183 }
184 return result;
185}
186
187void addClientAndAppData(const hidl_vec<uint8_t>& clientId, const hidl_vec<uint8_t>& appData,
188 ::keymaster::AuthorizationSet* params) {
189 params->Clear();
190 if (clientId.size()) {
191 params->push_back(::keymaster::TAG_APPLICATION_ID, clientId.data(), clientId.size());
192 }
193 if (appData.size()) {
194 params->push_back(::keymaster::TAG_APPLICATION_DATA, appData.data(), appData.size());
195 }
196}
197
198} // anonymous namespace
199
Shawn Willdenefd06732017-11-30 19:34:16 -0700200AndroidKeymaster4Device::AndroidKeymaster4Device()
Shawn Willden9e149572017-10-30 16:08:21 -0600201 : impl_(new ::keymaster::AndroidKeymaster(
202 []() -> auto {
203 auto context = new PureSoftKeymasterContext();
204 context->SetSystemVersion(GetOsVersion(), GetOsPatchlevel());
205 return context;
206 }(),
207 kOperationTableSize)) {}
208
Shawn Willdenefd06732017-11-30 19:34:16 -0700209AndroidKeymaster4Device::~AndroidKeymaster4Device() {}
Shawn Willden9e149572017-10-30 16:08:21 -0600210
Shawn Willdenefd06732017-11-30 19:34:16 -0700211Return<void> AndroidKeymaster4Device::getHardwareInfo(getHardwareInfo_cb _hidl_cb) {
Shawn Willden8123da82017-10-31 09:01:26 -0600212 _hidl_cb(::android::hardware::keymaster::V4_0::SecurityLevel::SOFTWARE,
213 "SoftwareKeymasterDevice", "Google");
214 return Void();
215}
216
217Return<void>
218AndroidKeymaster4Device::getHmacSharingParameters(getHmacSharingParameters_cb _hidl_cb) {
Shawn Willden8b940582018-01-02 10:53:39 -0700219 auto response = impl_->GetHmacSharingParameters();
220
221 ::android::hardware::keymaster::V4_0::HmacSharingParameters params;
222 params.seed.setToExternal(const_cast<uint8_t*>(response.params.seed.data),
223 response.params.seed.data_length);
224 static_assert(sizeof(response.params.nonce) == params.nonce.size(), "Nonce sizes don't match");
225 memcpy(params.nonce.data(), response.params.nonce, params.nonce.size());
226 _hidl_cb(legacy_enum_conversion(response.error), params);
Shawn Willden8123da82017-10-31 09:01:26 -0600227 return Void();
228}
229
Shawn Willden8b940582018-01-02 10:53:39 -0700230Return<void> AndroidKeymaster4Device::computeSharedHmac(
231 const hidl_vec<::android::hardware::keymaster::V4_0::HmacSharingParameters>& params,
232 computeSharedHmac_cb _hidl_cb) {
233 ComputeSharedHmacRequest request;
234 request.params_array.params_array = new keymaster::HmacSharingParameters[params.size()];
235 request.params_array.num_params = params.size();
236 for (size_t i = 0; i < params.size(); ++i) {
237 request.params_array.params_array[i].seed = {params[i].seed.data(), params[i].seed.size()};
238 static_assert(sizeof(request.params_array.params_array[i].nonce) ==
239 decltype(params[i].nonce)::size(),
240 "Nonce sizes don't match");
241 memcpy(request.params_array.params_array[i].nonce, params[i].nonce.data(),
242 params[i].nonce.size());
243 }
244
245 auto response = impl_->ComputeSharedHmac(request);
246 hidl_vec<uint8_t> sharing_check;
247 if (response.error == KM_ERROR_OK) sharing_check = kmBlob2hidlVec(response.sharing_check);
248
249 _hidl_cb(legacy_enum_conversion(response.error), sharing_check);
Shawn Willden8123da82017-10-31 09:01:26 -0600250 return Void();
251}
252
253Return<void> AndroidKeymaster4Device::verifyAuthorization(
254 uint64_t /* challenge */, const hidl_vec<KeyParameter>& /* parametersToVerify */,
255 const HardwareAuthToken& /* authToken */, verifyAuthorization_cb _hidl_cb) {
256 _hidl_cb(ErrorCode::UNIMPLEMENTED, {});
Shawn Willden9e149572017-10-30 16:08:21 -0600257 return Void();
258}
259
Shawn Willdenefd06732017-11-30 19:34:16 -0700260Return<ErrorCode> AndroidKeymaster4Device::addRngEntropy(const hidl_vec<uint8_t>& data) {
Shawn Willden9e149572017-10-30 16:08:21 -0600261 if (data.size() == 0)
262 return ErrorCode::OK;
263 AddEntropyRequest request;
264 request.random_data.Reinitialize(data.data(), data.size());
265
266 AddEntropyResponse response;
267 impl_->AddRngEntropy(request, &response);
268
269 return legacy_enum_conversion(response.error);
270}
271
Shawn Willdenefd06732017-11-30 19:34:16 -0700272Return<void> AndroidKeymaster4Device::generateKey(const hidl_vec<KeyParameter>& keyParams,
273 generateKey_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600274 GenerateKeyRequest request;
275 request.key_description.Reinitialize(KmParamSet(keyParams));
276
277 GenerateKeyResponse response;
278 impl_->GenerateKey(request, &response);
279
280 KeyCharacteristics resultCharacteristics;
281 hidl_vec<uint8_t> resultKeyBlob;
282 if (response.error == KM_ERROR_OK) {
283 resultKeyBlob = kmBlob2hidlVec(response.key_blob);
284 resultCharacteristics.hardwareEnforced = kmParamSet2Hidl(response.enforced);
285 resultCharacteristics.softwareEnforced = kmParamSet2Hidl(response.unenforced);
286 }
287 _hidl_cb(legacy_enum_conversion(response.error), resultKeyBlob, resultCharacteristics);
288 return Void();
289}
290
Shawn Willdenefd06732017-11-30 19:34:16 -0700291Return<void> AndroidKeymaster4Device::getKeyCharacteristics(const hidl_vec<uint8_t>& keyBlob,
292 const hidl_vec<uint8_t>& clientId,
293 const hidl_vec<uint8_t>& appData,
294 getKeyCharacteristics_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600295 GetKeyCharacteristicsRequest request;
296 request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
297 addClientAndAppData(clientId, appData, &request.additional_params);
298
299 GetKeyCharacteristicsResponse response;
300 impl_->GetKeyCharacteristics(request, &response);
301
302 KeyCharacteristics resultCharacteristics;
303 if (response.error == KM_ERROR_OK) {
304 resultCharacteristics.hardwareEnforced = kmParamSet2Hidl(response.enforced);
305 resultCharacteristics.softwareEnforced = kmParamSet2Hidl(response.unenforced);
306 }
307 _hidl_cb(legacy_enum_conversion(response.error), resultCharacteristics);
308 return Void();
309}
310
Shawn Willdenefd06732017-11-30 19:34:16 -0700311Return<void> AndroidKeymaster4Device::importKey(const hidl_vec<KeyParameter>& params,
312 KeyFormat keyFormat,
313 const hidl_vec<uint8_t>& keyData,
314 importKey_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600315 ImportKeyRequest request;
316 request.key_description.Reinitialize(KmParamSet(params));
317 request.key_format = legacy_enum_conversion(keyFormat);
318 request.SetKeyMaterial(keyData.data(), keyData.size());
319
320 ImportKeyResponse response;
321 impl_->ImportKey(request, &response);
322
323 KeyCharacteristics resultCharacteristics;
324 hidl_vec<uint8_t> resultKeyBlob;
325 if (response.error == KM_ERROR_OK) {
326 resultKeyBlob = kmBlob2hidlVec(response.key_blob);
327 resultCharacteristics.hardwareEnforced = kmParamSet2Hidl(response.enforced);
328 resultCharacteristics.softwareEnforced = kmParamSet2Hidl(response.unenforced);
329 }
330 _hidl_cb(legacy_enum_conversion(response.error), resultKeyBlob, resultCharacteristics);
331 return Void();
332}
333
Shawn Willden8123da82017-10-31 09:01:26 -0600334Return<void> AndroidKeymaster4Device::importWrappedKey(
335 const hidl_vec<uint8_t>& /* wrappedKeyData */, const hidl_vec<uint8_t>& /* wrappingKeyBlob */,
336 const hidl_vec<uint8_t>& /* maskingKey */, importWrappedKey_cb _hidl_cb) {
337 // TODO(franksalim): PUT CODE HERE
338 _hidl_cb(ErrorCode::UNIMPLEMENTED, hidl_vec<uint8_t>(), KeyCharacteristics());
339 return Void();
340}
341
Shawn Willdenefd06732017-11-30 19:34:16 -0700342Return<void> AndroidKeymaster4Device::exportKey(KeyFormat exportFormat,
343 const hidl_vec<uint8_t>& keyBlob,
344 const hidl_vec<uint8_t>& clientId,
345 const hidl_vec<uint8_t>& appData,
346 exportKey_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600347 ExportKeyRequest request;
348 request.key_format = legacy_enum_conversion(exportFormat);
349 request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
350 addClientAndAppData(clientId, appData, &request.additional_params);
351
352 ExportKeyResponse response;
353 impl_->ExportKey(request, &response);
354
355 hidl_vec<uint8_t> resultKeyBlob;
356 if (response.error == KM_ERROR_OK) {
357 resultKeyBlob.setToExternal(response.key_data, response.key_data_length);
358 }
359 _hidl_cb(legacy_enum_conversion(response.error), resultKeyBlob);
360 return Void();
361}
362
Shawn Willdenefd06732017-11-30 19:34:16 -0700363Return<void> AndroidKeymaster4Device::attestKey(const hidl_vec<uint8_t>& keyToAttest,
364 const hidl_vec<KeyParameter>& attestParams,
365 attestKey_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600366 AttestKeyRequest request;
367 request.SetKeyMaterial(keyToAttest.data(), keyToAttest.size());
368 request.attest_params.Reinitialize(KmParamSet(attestParams));
369
370 AttestKeyResponse response;
371 impl_->AttestKey(request, &response);
372
373 hidl_vec<hidl_vec<uint8_t>> resultCertChain;
374 if (response.error == KM_ERROR_OK) {
375 resultCertChain = kmCertChain2Hidl(response.certificate_chain);
376 }
377 _hidl_cb(legacy_enum_conversion(response.error), resultCertChain);
378 return Void();
379}
380
Shawn Willdenefd06732017-11-30 19:34:16 -0700381Return<void> AndroidKeymaster4Device::upgradeKey(const hidl_vec<uint8_t>& keyBlobToUpgrade,
382 const hidl_vec<KeyParameter>& upgradeParams,
383 upgradeKey_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600384 // There's nothing to be done to upgrade software key blobs. Further, the software
385 // implementation never returns ErrorCode::KEY_REQUIRES_UPGRADE, so this should never be called.
386 UpgradeKeyRequest request;
387 request.SetKeyMaterial(keyBlobToUpgrade.data(), keyBlobToUpgrade.size());
388 request.upgrade_params.Reinitialize(KmParamSet(upgradeParams));
389
390 UpgradeKeyResponse response;
391 impl_->UpgradeKey(request, &response);
392
393 if (response.error == KM_ERROR_OK) {
394 _hidl_cb(ErrorCode::OK, kmBlob2hidlVec(response.upgraded_key));
395 } else {
396 _hidl_cb(legacy_enum_conversion(response.error), hidl_vec<uint8_t>());
397 }
398 return Void();
399}
400
Shawn Willdenefd06732017-11-30 19:34:16 -0700401Return<ErrorCode> AndroidKeymaster4Device::deleteKey(const hidl_vec<uint8_t>& keyBlob) {
Shawn Willden9e149572017-10-30 16:08:21 -0600402 // There's nothing to be done to delete software key blobs.
403 DeleteKeyRequest request;
404 request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
405
406 DeleteKeyResponse response;
407 impl_->DeleteKey(request, &response);
408
409 return legacy_enum_conversion(response.error);
410}
411
Shawn Willdenefd06732017-11-30 19:34:16 -0700412Return<ErrorCode> AndroidKeymaster4Device::deleteAllKeys() {
Shawn Willden9e149572017-10-30 16:08:21 -0600413 // There's nothing to be done to delete software key blobs.
414 DeleteAllKeysRequest request;
415 DeleteAllKeysResponse response;
416 impl_->DeleteAllKeys(request, &response);
417
418 return legacy_enum_conversion(response.error);
419}
420
Shawn Willdenefd06732017-11-30 19:34:16 -0700421Return<ErrorCode> AndroidKeymaster4Device::destroyAttestationIds() {
Shawn Willden9e149572017-10-30 16:08:21 -0600422 return ErrorCode::UNIMPLEMENTED;
423}
424
Shawn Willdenefd06732017-11-30 19:34:16 -0700425Return<void> AndroidKeymaster4Device::begin(KeyPurpose purpose, const hidl_vec<uint8_t>& key,
426 const hidl_vec<KeyParameter>& inParams,
427 const HardwareAuthToken& /* authToken */,
428 begin_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600429
430 BeginOperationRequest request;
431 request.purpose = legacy_enum_conversion(purpose);
432 request.SetKeyMaterial(key.data(), key.size());
433 request.additional_params.Reinitialize(KmParamSet(inParams));
434
435 BeginOperationResponse response;
436 impl_->BeginOperation(request, &response);
437
438 hidl_vec<KeyParameter> resultParams;
439 if (response.error == KM_ERROR_OK) {
440 resultParams = kmParamSet2Hidl(response.output_params);
441 }
442
443 _hidl_cb(legacy_enum_conversion(response.error), resultParams, response.op_handle);
444 return Void();
445}
446
Shawn Willdenefd06732017-11-30 19:34:16 -0700447Return<void> AndroidKeymaster4Device::update(uint64_t operationHandle,
448 const hidl_vec<KeyParameter>& inParams,
449 const hidl_vec<uint8_t>& input,
450 const HardwareAuthToken& /* authToken */,
Shawn Willden8123da82017-10-31 09:01:26 -0600451 const VerificationToken& /* verificationToken */,
Shawn Willdenefd06732017-11-30 19:34:16 -0700452 update_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600453 UpdateOperationRequest request;
454 request.op_handle = operationHandle;
455 request.input.Reinitialize(input.data(), input.size());
456 request.additional_params.Reinitialize(KmParamSet(inParams));
457
458 UpdateOperationResponse response;
459 impl_->UpdateOperation(request, &response);
460
461 uint32_t resultConsumed = 0;
462 hidl_vec<KeyParameter> resultParams;
463 hidl_vec<uint8_t> resultBlob;
464 if (response.error == KM_ERROR_OK) {
465 resultConsumed = response.input_consumed;
466 resultParams = kmParamSet2Hidl(response.output_params);
467 resultBlob = kmBuffer2hidlVec(response.output);
468 }
469 _hidl_cb(legacy_enum_conversion(response.error), resultConsumed, resultParams, resultBlob);
470 return Void();
471}
472
Shawn Willden8123da82017-10-31 09:01:26 -0600473Return<void> AndroidKeymaster4Device::finish(uint64_t operationHandle,
474 const hidl_vec<KeyParameter>& inParams,
475 const hidl_vec<uint8_t>& input,
476 const hidl_vec<uint8_t>& signature,
477 const HardwareAuthToken& /* authToken */,
478 const VerificationToken& /* verificationToken */,
479 finish_cb _hidl_cb) {
Shawn Willden9e149572017-10-30 16:08:21 -0600480 FinishOperationRequest request;
481 request.op_handle = operationHandle;
482 request.input.Reinitialize(input.data(), input.size());
483 request.signature.Reinitialize(signature.data(), signature.size());
484 request.additional_params.Reinitialize(KmParamSet(inParams));
485
486 FinishOperationResponse response;
487 impl_->FinishOperation(request, &response);
488
489 hidl_vec<KeyParameter> resultParams;
490 hidl_vec<uint8_t> resultBlob;
491 if (response.error == KM_ERROR_OK) {
492 resultParams = kmParamSet2Hidl(response.output_params);
493 resultBlob = kmBuffer2hidlVec(response.output);
494 }
495 _hidl_cb(legacy_enum_conversion(response.error), resultParams, resultBlob);
496 return Void();
497}
498
Shawn Willdenefd06732017-11-30 19:34:16 -0700499Return<ErrorCode> AndroidKeymaster4Device::abort(uint64_t operationHandle) {
Shawn Willden9e149572017-10-30 16:08:21 -0600500 AbortOperationRequest request;
501 request.op_handle = operationHandle;
502
503 AbortOperationResponse response;
504 impl_->AbortOperation(request, &response);
505
506 return legacy_enum_conversion(response.error);
507}
508
Shawn Willdenefd06732017-11-30 19:34:16 -0700509IKeymasterDevice* CreateKeymasterDevice() {
510 return new AndroidKeymaster4Device();
Shawn Willden9e149572017-10-30 16:08:21 -0600511}
512
513} // namespace ng
514} // namespace V4_0
515} // namespace keymaster