blob: 8bc4b49a798ae230059518340448492a90e3d6b2 [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"
20#include "IdentityCredentialStore.h"
21
David Zeuthenc75ac312019-10-28 13:16:45 -040022#include <android/hardware/identity/support/IdentityCredentialSupport.h>
23
24#include <android-base/logging.h>
David Zeuthen28edb102020-04-28 18:54:55 -040025#include <android-base/stringprintf.h>
David Zeuthenc75ac312019-10-28 13:16:45 -040026
27#include <cppbor/cppbor.h>
28#include <cppbor/cppbor_parse.h>
29
Selene Huang459cb802020-01-08 22:59:02 -080030#include <utility>
31
David Zeuthen81603152020-02-11 22:04:24 -050032#include "IdentityCredentialStore.h"
33#include "Util.h"
34#include "WritableIdentityCredential.h"
35
36namespace aidl::android::hardware::identity {
David Zeuthenc75ac312019-10-28 13:16:45 -040037
David Zeuthen28edb102020-04-28 18:54:55 -040038using ::android::base::StringPrintf;
David Zeuthenc75ac312019-10-28 13:16:45 -040039using ::std::optional;
David Zeuthen81603152020-02-11 22:04:24 -050040using namespace ::android::hardware::identity;
41
42bool WritableIdentityCredential::initialize() {
David Zeuthen81603152020-02-11 22:04:24 -050043 optional<vector<uint8_t>> random = support::getRandom(16);
44 if (!random) {
45 LOG(ERROR) << "Error creating storageKey";
46 return false;
47 }
48 storageKey_ = random.value();
Selene Huang92b61d62020-03-04 02:24:16 -080049 startPersonalizationCalled_ = false;
50 firstEntry_ = true;
David Zeuthen81603152020-02-11 22:04:24 -050051
52 return true;
53}
54
Selene Huang459cb802020-01-08 22:59:02 -080055// This function generates the attestation certificate using the passed in
56// |attestationApplicationId| and |attestationChallenge|. It will generate an
57// attestation certificate with current time and expires one year from now. The
58// certificate shall contain all values as specified in hal.
David Zeuthen81603152020-02-11 22:04:24 -050059ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
Jooyung Han17be89b2020-02-21 21:17:06 +090060 const vector<uint8_t>& attestationApplicationId, //
61 const vector<uint8_t>& attestationChallenge, //
Selene Huang459cb802020-01-08 22:59:02 -080062 vector<Certificate>* outCertificateChain) {
63 if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
David Zeuthen81603152020-02-11 22:04:24 -050064 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
65 IIdentityCredentialStore::STATUS_FAILED,
Selene Huang459cb802020-01-08 22:59:02 -080066 "Error attestation certificate previously generated"));
David Zeuthen81603152020-02-11 22:04:24 -050067 }
68
Selene Huang459cb802020-01-08 22:59:02 -080069 vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
70 vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
71
72 optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
73 support::createEcKeyPairAndAttestation(challenge, appId);
74 if (!keyAttestationPair) {
75 LOG(ERROR) << "Error creating credentialKey and attestation";
David Zeuthen81603152020-02-11 22:04:24 -050076 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
77 IIdentityCredentialStore::STATUS_FAILED,
Selene Huang459cb802020-01-08 22:59:02 -080078 "Error creating credentialKey and attestation"));
David Zeuthen81603152020-02-11 22:04:24 -050079 }
80
Selene Huang459cb802020-01-08 22:59:02 -080081 vector<uint8_t> keyPair = keyAttestationPair.value().first;
82 certificateChain_ = keyAttestationPair.value().second;
David Zeuthen81603152020-02-11 22:04:24 -050083
Selene Huang459cb802020-01-08 22:59:02 -080084 optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
85 if (!pubKey) {
David Zeuthen81603152020-02-11 22:04:24 -050086 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
87 IIdentityCredentialStore::STATUS_FAILED,
Selene Huang459cb802020-01-08 22:59:02 -080088 "Error getting public part of credentialKey"));
David Zeuthen81603152020-02-11 22:04:24 -050089 }
Selene Huang459cb802020-01-08 22:59:02 -080090 credentialPubKey_ = pubKey.value();
David Zeuthen81603152020-02-11 22:04:24 -050091
Selene Huang459cb802020-01-08 22:59:02 -080092 optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair);
93 if (!privKey) {
David Zeuthen81603152020-02-11 22:04:24 -050094 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
95 IIdentityCredentialStore::STATUS_FAILED,
Selene Huang459cb802020-01-08 22:59:02 -080096 "Error getting private part of credentialKey"));
David Zeuthen81603152020-02-11 22:04:24 -050097 }
Selene Huang459cb802020-01-08 22:59:02 -080098 credentialPrivKey_ = privKey.value();
David Zeuthen81603152020-02-11 22:04:24 -050099
Selene Huang459cb802020-01-08 22:59:02 -0800100 // convert from vector<vector<uint8_t>>> to vector<Certificate>*
David Zeuthen81603152020-02-11 22:04:24 -0500101 *outCertificateChain = vector<Certificate>();
Selene Huang459cb802020-01-08 22:59:02 -0800102 for (const vector<uint8_t>& cert : certificateChain_) {
David Zeuthen81603152020-02-11 22:04:24 -0500103 Certificate c = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900104 c.encodedCertificate = cert;
David Zeuthen81603152020-02-11 22:04:24 -0500105 outCertificateChain->push_back(std::move(c));
106 }
107 return ndk::ScopedAStatus::ok();
108}
109
David Zeuthen28edb102020-04-28 18:54:55 -0400110ndk::ScopedAStatus WritableIdentityCredential::setExpectedProofOfProvisioningSize(
111 int32_t expectedProofOfProvisioningSize) {
112 expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
113 return ndk::ScopedAStatus::ok();
114}
115
David Zeuthen81603152020-02-11 22:04:24 -0500116ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
117 int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
Selene Huang92b61d62020-03-04 02:24:16 -0800118 if (startPersonalizationCalled_) {
119 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
120 IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
121 }
122
123 startPersonalizationCalled_ = true;
David Zeuthen81603152020-02-11 22:04:24 -0500124 numAccessControlProfileRemaining_ = accessControlProfileCount;
125 remainingEntryCounts_ = entryCounts;
126 entryNameSpace_ = "";
127
128 signedDataAccessControlProfiles_ = cppbor::Array();
129 signedDataNamespaces_ = cppbor::Map();
130 signedDataCurrentNamespace_ = cppbor::Array();
131
132 return ndk::ScopedAStatus::ok();
133}
134
135ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
136 int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
137 int64_t timeoutMillis, int64_t secureUserId,
138 SecureAccessControlProfile* outSecureAccessControlProfile) {
139 SecureAccessControlProfile profile;
140
141 if (numAccessControlProfileRemaining_ == 0) {
142 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
143 IIdentityCredentialStore::STATUS_INVALID_DATA,
144 "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
145 }
146
Selene Huang92b61d62020-03-04 02:24:16 -0800147 if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
148 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
149 IIdentityCredentialStore::STATUS_INVALID_DATA,
150 "Access Control Profile id must be unique"));
151 }
152 accessControlProfileIds_.insert(id);
153
David Zeuthena0796e92020-04-27 15:24:55 -0400154 if (id < 0 || id >= 32) {
155 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
156 IIdentityCredentialStore::STATUS_INVALID_DATA,
157 "Access Control Profile id must be non-negative and less than 32"));
158 }
159
David Zeuthen81603152020-02-11 22:04:24 -0500160 // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
161 // be zero.
162 if (!userAuthenticationRequired && timeoutMillis != 0) {
163 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
164 IIdentityCredentialStore::STATUS_INVALID_DATA,
165 "userAuthenticationRequired is false but timeout is non-zero"));
166 }
167
168 profile.id = id;
169 profile.readerCertificate = readerCertificate;
170 profile.userAuthenticationRequired = userAuthenticationRequired;
171 profile.timeoutMillis = timeoutMillis;
172 profile.secureUserId = secureUserId;
173 optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_);
174 if (!mac) {
175 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
176 IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile"));
177 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900178 profile.mac = mac.value();
David Zeuthen81603152020-02-11 22:04:24 -0500179
180 cppbor::Map profileMap;
181 profileMap.add("id", profile.id);
182 if (profile.readerCertificate.encodedCertificate.size() > 0) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900183 profileMap.add("readerCertificate",
184 cppbor::Bstr(profile.readerCertificate.encodedCertificate));
David Zeuthen81603152020-02-11 22:04:24 -0500185 }
186 if (profile.userAuthenticationRequired) {
187 profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired);
188 profileMap.add("timeoutMillis", profile.timeoutMillis);
189 }
190 signedDataAccessControlProfiles_.add(std::move(profileMap));
191
192 numAccessControlProfileRemaining_--;
193
194 *outSecureAccessControlProfile = profile;
195 return ndk::ScopedAStatus::ok();
196}
197
198ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry(
199 const vector<int32_t>& accessControlProfileIds, const string& nameSpace, const string& name,
200 int32_t entrySize) {
201 if (numAccessControlProfileRemaining_ != 0) {
202 LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_
203 << " and expected zero";
204 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
205 IIdentityCredentialStore::STATUS_INVALID_DATA,
206 "numAccessControlProfileRemaining_ is not zero"));
207 }
208
209 if (remainingEntryCounts_.size() == 0) {
210 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
211 IIdentityCredentialStore::STATUS_INVALID_DATA, "No more namespaces to add to"));
212 }
213
214 // Handle initial beginEntry() call.
Selene Huang92b61d62020-03-04 02:24:16 -0800215 if (firstEntry_) {
216 firstEntry_ = false;
David Zeuthen81603152020-02-11 22:04:24 -0500217 entryNameSpace_ = nameSpace;
Selene Huang92b61d62020-03-04 02:24:16 -0800218 allNameSpaces_.insert(nameSpace);
David Zeuthen81603152020-02-11 22:04:24 -0500219 }
220
221 // If the namespace changed...
222 if (nameSpace != entryNameSpace_) {
Selene Huang92b61d62020-03-04 02:24:16 -0800223 if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
224 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
225 IIdentityCredentialStore::STATUS_INVALID_DATA,
226 "Name space cannot be added in interleaving fashion"));
227 }
228
David Zeuthen81603152020-02-11 22:04:24 -0500229 // Then check that all entries in the previous namespace have been added..
230 if (remainingEntryCounts_[0] != 0) {
231 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
232 IIdentityCredentialStore::STATUS_INVALID_DATA,
233 "New namespace but a non-zero number of entries remain to be added"));
234 }
235 remainingEntryCounts_.erase(remainingEntryCounts_.begin());
Selene Huang92b61d62020-03-04 02:24:16 -0800236 remainingEntryCounts_[0] -= 1;
237 allNameSpaces_.insert(nameSpace);
David Zeuthen81603152020-02-11 22:04:24 -0500238
239 if (signedDataCurrentNamespace_.size() > 0) {
240 signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
241 signedDataCurrentNamespace_ = cppbor::Array();
242 }
243 } else {
244 // Same namespace...
245 if (remainingEntryCounts_[0] == 0) {
246 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
247 IIdentityCredentialStore::STATUS_INVALID_DATA,
248 "Same namespace but no entries remain to be added"));
249 }
250 remainingEntryCounts_[0] -= 1;
251 }
252
253 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
254
255 entryRemainingBytes_ = entrySize;
256 entryNameSpace_ = nameSpace;
257 entryName_ = name;
258 entryAccessControlProfileIds_ = accessControlProfileIds;
259 entryBytes_.resize(0);
260 // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
261 return ndk::ScopedAStatus::ok();
262}
263
Jooyung Han17be89b2020-02-21 21:17:06 +0900264ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<uint8_t>& content,
265 vector<uint8_t>* outEncryptedContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500266 size_t contentSize = content.size();
267
268 if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
269 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
270 IIdentityCredentialStore::STATUS_INVALID_DATA,
271 "Passed in chunk of is bigger than kGcmChunkSize"));
272 }
273 if (contentSize > entryRemainingBytes_) {
274 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
275 IIdentityCredentialStore::STATUS_INVALID_DATA,
276 "Passed in chunk is bigger than remaining space"));
277 }
278
279 entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
280 entryRemainingBytes_ -= contentSize;
281 if (entryRemainingBytes_ > 0) {
282 if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
283 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
284 IIdentityCredentialStore::STATUS_INVALID_DATA,
285 "Retrieved non-final chunk which isn't kGcmChunkSize"));
286 }
287 }
288
289 optional<vector<uint8_t>> nonce = support::getRandom(12);
290 if (!nonce) {
291 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
292 IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
293 }
294 optional<vector<uint8_t>> encryptedContent =
295 support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
296 if (!encryptedContent) {
297 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
298 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content"));
299 }
300
301 if (entryRemainingBytes_ == 0) {
302 // TODO: ideally do do this without parsing the data (but still validate data is valid
303 // CBOR).
304 auto [item, _, message] = cppbor::parse(entryBytes_);
305 if (item == nullptr) {
306 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
307 IIdentityCredentialStore::STATUS_INVALID_DATA, "Data is not valid CBOR"));
308 }
309 cppbor::Map entryMap;
310 entryMap.add("name", entryName_);
311 entryMap.add("value", std::move(item));
312 cppbor::Array profileIdArray;
313 for (auto id : entryAccessControlProfileIds_) {
314 profileIdArray.add(id);
315 }
316 entryMap.add("accessControlProfiles", std::move(profileIdArray));
317 signedDataCurrentNamespace_.add(std::move(entryMap));
318 }
319
Jooyung Han17be89b2020-02-21 21:17:06 +0900320 *outEncryptedContent = encryptedContent.value();
David Zeuthen81603152020-02-11 22:04:24 -0500321 return ndk::ScopedAStatus::ok();
322}
David Zeuthenc75ac312019-10-28 13:16:45 -0400323
324// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
325// |credentialPrivKey|.
326static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
327 const vector<uint8_t>& credentialPrivKey,
328 vector<uint8_t>& credentialKeys) {
329 if (storageKey.size() != 16) {
330 LOG(ERROR) << "Size of storageKey is not 16";
331 return false;
332 }
333
334 cppbor::Array array;
335 array.add(cppbor::Bstr(storageKey));
336 array.add(cppbor::Bstr(credentialPrivKey));
337 credentialKeys = array.encode();
338 return true;
339}
340
341// Writes CBOR-encoded structure to |credentialData| containing |docType|,
342// |testCredential| and |credentialKeys|. The latter element will be stored in
343// encrypted form, using |hardwareBoundKey| as the encryption key.
344bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
345 bool testCredential, const vector<uint8_t>& credentialKeys,
346 vector<uint8_t>& credentialData) {
347 optional<vector<uint8_t>> nonce = support::getRandom(12);
348 if (!nonce) {
349 LOG(ERROR) << "Error getting random";
350 return false;
351 }
352 vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
353 optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
354 hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
355 if (!credentialBlob) {
356 LOG(ERROR) << "Error encrypting CredentialKeys blob";
357 return false;
358 }
359
360 cppbor::Array array;
361 array.add(docType);
362 array.add(testCredential);
363 array.add(cppbor::Bstr(credentialBlob.value()));
364 credentialData = array.encode();
365 return true;
366}
367
David Zeuthen81603152020-02-11 22:04:24 -0500368ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
Jooyung Han17be89b2020-02-21 21:17:06 +0900369 vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
Selene Huang92b61d62020-03-04 02:24:16 -0800370 if (numAccessControlProfileRemaining_ != 0) {
371 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
372 IIdentityCredentialStore::STATUS_INVALID_DATA,
373 "numAccessControlProfileRemaining_ is not 0 and expected zero"));
374 }
375
376 if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
377 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
378 IIdentityCredentialStore::STATUS_INVALID_DATA,
379 "More entry spaces remain than startPersonalization configured"));
380 }
381
David Zeuthenc75ac312019-10-28 13:16:45 -0400382 if (signedDataCurrentNamespace_.size() > 0) {
383 signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
384 }
385 cppbor::Array popArray;
386 popArray.add("ProofOfProvisioning")
387 .add(docType_)
388 .add(std::move(signedDataAccessControlProfiles_))
389 .add(std::move(signedDataNamespaces_))
390 .add(testCredential_);
391 vector<uint8_t> encodedCbor = popArray.encode();
392
David Zeuthen28edb102020-04-28 18:54:55 -0400393 if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
394 LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size() << " bytes, "
395 << "was expecting " << expectedProofOfProvisioningSize_;
396 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
397 IIdentityCredentialStore::STATUS_INVALID_DATA,
398 StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was expecting %zd",
399 encodedCbor.size(), expectedProofOfProvisioningSize_)
400 .c_str()));
401 }
402
David Zeuthenc75ac312019-10-28 13:16:45 -0400403 optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
404 encodedCbor, // payload
405 {}, // additionalData
406 {}); // certificateChain
407 if (!signature) {
David Zeuthen81603152020-02-11 22:04:24 -0500408 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
409 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
David Zeuthenc75ac312019-10-28 13:16:45 -0400410 }
411
412 vector<uint8_t> credentialKeys;
413 if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
David Zeuthen81603152020-02-11 22:04:24 -0500414 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
415 IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
David Zeuthenc75ac312019-10-28 13:16:45 -0400416 }
417
418 vector<uint8_t> credentialData;
David Zeuthen81603152020-02-11 22:04:24 -0500419 if (!generateCredentialData(
420 testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(),
421 docType_, testCredential_, credentialKeys, credentialData)) {
422 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
423 IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData"));
David Zeuthenc75ac312019-10-28 13:16:45 -0400424 }
425
Jooyung Han17be89b2020-02-21 21:17:06 +0900426 *outCredentialData = credentialData;
427 *outProofOfProvisioningSignature = signature.value();
David Zeuthen81603152020-02-11 22:04:24 -0500428 return ndk::ScopedAStatus::ok();
David Zeuthenc75ac312019-10-28 13:16:45 -0400429}
430
David Zeuthen81603152020-02-11 22:04:24 -0500431} // namespace aidl::android::hardware::identity