blob: aaae1f6ae543e2288d904fcf7fb696e703888a2a [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>
28
29#include <cppbor.h>
30#include <cppbor_parse.h>
31
32namespace aidl::android::hardware::identity {
33
34using ::aidl::android::hardware::keymaster::Timestamp;
35using ::std::optional;
36
37using namespace ::android::hardware::identity;
38
39int IdentityCredential::initialize() {
40 auto [item, _, message] = cppbor::parse(credentialData_);
41 if (item == nullptr) {
42 LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
43 return IIdentityCredentialStore::STATUS_INVALID_DATA;
44 }
45
46 const cppbor::Array* arrayItem = item->asArray();
47 if (arrayItem == nullptr || arrayItem->size() != 3) {
48 LOG(ERROR) << "CredentialData is not an array with three elements";
49 return IIdentityCredentialStore::STATUS_INVALID_DATA;
50 }
51
52 const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
53 const cppbor::Bool* testCredentialItem =
54 ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
55 : nullptr);
56 const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
57 if (docTypeItem == nullptr || testCredentialItem == nullptr ||
58 encryptedCredentialKeysItem == nullptr) {
59 LOG(ERROR) << "CredentialData unexpected item types";
60 return IIdentityCredentialStore::STATUS_INVALID_DATA;
61 }
62
63 docType_ = docTypeItem->value();
64 testCredential_ = testCredentialItem->value();
65
66 vector<uint8_t> hardwareBoundKey;
67 if (testCredential_) {
68 hardwareBoundKey = support::getTestHardwareBoundKey();
69 } else {
70 hardwareBoundKey = getHardwareBoundKey();
71 }
72
73 const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
74 const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
75 optional<vector<uint8_t>> decryptedCredentialKeys =
76 support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
77 if (!decryptedCredentialKeys) {
78 LOG(ERROR) << "Error decrypting CredentialKeys";
79 return IIdentityCredentialStore::STATUS_INVALID_DATA;
80 }
81
82 auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
83 if (dckItem == nullptr) {
84 LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
85 return IIdentityCredentialStore::STATUS_INVALID_DATA;
86 }
87 const cppbor::Array* dckArrayItem = dckItem->asArray();
88 if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
89 LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
90 return IIdentityCredentialStore::STATUS_INVALID_DATA;
91 }
92 const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
93 const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
94 if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
95 LOG(ERROR) << "CredentialKeys unexpected item types";
96 return IIdentityCredentialStore::STATUS_INVALID_DATA;
97 }
98 storageKey_ = storageKeyItem->value();
99 credentialPrivKey_ = credentialPrivKeyItem->value();
100
101 return IIdentityCredentialStore::STATUS_OK;
102}
103
104ndk::ScopedAStatus IdentityCredential::deleteCredential(
Jooyung Han17be89b2020-02-21 21:17:06 +0900105 vector<uint8_t>* outProofOfDeletionSignature) {
David Zeuthen81603152020-02-11 22:04:24 -0500106 cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
107 vector<uint8_t> proofOfDeletion = array.encode();
108
109 optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
110 proofOfDeletion, // payload
111 {}, // additionalData
112 {}); // certificateChain
113 if (!signature) {
114 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
115 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
116 }
117
Jooyung Han17be89b2020-02-21 21:17:06 +0900118 *outProofOfDeletionSignature = signature.value();
David Zeuthen81603152020-02-11 22:04:24 -0500119 return ndk::ScopedAStatus::ok();
120}
121
Jooyung Han17be89b2020-02-21 21:17:06 +0900122ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
David Zeuthen81603152020-02-11 22:04:24 -0500123 optional<vector<uint8_t>> kp = support::createEcKeyPair();
124 if (!kp) {
125 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
126 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
127 }
128
129 // Stash public key of this key-pair for later check in startRetrieval().
130 optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
131 if (!publicKey) {
132 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
133 IIdentityCredentialStore::STATUS_FAILED,
134 "Error getting public part of ephemeral key pair"));
135 }
136 ephemeralPublicKey_ = publicKey.value();
137
Jooyung Han17be89b2020-02-21 21:17:06 +0900138 *outKeyPair = kp.value();
David Zeuthen81603152020-02-11 22:04:24 -0500139 return ndk::ScopedAStatus::ok();
140}
141
142ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
Jooyung Han17be89b2020-02-21 21:17:06 +0900143 const vector<uint8_t>& publicKey) {
144 readerPublicKey_ = publicKey;
David Zeuthen81603152020-02-11 22:04:24 -0500145 return ndk::ScopedAStatus::ok();
146}
147
148ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
149 uint64_t challenge = 0;
150 while (challenge == 0) {
151 optional<vector<uint8_t>> bytes = support::getRandom(8);
152 if (!bytes) {
153 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
154 IIdentityCredentialStore::STATUS_FAILED,
155 "Error getting random data for challenge"));
156 }
157
158 challenge = 0;
159 for (size_t n = 0; n < bytes.value().size(); n++) {
160 challenge |= ((bytes.value())[n] << (n * 8));
161 }
162 }
163
164 *outChallenge = challenge;
165 return ndk::ScopedAStatus::ok();
166}
167
168// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
169// ahead of time.
170bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
171 const vector<uint8_t>& readerCertificateChain) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900172 optional<vector<uint8_t>> acpPubKey =
173 support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
David Zeuthen81603152020-02-11 22:04:24 -0500174 if (!acpPubKey) {
175 LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
176 return false;
177 }
178
179 optional<vector<vector<uint8_t>>> certificatesInChain =
180 support::certificateChainSplit(readerCertificateChain);
181 if (!certificatesInChain) {
182 LOG(ERROR) << "Error splitting readerCertificateChain";
183 return false;
184 }
185 for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
186 optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
187 if (!certPubKey) {
188 LOG(ERROR)
189 << "Error extracting public key from certificate in chain presented by reader";
190 return false;
191 }
192 if (acpPubKey.value() == certPubKey.value()) {
193 return true;
194 }
195 }
196 return false;
197}
198
199Timestamp clockGetTime() {
200 struct timespec time;
201 clock_gettime(CLOCK_MONOTONIC, &time);
202 Timestamp ts;
203 ts.milliSeconds = time.tv_sec * 1000 + time.tv_nsec / 1000000;
204 return ts;
205}
206
207bool checkUserAuthentication(const SecureAccessControlProfile& profile,
208 const HardwareAuthToken& authToken, uint64_t authChallenge) {
209 if (profile.secureUserId != authToken.userId) {
210 LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
211 << ") differs from userId in authToken (" << authToken.userId << ")";
212 return false;
213 }
214
215 if (profile.timeoutMillis == 0) {
216 if (authToken.challenge == 0) {
217 LOG(ERROR) << "No challenge in authToken";
218 return false;
219 }
220
221 if (authToken.challenge != int64_t(authChallenge)) {
222 LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
223 return false;
224 }
225 return true;
226 }
227
228 // Note that the Epoch for timestamps in HardwareAuthToken is at the
229 // discretion of the vendor:
230 //
231 // "[...] since some starting point (generally the most recent device
232 // boot) which all of the applications within one secure environment
233 // must agree upon."
234 //
235 // Therefore, if this software implementation is used on a device which isn't
236 // the emulator then the assumption that the epoch is the same as used in
237 // clockGetTime above will not hold. This is OK as this software
238 // implementation should never be used on a real device.
239 //
240 Timestamp now = clockGetTime();
241 if (authToken.timestamp.milliSeconds > now.milliSeconds) {
242 LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
243 << ") is in the future (now: " << now.milliSeconds << ")";
244 return false;
245 }
246 if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
247 LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
248 << profile.timeoutMillis << " = "
249 << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
250 << ") is in the past (now: " << now.milliSeconds << ")";
251 return false;
252 }
253 return true;
254}
255
256ndk::ScopedAStatus IdentityCredential::startRetrieval(
257 const vector<SecureAccessControlProfile>& accessControlProfiles,
Jooyung Han17be89b2020-02-21 21:17:06 +0900258 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
259 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
260 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
David Zeuthen81603152020-02-11 22:04:24 -0500261 if (sessionTranscript.size() > 0) {
262 auto [item, _, message] = cppbor::parse(sessionTranscript);
263 if (item == nullptr) {
264 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
265 IIdentityCredentialStore::STATUS_INVALID_DATA,
266 "SessionTranscript contains invalid CBOR"));
267 }
268 sessionTranscriptItem_ = std::move(item);
269 }
270 if (numStartRetrievalCalls_ > 0) {
271 if (sessionTranscript_ != sessionTranscript) {
272 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
273 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
274 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
275 }
276 }
277 sessionTranscript_ = sessionTranscript;
278
279 // If there is a signature, validate that it was made with the top-most key in the
280 // certificate chain embedded in the COSE_Sign1 structure.
281 optional<vector<uint8_t>> readerCertificateChain;
282 if (readerSignature.size() > 0) {
283 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
284 if (!readerCertificateChain) {
285 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
286 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
287 "Unable to get reader certificate chain from COSE_Sign1"));
288 }
289
290 if (!support::certificateChainValidate(readerCertificateChain.value())) {
291 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
292 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
293 "Error validating reader certificate chain"));
294 }
295
296 optional<vector<uint8_t>> readerPublicKey =
297 support::certificateChainGetTopMostKey(readerCertificateChain.value());
298 if (!readerPublicKey) {
299 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
300 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
301 "Unable to get public key from reader certificate chain"));
302 }
303
304 const vector<uint8_t>& itemsRequestBytes = itemsRequest;
305 vector<uint8_t> dataThatWasSigned = cppbor::Array()
306 .add("ReaderAuthentication")
307 .add(sessionTranscriptItem_->clone())
308 .add(cppbor::Semantic(24, itemsRequestBytes))
309 .encode();
310 if (!support::coseCheckEcDsaSignature(readerSignature,
311 dataThatWasSigned, // detached content
312 readerPublicKey.value())) {
313 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
314 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
315 "readerSignature check failed"));
316 }
317 }
318
319 // Here's where we would validate the passed-in |authToken| to assure ourselves
320 // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
321 //
322 // However this involves calculating the MAC. However this requires access
323 // to the key needed to a pre-shared key which we don't have...
324 //
325
326 // To prevent replay-attacks, we check that the public part of the ephemeral
327 // key we previously created, is present in the DeviceEngagement part of
328 // SessionTranscript as a COSE_Key, in uncompressed form.
329 //
330 // We do this by just searching for the X and Y coordinates.
331 if (sessionTranscript.size() > 0) {
332 const cppbor::Array* array = sessionTranscriptItem_->asArray();
333 if (array == nullptr || array->size() != 2) {
334 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
335 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
336 "SessionTranscript is not an array with two items"));
337 }
338 const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
339 if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
340 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
341 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
342 "First item in SessionTranscript array is not a "
343 "semantic with value 24"));
344 }
345 const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
346 if (encodedDE == nullptr) {
347 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
348 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
349 "Child of semantic in first item in SessionTranscript "
350 "array is not a bstr"));
351 }
352 const vector<uint8_t>& bytesDE = encodedDE->value();
353
354 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
355 if (!getXYSuccess) {
356 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
357 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
358 "Error extracting X and Y from ePub"));
359 }
360 if (sessionTranscript.size() > 0 &&
361 !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
362 memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
363 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
364 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
365 "Did not find ephemeral public key's X and Y coordinates in "
366 "SessionTranscript (make sure leading zeroes are not used)"));
367 }
368 }
369
370 // itemsRequest: If non-empty, contains request data that may be signed by the
371 // reader. The content can be defined in the way appropriate for the
372 // credential, but there are three requirements that must be met to work with
373 // this HAL:
374 if (itemsRequest.size() > 0) {
375 // 1. The content must be a CBOR-encoded structure.
376 auto [item, _, message] = cppbor::parse(itemsRequest);
377 if (item == nullptr) {
378 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
379 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
380 "Error decoding CBOR in itemsRequest"));
381 }
382
383 // 2. The CBOR structure must be a map.
384 const cppbor::Map* map = item->asMap();
385 if (map == nullptr) {
386 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
387 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
388 "itemsRequest is not a CBOR map"));
389 }
390
391 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
392 // the example below.
393 //
394 // NameSpaces = {
395 // + NameSpace => DataElements ; Requested data elements for each NameSpace
396 // }
397 //
398 // NameSpace = tstr
399 //
400 // DataElements = {
401 // + DataElement => IntentToRetain
402 // }
403 //
404 // DataElement = tstr
405 // IntentToRetain = bool
406 //
407 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
408 // through 3.:
409 //
410 // {
411 // 'docType' : 'org.iso.18013-5.2019',
412 // 'nameSpaces' : {
413 // 'org.iso.18013-5.2019' : {
414 // 'Last name' : false,
415 // 'Birth date' : false,
416 // 'First name' : false,
417 // 'Home address' : true
418 // },
419 // 'org.aamva.iso.18013-5.2019' : {
420 // 'Real Id' : false
421 // }
422 // }
423 // }
424 //
425 const cppbor::Map* nsMap = nullptr;
426 for (size_t n = 0; n < map->size(); n++) {
427 const auto& [keyItem, valueItem] = (*map)[n];
428 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
429 valueItem->type() == cppbor::MAP) {
430 nsMap = valueItem->asMap();
431 break;
432 }
433 }
434 if (nsMap == nullptr) {
435 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
436 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
437 "No nameSpaces map in top-most map"));
438 }
439
440 for (size_t n = 0; n < nsMap->size(); n++) {
441 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
442 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
443 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
444 if (nsKey == nullptr || nsInnerMap == nullptr) {
445 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
446 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
447 "Type mismatch in nameSpaces map"));
448 }
449 string requestedNamespace = nsKey->value();
450 vector<string> requestedKeys;
451 for (size_t m = 0; m < nsInnerMap->size(); m++) {
452 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
453 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
454 const cppbor::Simple* simple = innerMapValueItem->asSimple();
455 const cppbor::Bool* intentToRetainItem =
456 (simple != nullptr) ? simple->asBool() : nullptr;
457 if (nameItem == nullptr || intentToRetainItem == nullptr) {
458 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
459 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
460 "Type mismatch in value in nameSpaces map"));
461 }
462 requestedKeys.push_back(nameItem->value());
463 }
464 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
465 }
466 }
467
468 // Finally, validate all the access control profiles in the requestData.
469 bool haveAuthToken = (authToken.mac.size() > 0);
470 for (const auto& profile : accessControlProfiles) {
471 if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
472 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
473 IIdentityCredentialStore::STATUS_INVALID_DATA,
474 "Error checking MAC for profile"));
475 }
476 int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
477 if (profile.userAuthenticationRequired) {
478 if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
479 accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
480 }
481 } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
482 if (!readerCertificateChain ||
483 !checkReaderAuthentication(profile, readerCertificateChain.value())) {
484 accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
485 }
486 }
487 profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
488 }
489
490 deviceNameSpacesMap_ = cppbor::Map();
491 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
492
493 requestCountsRemaining_ = requestCounts;
494 currentNameSpace_ = "";
495
496 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900497 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500498
499 numStartRetrievalCalls_ += 1;
500 return ndk::ScopedAStatus::ok();
501}
502
503ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
504 const string& nameSpace, const string& name, int32_t entrySize,
505 const vector<int32_t>& accessControlProfileIds) {
506 if (name.empty()) {
507 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
508 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
509 }
510 if (nameSpace.empty()) {
511 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
512 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
513 }
514
515 if (requestCountsRemaining_.size() == 0) {
516 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
517 IIdentityCredentialStore::STATUS_INVALID_DATA,
518 "No more name spaces left to go through"));
519 }
520
521 if (currentNameSpace_ == "") {
522 // First call.
523 currentNameSpace_ = nameSpace;
524 }
525
526 if (nameSpace == currentNameSpace_) {
527 // Same namespace.
528 if (requestCountsRemaining_[0] == 0) {
529 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
530 IIdentityCredentialStore::STATUS_INVALID_DATA,
531 "No more entries to be retrieved in current name space"));
532 }
533 requestCountsRemaining_[0] -= 1;
534 } else {
535 // New namespace.
536 if (requestCountsRemaining_[0] != 0) {
537 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
538 IIdentityCredentialStore::STATUS_INVALID_DATA,
539 "Moved to new name space but one or more entries need to be retrieved "
540 "in current name space"));
541 }
542 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
543 deviceNameSpacesMap_.add(currentNameSpace_,
544 std::move(currentNameSpaceDeviceNameSpacesMap_));
545 }
546 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
547
548 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
549 currentNameSpace_ = nameSpace;
550 }
551
552 // It's permissible to have an empty itemsRequest... but if non-empty you can
553 // only request what was specified in said itemsRequest. Enforce that.
554 if (itemsRequest_.size() > 0) {
555 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
556 if (it == requestedNameSpacesAndNames_.end()) {
557 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
558 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
559 "Name space was not requested in startRetrieval"));
560 }
561 const auto& dataItemNames = it->second;
562 if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
563 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
564 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
565 "Data item name in name space was not requested in startRetrieval"));
566 }
567 }
568
569 // Enforce access control.
570 //
571 // Access is granted if at least one of the profiles grants access.
572 //
573 // If an item is configured without any profiles, access is denied.
574 //
575 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
576 for (auto id : accessControlProfileIds) {
577 auto search = profileIdToAccessCheckResult_.find(id);
578 if (search == profileIdToAccessCheckResult_.end()) {
579 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
580 IIdentityCredentialStore::STATUS_INVALID_DATA,
581 "Requested entry with unvalidated profile id"));
582 }
583 int accessControlForProfile = search->second;
584 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
585 accessControl = IIdentityCredentialStore::STATUS_OK;
586 break;
587 }
588 accessControl = accessControlForProfile;
589 }
590 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
591 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
592 int(accessControl), "Access control check failed"));
593 }
594
595 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
596
597 currentName_ = name;
598 entryRemainingBytes_ = entrySize;
599 entryValue_.resize(0);
600
601 return ndk::ScopedAStatus::ok();
602}
603
Jooyung Han17be89b2020-02-21 21:17:06 +0900604ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
605 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500606 optional<vector<uint8_t>> content =
607 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
608 if (!content) {
609 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
610 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
611 }
612
613 size_t chunkSize = content.value().size();
614
615 if (chunkSize > entryRemainingBytes_) {
616 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
617 << " is bigger than remaining space of size " << entryRemainingBytes_;
618 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
619 IIdentityCredentialStore::STATUS_INVALID_DATA,
620 "Retrieved chunk is bigger than remaining space"));
621 }
622
623 entryRemainingBytes_ -= chunkSize;
624 if (entryRemainingBytes_ > 0) {
625 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
626 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
627 IIdentityCredentialStore::STATUS_INVALID_DATA,
628 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
629 }
630 }
631
632 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
633
634 if (entryRemainingBytes_ == 0) {
635 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
636 if (entryValueItem == nullptr) {
637 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
638 IIdentityCredentialStore::STATUS_INVALID_DATA,
639 "Retrieved data which is invalid CBOR"));
640 }
641 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
642 }
643
Jooyung Han17be89b2020-02-21 21:17:06 +0900644 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500645 return ndk::ScopedAStatus::ok();
646}
647
Jooyung Han17be89b2020-02-21 21:17:06 +0900648ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
649 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500650 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
651 deviceNameSpacesMap_.add(currentNameSpace_,
652 std::move(currentNameSpaceDeviceNameSpacesMap_));
653 }
654 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
655
656 // If there's no signing key or no sessionTranscript or no reader ephemeral
657 // public key, we return the empty MAC.
658 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500659 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
660 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500661 cppbor::Array array;
662 array.add("DeviceAuthentication");
663 array.add(sessionTranscriptItem_->clone());
664 array.add(docType_);
665 array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
666 vector<uint8_t> encodedDeviceAuthentication = array.encode();
667
668 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
669 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500670 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500671 if (!signingKey) {
672 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
673 IIdentityCredentialStore::STATUS_INVALID_DATA,
674 "Error decrypting signingKeyBlob"));
675 }
676
677 optional<vector<uint8_t>> sharedSecret =
678 support::ecdh(readerPublicKey_, signingKey.value());
679 if (!sharedSecret) {
680 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
681 IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
682 }
683
684 vector<uint8_t> salt = {0x00};
685 vector<uint8_t> info = {};
686 optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
687 if (!derivedKey) {
688 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
689 IIdentityCredentialStore::STATUS_FAILED,
690 "Error deriving key from shared secret"));
691 }
692
693 mac = support::coseMac0(derivedKey.value(), {}, // payload
694 encodedDeviceAuthentication); // additionalData
695 if (!mac) {
696 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
697 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
698 }
699 }
700
Jooyung Han17be89b2020-02-21 21:17:06 +0900701 *outMac = mac.value_or(vector<uint8_t>({}));
702 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500703 return ndk::ScopedAStatus::ok();
704}
705
706ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900707 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen81603152020-02-11 22:04:24 -0500708 string serialDecimal = "0"; // TODO: set serial to something unique
709 string issuer = "Android Open Source Project";
710 string subject = "Android IdentityCredential Reference Implementation";
711 time_t validityNotBefore = time(nullptr);
712 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
713
714 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
715 if (!signingKeyPKCS8) {
716 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
717 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
718 }
719
720 optional<vector<uint8_t>> signingPublicKey =
721 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
722 if (!signingPublicKey) {
723 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
724 IIdentityCredentialStore::STATUS_FAILED,
725 "Error getting public part of signingKey"));
726 }
727
728 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
729 if (!signingKey) {
730 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
731 IIdentityCredentialStore::STATUS_FAILED,
732 "Error getting private part of signingKey"));
733 }
734
735 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
736 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
737 validityNotBefore, validityNotAfter);
738 if (!certificate) {
739 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
740 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
741 }
742
743 optional<vector<uint8_t>> nonce = support::getRandom(12);
744 if (!nonce) {
745 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
746 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
747 }
748 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
749 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
750 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
751 if (!encryptedSigningKey) {
752 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
753 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
754 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900755 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500756 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900757 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500758 return ndk::ScopedAStatus::ok();
759}
760
761} // namespace aidl::android::hardware::identity