blob: ff4107a7b4efdda11dd429e079ccc1c6d9a32628 [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
201Timestamp clockGetTime() {
202 struct timespec time;
203 clock_gettime(CLOCK_MONOTONIC, &time);
204 Timestamp ts;
205 ts.milliSeconds = time.tv_sec * 1000 + time.tv_nsec / 1000000;
206 return ts;
207}
208
209bool checkUserAuthentication(const SecureAccessControlProfile& profile,
210 const HardwareAuthToken& authToken, uint64_t authChallenge) {
211 if (profile.secureUserId != authToken.userId) {
212 LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
213 << ") differs from userId in authToken (" << authToken.userId << ")";
214 return false;
215 }
216
217 if (profile.timeoutMillis == 0) {
218 if (authToken.challenge == 0) {
219 LOG(ERROR) << "No challenge in authToken";
220 return false;
221 }
222
223 if (authToken.challenge != int64_t(authChallenge)) {
224 LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
225 return false;
226 }
227 return true;
228 }
229
230 // Note that the Epoch for timestamps in HardwareAuthToken is at the
231 // discretion of the vendor:
232 //
233 // "[...] since some starting point (generally the most recent device
234 // boot) which all of the applications within one secure environment
235 // must agree upon."
236 //
237 // Therefore, if this software implementation is used on a device which isn't
238 // the emulator then the assumption that the epoch is the same as used in
239 // clockGetTime above will not hold. This is OK as this software
240 // implementation should never be used on a real device.
241 //
242 Timestamp now = clockGetTime();
243 if (authToken.timestamp.milliSeconds > now.milliSeconds) {
244 LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
245 << ") is in the future (now: " << now.milliSeconds << ")";
246 return false;
247 }
248 if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
249 LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
250 << profile.timeoutMillis << " = "
251 << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
252 << ") is in the past (now: " << now.milliSeconds << ")";
253 return false;
254 }
255 return true;
256}
257
David Zeuthen28edb102020-04-28 18:54:55 -0400258ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
259 const vector<RequestNamespace>& requestNamespaces) {
260 requestNamespaces_ = requestNamespaces;
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) {
486 if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
487 accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
488 }
489 } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
490 if (!readerCertificateChain ||
491 !checkReaderAuthentication(profile, readerCertificateChain.value())) {
492 accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
493 }
494 }
495 profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
496 }
497
498 deviceNameSpacesMap_ = cppbor::Map();
499 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
500
501 requestCountsRemaining_ = requestCounts;
502 currentNameSpace_ = "";
503
504 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900505 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500506
David Zeuthen28edb102020-04-28 18:54:55 -0400507 // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
508 expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
509
David Zeuthen81603152020-02-11 22:04:24 -0500510 numStartRetrievalCalls_ += 1;
511 return ndk::ScopedAStatus::ok();
512}
513
David Zeuthen28edb102020-04-28 18:54:55 -0400514size_t cborNumBytesForLength(size_t length) {
515 if (length < 24) {
516 return 0;
517 } else if (length <= 0xff) {
518 return 1;
519 } else if (length <= 0xffff) {
520 return 2;
521 } else if (length <= 0xffffffff) {
522 return 4;
523 }
524 return 8;
525}
526
527size_t cborNumBytesForTstr(const string& value) {
528 return 1 + cborNumBytesForLength(value.size()) + value.size();
529}
530
531size_t IdentityCredential::calcDeviceNameSpacesSize() {
532 /*
533 * This is how DeviceNameSpaces is defined:
534 *
535 * DeviceNameSpaces = {
536 * * NameSpace => DeviceSignedItems
537 * }
538 * DeviceSignedItems = {
539 * + DataItemName => DataItemValue
540 * }
541 *
542 * Namespace = tstr
543 * DataItemName = tstr
544 * DataItemValue = any
545 *
546 * This function will calculate its length using knowledge of how CBOR is
547 * encoded.
548 */
549 size_t ret = 0;
550 size_t numNamespacesWithValues = 0;
551 for (const RequestNamespace& rns : requestNamespaces_) {
552 vector<RequestDataItem> itemsToInclude;
553
554 for (const RequestDataItem& rdi : rns.items) {
555 // If we have a CBOR request message, skip if item isn't in it
556 if (itemsRequest_.size() > 0) {
557 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
558 if (it == requestedNameSpacesAndNames_.end()) {
559 continue;
560 }
561 const set<string>& dataItemNames = it->second;
562 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
563 continue;
564 }
565 }
566
567 // Access is granted if at least one of the profiles grants access.
568 //
569 // If an item is configured without any profiles, access is denied.
570 //
571 bool authorized = false;
572 for (auto id : rdi.accessControlProfileIds) {
573 auto it = profileIdToAccessCheckResult_.find(id);
574 if (it != profileIdToAccessCheckResult_.end()) {
575 int accessControlForProfile = it->second;
576 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
577 authorized = true;
578 break;
579 }
580 }
581 }
582 if (!authorized) {
583 continue;
584 }
585
586 itemsToInclude.push_back(rdi);
587 }
588
589 // If no entries are to be in the namespace, we don't include it...
590 if (itemsToInclude.size() == 0) {
591 continue;
592 }
593
594 // Key: NameSpace
595 ret += cborNumBytesForTstr(rns.namespaceName);
596
597 // Value: Open the DeviceSignedItems map
598 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
599
600 for (const RequestDataItem& item : itemsToInclude) {
601 // Key: DataItemName
602 ret += cborNumBytesForTstr(item.name);
603
604 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
605 // that.
606 ret += item.size;
607 }
608
609 numNamespacesWithValues++;
610 }
611
612 // Now that we now the nunber of namespaces with values, we know how many
613 // bytes the DeviceNamespaces map in the beginning is going to take up.
614 ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
615
616 return ret;
617}
618
David Zeuthen81603152020-02-11 22:04:24 -0500619ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
620 const string& nameSpace, const string& name, int32_t entrySize,
621 const vector<int32_t>& accessControlProfileIds) {
622 if (name.empty()) {
623 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
624 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
625 }
626 if (nameSpace.empty()) {
627 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
628 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
629 }
630
631 if (requestCountsRemaining_.size() == 0) {
632 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
633 IIdentityCredentialStore::STATUS_INVALID_DATA,
634 "No more name spaces left to go through"));
635 }
636
637 if (currentNameSpace_ == "") {
638 // First call.
639 currentNameSpace_ = nameSpace;
640 }
641
642 if (nameSpace == currentNameSpace_) {
643 // Same namespace.
644 if (requestCountsRemaining_[0] == 0) {
645 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
646 IIdentityCredentialStore::STATUS_INVALID_DATA,
647 "No more entries to be retrieved in current name space"));
648 }
649 requestCountsRemaining_[0] -= 1;
650 } else {
651 // New namespace.
652 if (requestCountsRemaining_[0] != 0) {
653 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
654 IIdentityCredentialStore::STATUS_INVALID_DATA,
655 "Moved to new name space but one or more entries need to be retrieved "
656 "in current name space"));
657 }
658 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
659 deviceNameSpacesMap_.add(currentNameSpace_,
660 std::move(currentNameSpaceDeviceNameSpacesMap_));
661 }
662 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
663
664 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
665 currentNameSpace_ = nameSpace;
666 }
667
668 // It's permissible to have an empty itemsRequest... but if non-empty you can
669 // only request what was specified in said itemsRequest. Enforce that.
670 if (itemsRequest_.size() > 0) {
671 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
672 if (it == requestedNameSpacesAndNames_.end()) {
673 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
674 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
675 "Name space was not requested in startRetrieval"));
676 }
David Zeuthen28edb102020-04-28 18:54:55 -0400677 const set<string>& dataItemNames = it->second;
678 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500679 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
680 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
681 "Data item name in name space was not requested in startRetrieval"));
682 }
683 }
684
685 // Enforce access control.
686 //
687 // Access is granted if at least one of the profiles grants access.
688 //
689 // If an item is configured without any profiles, access is denied.
690 //
691 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
692 for (auto id : accessControlProfileIds) {
693 auto search = profileIdToAccessCheckResult_.find(id);
694 if (search == profileIdToAccessCheckResult_.end()) {
695 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
696 IIdentityCredentialStore::STATUS_INVALID_DATA,
697 "Requested entry with unvalidated profile id"));
698 }
699 int accessControlForProfile = search->second;
700 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
701 accessControl = IIdentityCredentialStore::STATUS_OK;
702 break;
703 }
704 accessControl = accessControlForProfile;
705 }
706 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
707 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
708 int(accessControl), "Access control check failed"));
709 }
710
711 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
712
713 currentName_ = name;
714 entryRemainingBytes_ = entrySize;
715 entryValue_.resize(0);
716
717 return ndk::ScopedAStatus::ok();
718}
719
Jooyung Han17be89b2020-02-21 21:17:06 +0900720ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
721 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500722 optional<vector<uint8_t>> content =
723 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
724 if (!content) {
725 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
726 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
727 }
728
729 size_t chunkSize = content.value().size();
730
731 if (chunkSize > entryRemainingBytes_) {
732 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
733 << " is bigger than remaining space of size " << entryRemainingBytes_;
734 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
735 IIdentityCredentialStore::STATUS_INVALID_DATA,
736 "Retrieved chunk is bigger than remaining space"));
737 }
738
739 entryRemainingBytes_ -= chunkSize;
740 if (entryRemainingBytes_ > 0) {
741 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
742 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
743 IIdentityCredentialStore::STATUS_INVALID_DATA,
744 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
745 }
746 }
747
748 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
749
750 if (entryRemainingBytes_ == 0) {
751 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
752 if (entryValueItem == nullptr) {
753 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
754 IIdentityCredentialStore::STATUS_INVALID_DATA,
755 "Retrieved data which is invalid CBOR"));
756 }
757 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
758 }
759
Jooyung Han17be89b2020-02-21 21:17:06 +0900760 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500761 return ndk::ScopedAStatus::ok();
762}
763
Jooyung Han17be89b2020-02-21 21:17:06 +0900764ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
765 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500766 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
767 deviceNameSpacesMap_.add(currentNameSpace_,
768 std::move(currentNameSpaceDeviceNameSpacesMap_));
769 }
770 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
771
David Zeuthen28edb102020-04-28 18:54:55 -0400772 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
773 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
774 << "was expecting " << expectedDeviceNameSpacesSize_;
775 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
776 IIdentityCredentialStore::STATUS_INVALID_DATA,
777 StringPrintf(
778 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
779 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
780 .c_str()));
781 }
782
David Zeuthen81603152020-02-11 22:04:24 -0500783 // If there's no signing key or no sessionTranscript or no reader ephemeral
784 // public key, we return the empty MAC.
785 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500786 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
787 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500788 cppbor::Array array;
789 array.add("DeviceAuthentication");
790 array.add(sessionTranscriptItem_->clone());
791 array.add(docType_);
792 array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
793 vector<uint8_t> encodedDeviceAuthentication = array.encode();
794
795 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
796 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500797 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500798 if (!signingKey) {
799 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
800 IIdentityCredentialStore::STATUS_INVALID_DATA,
801 "Error decrypting signingKeyBlob"));
802 }
803
804 optional<vector<uint8_t>> sharedSecret =
805 support::ecdh(readerPublicKey_, signingKey.value());
806 if (!sharedSecret) {
807 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
808 IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
809 }
810
811 vector<uint8_t> salt = {0x00};
812 vector<uint8_t> info = {};
813 optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
814 if (!derivedKey) {
815 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
816 IIdentityCredentialStore::STATUS_FAILED,
817 "Error deriving key from shared secret"));
818 }
819
820 mac = support::coseMac0(derivedKey.value(), {}, // payload
821 encodedDeviceAuthentication); // additionalData
822 if (!mac) {
823 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
824 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
825 }
826 }
827
Jooyung Han17be89b2020-02-21 21:17:06 +0900828 *outMac = mac.value_or(vector<uint8_t>({}));
829 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500830 return ndk::ScopedAStatus::ok();
831}
832
833ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900834 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen81603152020-02-11 22:04:24 -0500835 string serialDecimal = "0"; // TODO: set serial to something unique
836 string issuer = "Android Open Source Project";
837 string subject = "Android IdentityCredential Reference Implementation";
838 time_t validityNotBefore = time(nullptr);
839 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
840
841 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
842 if (!signingKeyPKCS8) {
843 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
844 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
845 }
846
847 optional<vector<uint8_t>> signingPublicKey =
848 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
849 if (!signingPublicKey) {
850 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
851 IIdentityCredentialStore::STATUS_FAILED,
852 "Error getting public part of signingKey"));
853 }
854
855 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
856 if (!signingKey) {
857 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
858 IIdentityCredentialStore::STATUS_FAILED,
859 "Error getting private part of signingKey"));
860 }
861
862 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
863 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
864 validityNotBefore, validityNotAfter);
865 if (!certificate) {
866 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
867 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
868 }
869
870 optional<vector<uint8_t>> nonce = support::getRandom(12);
871 if (!nonce) {
872 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
873 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
874 }
875 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
876 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
877 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
878 if (!encryptedSigningKey) {
879 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
880 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
881 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900882 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500883 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900884 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500885 return ndk::ScopedAStatus::ok();
886}
887
888} // namespace aidl::android::hardware::identity