1 /*
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 "IdentityCredential"
18
19 #include "IdentityCredential.h"
20 #include "IdentityCredentialStore.h"
21
22 #include <android/hardware/identity/support/IdentityCredentialSupport.h>
23
24 #include <string.h>
25
26 #include <android-base/logging.h>
27 #include <android-base/stringprintf.h>
28
29 #include <cppbor.h>
30 #include <cppbor_parse.h>
31
32 #include "FakeSecureHardwareProxy.h"
33 #include "WritableIdentityCredential.h"
34
35 namespace aidl::android::hardware::identity {
36
37 using ::aidl::android::hardware::keymaster::Timestamp;
38 using ::android::base::StringPrintf;
39 using ::std::optional;
40
41 using namespace ::android::hardware::identity;
42
initialize()43 int IdentityCredential::initialize() {
44 if (credentialData_.size() == 0) {
45 LOG(ERROR) << "CredentialData is empty";
46 return IIdentityCredentialStore::STATUS_INVALID_DATA;
47 }
48 auto [item, _, message] = cppbor::parse(credentialData_);
49 if (item == nullptr) {
50 LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
51 return IIdentityCredentialStore::STATUS_INVALID_DATA;
52 }
53
54 const cppbor::Array* arrayItem = item->asArray();
55 if (arrayItem == nullptr || arrayItem->size() != 3) {
56 LOG(ERROR) << "CredentialData is not an array with three elements";
57 return IIdentityCredentialStore::STATUS_INVALID_DATA;
58 }
59
60 const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
61 const cppbor::Bool* testCredentialItem =
62 ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
63 : nullptr);
64 const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
65 if (docTypeItem == nullptr || testCredentialItem == nullptr ||
66 encryptedCredentialKeysItem == nullptr) {
67 LOG(ERROR) << "CredentialData unexpected item types";
68 return IIdentityCredentialStore::STATUS_INVALID_DATA;
69 }
70
71 docType_ = docTypeItem->value();
72 testCredential_ = testCredentialItem->value();
73
74 encryptedCredentialKeys_ = encryptedCredentialKeysItem->value();
75
76 // If in a session, delay the initialization of the proxy.
77 //
78 if (!session_) {
79 ndk::ScopedAStatus status = ensureHwProxy();
80 if (!status.isOk()) {
81 LOG(ERROR) << "Error initializing hw proxy";
82 return IIdentityCredentialStore::STATUS_FAILED;
83 }
84 }
85
86 return IIdentityCredentialStore::STATUS_OK;
87 }
88
ensureHwProxy()89 ndk::ScopedAStatus IdentityCredential::ensureHwProxy() {
90 if (hwProxy_) {
91 return ndk::ScopedAStatus::ok();
92 }
93 hwProxy_ = hwProxyFactory_->createPresentationProxy();
94 if (!hwProxy_) {
95 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
96 IIdentityCredentialStore::STATUS_FAILED, "Error creating hw proxy"));
97 }
98 uint64_t sessionId = session_ ? session_->getSessionId() : EIC_PRESENTATION_ID_UNSET;
99 if (!hwProxy_->initialize(sessionId, testCredential_, docType_, encryptedCredentialKeys_)) {
100 hwProxy_.clear();
101 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
102 IIdentityCredentialStore::STATUS_FAILED, "Error initializing hw proxy"));
103 }
104 return ndk::ScopedAStatus::ok();
105 }
106
deleteCredential(vector<uint8_t> * outProofOfDeletionSignature)107 ndk::ScopedAStatus IdentityCredential::deleteCredential(
108 vector<uint8_t>* outProofOfDeletionSignature) {
109 return deleteCredentialCommon({}, false, outProofOfDeletionSignature);
110 }
111
deleteCredentialWithChallenge(const vector<uint8_t> & challenge,vector<uint8_t> * outProofOfDeletionSignature)112 ndk::ScopedAStatus IdentityCredential::deleteCredentialWithChallenge(
113 const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfDeletionSignature) {
114 return deleteCredentialCommon(challenge, true, outProofOfDeletionSignature);
115 }
116
deleteCredentialCommon(const vector<uint8_t> & challenge,bool includeChallenge,vector<uint8_t> * outProofOfDeletionSignature)117 ndk::ScopedAStatus IdentityCredential::deleteCredentialCommon(
118 const vector<uint8_t>& challenge, bool includeChallenge,
119 vector<uint8_t>* outProofOfDeletionSignature) {
120 if (session_) {
121 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
122 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
123 }
124 ndk::ScopedAStatus status = ensureHwProxy();
125 if (!status.isOk()) {
126 return status;
127 }
128 if (challenge.size() > 32) {
129 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
130 IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
131 }
132
133 cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
134 if (includeChallenge) {
135 array = {"ProofOfDeletion", docType_, challenge, testCredential_};
136 }
137
138 vector<uint8_t> proofOfDeletionCbor = array.encode();
139 vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
140
141 optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->deleteCredential(
142 docType_, challenge, includeChallenge, proofOfDeletionCbor.size());
143 if (!signatureOfToBeSigned) {
144 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
145 IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
146 }
147
148 optional<vector<uint8_t>> signature =
149 support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
150 proofOfDeletionCbor, // data
151 {}); // certificateChain
152 if (!signature) {
153 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
154 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
155 }
156
157 *outProofOfDeletionSignature = signature.value();
158 return ndk::ScopedAStatus::ok();
159 }
160
proveOwnership(const vector<uint8_t> & challenge,vector<uint8_t> * outProofOfOwnershipSignature)161 ndk::ScopedAStatus IdentityCredential::proveOwnership(
162 const vector<uint8_t>& challenge, vector<uint8_t>* outProofOfOwnershipSignature) {
163 if (session_) {
164 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
165 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
166 }
167 ndk::ScopedAStatus status = ensureHwProxy();
168 if (!status.isOk()) {
169 return status;
170 }
171 if (challenge.size() > 32) {
172 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
173 IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge too big"));
174 }
175
176 cppbor::Array array;
177 array = {"ProofOfOwnership", docType_, challenge, testCredential_};
178 vector<uint8_t> proofOfOwnershipCbor = array.encode();
179 vector<uint8_t> podDigest = support::sha256(proofOfOwnershipCbor);
180
181 optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->proveOwnership(
182 docType_, testCredential_, challenge, proofOfOwnershipCbor.size());
183 if (!signatureOfToBeSigned) {
184 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
185 IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfOwnership"));
186 }
187
188 optional<vector<uint8_t>> signature =
189 support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
190 proofOfOwnershipCbor, // data
191 {}); // certificateChain
192 if (!signature) {
193 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
194 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
195 }
196
197 *outProofOfOwnershipSignature = signature.value();
198 return ndk::ScopedAStatus::ok();
199 }
200
createEphemeralKeyPair(vector<uint8_t> * outKeyPair)201 ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
202 if (session_) {
203 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
204 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
205 }
206 ndk::ScopedAStatus status = ensureHwProxy();
207 if (!status.isOk()) {
208 return status;
209 }
210 optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
211 if (!ephemeralPriv) {
212 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
213 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key"));
214 }
215 optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
216 if (!keyPair) {
217 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
218 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
219 }
220
221 // Stash public key of this key-pair for later check in startRetrieval().
222 optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
223 if (!publicKey) {
224 LOG(ERROR) << "Error getting public part of ephemeral key pair";
225 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
226 IIdentityCredentialStore::STATUS_FAILED,
227 "Error getting public part of ephemeral key pair"));
228 }
229 ephemeralPublicKey_ = publicKey.value();
230
231 *outKeyPair = keyPair.value();
232 return ndk::ScopedAStatus::ok();
233 }
234
setReaderEphemeralPublicKey(const vector<uint8_t> & publicKey)235 ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
236 const vector<uint8_t>& publicKey) {
237 if (session_) {
238 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
239 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
240 }
241 readerPublicKey_ = publicKey;
242 return ndk::ScopedAStatus::ok();
243 }
244
createAuthChallenge(int64_t * outChallenge)245 ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
246 if (session_) {
247 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
248 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
249 }
250 ndk::ScopedAStatus status = ensureHwProxy();
251 if (!status.isOk()) {
252 return status;
253 }
254 optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
255 if (!challenge) {
256 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
257 IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
258 }
259 *outChallenge = challenge.value();
260 return ndk::ScopedAStatus::ok();
261 }
262
setRequestedNamespaces(const vector<RequestNamespace> & requestNamespaces)263 ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
264 const vector<RequestNamespace>& requestNamespaces) {
265 requestNamespaces_ = requestNamespaces;
266 return ndk::ScopedAStatus::ok();
267 }
268
setVerificationToken(const VerificationToken & verificationToken)269 ndk::ScopedAStatus IdentityCredential::setVerificationToken(
270 const VerificationToken& verificationToken) {
271 verificationToken_ = verificationToken;
272 return ndk::ScopedAStatus::ok();
273 }
274
startRetrieval(const vector<SecureAccessControlProfile> & accessControlProfiles,const HardwareAuthToken & authToken,const vector<uint8_t> & itemsRequest,const vector<uint8_t> & signingKeyBlob,const vector<uint8_t> & sessionTranscript,const vector<uint8_t> & readerSignature,const vector<int32_t> & requestCounts)275 ndk::ScopedAStatus IdentityCredential::startRetrieval(
276 const vector<SecureAccessControlProfile>& accessControlProfiles,
277 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
278 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
279 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
280 ndk::ScopedAStatus status = ensureHwProxy();
281 if (!status.isOk()) {
282 return status;
283 }
284
285 // If in a session, ensure the passed-in session transcript matches the
286 // session transcript from the session.
287 if (session_) {
288 if (sessionTranscript != session_->getSessionTranscript()) {
289 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
290 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
291 "In a session and passed-in SessionTranscript doesn't match the one "
292 "from the session"));
293 }
294 }
295
296 if (numStartRetrievalCalls_ > 0) {
297 if (sessionTranscript_ != sessionTranscript) {
298 LOG(ERROR) << "Session Transcript changed";
299 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
300 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
301 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
302 }
303 }
304 sessionTranscript_ = sessionTranscript;
305
306 // This resets various state in the TA...
307 if (!hwProxy_->startRetrieveEntries()) {
308 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
309 IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
310 }
311
312 optional<vector<uint8_t>> signatureOfToBeSigned;
313 if (readerSignature.size() > 0) {
314 signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
315 if (!signatureOfToBeSigned) {
316 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
317 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
318 "Error extracting signatureOfToBeSigned from COSE_Sign1"));
319 }
320 }
321
322 // Feed the auth token to secure hardware only if they're valid.
323 if (authToken.timestamp.milliSeconds != 0) {
324 if (!hwProxy_->setAuthToken(
325 authToken.challenge, authToken.userId, authToken.authenticatorId,
326 int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
327 authToken.mac, verificationToken_.challenge,
328 verificationToken_.timestamp.milliSeconds,
329 int(verificationToken_.securityLevel), verificationToken_.mac)) {
330 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
331 IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
332 }
333 }
334
335 // We'll be feeding ACPs interleaved with certificates from the reader
336 // certificate chain...
337 vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
338
339 // ... and we'll use those ACPs to build up a 32-bit mask indicating which
340 // of the possible 32 ACPs grants access.
341 uint32_t accessControlProfileMask = 0;
342
343 // If there is a signature, validate that it was made with the top-most key in the
344 // certificate chain embedded in the COSE_Sign1 structure.
345 optional<vector<uint8_t>> readerCertificateChain;
346 if (readerSignature.size() > 0) {
347 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
348 if (!readerCertificateChain) {
349 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
350 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
351 "Unable to get reader certificate chain from COSE_Sign1"));
352 }
353
354 // First, feed all the reader certificates to the secure hardware. We start
355 // at the end..
356 optional<vector<vector<uint8_t>>> splitCerts =
357 support::certificateChainSplit(readerCertificateChain.value());
358 if (!splitCerts || splitCerts.value().size() == 0) {
359 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
360 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
361 "Error splitting certificate chain from COSE_Sign1"));
362 }
363 for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
364 const vector<uint8_t>& x509Cert = splitCerts.value()[n];
365 if (!hwProxy_->pushReaderCert(x509Cert)) {
366 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
367 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
368 StringPrintf("Error validating reader certificate %zd", n).c_str()));
369 }
370
371 // If we have ACPs for that particular certificate, send them to the
372 // TA right now...
373 //
374 // Remember in this case certificate equality is done by comparing public keys,
375 // not bitwise comparison of the certificates.
376 //
377 optional<vector<uint8_t>> x509CertPubKey =
378 support::certificateChainGetTopMostKey(x509Cert);
379 if (!x509CertPubKey) {
380 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
381 IIdentityCredentialStore::STATUS_FAILED,
382 StringPrintf("Error getting public key from reader certificate %zd", n)
383 .c_str()));
384 }
385 vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
386 while (it != remainingAcps.end()) {
387 const SecureAccessControlProfile& profile = *it;
388 if (profile.readerCertificate.encodedCertificate.size() == 0) {
389 ++it;
390 continue;
391 }
392 optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey(
393 profile.readerCertificate.encodedCertificate);
394 if (!profilePubKey) {
395 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
396 IIdentityCredentialStore::STATUS_FAILED,
397 "Error getting public key from profile"));
398 }
399 if (profilePubKey.value() == x509CertPubKey.value()) {
400 optional<bool> res = hwProxy_->validateAccessControlProfile(
401 profile.id, profile.readerCertificate.encodedCertificate,
402 profile.userAuthenticationRequired, profile.timeoutMillis,
403 profile.secureUserId, profile.mac);
404 if (!res) {
405 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
406 IIdentityCredentialStore::STATUS_INVALID_DATA,
407 "Error validating access control profile"));
408 }
409 if (res.value()) {
410 accessControlProfileMask |= (1 << profile.id);
411 }
412 it = remainingAcps.erase(it);
413 } else {
414 ++it;
415 }
416 }
417 }
418
419 // ... then pass the request message and have the TA check it's signed by the
420 // key in last certificate we pushed.
421 if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) {
422 optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
423 if (!tbsSignature) {
424 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
425 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
426 "Error extracting toBeSigned from COSE_Sign1"));
427 }
428 optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
429 if (!coseSignAlg) {
430 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
431 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
432 "Error extracting signature algorithm from COSE_Sign1"));
433 }
434 if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
435 coseSignAlg.value(), tbsSignature.value())) {
436 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
437 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
438 "readerMessage is not signed by top-level certificate"));
439 }
440 }
441 }
442
443 // Feed remaining access control profiles...
444 for (const SecureAccessControlProfile& profile : remainingAcps) {
445 optional<bool> res = hwProxy_->validateAccessControlProfile(
446 profile.id, profile.readerCertificate.encodedCertificate,
447 profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId,
448 profile.mac);
449 if (!res) {
450 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
451 IIdentityCredentialStore::STATUS_INVALID_DATA,
452 "Error validating access control profile"));
453 }
454 if (res.value()) {
455 accessControlProfileMask |= (1 << profile.id);
456 }
457 }
458
459 if (session_) {
460 // If presenting in a session, the TA has already done this check.
461
462 } else {
463 // To prevent replay-attacks, we check that the public part of the ephemeral
464 // key we previously created, is present in the DeviceEngagement part of
465 // SessionTranscript as a COSE_Key, in uncompressed form.
466 //
467 // We do this by just searching for the X and Y coordinates.
468 //
469 // Would be nice to move this check to the TA.
470 if (sessionTranscript.size() > 0) {
471 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
472 if (!getXYSuccess) {
473 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
474 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
475 "Error extracting X and Y from ePub"));
476 }
477 if (sessionTranscript.size() > 0 &&
478 !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
479 ePubX.size()) != nullptr &&
480 memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
481 ePubY.size()) != nullptr)) {
482 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
483 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
484 "Did not find ephemeral public key's X and Y coordinates in "
485 "SessionTranscript (make sure leading zeroes are not used)"));
486 }
487 }
488 }
489
490 // itemsRequest: If non-empty, contains request data that may be signed by the
491 // reader. The content can be defined in the way appropriate for the
492 // credential, but there are three requirements that must be met to work with
493 // this HAL:
494 if (itemsRequest.size() > 0) {
495 // 1. The content must be a CBOR-encoded structure.
496 auto [item, _, message] = cppbor::parse(itemsRequest);
497 if (item == nullptr) {
498 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
499 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
500 "Error decoding CBOR in itemsRequest"));
501 }
502
503 // 2. The CBOR structure must be a map.
504 const cppbor::Map* map = item->asMap();
505 if (map == nullptr) {
506 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
507 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
508 "itemsRequest is not a CBOR map"));
509 }
510
511 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
512 // the example below.
513 //
514 // NameSpaces = {
515 // + NameSpace => DataElements ; Requested data elements for each NameSpace
516 // }
517 //
518 // NameSpace = tstr
519 //
520 // DataElements = {
521 // + DataElement => IntentToRetain
522 // }
523 //
524 // DataElement = tstr
525 // IntentToRetain = bool
526 //
527 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
528 // through 3.:
529 //
530 // {
531 // 'docType' : 'org.iso.18013-5.2019',
532 // 'nameSpaces' : {
533 // 'org.iso.18013-5.2019' : {
534 // 'Last name' : false,
535 // 'Birth date' : false,
536 // 'First name' : false,
537 // 'Home address' : true
538 // },
539 // 'org.aamva.iso.18013-5.2019' : {
540 // 'Real Id' : false
541 // }
542 // }
543 // }
544 //
545 const cppbor::Map* nsMap = nullptr;
546 for (size_t n = 0; n < map->size(); n++) {
547 const auto& [keyItem, valueItem] = (*map)[n];
548 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
549 valueItem->type() == cppbor::MAP) {
550 nsMap = valueItem->asMap();
551 break;
552 }
553 }
554 if (nsMap == nullptr) {
555 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
556 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
557 "No nameSpaces map in top-most map"));
558 }
559
560 for (size_t n = 0; n < nsMap->size(); n++) {
561 auto& [nsKeyItem, nsValueItem] = (*nsMap)[n];
562 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
563 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
564 if (nsKey == nullptr || nsInnerMap == nullptr) {
565 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
566 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
567 "Type mismatch in nameSpaces map"));
568 }
569 string requestedNamespace = nsKey->value();
570 set<string> requestedKeys;
571 for (size_t m = 0; m < nsInnerMap->size(); m++) {
572 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
573 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
574 const cppbor::Simple* simple = innerMapValueItem->asSimple();
575 const cppbor::Bool* intentToRetainItem =
576 (simple != nullptr) ? simple->asBool() : nullptr;
577 if (nameItem == nullptr || intentToRetainItem == nullptr) {
578 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
579 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
580 "Type mismatch in value in nameSpaces map"));
581 }
582 requestedKeys.insert(nameItem->value());
583 }
584 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
585 }
586 }
587
588 deviceNameSpacesMap_ = cppbor::Map();
589 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
590
591 requestCountsRemaining_ = requestCounts;
592 currentNameSpace_ = "";
593
594 itemsRequest_ = itemsRequest;
595 signingKeyBlob_ = signingKeyBlob;
596
597 // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
598 calcDeviceNameSpacesSize(accessControlProfileMask);
599
600 // Count the number of non-empty namespaces
601 size_t numNamespacesWithValues = 0;
602 for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
603 if (expectedNumEntriesPerNamespace_[n] > 0) {
604 numNamespacesWithValues += 1;
605 }
606 }
607
608 // Finally, pass info so the HMAC key can be derived and the TA can start
609 // creating the DeviceNameSpaces CBOR...
610 if (!session_) {
611 if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 &&
612 signingKeyBlob.size() > 0) {
613 // We expect the reader ephemeral public key to be same size and curve
614 // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
615 // won't work. So its length should be 65 bytes and it should be
616 // starting with 0x04.
617 if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
618 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
619 IIdentityCredentialStore::STATUS_FAILED,
620 "Reader public key is not in expected format"));
621 }
622 vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
623 if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
624 numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
625 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
626 IIdentityCredentialStore::STATUS_FAILED,
627 "Error starting retrieving entries"));
628 }
629 }
630 } else {
631 if (session_->getSessionTranscript().size() > 0 &&
632 session_->getReaderEphemeralPublicKey().size() > 0 && signingKeyBlob.size() > 0) {
633 // Don't actually pass the reader ephemeral public key in, the TA will get
634 // it from the session object.
635 //
636 if (!hwProxy_->calcMacKey(sessionTranscript_, {}, signingKeyBlob, docType_,
637 numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
638 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
639 IIdentityCredentialStore::STATUS_FAILED,
640 "Error starting retrieving entries"));
641 }
642 }
643 }
644
645 numStartRetrievalCalls_ += 1;
646 return ndk::ScopedAStatus::ok();
647 }
648
cborNumBytesForLength(size_t length)649 size_t cborNumBytesForLength(size_t length) {
650 if (length < 24) {
651 return 0;
652 } else if (length <= 0xff) {
653 return 1;
654 } else if (length <= 0xffff) {
655 return 2;
656 } else if (length <= 0xffffffff) {
657 return 4;
658 }
659 return 8;
660 }
661
cborNumBytesForTstr(const string & value)662 size_t cborNumBytesForTstr(const string& value) {
663 return 1 + cborNumBytesForLength(value.size()) + value.size();
664 }
665
calcDeviceNameSpacesSize(uint32_t accessControlProfileMask)666 void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) {
667 /*
668 * This is how DeviceNameSpaces is defined:
669 *
670 * DeviceNameSpaces = {
671 * * NameSpace => DeviceSignedItems
672 * }
673 * DeviceSignedItems = {
674 * + DataItemName => DataItemValue
675 * }
676 *
677 * Namespace = tstr
678 * DataItemName = tstr
679 * DataItemValue = any
680 *
681 * This function will calculate its length using knowledge of how CBOR is
682 * encoded.
683 */
684 size_t ret = 0;
685 vector<unsigned int> numEntriesPerNamespace;
686 for (const RequestNamespace& rns : requestNamespaces_) {
687 vector<RequestDataItem> itemsToInclude;
688
689 for (const RequestDataItem& rdi : rns.items) {
690 // If we have a CBOR request message, skip if item isn't in it
691 if (itemsRequest_.size() > 0) {
692 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
693 if (it == requestedNameSpacesAndNames_.end()) {
694 continue;
695 }
696 const set<string>& dataItemNames = it->second;
697 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
698 continue;
699 }
700 }
701
702 // Access is granted if at least one of the profiles grants access.
703 //
704 // If an item is configured without any profiles, access is denied.
705 //
706 bool authorized = false;
707 for (auto id : rdi.accessControlProfileIds) {
708 if (accessControlProfileMask & (1 << id)) {
709 authorized = true;
710 break;
711 }
712 }
713 if (!authorized) {
714 continue;
715 }
716
717 itemsToInclude.push_back(rdi);
718 }
719
720 numEntriesPerNamespace.push_back(itemsToInclude.size());
721
722 // If no entries are to be in the namespace, we don't include it in
723 // the CBOR...
724 if (itemsToInclude.size() == 0) {
725 continue;
726 }
727
728 // Key: NameSpace
729 ret += cborNumBytesForTstr(rns.namespaceName);
730
731 // Value: Open the DeviceSignedItems map
732 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
733
734 for (const RequestDataItem& item : itemsToInclude) {
735 // Key: DataItemName
736 ret += cborNumBytesForTstr(item.name);
737
738 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
739 // that.
740 ret += item.size;
741 }
742 }
743
744 // Now that we know the number of namespaces with values, we know how many
745 // bytes the DeviceNamespaces map in the beginning is going to take up.
746 ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
747
748 expectedDeviceNameSpacesSize_ = ret;
749 expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
750 }
751
startRetrieveEntryValue(const string & nameSpace,const string & name,int32_t entrySize,const vector<int32_t> & accessControlProfileIds)752 ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
753 const string& nameSpace, const string& name, int32_t entrySize,
754 const vector<int32_t>& accessControlProfileIds) {
755 ndk::ScopedAStatus status = ensureHwProxy();
756 if (!status.isOk()) {
757 return status;
758 }
759
760 if (name.empty()) {
761 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
762 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
763 }
764 if (nameSpace.empty()) {
765 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
766 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
767 }
768
769 if (requestCountsRemaining_.size() == 0) {
770 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
771 IIdentityCredentialStore::STATUS_INVALID_DATA,
772 "No more name spaces left to go through"));
773 }
774
775 bool newNamespace;
776 if (currentNameSpace_ == "") {
777 // First call.
778 currentNameSpace_ = nameSpace;
779 newNamespace = true;
780 }
781
782 if (nameSpace == currentNameSpace_) {
783 // Same namespace.
784 if (requestCountsRemaining_[0] == 0) {
785 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
786 IIdentityCredentialStore::STATUS_INVALID_DATA,
787 "No more entries to be retrieved in current name space"));
788 }
789 requestCountsRemaining_[0] -= 1;
790 } else {
791 // New namespace.
792 if (requestCountsRemaining_[0] != 0) {
793 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
794 IIdentityCredentialStore::STATUS_INVALID_DATA,
795 "Moved to new name space but one or more entries need to be retrieved "
796 "in current name space"));
797 }
798 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
799 deviceNameSpacesMap_.add(currentNameSpace_,
800 std::move(currentNameSpaceDeviceNameSpacesMap_));
801 }
802 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
803
804 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
805 currentNameSpace_ = nameSpace;
806 newNamespace = true;
807 }
808
809 // It's permissible to have an empty itemsRequest... but if non-empty you can
810 // only request what was specified in said itemsRequest. Enforce that.
811 if (itemsRequest_.size() > 0) {
812 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
813 if (it == requestedNameSpacesAndNames_.end()) {
814 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
815 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
816 "Name space was not requested in startRetrieval"));
817 }
818 const set<string>& dataItemNames = it->second;
819 if (dataItemNames.find(name) == dataItemNames.end()) {
820 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
821 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
822 "Data item name in name space was not requested in startRetrieval"));
823 }
824 }
825
826 unsigned int newNamespaceNumEntries = 0;
827 if (newNamespace) {
828 if (expectedNumEntriesPerNamespace_.size() == 0) {
829 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
830 IIdentityCredentialStore::STATUS_INVALID_DATA,
831 "No more populated name spaces left to go through"));
832 }
833 newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
834 expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin());
835 }
836
837 // Access control is enforced in the secure hardware.
838 //
839 // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
840 // consolidate).
841 //
842 AccessCheckResult res = hwProxy_->startRetrieveEntryValue(
843 nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds);
844 switch (res) {
845 case AccessCheckResult::kOk:
846 /* Do nothing. */
847 break;
848 case AccessCheckResult::kFailed:
849 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
850 IIdentityCredentialStore::STATUS_FAILED,
851 "Access control check failed (failed)"));
852 break;
853 case AccessCheckResult::kNoAccessControlProfiles:
854 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
855 IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
856 "Access control check failed (no access control profiles)"));
857 break;
858 case AccessCheckResult::kUserAuthenticationFailed:
859 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
860 IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
861 "Access control check failed (user auth)"));
862 break;
863 case AccessCheckResult::kReaderAuthenticationFailed:
864 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
865 IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
866 "Access control check failed (reader auth)"));
867 break;
868 }
869
870 currentName_ = name;
871 currentAccessControlProfileIds_ = accessControlProfileIds;
872 entryRemainingBytes_ = entrySize;
873 entryValue_.resize(0);
874
875 return ndk::ScopedAStatus::ok();
876 }
877
retrieveEntryValue(const vector<uint8_t> & encryptedContent,vector<uint8_t> * outContent)878 ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
879 vector<uint8_t>* outContent) {
880 ndk::ScopedAStatus status = ensureHwProxy();
881 if (!status.isOk()) {
882 return status;
883 }
884
885 optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
886 encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
887 if (!content) {
888 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
889 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
890 }
891
892 size_t chunkSize = content.value().size();
893
894 if (chunkSize > entryRemainingBytes_) {
895 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
896 << " is bigger than remaining space of size " << entryRemainingBytes_;
897 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
898 IIdentityCredentialStore::STATUS_INVALID_DATA,
899 "Retrieved chunk is bigger than remaining space"));
900 }
901
902 entryRemainingBytes_ -= chunkSize;
903 if (entryRemainingBytes_ > 0) {
904 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
905 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
906 IIdentityCredentialStore::STATUS_INVALID_DATA,
907 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
908 }
909 }
910
911 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
912
913 if (entryRemainingBytes_ == 0) {
914 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
915 if (entryValueItem == nullptr) {
916 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
917 IIdentityCredentialStore::STATUS_INVALID_DATA,
918 "Retrieved data which is invalid CBOR"));
919 }
920 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
921 }
922
923 *outContent = content.value();
924 return ndk::ScopedAStatus::ok();
925 }
926
finishRetrieval(vector<uint8_t> * outMac,vector<uint8_t> * outDeviceNameSpaces)927 ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
928 vector<uint8_t>* outDeviceNameSpaces) {
929 ndk::ScopedAStatus status = ensureHwProxy();
930 if (!status.isOk()) {
931 return status;
932 }
933
934 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
935 deviceNameSpacesMap_.add(currentNameSpace_,
936 std::move(currentNameSpaceDeviceNameSpacesMap_));
937 }
938 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
939
940 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
941 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
942 << "was expecting " << expectedDeviceNameSpacesSize_;
943 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
944 IIdentityCredentialStore::STATUS_INVALID_DATA,
945 StringPrintf(
946 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
947 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
948 .c_str()));
949 }
950
951 // If the TA calculated a MAC (it might not have), format it as a COSE_Mac0
952 //
953 optional<vector<uint8_t>> mac;
954 optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
955
956 // The MAC not being set means an error occurred.
957 if (!digestToBeMaced) {
958 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
959 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error generating digestToBeMaced"));
960 }
961 // Size 0 means that the MAC isn't set. If it's set, it has to be 32 bytes.
962 if (digestToBeMaced.value().size() != 0) {
963 if (digestToBeMaced.value().size() != 32) {
964 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
965 IIdentityCredentialStore::STATUS_INVALID_DATA,
966 "Unexpected size for digestToBeMaced"));
967 }
968 mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
969 }
970
971 *outMac = mac.value_or(vector<uint8_t>({}));
972 *outDeviceNameSpaces = encodedDeviceNameSpaces;
973 return ndk::ScopedAStatus::ok();
974 }
975
generateSigningKeyPair(vector<uint8_t> * outSigningKeyBlob,Certificate * outSigningKeyCertificate)976 ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
977 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
978 if (session_) {
979 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
980 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
981 }
982 ndk::ScopedAStatus status = ensureHwProxy();
983 if (!status.isOk()) {
984 return status;
985 }
986
987 time_t now = time(NULL);
988 optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
989 hwProxy_->generateSigningKeyPair(docType_, now);
990 if (!pair) {
991 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
992 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
993 }
994
995 *outSigningKeyCertificate = Certificate();
996 outSigningKeyCertificate->encodedCertificate = pair->first;
997
998 *outSigningKeyBlob = pair->second;
999 return ndk::ScopedAStatus::ok();
1000 }
1001
updateCredential(shared_ptr<IWritableIdentityCredential> * outWritableCredential)1002 ndk::ScopedAStatus IdentityCredential::updateCredential(
1003 shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
1004 if (session_) {
1005 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
1006 IIdentityCredentialStore::STATUS_FAILED, "Cannot be called in a session"));
1007 }
1008 sp<SecureHardwareProvisioningProxy> provisioningHwProxy =
1009 hwProxyFactory_->createProvisioningProxy();
1010 if (!provisioningHwProxy) {
1011 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
1012 IIdentityCredentialStore::STATUS_FAILED, "Error creating provisioning proxy"));
1013 }
1014 shared_ptr<WritableIdentityCredential> wc =
1015 ndk::SharedRefBase::make<WritableIdentityCredential>(
1016 provisioningHwProxy, docType_, testCredential_, hardwareInformation_);
1017 if (!wc->initializeForUpdate(encryptedCredentialKeys_)) {
1018 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
1019 IIdentityCredentialStore::STATUS_FAILED,
1020 "Error initializing WritableIdentityCredential for update"));
1021 }
1022 *outWritableCredential = wc;
1023 return ndk::ScopedAStatus::ok();
1024 }
1025
1026 } // namespace aidl::android::hardware::identity
1027