blob: dfcd4f557f8defd36c4f632ee10b4d4e8d3a26f1 [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() {
David Zeuthen2e4533e2020-06-20 17:04:41 -040042 if (credentialData_.size() == 0) {
43 LOG(ERROR) << "CredentialData is empty";
44 return IIdentityCredentialStore::STATUS_INVALID_DATA;
45 }
David Zeuthen81603152020-02-11 22:04:24 -050046 auto [item, _, message] = cppbor::parse(credentialData_);
47 if (item == nullptr) {
48 LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
49 return IIdentityCredentialStore::STATUS_INVALID_DATA;
50 }
51
52 const cppbor::Array* arrayItem = item->asArray();
53 if (arrayItem == nullptr || arrayItem->size() != 3) {
54 LOG(ERROR) << "CredentialData is not an array with three elements";
55 return IIdentityCredentialStore::STATUS_INVALID_DATA;
56 }
57
58 const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
59 const cppbor::Bool* testCredentialItem =
60 ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
61 : nullptr);
62 const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
63 if (docTypeItem == nullptr || testCredentialItem == nullptr ||
64 encryptedCredentialKeysItem == nullptr) {
65 LOG(ERROR) << "CredentialData unexpected item types";
66 return IIdentityCredentialStore::STATUS_INVALID_DATA;
67 }
68
69 docType_ = docTypeItem->value();
70 testCredential_ = testCredentialItem->value();
71
72 vector<uint8_t> hardwareBoundKey;
73 if (testCredential_) {
74 hardwareBoundKey = support::getTestHardwareBoundKey();
75 } else {
76 hardwareBoundKey = getHardwareBoundKey();
77 }
78
79 const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
80 const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
81 optional<vector<uint8_t>> decryptedCredentialKeys =
82 support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
83 if (!decryptedCredentialKeys) {
84 LOG(ERROR) << "Error decrypting CredentialKeys";
85 return IIdentityCredentialStore::STATUS_INVALID_DATA;
86 }
87
88 auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
89 if (dckItem == nullptr) {
90 LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
91 return IIdentityCredentialStore::STATUS_INVALID_DATA;
92 }
93 const cppbor::Array* dckArrayItem = dckItem->asArray();
94 if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
95 LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
96 return IIdentityCredentialStore::STATUS_INVALID_DATA;
97 }
98 const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
99 const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
100 if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
101 LOG(ERROR) << "CredentialKeys unexpected item types";
102 return IIdentityCredentialStore::STATUS_INVALID_DATA;
103 }
104 storageKey_ = storageKeyItem->value();
105 credentialPrivKey_ = credentialPrivKeyItem->value();
106
107 return IIdentityCredentialStore::STATUS_OK;
108}
109
110ndk::ScopedAStatus IdentityCredential::deleteCredential(
Jooyung Han17be89b2020-02-21 21:17:06 +0900111 vector<uint8_t>* outProofOfDeletionSignature) {
David Zeuthen81603152020-02-11 22:04:24 -0500112 cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
113 vector<uint8_t> proofOfDeletion = array.encode();
114
115 optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
116 proofOfDeletion, // payload
117 {}, // additionalData
118 {}); // certificateChain
119 if (!signature) {
120 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
121 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
122 }
123
Jooyung Han17be89b2020-02-21 21:17:06 +0900124 *outProofOfDeletionSignature = signature.value();
David Zeuthen81603152020-02-11 22:04:24 -0500125 return ndk::ScopedAStatus::ok();
126}
127
Jooyung Han17be89b2020-02-21 21:17:06 +0900128ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
David Zeuthen81603152020-02-11 22:04:24 -0500129 optional<vector<uint8_t>> kp = support::createEcKeyPair();
130 if (!kp) {
131 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
132 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
133 }
134
135 // Stash public key of this key-pair for later check in startRetrieval().
136 optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
137 if (!publicKey) {
138 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
139 IIdentityCredentialStore::STATUS_FAILED,
140 "Error getting public part of ephemeral key pair"));
141 }
142 ephemeralPublicKey_ = publicKey.value();
143
Jooyung Han17be89b2020-02-21 21:17:06 +0900144 *outKeyPair = kp.value();
David Zeuthen81603152020-02-11 22:04:24 -0500145 return ndk::ScopedAStatus::ok();
146}
147
148ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
Jooyung Han17be89b2020-02-21 21:17:06 +0900149 const vector<uint8_t>& publicKey) {
150 readerPublicKey_ = publicKey;
David Zeuthen81603152020-02-11 22:04:24 -0500151 return ndk::ScopedAStatus::ok();
152}
153
154ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
155 uint64_t challenge = 0;
156 while (challenge == 0) {
157 optional<vector<uint8_t>> bytes = support::getRandom(8);
158 if (!bytes) {
159 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
160 IIdentityCredentialStore::STATUS_FAILED,
161 "Error getting random data for challenge"));
162 }
163
164 challenge = 0;
165 for (size_t n = 0; n < bytes.value().size(); n++) {
166 challenge |= ((bytes.value())[n] << (n * 8));
167 }
168 }
169
170 *outChallenge = challenge;
David Zeuthenef739512020-06-03 13:24:52 -0400171 authChallenge_ = challenge;
David Zeuthen81603152020-02-11 22:04:24 -0500172 return ndk::ScopedAStatus::ok();
173}
174
175// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
176// ahead of time.
177bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
178 const vector<uint8_t>& readerCertificateChain) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900179 optional<vector<uint8_t>> acpPubKey =
180 support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
David Zeuthen81603152020-02-11 22:04:24 -0500181 if (!acpPubKey) {
182 LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
183 return false;
184 }
185
186 optional<vector<vector<uint8_t>>> certificatesInChain =
187 support::certificateChainSplit(readerCertificateChain);
188 if (!certificatesInChain) {
189 LOG(ERROR) << "Error splitting readerCertificateChain";
190 return false;
191 }
192 for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
193 optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
194 if (!certPubKey) {
195 LOG(ERROR)
196 << "Error extracting public key from certificate in chain presented by reader";
197 return false;
198 }
199 if (acpPubKey.value() == certPubKey.value()) {
200 return true;
201 }
202 }
203 return false;
204}
205
David Zeuthen81603152020-02-11 22:04:24 -0500206bool checkUserAuthentication(const SecureAccessControlProfile& profile,
David Zeuthena8ed82c2020-05-08 10:03:28 -0400207 const VerificationToken& verificationToken,
David Zeuthen81603152020-02-11 22:04:24 -0500208 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
David Zeuthena8ed82c2020-05-08 10:03:28 -0400215 if (verificationToken.timestamp.milliSeconds == 0) {
216 LOG(ERROR) << "VerificationToken is not set";
217 return false;
218 }
219 if (authToken.timestamp.milliSeconds == 0) {
220 LOG(ERROR) << "AuthToken is not set";
221 return false;
222 }
223
David Zeuthen81603152020-02-11 22:04:24 -0500224 if (profile.timeoutMillis == 0) {
225 if (authToken.challenge == 0) {
226 LOG(ERROR) << "No challenge in authToken";
227 return false;
228 }
229
230 if (authToken.challenge != int64_t(authChallenge)) {
David Zeuthenef739512020-06-03 13:24:52 -0400231 LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
232 << "doesn't match the challenge we created (" << authChallenge << ")";
David Zeuthen81603152020-02-11 22:04:24 -0500233 return false;
234 }
235 return true;
236 }
237
David Zeuthena8ed82c2020-05-08 10:03:28 -0400238 // Timeout-based user auth follows. The verification token conveys what the
239 // time is right now in the environment which generated the auth token. This
240 // is what makes it possible to do timeout-based checks.
David Zeuthen81603152020-02-11 22:04:24 -0500241 //
David Zeuthena8ed82c2020-05-08 10:03:28 -0400242 const Timestamp now = verificationToken.timestamp;
David Zeuthen81603152020-02-11 22:04:24 -0500243 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 Zeuthena8ed82c2020-05-08 10:03:28 -0400264ndk::ScopedAStatus IdentityCredential::setVerificationToken(
265 const VerificationToken& verificationToken) {
266 verificationToken_ = verificationToken;
267 return ndk::ScopedAStatus::ok();
268}
269
David Zeuthen81603152020-02-11 22:04:24 -0500270ndk::ScopedAStatus IdentityCredential::startRetrieval(
271 const vector<SecureAccessControlProfile>& accessControlProfiles,
Jooyung Han17be89b2020-02-21 21:17:06 +0900272 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
273 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
274 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
David Zeuthen34abaae2020-10-26 20:26:36 -0400275 std::unique_ptr<cppbor::Item> sessionTranscriptItem;
David Zeuthen81603152020-02-11 22:04:24 -0500276 if (sessionTranscript.size() > 0) {
277 auto [item, _, message] = cppbor::parse(sessionTranscript);
278 if (item == nullptr) {
279 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
280 IIdentityCredentialStore::STATUS_INVALID_DATA,
281 "SessionTranscript contains invalid CBOR"));
282 }
David Zeuthen34abaae2020-10-26 20:26:36 -0400283 sessionTranscriptItem = std::move(item);
David Zeuthen81603152020-02-11 22:04:24 -0500284 }
285 if (numStartRetrievalCalls_ > 0) {
286 if (sessionTranscript_ != sessionTranscript) {
287 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
288 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
289 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
290 }
291 }
292 sessionTranscript_ = sessionTranscript;
293
294 // If there is a signature, validate that it was made with the top-most key in the
295 // certificate chain embedded in the COSE_Sign1 structure.
296 optional<vector<uint8_t>> readerCertificateChain;
297 if (readerSignature.size() > 0) {
298 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
299 if (!readerCertificateChain) {
300 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
301 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
302 "Unable to get reader certificate chain from COSE_Sign1"));
303 }
304
305 if (!support::certificateChainValidate(readerCertificateChain.value())) {
306 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
307 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
308 "Error validating reader certificate chain"));
309 }
310
311 optional<vector<uint8_t>> readerPublicKey =
312 support::certificateChainGetTopMostKey(readerCertificateChain.value());
313 if (!readerPublicKey) {
314 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
315 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
316 "Unable to get public key from reader certificate chain"));
317 }
318
319 const vector<uint8_t>& itemsRequestBytes = itemsRequest;
David Zeuthen2e4533e2020-06-20 17:04:41 -0400320 vector<uint8_t> encodedReaderAuthentication =
321 cppbor::Array()
322 .add("ReaderAuthentication")
David Zeuthen34abaae2020-10-26 20:26:36 -0400323 .add(std::move(sessionTranscriptItem))
David Zeuthen2e4533e2020-06-20 17:04:41 -0400324 .add(cppbor::Semantic(24, itemsRequestBytes))
325 .encode();
326 vector<uint8_t> encodedReaderAuthenticationBytes =
327 cppbor::Semantic(24, encodedReaderAuthentication).encode();
David Zeuthen81603152020-02-11 22:04:24 -0500328 if (!support::coseCheckEcDsaSignature(readerSignature,
David Zeuthen2e4533e2020-06-20 17:04:41 -0400329 encodedReaderAuthenticationBytes, // detached content
David Zeuthen81603152020-02-11 22:04:24 -0500330 readerPublicKey.value())) {
331 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
332 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
333 "readerSignature check failed"));
334 }
335 }
336
337 // Here's where we would validate the passed-in |authToken| to assure ourselves
338 // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
339 //
340 // However this involves calculating the MAC. However this requires access
341 // to the key needed to a pre-shared key which we don't have...
342 //
343
344 // To prevent replay-attacks, we check that the public part of the ephemeral
345 // key we previously created, is present in the DeviceEngagement part of
346 // SessionTranscript as a COSE_Key, in uncompressed form.
347 //
348 // We do this by just searching for the X and Y coordinates.
349 if (sessionTranscript.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500350 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
351 if (!getXYSuccess) {
352 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
353 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
354 "Error extracting X and Y from ePub"));
355 }
356 if (sessionTranscript.size() > 0 &&
David Zeuthenef739512020-06-03 13:24:52 -0400357 !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
358 ePubX.size()) != nullptr &&
359 memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
360 ePubY.size()) != nullptr)) {
David Zeuthen81603152020-02-11 22:04:24 -0500361 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
362 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
363 "Did not find ephemeral public key's X and Y coordinates in "
364 "SessionTranscript (make sure leading zeroes are not used)"));
365 }
366 }
367
368 // itemsRequest: If non-empty, contains request data that may be signed by the
369 // reader. The content can be defined in the way appropriate for the
370 // credential, but there are three requirements that must be met to work with
371 // this HAL:
372 if (itemsRequest.size() > 0) {
373 // 1. The content must be a CBOR-encoded structure.
374 auto [item, _, message] = cppbor::parse(itemsRequest);
375 if (item == nullptr) {
376 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
377 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
378 "Error decoding CBOR in itemsRequest"));
379 }
380
381 // 2. The CBOR structure must be a map.
382 const cppbor::Map* map = item->asMap();
383 if (map == nullptr) {
384 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
385 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
386 "itemsRequest is not a CBOR map"));
387 }
388
389 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
390 // the example below.
391 //
392 // NameSpaces = {
393 // + NameSpace => DataElements ; Requested data elements for each NameSpace
394 // }
395 //
396 // NameSpace = tstr
397 //
398 // DataElements = {
399 // + DataElement => IntentToRetain
400 // }
401 //
402 // DataElement = tstr
403 // IntentToRetain = bool
404 //
405 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
406 // through 3.:
407 //
408 // {
409 // 'docType' : 'org.iso.18013-5.2019',
410 // 'nameSpaces' : {
411 // 'org.iso.18013-5.2019' : {
412 // 'Last name' : false,
413 // 'Birth date' : false,
414 // 'First name' : false,
415 // 'Home address' : true
416 // },
417 // 'org.aamva.iso.18013-5.2019' : {
418 // 'Real Id' : false
419 // }
420 // }
421 // }
422 //
423 const cppbor::Map* nsMap = nullptr;
424 for (size_t n = 0; n < map->size(); n++) {
425 const auto& [keyItem, valueItem] = (*map)[n];
426 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
427 valueItem->type() == cppbor::MAP) {
428 nsMap = valueItem->asMap();
429 break;
430 }
431 }
432 if (nsMap == nullptr) {
433 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
434 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
435 "No nameSpaces map in top-most map"));
436 }
437
438 for (size_t n = 0; n < nsMap->size(); n++) {
439 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
440 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
441 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
442 if (nsKey == nullptr || nsInnerMap == nullptr) {
443 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
444 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
445 "Type mismatch in nameSpaces map"));
446 }
447 string requestedNamespace = nsKey->value();
David Zeuthen28edb102020-04-28 18:54:55 -0400448 set<string> requestedKeys;
David Zeuthen81603152020-02-11 22:04:24 -0500449 for (size_t m = 0; m < nsInnerMap->size(); m++) {
450 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
451 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
452 const cppbor::Simple* simple = innerMapValueItem->asSimple();
453 const cppbor::Bool* intentToRetainItem =
454 (simple != nullptr) ? simple->asBool() : nullptr;
455 if (nameItem == nullptr || intentToRetainItem == nullptr) {
456 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
457 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
458 "Type mismatch in value in nameSpaces map"));
459 }
David Zeuthen28edb102020-04-28 18:54:55 -0400460 requestedKeys.insert(nameItem->value());
David Zeuthen81603152020-02-11 22:04:24 -0500461 }
462 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
463 }
464 }
465
David Zeuthen28edb102020-04-28 18:54:55 -0400466 // Validate all the access control profiles in the requestData.
David Zeuthenef739512020-06-03 13:24:52 -0400467 bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
David Zeuthen81603152020-02-11 22:04:24 -0500468 for (const auto& profile : accessControlProfiles) {
469 if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
David Zeuthenef739512020-06-03 13:24:52 -0400470 LOG(ERROR) << "Error checking MAC for profile";
David Zeuthen81603152020-02-11 22:04:24 -0500471 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
472 IIdentityCredentialStore::STATUS_INVALID_DATA,
473 "Error checking MAC for profile"));
474 }
475 int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
476 if (profile.userAuthenticationRequired) {
David Zeuthena8ed82c2020-05-08 10:03:28 -0400477 if (!haveAuthToken ||
478 !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
David Zeuthen81603152020-02-11 22:04:24 -0500479 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
David Zeuthen28edb102020-04-28 18:54:55 -0400499 // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
500 expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
501
David Zeuthen81603152020-02-11 22:04:24 -0500502 numStartRetrievalCalls_ += 1;
503 return ndk::ScopedAStatus::ok();
504}
505
David Zeuthen28edb102020-04-28 18:54:55 -0400506size_t cborNumBytesForLength(size_t length) {
507 if (length < 24) {
508 return 0;
509 } else if (length <= 0xff) {
510 return 1;
511 } else if (length <= 0xffff) {
512 return 2;
513 } else if (length <= 0xffffffff) {
514 return 4;
515 }
516 return 8;
517}
518
519size_t cborNumBytesForTstr(const string& value) {
520 return 1 + cborNumBytesForLength(value.size()) + value.size();
521}
522
523size_t IdentityCredential::calcDeviceNameSpacesSize() {
524 /*
525 * This is how DeviceNameSpaces is defined:
526 *
527 * DeviceNameSpaces = {
528 * * NameSpace => DeviceSignedItems
529 * }
530 * DeviceSignedItems = {
531 * + DataItemName => DataItemValue
532 * }
533 *
534 * Namespace = tstr
535 * DataItemName = tstr
536 * DataItemValue = any
537 *
538 * This function will calculate its length using knowledge of how CBOR is
539 * encoded.
540 */
541 size_t ret = 0;
542 size_t numNamespacesWithValues = 0;
543 for (const RequestNamespace& rns : requestNamespaces_) {
544 vector<RequestDataItem> itemsToInclude;
545
546 for (const RequestDataItem& rdi : rns.items) {
547 // If we have a CBOR request message, skip if item isn't in it
548 if (itemsRequest_.size() > 0) {
549 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
550 if (it == requestedNameSpacesAndNames_.end()) {
551 continue;
552 }
553 const set<string>& dataItemNames = it->second;
554 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
555 continue;
556 }
557 }
558
559 // Access is granted if at least one of the profiles grants access.
560 //
561 // If an item is configured without any profiles, access is denied.
562 //
563 bool authorized = false;
564 for (auto id : rdi.accessControlProfileIds) {
565 auto it = profileIdToAccessCheckResult_.find(id);
566 if (it != profileIdToAccessCheckResult_.end()) {
567 int accessControlForProfile = it->second;
568 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
569 authorized = true;
570 break;
571 }
572 }
573 }
574 if (!authorized) {
575 continue;
576 }
577
578 itemsToInclude.push_back(rdi);
579 }
580
581 // If no entries are to be in the namespace, we don't include it...
582 if (itemsToInclude.size() == 0) {
583 continue;
584 }
585
586 // Key: NameSpace
587 ret += cborNumBytesForTstr(rns.namespaceName);
588
589 // Value: Open the DeviceSignedItems map
590 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
591
592 for (const RequestDataItem& item : itemsToInclude) {
593 // Key: DataItemName
594 ret += cborNumBytesForTstr(item.name);
595
596 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
597 // that.
598 ret += item.size;
599 }
600
601 numNamespacesWithValues++;
602 }
603
604 // Now that we now the nunber of namespaces with values, we know how many
605 // bytes the DeviceNamespaces map in the beginning is going to take up.
606 ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
607
608 return ret;
609}
610
David Zeuthen81603152020-02-11 22:04:24 -0500611ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
612 const string& nameSpace, const string& name, int32_t entrySize,
613 const vector<int32_t>& accessControlProfileIds) {
614 if (name.empty()) {
615 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
616 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
617 }
618 if (nameSpace.empty()) {
619 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
620 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
621 }
622
623 if (requestCountsRemaining_.size() == 0) {
624 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
625 IIdentityCredentialStore::STATUS_INVALID_DATA,
626 "No more name spaces left to go through"));
627 }
628
629 if (currentNameSpace_ == "") {
630 // First call.
631 currentNameSpace_ = nameSpace;
632 }
633
634 if (nameSpace == currentNameSpace_) {
635 // Same namespace.
636 if (requestCountsRemaining_[0] == 0) {
637 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
638 IIdentityCredentialStore::STATUS_INVALID_DATA,
639 "No more entries to be retrieved in current name space"));
640 }
641 requestCountsRemaining_[0] -= 1;
642 } else {
643 // New namespace.
644 if (requestCountsRemaining_[0] != 0) {
645 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
646 IIdentityCredentialStore::STATUS_INVALID_DATA,
647 "Moved to new name space but one or more entries need to be retrieved "
648 "in current name space"));
649 }
650 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
651 deviceNameSpacesMap_.add(currentNameSpace_,
652 std::move(currentNameSpaceDeviceNameSpacesMap_));
653 }
654 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
655
656 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
657 currentNameSpace_ = nameSpace;
658 }
659
660 // It's permissible to have an empty itemsRequest... but if non-empty you can
661 // only request what was specified in said itemsRequest. Enforce that.
662 if (itemsRequest_.size() > 0) {
663 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
664 if (it == requestedNameSpacesAndNames_.end()) {
665 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
666 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
667 "Name space was not requested in startRetrieval"));
668 }
David Zeuthen28edb102020-04-28 18:54:55 -0400669 const set<string>& dataItemNames = it->second;
670 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500671 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
672 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
673 "Data item name in name space was not requested in startRetrieval"));
674 }
675 }
676
677 // Enforce access control.
678 //
679 // Access is granted if at least one of the profiles grants access.
680 //
681 // If an item is configured without any profiles, access is denied.
682 //
683 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
684 for (auto id : accessControlProfileIds) {
685 auto search = profileIdToAccessCheckResult_.find(id);
686 if (search == profileIdToAccessCheckResult_.end()) {
687 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
688 IIdentityCredentialStore::STATUS_INVALID_DATA,
689 "Requested entry with unvalidated profile id"));
690 }
691 int accessControlForProfile = search->second;
692 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
693 accessControl = IIdentityCredentialStore::STATUS_OK;
694 break;
695 }
696 accessControl = accessControlForProfile;
697 }
698 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
699 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
700 int(accessControl), "Access control check failed"));
701 }
702
703 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
704
705 currentName_ = name;
706 entryRemainingBytes_ = entrySize;
707 entryValue_.resize(0);
708
709 return ndk::ScopedAStatus::ok();
710}
711
Jooyung Han17be89b2020-02-21 21:17:06 +0900712ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
713 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500714 optional<vector<uint8_t>> content =
715 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
716 if (!content) {
717 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
718 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
719 }
720
721 size_t chunkSize = content.value().size();
722
723 if (chunkSize > entryRemainingBytes_) {
724 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
725 << " is bigger than remaining space of size " << entryRemainingBytes_;
726 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
727 IIdentityCredentialStore::STATUS_INVALID_DATA,
728 "Retrieved chunk is bigger than remaining space"));
729 }
730
731 entryRemainingBytes_ -= chunkSize;
732 if (entryRemainingBytes_ > 0) {
733 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
734 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
735 IIdentityCredentialStore::STATUS_INVALID_DATA,
736 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
737 }
738 }
739
740 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
741
742 if (entryRemainingBytes_ == 0) {
743 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
744 if (entryValueItem == nullptr) {
745 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
746 IIdentityCredentialStore::STATUS_INVALID_DATA,
747 "Retrieved data which is invalid CBOR"));
748 }
749 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
750 }
751
Jooyung Han17be89b2020-02-21 21:17:06 +0900752 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500753 return ndk::ScopedAStatus::ok();
754}
755
Jooyung Han17be89b2020-02-21 21:17:06 +0900756ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
757 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500758 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
759 deviceNameSpacesMap_.add(currentNameSpace_,
760 std::move(currentNameSpaceDeviceNameSpacesMap_));
761 }
762 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
763
David Zeuthen28edb102020-04-28 18:54:55 -0400764 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
765 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
766 << "was expecting " << expectedDeviceNameSpacesSize_;
767 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
768 IIdentityCredentialStore::STATUS_INVALID_DATA,
769 StringPrintf(
770 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
771 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
772 .c_str()));
773 }
774
David Zeuthen81603152020-02-11 22:04:24 -0500775 // If there's no signing key or no sessionTranscript or no reader ephemeral
776 // public key, we return the empty MAC.
777 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500778 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
779 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500780 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
781 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500782 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500783 if (!signingKey) {
784 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
785 IIdentityCredentialStore::STATUS_INVALID_DATA,
786 "Error decrypting signingKeyBlob"));
787 }
788
David Zeuthen2e4533e2020-06-20 17:04:41 -0400789 vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
David Zeuthen34abaae2020-10-26 20:26:36 -0400790 optional<vector<uint8_t>> eMacKey =
791 support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
792 if (!eMacKey) {
David Zeuthen81603152020-02-11 22:04:24 -0500793 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
David Zeuthen34abaae2020-10-26 20:26:36 -0400794 IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
David Zeuthen81603152020-02-11 22:04:24 -0500795 }
David Zeuthen34abaae2020-10-26 20:26:36 -0400796 mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
797 eMacKey.value());
David Zeuthen81603152020-02-11 22:04:24 -0500798 if (!mac) {
799 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
800 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
801 }
802 }
803
Jooyung Han17be89b2020-02-21 21:17:06 +0900804 *outMac = mac.value_or(vector<uint8_t>({}));
805 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500806 return ndk::ScopedAStatus::ok();
807}
808
809ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900810 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen34abaae2020-10-26 20:26:36 -0400811 string serialDecimal = "1";
812 string issuer = "Android Identity Credential Key";
813 string subject = "Android Identity Credential Authentication Key";
David Zeuthen81603152020-02-11 22:04:24 -0500814 time_t validityNotBefore = time(nullptr);
815 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
816
817 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
818 if (!signingKeyPKCS8) {
819 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
820 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
821 }
822
823 optional<vector<uint8_t>> signingPublicKey =
824 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
825 if (!signingPublicKey) {
826 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
827 IIdentityCredentialStore::STATUS_FAILED,
828 "Error getting public part of signingKey"));
829 }
830
831 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
832 if (!signingKey) {
833 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
834 IIdentityCredentialStore::STATUS_FAILED,
835 "Error getting private part of signingKey"));
836 }
837
838 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
839 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
840 validityNotBefore, validityNotAfter);
841 if (!certificate) {
842 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
843 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
844 }
845
846 optional<vector<uint8_t>> nonce = support::getRandom(12);
847 if (!nonce) {
848 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
849 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
850 }
851 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
852 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
853 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
854 if (!encryptedSigningKey) {
855 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
856 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
857 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900858 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500859 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900860 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500861 return ndk::ScopedAStatus::ok();
862}
863
864} // namespace aidl::android::hardware::identity