blob: 381eb8426ff7ba833ef1ac4ae283d2f940432bc9 [file] [log] [blame]
David Zeuthen81603152020-02-11 22:04:24 -05001/*
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#include "Util.h"
22
23#include <android/hardware/identity/support/IdentityCredentialSupport.h>
24
25#include <string.h>
26
27#include <android-base/logging.h>
David Zeuthen28edb102020-04-28 18:54:55 -040028#include <android-base/stringprintf.h>
David Zeuthen81603152020-02-11 22:04:24 -050029
30#include <cppbor.h>
31#include <cppbor_parse.h>
32
33namespace aidl::android::hardware::identity {
34
35using ::aidl::android::hardware::keymaster::Timestamp;
David Zeuthen28edb102020-04-28 18:54:55 -040036using ::android::base::StringPrintf;
David Zeuthen81603152020-02-11 22:04:24 -050037using ::std::optional;
38
39using namespace ::android::hardware::identity;
40
41int IdentityCredential::initialize() {
42 auto [item, _, message] = cppbor::parse(credentialData_);
43 if (item == nullptr) {
44 LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
45 return IIdentityCredentialStore::STATUS_INVALID_DATA;
46 }
47
48 const cppbor::Array* arrayItem = item->asArray();
49 if (arrayItem == nullptr || arrayItem->size() != 3) {
50 LOG(ERROR) << "CredentialData is not an array with three elements";
51 return IIdentityCredentialStore::STATUS_INVALID_DATA;
52 }
53
54 const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
55 const cppbor::Bool* testCredentialItem =
56 ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
57 : nullptr);
58 const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
59 if (docTypeItem == nullptr || testCredentialItem == nullptr ||
60 encryptedCredentialKeysItem == nullptr) {
61 LOG(ERROR) << "CredentialData unexpected item types";
62 return IIdentityCredentialStore::STATUS_INVALID_DATA;
63 }
64
65 docType_ = docTypeItem->value();
66 testCredential_ = testCredentialItem->value();
67
68 vector<uint8_t> hardwareBoundKey;
69 if (testCredential_) {
70 hardwareBoundKey = support::getTestHardwareBoundKey();
71 } else {
72 hardwareBoundKey = getHardwareBoundKey();
73 }
74
75 const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
76 const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
77 optional<vector<uint8_t>> decryptedCredentialKeys =
78 support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
79 if (!decryptedCredentialKeys) {
80 LOG(ERROR) << "Error decrypting CredentialKeys";
81 return IIdentityCredentialStore::STATUS_INVALID_DATA;
82 }
83
84 auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
85 if (dckItem == nullptr) {
86 LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
87 return IIdentityCredentialStore::STATUS_INVALID_DATA;
88 }
89 const cppbor::Array* dckArrayItem = dckItem->asArray();
90 if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
91 LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
92 return IIdentityCredentialStore::STATUS_INVALID_DATA;
93 }
94 const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
95 const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
96 if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
97 LOG(ERROR) << "CredentialKeys unexpected item types";
98 return IIdentityCredentialStore::STATUS_INVALID_DATA;
99 }
100 storageKey_ = storageKeyItem->value();
101 credentialPrivKey_ = credentialPrivKeyItem->value();
102
103 return IIdentityCredentialStore::STATUS_OK;
104}
105
106ndk::ScopedAStatus IdentityCredential::deleteCredential(
Jooyung Han17be89b2020-02-21 21:17:06 +0900107 vector<uint8_t>* outProofOfDeletionSignature) {
David Zeuthen81603152020-02-11 22:04:24 -0500108 cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
109 vector<uint8_t> proofOfDeletion = array.encode();
110
111 optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
112 proofOfDeletion, // payload
113 {}, // additionalData
114 {}); // certificateChain
115 if (!signature) {
116 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
117 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
118 }
119
Jooyung Han17be89b2020-02-21 21:17:06 +0900120 *outProofOfDeletionSignature = signature.value();
David Zeuthen81603152020-02-11 22:04:24 -0500121 return ndk::ScopedAStatus::ok();
122}
123
Jooyung Han17be89b2020-02-21 21:17:06 +0900124ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
David Zeuthen81603152020-02-11 22:04:24 -0500125 optional<vector<uint8_t>> kp = support::createEcKeyPair();
126 if (!kp) {
127 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
128 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
129 }
130
131 // Stash public key of this key-pair for later check in startRetrieval().
132 optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
133 if (!publicKey) {
134 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
135 IIdentityCredentialStore::STATUS_FAILED,
136 "Error getting public part of ephemeral key pair"));
137 }
138 ephemeralPublicKey_ = publicKey.value();
139
Jooyung Han17be89b2020-02-21 21:17:06 +0900140 *outKeyPair = kp.value();
David Zeuthen81603152020-02-11 22:04:24 -0500141 return ndk::ScopedAStatus::ok();
142}
143
144ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
Jooyung Han17be89b2020-02-21 21:17:06 +0900145 const vector<uint8_t>& publicKey) {
146 readerPublicKey_ = publicKey;
David Zeuthen81603152020-02-11 22:04:24 -0500147 return ndk::ScopedAStatus::ok();
148}
149
150ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
151 uint64_t challenge = 0;
152 while (challenge == 0) {
153 optional<vector<uint8_t>> bytes = support::getRandom(8);
154 if (!bytes) {
155 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
156 IIdentityCredentialStore::STATUS_FAILED,
157 "Error getting random data for challenge"));
158 }
159
160 challenge = 0;
161 for (size_t n = 0; n < bytes.value().size(); n++) {
162 challenge |= ((bytes.value())[n] << (n * 8));
163 }
164 }
165
166 *outChallenge = challenge;
167 return ndk::ScopedAStatus::ok();
168}
169
170// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
171// ahead of time.
172bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
173 const vector<uint8_t>& readerCertificateChain) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900174 optional<vector<uint8_t>> acpPubKey =
175 support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
David Zeuthen81603152020-02-11 22:04:24 -0500176 if (!acpPubKey) {
177 LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
178 return false;
179 }
180
181 optional<vector<vector<uint8_t>>> certificatesInChain =
182 support::certificateChainSplit(readerCertificateChain);
183 if (!certificatesInChain) {
184 LOG(ERROR) << "Error splitting readerCertificateChain";
185 return false;
186 }
187 for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
188 optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
189 if (!certPubKey) {
190 LOG(ERROR)
191 << "Error extracting public key from certificate in chain presented by reader";
192 return false;
193 }
194 if (acpPubKey.value() == certPubKey.value()) {
195 return true;
196 }
197 }
198 return false;
199}
200
David Zeuthen81603152020-02-11 22:04:24 -0500201bool checkUserAuthentication(const SecureAccessControlProfile& profile,
David Zeuthena8ed82c2020-05-08 10:03:28 -0400202 const VerificationToken& verificationToken,
David Zeuthen81603152020-02-11 22:04:24 -0500203 const HardwareAuthToken& authToken, uint64_t authChallenge) {
204 if (profile.secureUserId != authToken.userId) {
205 LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
206 << ") differs from userId in authToken (" << authToken.userId << ")";
207 return false;
208 }
209
David Zeuthena8ed82c2020-05-08 10:03:28 -0400210 if (verificationToken.timestamp.milliSeconds == 0) {
211 LOG(ERROR) << "VerificationToken is not set";
212 return false;
213 }
214 if (authToken.timestamp.milliSeconds == 0) {
215 LOG(ERROR) << "AuthToken is not set";
216 return false;
217 }
218
David Zeuthen81603152020-02-11 22:04:24 -0500219 if (profile.timeoutMillis == 0) {
220 if (authToken.challenge == 0) {
221 LOG(ERROR) << "No challenge in authToken";
222 return false;
223 }
224
225 if (authToken.challenge != int64_t(authChallenge)) {
226 LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
227 return false;
228 }
229 return true;
230 }
231
David Zeuthena8ed82c2020-05-08 10:03:28 -0400232 // Timeout-based user auth follows. The verification token conveys what the
233 // time is right now in the environment which generated the auth token. This
234 // is what makes it possible to do timeout-based checks.
David Zeuthen81603152020-02-11 22:04:24 -0500235 //
David Zeuthena8ed82c2020-05-08 10:03:28 -0400236 const Timestamp now = verificationToken.timestamp;
David Zeuthen81603152020-02-11 22:04:24 -0500237 if (authToken.timestamp.milliSeconds > now.milliSeconds) {
238 LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
239 << ") is in the future (now: " << now.milliSeconds << ")";
240 return false;
241 }
242 if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
243 LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
244 << profile.timeoutMillis << " = "
245 << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
246 << ") is in the past (now: " << now.milliSeconds << ")";
247 return false;
248 }
249 return true;
250}
251
David Zeuthen28edb102020-04-28 18:54:55 -0400252ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
253 const vector<RequestNamespace>& requestNamespaces) {
254 requestNamespaces_ = requestNamespaces;
255 return ndk::ScopedAStatus::ok();
256}
257
David Zeuthena8ed82c2020-05-08 10:03:28 -0400258ndk::ScopedAStatus IdentityCredential::setVerificationToken(
259 const VerificationToken& verificationToken) {
260 verificationToken_ = verificationToken;
261 return ndk::ScopedAStatus::ok();
262}
263
David Zeuthen81603152020-02-11 22:04:24 -0500264ndk::ScopedAStatus IdentityCredential::startRetrieval(
265 const vector<SecureAccessControlProfile>& accessControlProfiles,
Jooyung Han17be89b2020-02-21 21:17:06 +0900266 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
267 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
268 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
David Zeuthen81603152020-02-11 22:04:24 -0500269 if (sessionTranscript.size() > 0) {
270 auto [item, _, message] = cppbor::parse(sessionTranscript);
271 if (item == nullptr) {
272 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
273 IIdentityCredentialStore::STATUS_INVALID_DATA,
274 "SessionTranscript contains invalid CBOR"));
275 }
276 sessionTranscriptItem_ = std::move(item);
277 }
278 if (numStartRetrievalCalls_ > 0) {
279 if (sessionTranscript_ != sessionTranscript) {
280 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
281 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
282 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
283 }
284 }
285 sessionTranscript_ = sessionTranscript;
286
287 // If there is a signature, validate that it was made with the top-most key in the
288 // certificate chain embedded in the COSE_Sign1 structure.
289 optional<vector<uint8_t>> readerCertificateChain;
290 if (readerSignature.size() > 0) {
291 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
292 if (!readerCertificateChain) {
293 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
294 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
295 "Unable to get reader certificate chain from COSE_Sign1"));
296 }
297
298 if (!support::certificateChainValidate(readerCertificateChain.value())) {
299 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
300 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
301 "Error validating reader certificate chain"));
302 }
303
304 optional<vector<uint8_t>> readerPublicKey =
305 support::certificateChainGetTopMostKey(readerCertificateChain.value());
306 if (!readerPublicKey) {
307 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
308 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
309 "Unable to get public key from reader certificate chain"));
310 }
311
312 const vector<uint8_t>& itemsRequestBytes = itemsRequest;
313 vector<uint8_t> dataThatWasSigned = cppbor::Array()
314 .add("ReaderAuthentication")
315 .add(sessionTranscriptItem_->clone())
316 .add(cppbor::Semantic(24, itemsRequestBytes))
317 .encode();
318 if (!support::coseCheckEcDsaSignature(readerSignature,
319 dataThatWasSigned, // detached content
320 readerPublicKey.value())) {
321 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
322 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
323 "readerSignature check failed"));
324 }
325 }
326
327 // Here's where we would validate the passed-in |authToken| to assure ourselves
328 // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
329 //
330 // However this involves calculating the MAC. However this requires access
331 // to the key needed to a pre-shared key which we don't have...
332 //
333
334 // To prevent replay-attacks, we check that the public part of the ephemeral
335 // key we previously created, is present in the DeviceEngagement part of
336 // SessionTranscript as a COSE_Key, in uncompressed form.
337 //
338 // We do this by just searching for the X and Y coordinates.
339 if (sessionTranscript.size() > 0) {
340 const cppbor::Array* array = sessionTranscriptItem_->asArray();
341 if (array == nullptr || array->size() != 2) {
342 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
343 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
344 "SessionTranscript is not an array with two items"));
345 }
346 const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
347 if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
348 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
349 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
350 "First item in SessionTranscript array is not a "
351 "semantic with value 24"));
352 }
353 const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
354 if (encodedDE == nullptr) {
355 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
356 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
357 "Child of semantic in first item in SessionTranscript "
358 "array is not a bstr"));
359 }
360 const vector<uint8_t>& bytesDE = encodedDE->value();
361
362 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
363 if (!getXYSuccess) {
364 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
365 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
366 "Error extracting X and Y from ePub"));
367 }
368 if (sessionTranscript.size() > 0 &&
369 !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
370 memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
371 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
372 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
373 "Did not find ephemeral public key's X and Y coordinates in "
374 "SessionTranscript (make sure leading zeroes are not used)"));
375 }
376 }
377
378 // itemsRequest: If non-empty, contains request data that may be signed by the
379 // reader. The content can be defined in the way appropriate for the
380 // credential, but there are three requirements that must be met to work with
381 // this HAL:
382 if (itemsRequest.size() > 0) {
383 // 1. The content must be a CBOR-encoded structure.
384 auto [item, _, message] = cppbor::parse(itemsRequest);
385 if (item == nullptr) {
386 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
387 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
388 "Error decoding CBOR in itemsRequest"));
389 }
390
391 // 2. The CBOR structure must be a map.
392 const cppbor::Map* map = item->asMap();
393 if (map == nullptr) {
394 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
395 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
396 "itemsRequest is not a CBOR map"));
397 }
398
399 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
400 // the example below.
401 //
402 // NameSpaces = {
403 // + NameSpace => DataElements ; Requested data elements for each NameSpace
404 // }
405 //
406 // NameSpace = tstr
407 //
408 // DataElements = {
409 // + DataElement => IntentToRetain
410 // }
411 //
412 // DataElement = tstr
413 // IntentToRetain = bool
414 //
415 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
416 // through 3.:
417 //
418 // {
419 // 'docType' : 'org.iso.18013-5.2019',
420 // 'nameSpaces' : {
421 // 'org.iso.18013-5.2019' : {
422 // 'Last name' : false,
423 // 'Birth date' : false,
424 // 'First name' : false,
425 // 'Home address' : true
426 // },
427 // 'org.aamva.iso.18013-5.2019' : {
428 // 'Real Id' : false
429 // }
430 // }
431 // }
432 //
433 const cppbor::Map* nsMap = nullptr;
434 for (size_t n = 0; n < map->size(); n++) {
435 const auto& [keyItem, valueItem] = (*map)[n];
436 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
437 valueItem->type() == cppbor::MAP) {
438 nsMap = valueItem->asMap();
439 break;
440 }
441 }
442 if (nsMap == nullptr) {
443 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
444 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
445 "No nameSpaces map in top-most map"));
446 }
447
448 for (size_t n = 0; n < nsMap->size(); n++) {
449 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
450 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
451 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
452 if (nsKey == nullptr || nsInnerMap == nullptr) {
453 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
454 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
455 "Type mismatch in nameSpaces map"));
456 }
457 string requestedNamespace = nsKey->value();
David Zeuthen28edb102020-04-28 18:54:55 -0400458 set<string> requestedKeys;
David Zeuthen81603152020-02-11 22:04:24 -0500459 for (size_t m = 0; m < nsInnerMap->size(); m++) {
460 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
461 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
462 const cppbor::Simple* simple = innerMapValueItem->asSimple();
463 const cppbor::Bool* intentToRetainItem =
464 (simple != nullptr) ? simple->asBool() : nullptr;
465 if (nameItem == nullptr || intentToRetainItem == nullptr) {
466 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
467 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
468 "Type mismatch in value in nameSpaces map"));
469 }
David Zeuthen28edb102020-04-28 18:54:55 -0400470 requestedKeys.insert(nameItem->value());
David Zeuthen81603152020-02-11 22:04:24 -0500471 }
472 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
473 }
474 }
475
David Zeuthen28edb102020-04-28 18:54:55 -0400476 // Validate all the access control profiles in the requestData.
David Zeuthen81603152020-02-11 22:04:24 -0500477 bool haveAuthToken = (authToken.mac.size() > 0);
478 for (const auto& profile : accessControlProfiles) {
479 if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
480 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
481 IIdentityCredentialStore::STATUS_INVALID_DATA,
482 "Error checking MAC for profile"));
483 }
484 int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
485 if (profile.userAuthenticationRequired) {
David Zeuthena8ed82c2020-05-08 10:03:28 -0400486 if (!haveAuthToken ||
487 !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
David Zeuthen81603152020-02-11 22:04:24 -0500488 accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
489 }
490 } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
491 if (!readerCertificateChain ||
492 !checkReaderAuthentication(profile, readerCertificateChain.value())) {
493 accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
494 }
495 }
496 profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
497 }
498
499 deviceNameSpacesMap_ = cppbor::Map();
500 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
501
502 requestCountsRemaining_ = requestCounts;
503 currentNameSpace_ = "";
504
505 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900506 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500507
David Zeuthen28edb102020-04-28 18:54:55 -0400508 // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
509 expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
510
David Zeuthen81603152020-02-11 22:04:24 -0500511 numStartRetrievalCalls_ += 1;
512 return ndk::ScopedAStatus::ok();
513}
514
David Zeuthen28edb102020-04-28 18:54:55 -0400515size_t cborNumBytesForLength(size_t length) {
516 if (length < 24) {
517 return 0;
518 } else if (length <= 0xff) {
519 return 1;
520 } else if (length <= 0xffff) {
521 return 2;
522 } else if (length <= 0xffffffff) {
523 return 4;
524 }
525 return 8;
526}
527
528size_t cborNumBytesForTstr(const string& value) {
529 return 1 + cborNumBytesForLength(value.size()) + value.size();
530}
531
532size_t IdentityCredential::calcDeviceNameSpacesSize() {
533 /*
534 * This is how DeviceNameSpaces is defined:
535 *
536 * DeviceNameSpaces = {
537 * * NameSpace => DeviceSignedItems
538 * }
539 * DeviceSignedItems = {
540 * + DataItemName => DataItemValue
541 * }
542 *
543 * Namespace = tstr
544 * DataItemName = tstr
545 * DataItemValue = any
546 *
547 * This function will calculate its length using knowledge of how CBOR is
548 * encoded.
549 */
550 size_t ret = 0;
551 size_t numNamespacesWithValues = 0;
552 for (const RequestNamespace& rns : requestNamespaces_) {
553 vector<RequestDataItem> itemsToInclude;
554
555 for (const RequestDataItem& rdi : rns.items) {
556 // If we have a CBOR request message, skip if item isn't in it
557 if (itemsRequest_.size() > 0) {
558 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
559 if (it == requestedNameSpacesAndNames_.end()) {
560 continue;
561 }
562 const set<string>& dataItemNames = it->second;
563 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
564 continue;
565 }
566 }
567
568 // Access is granted if at least one of the profiles grants access.
569 //
570 // If an item is configured without any profiles, access is denied.
571 //
572 bool authorized = false;
573 for (auto id : rdi.accessControlProfileIds) {
574 auto it = profileIdToAccessCheckResult_.find(id);
575 if (it != profileIdToAccessCheckResult_.end()) {
576 int accessControlForProfile = it->second;
577 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
578 authorized = true;
579 break;
580 }
581 }
582 }
583 if (!authorized) {
584 continue;
585 }
586
587 itemsToInclude.push_back(rdi);
588 }
589
590 // If no entries are to be in the namespace, we don't include it...
591 if (itemsToInclude.size() == 0) {
592 continue;
593 }
594
595 // Key: NameSpace
596 ret += cborNumBytesForTstr(rns.namespaceName);
597
598 // Value: Open the DeviceSignedItems map
599 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
600
601 for (const RequestDataItem& item : itemsToInclude) {
602 // Key: DataItemName
603 ret += cborNumBytesForTstr(item.name);
604
605 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
606 // that.
607 ret += item.size;
608 }
609
610 numNamespacesWithValues++;
611 }
612
613 // Now that we now the nunber of namespaces with values, we know how many
614 // bytes the DeviceNamespaces map in the beginning is going to take up.
615 ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
616
617 return ret;
618}
619
David Zeuthen81603152020-02-11 22:04:24 -0500620ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
621 const string& nameSpace, const string& name, int32_t entrySize,
622 const vector<int32_t>& accessControlProfileIds) {
623 if (name.empty()) {
624 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
625 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
626 }
627 if (nameSpace.empty()) {
628 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
629 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
630 }
631
632 if (requestCountsRemaining_.size() == 0) {
633 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
634 IIdentityCredentialStore::STATUS_INVALID_DATA,
635 "No more name spaces left to go through"));
636 }
637
638 if (currentNameSpace_ == "") {
639 // First call.
640 currentNameSpace_ = nameSpace;
641 }
642
643 if (nameSpace == currentNameSpace_) {
644 // Same namespace.
645 if (requestCountsRemaining_[0] == 0) {
646 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
647 IIdentityCredentialStore::STATUS_INVALID_DATA,
648 "No more entries to be retrieved in current name space"));
649 }
650 requestCountsRemaining_[0] -= 1;
651 } else {
652 // New namespace.
653 if (requestCountsRemaining_[0] != 0) {
654 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
655 IIdentityCredentialStore::STATUS_INVALID_DATA,
656 "Moved to new name space but one or more entries need to be retrieved "
657 "in current name space"));
658 }
659 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
660 deviceNameSpacesMap_.add(currentNameSpace_,
661 std::move(currentNameSpaceDeviceNameSpacesMap_));
662 }
663 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
664
665 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
666 currentNameSpace_ = nameSpace;
667 }
668
669 // It's permissible to have an empty itemsRequest... but if non-empty you can
670 // only request what was specified in said itemsRequest. Enforce that.
671 if (itemsRequest_.size() > 0) {
672 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
673 if (it == requestedNameSpacesAndNames_.end()) {
674 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
675 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
676 "Name space was not requested in startRetrieval"));
677 }
David Zeuthen28edb102020-04-28 18:54:55 -0400678 const set<string>& dataItemNames = it->second;
679 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500680 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
681 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
682 "Data item name in name space was not requested in startRetrieval"));
683 }
684 }
685
686 // Enforce access control.
687 //
688 // Access is granted if at least one of the profiles grants access.
689 //
690 // If an item is configured without any profiles, access is denied.
691 //
692 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
693 for (auto id : accessControlProfileIds) {
694 auto search = profileIdToAccessCheckResult_.find(id);
695 if (search == profileIdToAccessCheckResult_.end()) {
696 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
697 IIdentityCredentialStore::STATUS_INVALID_DATA,
698 "Requested entry with unvalidated profile id"));
699 }
700 int accessControlForProfile = search->second;
701 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
702 accessControl = IIdentityCredentialStore::STATUS_OK;
703 break;
704 }
705 accessControl = accessControlForProfile;
706 }
707 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
708 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
709 int(accessControl), "Access control check failed"));
710 }
711
712 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
713
714 currentName_ = name;
715 entryRemainingBytes_ = entrySize;
716 entryValue_.resize(0);
717
718 return ndk::ScopedAStatus::ok();
719}
720
Jooyung Han17be89b2020-02-21 21:17:06 +0900721ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
722 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500723 optional<vector<uint8_t>> content =
724 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
725 if (!content) {
726 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
727 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
728 }
729
730 size_t chunkSize = content.value().size();
731
732 if (chunkSize > entryRemainingBytes_) {
733 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
734 << " is bigger than remaining space of size " << entryRemainingBytes_;
735 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
736 IIdentityCredentialStore::STATUS_INVALID_DATA,
737 "Retrieved chunk is bigger than remaining space"));
738 }
739
740 entryRemainingBytes_ -= chunkSize;
741 if (entryRemainingBytes_ > 0) {
742 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
743 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
744 IIdentityCredentialStore::STATUS_INVALID_DATA,
745 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
746 }
747 }
748
749 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
750
751 if (entryRemainingBytes_ == 0) {
752 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
753 if (entryValueItem == nullptr) {
754 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
755 IIdentityCredentialStore::STATUS_INVALID_DATA,
756 "Retrieved data which is invalid CBOR"));
757 }
758 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
759 }
760
Jooyung Han17be89b2020-02-21 21:17:06 +0900761 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500762 return ndk::ScopedAStatus::ok();
763}
764
Jooyung Han17be89b2020-02-21 21:17:06 +0900765ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
766 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500767 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
768 deviceNameSpacesMap_.add(currentNameSpace_,
769 std::move(currentNameSpaceDeviceNameSpacesMap_));
770 }
771 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
772
David Zeuthen28edb102020-04-28 18:54:55 -0400773 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
774 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
775 << "was expecting " << expectedDeviceNameSpacesSize_;
776 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
777 IIdentityCredentialStore::STATUS_INVALID_DATA,
778 StringPrintf(
779 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
780 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
781 .c_str()));
782 }
783
David Zeuthen81603152020-02-11 22:04:24 -0500784 // If there's no signing key or no sessionTranscript or no reader ephemeral
785 // public key, we return the empty MAC.
786 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500787 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
788 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500789 cppbor::Array array;
790 array.add("DeviceAuthentication");
791 array.add(sessionTranscriptItem_->clone());
792 array.add(docType_);
793 array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
794 vector<uint8_t> encodedDeviceAuthentication = array.encode();
795
796 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
797 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500798 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500799 if (!signingKey) {
800 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
801 IIdentityCredentialStore::STATUS_INVALID_DATA,
802 "Error decrypting signingKeyBlob"));
803 }
804
805 optional<vector<uint8_t>> sharedSecret =
806 support::ecdh(readerPublicKey_, signingKey.value());
807 if (!sharedSecret) {
808 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
809 IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
810 }
811
812 vector<uint8_t> salt = {0x00};
813 vector<uint8_t> info = {};
814 optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
815 if (!derivedKey) {
816 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
817 IIdentityCredentialStore::STATUS_FAILED,
818 "Error deriving key from shared secret"));
819 }
820
821 mac = support::coseMac0(derivedKey.value(), {}, // payload
822 encodedDeviceAuthentication); // additionalData
823 if (!mac) {
824 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
825 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
826 }
827 }
828
Jooyung Han17be89b2020-02-21 21:17:06 +0900829 *outMac = mac.value_or(vector<uint8_t>({}));
830 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500831 return ndk::ScopedAStatus::ok();
832}
833
834ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900835 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen81603152020-02-11 22:04:24 -0500836 string serialDecimal = "0"; // TODO: set serial to something unique
837 string issuer = "Android Open Source Project";
838 string subject = "Android IdentityCredential Reference Implementation";
839 time_t validityNotBefore = time(nullptr);
840 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
841
842 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
843 if (!signingKeyPKCS8) {
844 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
845 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
846 }
847
848 optional<vector<uint8_t>> signingPublicKey =
849 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
850 if (!signingPublicKey) {
851 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
852 IIdentityCredentialStore::STATUS_FAILED,
853 "Error getting public part of signingKey"));
854 }
855
856 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
857 if (!signingKey) {
858 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
859 IIdentityCredentialStore::STATUS_FAILED,
860 "Error getting private part of signingKey"));
861 }
862
863 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
864 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
865 validityNotBefore, validityNotAfter);
866 if (!certificate) {
867 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
868 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
869 }
870
871 optional<vector<uint8_t>> nonce = support::getRandom(12);
872 if (!nonce) {
873 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
874 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
875 }
876 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
877 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
878 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
879 if (!encryptedSigningKey) {
880 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
881 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
882 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900883 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500884 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900885 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500886 return ndk::ScopedAStatus::ok();
887}
888
889} // namespace aidl::android::hardware::identity