blob: 2d897c7d8bc83c68489aa45f16143eed0aef0018 [file] [log] [blame]
David Zeuthenc75ac312019-10-28 13:16:45 -04001/*
2 * Copyright 2019, 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#define LOG_TAG "WritableIdentityCredential"
18
Selene Huang459cb802020-01-08 22:59:02 -080019#include "WritableIdentityCredential.h"
Selene Huang459cb802020-01-08 22:59:02 -080020
David Zeuthenc75ac312019-10-28 13:16:45 -040021#include <android/hardware/identity/support/IdentityCredentialSupport.h>
22
23#include <android-base/logging.h>
David Zeuthen28edb102020-04-28 18:54:55 -040024#include <android-base/stringprintf.h>
David Zeuthenc75ac312019-10-28 13:16:45 -040025
26#include <cppbor/cppbor.h>
27#include <cppbor/cppbor_parse.h>
28
Selene Huang459cb802020-01-08 22:59:02 -080029#include <utility>
30
David Zeuthen81603152020-02-11 22:04:24 -050031#include "IdentityCredentialStore.h"
David Zeuthen630de2a2020-05-11 14:04:54 -040032
33#include "FakeSecureHardwareProxy.h"
David Zeuthen81603152020-02-11 22:04:24 -050034
35namespace aidl::android::hardware::identity {
David Zeuthenc75ac312019-10-28 13:16:45 -040036
David Zeuthen28edb102020-04-28 18:54:55 -040037using ::android::base::StringPrintf;
David Zeuthenc75ac312019-10-28 13:16:45 -040038using ::std::optional;
David Zeuthen81603152020-02-11 22:04:24 -050039using namespace ::android::hardware::identity;
40
41bool WritableIdentityCredential::initialize() {
David Zeuthen630de2a2020-05-11 14:04:54 -040042 if (!hwProxy_->initialize(testCredential_)) {
David Zeuthen49f2d252020-10-16 11:27:24 -040043 LOG(ERROR) << "hwProxy->initialize() failed";
44 return false;
45 }
46 startPersonalizationCalled_ = false;
47 firstEntry_ = true;
48
49 return true;
50}
51
52// Used when updating a credential. Returns false on failure.
53bool WritableIdentityCredential::initializeForUpdate(
54 const vector<uint8_t>& encryptedCredentialKeys) {
55 if (!hwProxy_->initializeForUpdate(testCredential_, docType_, encryptedCredentialKeys)) {
56 LOG(ERROR) << "hwProxy->initializeForUpdate() failed";
David Zeuthen81603152020-02-11 22:04:24 -050057 return false;
58 }
Selene Huang92b61d62020-03-04 02:24:16 -080059 startPersonalizationCalled_ = false;
60 firstEntry_ = true;
David Zeuthen81603152020-02-11 22:04:24 -050061
62 return true;
63}
64
David Zeuthen630de2a2020-05-11 14:04:54 -040065WritableIdentityCredential::~WritableIdentityCredential() {}
66
David Zeuthen81603152020-02-11 22:04:24 -050067ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
David Zeuthen630de2a2020-05-11 14:04:54 -040068 const vector<uint8_t>& attestationApplicationId,
69 const vector<uint8_t>& attestationChallenge, vector<Certificate>* outCertificateChain) {
70 if (getAttestationCertificateAlreadyCalled_) {
David Zeuthen81603152020-02-11 22:04:24 -050071 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
72 IIdentityCredentialStore::STATUS_FAILED,
Selene Huang459cb802020-01-08 22:59:02 -080073 "Error attestation certificate previously generated"));
David Zeuthen81603152020-02-11 22:04:24 -050074 }
David Zeuthen630de2a2020-05-11 14:04:54 -040075 getAttestationCertificateAlreadyCalled_ = true;
76
David Zeuthenef739512020-06-03 13:24:52 -040077 if (attestationChallenge.empty()) {
78 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
79 IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
80 }
David Zeuthen81603152020-02-11 22:04:24 -050081
David Zeuthen630de2a2020-05-11 14:04:54 -040082 optional<vector<uint8_t>> certChain =
83 hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId);
84 if (!certChain) {
David Zeuthen81603152020-02-11 22:04:24 -050085 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
86 IIdentityCredentialStore::STATUS_FAILED,
David Zeuthen630de2a2020-05-11 14:04:54 -040087 "Error generating attestation certificate chain"));
David Zeuthen81603152020-02-11 22:04:24 -050088 }
89
David Zeuthen630de2a2020-05-11 14:04:54 -040090 optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certChain.value());
91 if (!certs) {
David Zeuthen81603152020-02-11 22:04:24 -050092 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
93 IIdentityCredentialStore::STATUS_FAILED,
David Zeuthen630de2a2020-05-11 14:04:54 -040094 "Error splitting chain into separate certificates"));
David Zeuthen81603152020-02-11 22:04:24 -050095 }
96
David Zeuthen81603152020-02-11 22:04:24 -050097 *outCertificateChain = vector<Certificate>();
David Zeuthen630de2a2020-05-11 14:04:54 -040098 for (const vector<uint8_t>& cert : certs.value()) {
David Zeuthen81603152020-02-11 22:04:24 -050099 Certificate c = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900100 c.encodedCertificate = cert;
David Zeuthen81603152020-02-11 22:04:24 -0500101 outCertificateChain->push_back(std::move(c));
102 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400103
David Zeuthen81603152020-02-11 22:04:24 -0500104 return ndk::ScopedAStatus::ok();
105}
106
David Zeuthen28edb102020-04-28 18:54:55 -0400107ndk::ScopedAStatus WritableIdentityCredential::setExpectedProofOfProvisioningSize(
108 int32_t expectedProofOfProvisioningSize) {
109 expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
110 return ndk::ScopedAStatus::ok();
111}
112
David Zeuthen81603152020-02-11 22:04:24 -0500113ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
114 int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
Selene Huang92b61d62020-03-04 02:24:16 -0800115 if (startPersonalizationCalled_) {
116 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
117 IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
118 }
Selene Huang92b61d62020-03-04 02:24:16 -0800119 startPersonalizationCalled_ = true;
David Zeuthen630de2a2020-05-11 14:04:54 -0400120
David Zeuthen81603152020-02-11 22:04:24 -0500121 numAccessControlProfileRemaining_ = accessControlProfileCount;
122 remainingEntryCounts_ = entryCounts;
123 entryNameSpace_ = "";
124
125 signedDataAccessControlProfiles_ = cppbor::Array();
126 signedDataNamespaces_ = cppbor::Map();
127 signedDataCurrentNamespace_ = cppbor::Array();
128
David Zeuthen630de2a2020-05-11 14:04:54 -0400129 if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_,
130 expectedProofOfProvisioningSize_)) {
131 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
132 IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
133 }
134
David Zeuthen81603152020-02-11 22:04:24 -0500135 return ndk::ScopedAStatus::ok();
136}
137
138ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
139 int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
140 int64_t timeoutMillis, int64_t secureUserId,
141 SecureAccessControlProfile* outSecureAccessControlProfile) {
David Zeuthen81603152020-02-11 22:04:24 -0500142 if (numAccessControlProfileRemaining_ == 0) {
143 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
144 IIdentityCredentialStore::STATUS_INVALID_DATA,
145 "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
146 }
147
Selene Huang92b61d62020-03-04 02:24:16 -0800148 if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
149 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
150 IIdentityCredentialStore::STATUS_INVALID_DATA,
151 "Access Control Profile id must be unique"));
152 }
153 accessControlProfileIds_.insert(id);
154
David Zeuthena0796e92020-04-27 15:24:55 -0400155 if (id < 0 || id >= 32) {
156 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
157 IIdentityCredentialStore::STATUS_INVALID_DATA,
158 "Access Control Profile id must be non-negative and less than 32"));
159 }
160
David Zeuthen81603152020-02-11 22:04:24 -0500161 // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
162 // be zero.
163 if (!userAuthenticationRequired && timeoutMillis != 0) {
164 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
165 IIdentityCredentialStore::STATUS_INVALID_DATA,
166 "userAuthenticationRequired is false but timeout is non-zero"));
167 }
168
David Zeuthen630de2a2020-05-11 14:04:54 -0400169 optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
170 id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis,
171 secureUserId);
172 if (!mac) {
David Zeuthenef739512020-06-03 13:24:52 -0400173 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
David Zeuthen630de2a2020-05-11 14:04:54 -0400174 IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
David Zeuthenef739512020-06-03 13:24:52 -0400175 }
176
David Zeuthen630de2a2020-05-11 14:04:54 -0400177 SecureAccessControlProfile profile;
David Zeuthen81603152020-02-11 22:04:24 -0500178 profile.id = id;
179 profile.readerCertificate = readerCertificate;
180 profile.userAuthenticationRequired = userAuthenticationRequired;
181 profile.timeoutMillis = timeoutMillis;
182 profile.secureUserId = secureUserId;
Jooyung Han17be89b2020-02-21 21:17:06 +0900183 profile.mac = mac.value();
David Zeuthen81603152020-02-11 22:04:24 -0500184 cppbor::Map profileMap;
185 profileMap.add("id", profile.id);
186 if (profile.readerCertificate.encodedCertificate.size() > 0) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900187 profileMap.add("readerCertificate",
188 cppbor::Bstr(profile.readerCertificate.encodedCertificate));
David Zeuthen81603152020-02-11 22:04:24 -0500189 }
190 if (profile.userAuthenticationRequired) {
191 profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired);
192 profileMap.add("timeoutMillis", profile.timeoutMillis);
193 }
194 signedDataAccessControlProfiles_.add(std::move(profileMap));
195
196 numAccessControlProfileRemaining_--;
197
198 *outSecureAccessControlProfile = profile;
199 return ndk::ScopedAStatus::ok();
200}
201
202ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry(
203 const vector<int32_t>& accessControlProfileIds, const string& nameSpace, const string& name,
204 int32_t entrySize) {
205 if (numAccessControlProfileRemaining_ != 0) {
206 LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_
207 << " and expected zero";
208 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
209 IIdentityCredentialStore::STATUS_INVALID_DATA,
210 "numAccessControlProfileRemaining_ is not zero"));
211 }
212
213 if (remainingEntryCounts_.size() == 0) {
214 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
215 IIdentityCredentialStore::STATUS_INVALID_DATA, "No more namespaces to add to"));
216 }
217
218 // Handle initial beginEntry() call.
Selene Huang92b61d62020-03-04 02:24:16 -0800219 if (firstEntry_) {
220 firstEntry_ = false;
David Zeuthen81603152020-02-11 22:04:24 -0500221 entryNameSpace_ = nameSpace;
Selene Huang92b61d62020-03-04 02:24:16 -0800222 allNameSpaces_.insert(nameSpace);
David Zeuthen81603152020-02-11 22:04:24 -0500223 }
224
225 // If the namespace changed...
226 if (nameSpace != entryNameSpace_) {
Selene Huang92b61d62020-03-04 02:24:16 -0800227 if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
228 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
229 IIdentityCredentialStore::STATUS_INVALID_DATA,
230 "Name space cannot be added in interleaving fashion"));
231 }
232
David Zeuthen81603152020-02-11 22:04:24 -0500233 // Then check that all entries in the previous namespace have been added..
234 if (remainingEntryCounts_[0] != 0) {
235 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
236 IIdentityCredentialStore::STATUS_INVALID_DATA,
237 "New namespace but a non-zero number of entries remain to be added"));
238 }
239 remainingEntryCounts_.erase(remainingEntryCounts_.begin());
Selene Huang92b61d62020-03-04 02:24:16 -0800240 remainingEntryCounts_[0] -= 1;
241 allNameSpaces_.insert(nameSpace);
David Zeuthen81603152020-02-11 22:04:24 -0500242
243 if (signedDataCurrentNamespace_.size() > 0) {
244 signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
245 signedDataCurrentNamespace_ = cppbor::Array();
246 }
247 } else {
248 // Same namespace...
249 if (remainingEntryCounts_[0] == 0) {
250 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
251 IIdentityCredentialStore::STATUS_INVALID_DATA,
252 "Same namespace but no entries remain to be added"));
253 }
254 remainingEntryCounts_[0] -= 1;
255 }
256
David Zeuthen81603152020-02-11 22:04:24 -0500257 entryRemainingBytes_ = entrySize;
258 entryNameSpace_ = nameSpace;
259 entryName_ = name;
260 entryAccessControlProfileIds_ = accessControlProfileIds;
261 entryBytes_.resize(0);
262 // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
David Zeuthen630de2a2020-05-11 14:04:54 -0400263
264 if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) {
265 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
266 IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
267 }
268
David Zeuthen81603152020-02-11 22:04:24 -0500269 return ndk::ScopedAStatus::ok();
270}
271
Jooyung Han17be89b2020-02-21 21:17:06 +0900272ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<uint8_t>& content,
273 vector<uint8_t>* outEncryptedContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500274 size_t contentSize = content.size();
275
276 if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
277 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
278 IIdentityCredentialStore::STATUS_INVALID_DATA,
279 "Passed in chunk of is bigger than kGcmChunkSize"));
280 }
281 if (contentSize > entryRemainingBytes_) {
282 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
283 IIdentityCredentialStore::STATUS_INVALID_DATA,
284 "Passed in chunk is bigger than remaining space"));
285 }
286
287 entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
288 entryRemainingBytes_ -= contentSize;
289 if (entryRemainingBytes_ > 0) {
290 if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
291 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
292 IIdentityCredentialStore::STATUS_INVALID_DATA,
293 "Retrieved non-final chunk which isn't kGcmChunkSize"));
294 }
295 }
296
David Zeuthen630de2a2020-05-11 14:04:54 -0400297 optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
298 entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
David Zeuthen81603152020-02-11 22:04:24 -0500299 if (!encryptedContent) {
300 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
David Zeuthen630de2a2020-05-11 14:04:54 -0400301 IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
David Zeuthen81603152020-02-11 22:04:24 -0500302 }
303
304 if (entryRemainingBytes_ == 0) {
305 // TODO: ideally do do this without parsing the data (but still validate data is valid
306 // CBOR).
307 auto [item, _, message] = cppbor::parse(entryBytes_);
308 if (item == nullptr) {
309 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
310 IIdentityCredentialStore::STATUS_INVALID_DATA, "Data is not valid CBOR"));
311 }
312 cppbor::Map entryMap;
313 entryMap.add("name", entryName_);
314 entryMap.add("value", std::move(item));
315 cppbor::Array profileIdArray;
316 for (auto id : entryAccessControlProfileIds_) {
317 profileIdArray.add(id);
318 }
319 entryMap.add("accessControlProfiles", std::move(profileIdArray));
320 signedDataCurrentNamespace_.add(std::move(entryMap));
321 }
322
Jooyung Han17be89b2020-02-21 21:17:06 +0900323 *outEncryptedContent = encryptedContent.value();
David Zeuthen81603152020-02-11 22:04:24 -0500324 return ndk::ScopedAStatus::ok();
325}
David Zeuthenc75ac312019-10-28 13:16:45 -0400326
David Zeuthen81603152020-02-11 22:04:24 -0500327ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
Jooyung Han17be89b2020-02-21 21:17:06 +0900328 vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
Selene Huang92b61d62020-03-04 02:24:16 -0800329 if (numAccessControlProfileRemaining_ != 0) {
330 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
331 IIdentityCredentialStore::STATUS_INVALID_DATA,
332 "numAccessControlProfileRemaining_ is not 0 and expected zero"));
333 }
334
335 if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
336 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
337 IIdentityCredentialStore::STATUS_INVALID_DATA,
338 "More entry spaces remain than startPersonalization configured"));
339 }
340
David Zeuthenc75ac312019-10-28 13:16:45 -0400341 if (signedDataCurrentNamespace_.size() > 0) {
342 signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
343 }
344 cppbor::Array popArray;
345 popArray.add("ProofOfProvisioning")
346 .add(docType_)
347 .add(std::move(signedDataAccessControlProfiles_))
348 .add(std::move(signedDataNamespaces_))
349 .add(testCredential_);
350 vector<uint8_t> encodedCbor = popArray.encode();
351
David Zeuthen28edb102020-04-28 18:54:55 -0400352 if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
353 LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size() << " bytes, "
354 << "was expecting " << expectedProofOfProvisioningSize_;
355 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
356 IIdentityCredentialStore::STATUS_INVALID_DATA,
357 StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was expecting %zd",
358 encodedCbor.size(), expectedProofOfProvisioningSize_)
359 .c_str()));
360 }
361
David Zeuthen630de2a2020-05-11 14:04:54 -0400362 optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries();
363 if (!signatureOfToBeSigned) {
364 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
365 IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
366 }
367
368 optional<vector<uint8_t>> signature =
369 support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
370 encodedCbor, // data
371 {}); // certificateChain
David Zeuthenc75ac312019-10-28 13:16:45 -0400372 if (!signature) {
David Zeuthen81603152020-02-11 22:04:24 -0500373 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
374 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
David Zeuthenc75ac312019-10-28 13:16:45 -0400375 }
376
David Zeuthen630de2a2020-05-11 14:04:54 -0400377 optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_);
378 if (!encryptedCredentialKeys) {
David Zeuthen81603152020-02-11 22:04:24 -0500379 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
David Zeuthen630de2a2020-05-11 14:04:54 -0400380 IIdentityCredentialStore::STATUS_FAILED,
381 "Error generating encrypted CredentialKeys"));
David Zeuthenc75ac312019-10-28 13:16:45 -0400382 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400383 cppbor::Array array;
384 array.add(docType_);
385 array.add(testCredential_);
386 array.add(encryptedCredentialKeys.value());
387 vector<uint8_t> credentialData = array.encode();
David Zeuthenc75ac312019-10-28 13:16:45 -0400388
Jooyung Han17be89b2020-02-21 21:17:06 +0900389 *outCredentialData = credentialData;
390 *outProofOfProvisioningSignature = signature.value();
David Zeuthen630de2a2020-05-11 14:04:54 -0400391 hwProxy_->shutdown();
392
David Zeuthen81603152020-02-11 22:04:24 -0500393 return ndk::ScopedAStatus::ok();
David Zeuthenc75ac312019-10-28 13:16:45 -0400394}
395
David Zeuthen81603152020-02-11 22:04:24 -0500396} // namespace aidl::android::hardware::identity