blob: 4e9e0e698c4a8fb2594b7ed2aaed78e73b2e0700 [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;
David Zeuthenef739512020-06-03 13:24:52 -0400167 authChallenge_ = challenge;
David Zeuthen81603152020-02-11 22:04:24 -0500168 return ndk::ScopedAStatus::ok();
169}
170
171// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
172// ahead of time.
173bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
174 const vector<uint8_t>& readerCertificateChain) {
Jooyung Han17be89b2020-02-21 21:17:06 +0900175 optional<vector<uint8_t>> acpPubKey =
176 support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
David Zeuthen81603152020-02-11 22:04:24 -0500177 if (!acpPubKey) {
178 LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
179 return false;
180 }
181
182 optional<vector<vector<uint8_t>>> certificatesInChain =
183 support::certificateChainSplit(readerCertificateChain);
184 if (!certificatesInChain) {
185 LOG(ERROR) << "Error splitting readerCertificateChain";
186 return false;
187 }
188 for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
189 optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
190 if (!certPubKey) {
191 LOG(ERROR)
192 << "Error extracting public key from certificate in chain presented by reader";
193 return false;
194 }
195 if (acpPubKey.value() == certPubKey.value()) {
196 return true;
197 }
198 }
199 return false;
200}
201
David Zeuthen81603152020-02-11 22:04:24 -0500202bool checkUserAuthentication(const SecureAccessControlProfile& profile,
David Zeuthena8ed82c2020-05-08 10:03:28 -0400203 const VerificationToken& verificationToken,
David Zeuthen81603152020-02-11 22:04:24 -0500204 const HardwareAuthToken& authToken, uint64_t authChallenge) {
205 if (profile.secureUserId != authToken.userId) {
206 LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
207 << ") differs from userId in authToken (" << authToken.userId << ")";
208 return false;
209 }
210
David Zeuthena8ed82c2020-05-08 10:03:28 -0400211 if (verificationToken.timestamp.milliSeconds == 0) {
212 LOG(ERROR) << "VerificationToken is not set";
213 return false;
214 }
215 if (authToken.timestamp.milliSeconds == 0) {
216 LOG(ERROR) << "AuthToken is not set";
217 return false;
218 }
219
David Zeuthen81603152020-02-11 22:04:24 -0500220 if (profile.timeoutMillis == 0) {
221 if (authToken.challenge == 0) {
222 LOG(ERROR) << "No challenge in authToken";
223 return false;
224 }
225
226 if (authToken.challenge != int64_t(authChallenge)) {
David Zeuthenef739512020-06-03 13:24:52 -0400227 LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
228 << "doesn't match the challenge we created (" << authChallenge << ")";
David Zeuthen81603152020-02-11 22:04:24 -0500229 return false;
230 }
231 return true;
232 }
233
David Zeuthena8ed82c2020-05-08 10:03:28 -0400234 // Timeout-based user auth follows. The verification token conveys what the
235 // time is right now in the environment which generated the auth token. This
236 // is what makes it possible to do timeout-based checks.
David Zeuthen81603152020-02-11 22:04:24 -0500237 //
David Zeuthena8ed82c2020-05-08 10:03:28 -0400238 const Timestamp now = verificationToken.timestamp;
David Zeuthen81603152020-02-11 22:04:24 -0500239 if (authToken.timestamp.milliSeconds > now.milliSeconds) {
240 LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
241 << ") is in the future (now: " << now.milliSeconds << ")";
242 return false;
243 }
244 if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
245 LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
246 << profile.timeoutMillis << " = "
247 << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
248 << ") is in the past (now: " << now.milliSeconds << ")";
249 return false;
250 }
251 return true;
252}
253
David Zeuthen28edb102020-04-28 18:54:55 -0400254ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
255 const vector<RequestNamespace>& requestNamespaces) {
256 requestNamespaces_ = requestNamespaces;
257 return ndk::ScopedAStatus::ok();
258}
259
David Zeuthena8ed82c2020-05-08 10:03:28 -0400260ndk::ScopedAStatus IdentityCredential::setVerificationToken(
261 const VerificationToken& verificationToken) {
262 verificationToken_ = verificationToken;
263 return ndk::ScopedAStatus::ok();
264}
265
David Zeuthen81603152020-02-11 22:04:24 -0500266ndk::ScopedAStatus IdentityCredential::startRetrieval(
267 const vector<SecureAccessControlProfile>& accessControlProfiles,
Jooyung Han17be89b2020-02-21 21:17:06 +0900268 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
269 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
270 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
David Zeuthen81603152020-02-11 22:04:24 -0500271 if (sessionTranscript.size() > 0) {
272 auto [item, _, message] = cppbor::parse(sessionTranscript);
273 if (item == nullptr) {
274 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
275 IIdentityCredentialStore::STATUS_INVALID_DATA,
276 "SessionTranscript contains invalid CBOR"));
277 }
278 sessionTranscriptItem_ = std::move(item);
279 }
280 if (numStartRetrievalCalls_ > 0) {
281 if (sessionTranscript_ != sessionTranscript) {
282 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
283 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
284 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
285 }
286 }
287 sessionTranscript_ = sessionTranscript;
288
289 // If there is a signature, validate that it was made with the top-most key in the
290 // certificate chain embedded in the COSE_Sign1 structure.
291 optional<vector<uint8_t>> readerCertificateChain;
292 if (readerSignature.size() > 0) {
293 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
294 if (!readerCertificateChain) {
295 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
296 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
297 "Unable to get reader certificate chain from COSE_Sign1"));
298 }
299
300 if (!support::certificateChainValidate(readerCertificateChain.value())) {
301 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
302 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
303 "Error validating reader certificate chain"));
304 }
305
306 optional<vector<uint8_t>> readerPublicKey =
307 support::certificateChainGetTopMostKey(readerCertificateChain.value());
308 if (!readerPublicKey) {
309 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
310 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
311 "Unable to get public key from reader certificate chain"));
312 }
313
314 const vector<uint8_t>& itemsRequestBytes = itemsRequest;
315 vector<uint8_t> dataThatWasSigned = cppbor::Array()
316 .add("ReaderAuthentication")
317 .add(sessionTranscriptItem_->clone())
318 .add(cppbor::Semantic(24, itemsRequestBytes))
319 .encode();
320 if (!support::coseCheckEcDsaSignature(readerSignature,
321 dataThatWasSigned, // detached content
322 readerPublicKey.value())) {
323 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
324 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
325 "readerSignature check failed"));
326 }
327 }
328
329 // Here's where we would validate the passed-in |authToken| to assure ourselves
330 // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
331 //
332 // However this involves calculating the MAC. However this requires access
333 // to the key needed to a pre-shared key which we don't have...
334 //
335
336 // To prevent replay-attacks, we check that the public part of the ephemeral
337 // key we previously created, is present in the DeviceEngagement part of
338 // SessionTranscript as a COSE_Key, in uncompressed form.
339 //
340 // We do this by just searching for the X and Y coordinates.
341 if (sessionTranscript.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500342 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
343 if (!getXYSuccess) {
344 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
345 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
346 "Error extracting X and Y from ePub"));
347 }
348 if (sessionTranscript.size() > 0 &&
David Zeuthenef739512020-06-03 13:24:52 -0400349 !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
350 ePubX.size()) != nullptr &&
351 memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
352 ePubY.size()) != nullptr)) {
David Zeuthen81603152020-02-11 22:04:24 -0500353 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
354 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
355 "Did not find ephemeral public key's X and Y coordinates in "
356 "SessionTranscript (make sure leading zeroes are not used)"));
357 }
358 }
359
360 // itemsRequest: If non-empty, contains request data that may be signed by the
361 // reader. The content can be defined in the way appropriate for the
362 // credential, but there are three requirements that must be met to work with
363 // this HAL:
364 if (itemsRequest.size() > 0) {
365 // 1. The content must be a CBOR-encoded structure.
366 auto [item, _, message] = cppbor::parse(itemsRequest);
367 if (item == nullptr) {
368 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
369 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
370 "Error decoding CBOR in itemsRequest"));
371 }
372
373 // 2. The CBOR structure must be a map.
374 const cppbor::Map* map = item->asMap();
375 if (map == nullptr) {
376 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
377 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
378 "itemsRequest is not a CBOR map"));
379 }
380
381 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
382 // the example below.
383 //
384 // NameSpaces = {
385 // + NameSpace => DataElements ; Requested data elements for each NameSpace
386 // }
387 //
388 // NameSpace = tstr
389 //
390 // DataElements = {
391 // + DataElement => IntentToRetain
392 // }
393 //
394 // DataElement = tstr
395 // IntentToRetain = bool
396 //
397 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
398 // through 3.:
399 //
400 // {
401 // 'docType' : 'org.iso.18013-5.2019',
402 // 'nameSpaces' : {
403 // 'org.iso.18013-5.2019' : {
404 // 'Last name' : false,
405 // 'Birth date' : false,
406 // 'First name' : false,
407 // 'Home address' : true
408 // },
409 // 'org.aamva.iso.18013-5.2019' : {
410 // 'Real Id' : false
411 // }
412 // }
413 // }
414 //
415 const cppbor::Map* nsMap = nullptr;
416 for (size_t n = 0; n < map->size(); n++) {
417 const auto& [keyItem, valueItem] = (*map)[n];
418 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
419 valueItem->type() == cppbor::MAP) {
420 nsMap = valueItem->asMap();
421 break;
422 }
423 }
424 if (nsMap == nullptr) {
425 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
426 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
427 "No nameSpaces map in top-most map"));
428 }
429
430 for (size_t n = 0; n < nsMap->size(); n++) {
431 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
432 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
433 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
434 if (nsKey == nullptr || nsInnerMap == nullptr) {
435 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
436 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
437 "Type mismatch in nameSpaces map"));
438 }
439 string requestedNamespace = nsKey->value();
David Zeuthen28edb102020-04-28 18:54:55 -0400440 set<string> requestedKeys;
David Zeuthen81603152020-02-11 22:04:24 -0500441 for (size_t m = 0; m < nsInnerMap->size(); m++) {
442 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
443 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
444 const cppbor::Simple* simple = innerMapValueItem->asSimple();
445 const cppbor::Bool* intentToRetainItem =
446 (simple != nullptr) ? simple->asBool() : nullptr;
447 if (nameItem == nullptr || intentToRetainItem == nullptr) {
448 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
449 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
450 "Type mismatch in value in nameSpaces map"));
451 }
David Zeuthen28edb102020-04-28 18:54:55 -0400452 requestedKeys.insert(nameItem->value());
David Zeuthen81603152020-02-11 22:04:24 -0500453 }
454 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
455 }
456 }
457
David Zeuthen28edb102020-04-28 18:54:55 -0400458 // Validate all the access control profiles in the requestData.
David Zeuthenef739512020-06-03 13:24:52 -0400459 bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
David Zeuthen81603152020-02-11 22:04:24 -0500460 for (const auto& profile : accessControlProfiles) {
461 if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
David Zeuthenef739512020-06-03 13:24:52 -0400462 LOG(ERROR) << "Error checking MAC for profile";
David Zeuthen81603152020-02-11 22:04:24 -0500463 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
464 IIdentityCredentialStore::STATUS_INVALID_DATA,
465 "Error checking MAC for profile"));
466 }
467 int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
468 if (profile.userAuthenticationRequired) {
David Zeuthena8ed82c2020-05-08 10:03:28 -0400469 if (!haveAuthToken ||
470 !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
David Zeuthen81603152020-02-11 22:04:24 -0500471 accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
472 }
473 } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
474 if (!readerCertificateChain ||
475 !checkReaderAuthentication(profile, readerCertificateChain.value())) {
476 accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
477 }
478 }
479 profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
480 }
481
482 deviceNameSpacesMap_ = cppbor::Map();
483 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
484
485 requestCountsRemaining_ = requestCounts;
486 currentNameSpace_ = "";
487
488 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900489 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500490
David Zeuthen28edb102020-04-28 18:54:55 -0400491 // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
492 expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
493
David Zeuthen81603152020-02-11 22:04:24 -0500494 numStartRetrievalCalls_ += 1;
495 return ndk::ScopedAStatus::ok();
496}
497
David Zeuthen28edb102020-04-28 18:54:55 -0400498size_t cborNumBytesForLength(size_t length) {
499 if (length < 24) {
500 return 0;
501 } else if (length <= 0xff) {
502 return 1;
503 } else if (length <= 0xffff) {
504 return 2;
505 } else if (length <= 0xffffffff) {
506 return 4;
507 }
508 return 8;
509}
510
511size_t cborNumBytesForTstr(const string& value) {
512 return 1 + cborNumBytesForLength(value.size()) + value.size();
513}
514
515size_t IdentityCredential::calcDeviceNameSpacesSize() {
516 /*
517 * This is how DeviceNameSpaces is defined:
518 *
519 * DeviceNameSpaces = {
520 * * NameSpace => DeviceSignedItems
521 * }
522 * DeviceSignedItems = {
523 * + DataItemName => DataItemValue
524 * }
525 *
526 * Namespace = tstr
527 * DataItemName = tstr
528 * DataItemValue = any
529 *
530 * This function will calculate its length using knowledge of how CBOR is
531 * encoded.
532 */
533 size_t ret = 0;
534 size_t numNamespacesWithValues = 0;
535 for (const RequestNamespace& rns : requestNamespaces_) {
536 vector<RequestDataItem> itemsToInclude;
537
538 for (const RequestDataItem& rdi : rns.items) {
539 // If we have a CBOR request message, skip if item isn't in it
540 if (itemsRequest_.size() > 0) {
541 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
542 if (it == requestedNameSpacesAndNames_.end()) {
543 continue;
544 }
545 const set<string>& dataItemNames = it->second;
546 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
547 continue;
548 }
549 }
550
551 // Access is granted if at least one of the profiles grants access.
552 //
553 // If an item is configured without any profiles, access is denied.
554 //
555 bool authorized = false;
556 for (auto id : rdi.accessControlProfileIds) {
557 auto it = profileIdToAccessCheckResult_.find(id);
558 if (it != profileIdToAccessCheckResult_.end()) {
559 int accessControlForProfile = it->second;
560 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
561 authorized = true;
562 break;
563 }
564 }
565 }
566 if (!authorized) {
567 continue;
568 }
569
570 itemsToInclude.push_back(rdi);
571 }
572
573 // If no entries are to be in the namespace, we don't include it...
574 if (itemsToInclude.size() == 0) {
575 continue;
576 }
577
578 // Key: NameSpace
579 ret += cborNumBytesForTstr(rns.namespaceName);
580
581 // Value: Open the DeviceSignedItems map
582 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
583
584 for (const RequestDataItem& item : itemsToInclude) {
585 // Key: DataItemName
586 ret += cborNumBytesForTstr(item.name);
587
588 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
589 // that.
590 ret += item.size;
591 }
592
593 numNamespacesWithValues++;
594 }
595
596 // Now that we now the nunber of namespaces with values, we know how many
597 // bytes the DeviceNamespaces map in the beginning is going to take up.
598 ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
599
600 return ret;
601}
602
David Zeuthen81603152020-02-11 22:04:24 -0500603ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
604 const string& nameSpace, const string& name, int32_t entrySize,
605 const vector<int32_t>& accessControlProfileIds) {
606 if (name.empty()) {
607 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
608 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
609 }
610 if (nameSpace.empty()) {
611 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
612 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
613 }
614
615 if (requestCountsRemaining_.size() == 0) {
616 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
617 IIdentityCredentialStore::STATUS_INVALID_DATA,
618 "No more name spaces left to go through"));
619 }
620
621 if (currentNameSpace_ == "") {
622 // First call.
623 currentNameSpace_ = nameSpace;
624 }
625
626 if (nameSpace == currentNameSpace_) {
627 // Same namespace.
628 if (requestCountsRemaining_[0] == 0) {
629 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
630 IIdentityCredentialStore::STATUS_INVALID_DATA,
631 "No more entries to be retrieved in current name space"));
632 }
633 requestCountsRemaining_[0] -= 1;
634 } else {
635 // New namespace.
636 if (requestCountsRemaining_[0] != 0) {
637 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
638 IIdentityCredentialStore::STATUS_INVALID_DATA,
639 "Moved to new name space but one or more entries need to be retrieved "
640 "in current name space"));
641 }
642 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
643 deviceNameSpacesMap_.add(currentNameSpace_,
644 std::move(currentNameSpaceDeviceNameSpacesMap_));
645 }
646 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
647
648 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
649 currentNameSpace_ = nameSpace;
650 }
651
652 // It's permissible to have an empty itemsRequest... but if non-empty you can
653 // only request what was specified in said itemsRequest. Enforce that.
654 if (itemsRequest_.size() > 0) {
655 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
656 if (it == requestedNameSpacesAndNames_.end()) {
657 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
658 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
659 "Name space was not requested in startRetrieval"));
660 }
David Zeuthen28edb102020-04-28 18:54:55 -0400661 const set<string>& dataItemNames = it->second;
662 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500663 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
664 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
665 "Data item name in name space was not requested in startRetrieval"));
666 }
667 }
668
669 // Enforce access control.
670 //
671 // Access is granted if at least one of the profiles grants access.
672 //
673 // If an item is configured without any profiles, access is denied.
674 //
675 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
676 for (auto id : accessControlProfileIds) {
677 auto search = profileIdToAccessCheckResult_.find(id);
678 if (search == profileIdToAccessCheckResult_.end()) {
679 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
680 IIdentityCredentialStore::STATUS_INVALID_DATA,
681 "Requested entry with unvalidated profile id"));
682 }
683 int accessControlForProfile = search->second;
684 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
685 accessControl = IIdentityCredentialStore::STATUS_OK;
686 break;
687 }
688 accessControl = accessControlForProfile;
689 }
690 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
691 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
692 int(accessControl), "Access control check failed"));
693 }
694
695 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
696
697 currentName_ = name;
698 entryRemainingBytes_ = entrySize;
699 entryValue_.resize(0);
700
701 return ndk::ScopedAStatus::ok();
702}
703
Jooyung Han17be89b2020-02-21 21:17:06 +0900704ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
705 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500706 optional<vector<uint8_t>> content =
707 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
708 if (!content) {
709 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
710 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
711 }
712
713 size_t chunkSize = content.value().size();
714
715 if (chunkSize > entryRemainingBytes_) {
716 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
717 << " is bigger than remaining space of size " << entryRemainingBytes_;
718 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
719 IIdentityCredentialStore::STATUS_INVALID_DATA,
720 "Retrieved chunk is bigger than remaining space"));
721 }
722
723 entryRemainingBytes_ -= chunkSize;
724 if (entryRemainingBytes_ > 0) {
725 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
726 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
727 IIdentityCredentialStore::STATUS_INVALID_DATA,
728 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
729 }
730 }
731
732 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
733
734 if (entryRemainingBytes_ == 0) {
735 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
736 if (entryValueItem == nullptr) {
737 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
738 IIdentityCredentialStore::STATUS_INVALID_DATA,
739 "Retrieved data which is invalid CBOR"));
740 }
741 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
742 }
743
Jooyung Han17be89b2020-02-21 21:17:06 +0900744 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500745 return ndk::ScopedAStatus::ok();
746}
747
Jooyung Han17be89b2020-02-21 21:17:06 +0900748ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
749 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500750 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
751 deviceNameSpacesMap_.add(currentNameSpace_,
752 std::move(currentNameSpaceDeviceNameSpacesMap_));
753 }
754 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
755
David Zeuthen28edb102020-04-28 18:54:55 -0400756 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
757 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
758 << "was expecting " << expectedDeviceNameSpacesSize_;
759 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
760 IIdentityCredentialStore::STATUS_INVALID_DATA,
761 StringPrintf(
762 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
763 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
764 .c_str()));
765 }
766
David Zeuthen81603152020-02-11 22:04:24 -0500767 // If there's no signing key or no sessionTranscript or no reader ephemeral
768 // public key, we return the empty MAC.
769 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500770 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
771 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500772 cppbor::Array array;
773 array.add("DeviceAuthentication");
774 array.add(sessionTranscriptItem_->clone());
775 array.add(docType_);
776 array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
777 vector<uint8_t> encodedDeviceAuthentication = array.encode();
778
779 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
780 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500781 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500782 if (!signingKey) {
783 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
784 IIdentityCredentialStore::STATUS_INVALID_DATA,
785 "Error decrypting signingKeyBlob"));
786 }
787
788 optional<vector<uint8_t>> sharedSecret =
789 support::ecdh(readerPublicKey_, signingKey.value());
790 if (!sharedSecret) {
791 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
792 IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
793 }
794
795 vector<uint8_t> salt = {0x00};
796 vector<uint8_t> info = {};
797 optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
798 if (!derivedKey) {
799 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
800 IIdentityCredentialStore::STATUS_FAILED,
801 "Error deriving key from shared secret"));
802 }
803
804 mac = support::coseMac0(derivedKey.value(), {}, // payload
805 encodedDeviceAuthentication); // additionalData
806 if (!mac) {
807 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
808 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
809 }
810 }
811
Jooyung Han17be89b2020-02-21 21:17:06 +0900812 *outMac = mac.value_or(vector<uint8_t>({}));
813 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500814 return ndk::ScopedAStatus::ok();
815}
816
817ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900818 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen81603152020-02-11 22:04:24 -0500819 string serialDecimal = "0"; // TODO: set serial to something unique
820 string issuer = "Android Open Source Project";
821 string subject = "Android IdentityCredential Reference Implementation";
822 time_t validityNotBefore = time(nullptr);
823 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
824
825 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
826 if (!signingKeyPKCS8) {
827 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
828 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
829 }
830
831 optional<vector<uint8_t>> signingPublicKey =
832 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
833 if (!signingPublicKey) {
834 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
835 IIdentityCredentialStore::STATUS_FAILED,
836 "Error getting public part of signingKey"));
837 }
838
839 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
840 if (!signingKey) {
841 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
842 IIdentityCredentialStore::STATUS_FAILED,
843 "Error getting private part of signingKey"));
844 }
845
846 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
847 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
848 validityNotBefore, validityNotAfter);
849 if (!certificate) {
850 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
851 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
852 }
853
854 optional<vector<uint8_t>> nonce = support::getRandom(12);
855 if (!nonce) {
856 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
857 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
858 }
859 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
860 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
861 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
862 if (!encryptedSigningKey) {
863 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
864 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
865 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900866 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500867 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900868 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500869 return ndk::ScopedAStatus::ok();
870}
871
872} // namespace aidl::android::hardware::identity