blob: 87d9a935de81b958d9f3b17d2a1c3a826984a1a2 [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 Zeuthen81603152020-02-11 22:04:24 -0500275 if (sessionTranscript.size() > 0) {
276 auto [item, _, message] = cppbor::parse(sessionTranscript);
277 if (item == nullptr) {
278 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
279 IIdentityCredentialStore::STATUS_INVALID_DATA,
280 "SessionTranscript contains invalid CBOR"));
281 }
282 sessionTranscriptItem_ = std::move(item);
283 }
284 if (numStartRetrievalCalls_ > 0) {
285 if (sessionTranscript_ != sessionTranscript) {
286 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
287 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
288 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
289 }
290 }
291 sessionTranscript_ = sessionTranscript;
292
293 // If there is a signature, validate that it was made with the top-most key in the
294 // certificate chain embedded in the COSE_Sign1 structure.
295 optional<vector<uint8_t>> readerCertificateChain;
296 if (readerSignature.size() > 0) {
297 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
298 if (!readerCertificateChain) {
299 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
300 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
301 "Unable to get reader certificate chain from COSE_Sign1"));
302 }
303
304 if (!support::certificateChainValidate(readerCertificateChain.value())) {
305 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
306 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
307 "Error validating reader certificate chain"));
308 }
309
310 optional<vector<uint8_t>> readerPublicKey =
311 support::certificateChainGetTopMostKey(readerCertificateChain.value());
312 if (!readerPublicKey) {
313 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
314 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
315 "Unable to get public key from reader certificate chain"));
316 }
317
318 const vector<uint8_t>& itemsRequestBytes = itemsRequest;
David Zeuthen2e4533e2020-06-20 17:04:41 -0400319 vector<uint8_t> encodedReaderAuthentication =
320 cppbor::Array()
321 .add("ReaderAuthentication")
322 .add(sessionTranscriptItem_->clone())
323 .add(cppbor::Semantic(24, itemsRequestBytes))
324 .encode();
325 vector<uint8_t> encodedReaderAuthenticationBytes =
326 cppbor::Semantic(24, encodedReaderAuthentication).encode();
David Zeuthen81603152020-02-11 22:04:24 -0500327 if (!support::coseCheckEcDsaSignature(readerSignature,
David Zeuthen2e4533e2020-06-20 17:04:41 -0400328 encodedReaderAuthenticationBytes, // detached content
David Zeuthen81603152020-02-11 22:04:24 -0500329 readerPublicKey.value())) {
330 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
331 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
332 "readerSignature check failed"));
333 }
334 }
335
336 // Here's where we would validate the passed-in |authToken| to assure ourselves
337 // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
338 //
339 // However this involves calculating the MAC. However this requires access
340 // to the key needed to a pre-shared key which we don't have...
341 //
342
343 // To prevent replay-attacks, we check that the public part of the ephemeral
344 // key we previously created, is present in the DeviceEngagement part of
345 // SessionTranscript as a COSE_Key, in uncompressed form.
346 //
347 // We do this by just searching for the X and Y coordinates.
348 if (sessionTranscript.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500349 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
350 if (!getXYSuccess) {
351 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
352 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
353 "Error extracting X and Y from ePub"));
354 }
355 if (sessionTranscript.size() > 0 &&
David Zeuthenef739512020-06-03 13:24:52 -0400356 !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
357 ePubX.size()) != nullptr &&
358 memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
359 ePubY.size()) != nullptr)) {
David Zeuthen81603152020-02-11 22:04:24 -0500360 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
361 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
362 "Did not find ephemeral public key's X and Y coordinates in "
363 "SessionTranscript (make sure leading zeroes are not used)"));
364 }
365 }
366
367 // itemsRequest: If non-empty, contains request data that may be signed by the
368 // reader. The content can be defined in the way appropriate for the
369 // credential, but there are three requirements that must be met to work with
370 // this HAL:
371 if (itemsRequest.size() > 0) {
372 // 1. The content must be a CBOR-encoded structure.
373 auto [item, _, message] = cppbor::parse(itemsRequest);
374 if (item == nullptr) {
375 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
376 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
377 "Error decoding CBOR in itemsRequest"));
378 }
379
380 // 2. The CBOR structure must be a map.
381 const cppbor::Map* map = item->asMap();
382 if (map == nullptr) {
383 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
384 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
385 "itemsRequest is not a CBOR map"));
386 }
387
388 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
389 // the example below.
390 //
391 // NameSpaces = {
392 // + NameSpace => DataElements ; Requested data elements for each NameSpace
393 // }
394 //
395 // NameSpace = tstr
396 //
397 // DataElements = {
398 // + DataElement => IntentToRetain
399 // }
400 //
401 // DataElement = tstr
402 // IntentToRetain = bool
403 //
404 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
405 // through 3.:
406 //
407 // {
408 // 'docType' : 'org.iso.18013-5.2019',
409 // 'nameSpaces' : {
410 // 'org.iso.18013-5.2019' : {
411 // 'Last name' : false,
412 // 'Birth date' : false,
413 // 'First name' : false,
414 // 'Home address' : true
415 // },
416 // 'org.aamva.iso.18013-5.2019' : {
417 // 'Real Id' : false
418 // }
419 // }
420 // }
421 //
422 const cppbor::Map* nsMap = nullptr;
423 for (size_t n = 0; n < map->size(); n++) {
424 const auto& [keyItem, valueItem] = (*map)[n];
425 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
426 valueItem->type() == cppbor::MAP) {
427 nsMap = valueItem->asMap();
428 break;
429 }
430 }
431 if (nsMap == nullptr) {
432 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
433 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
434 "No nameSpaces map in top-most map"));
435 }
436
437 for (size_t n = 0; n < nsMap->size(); n++) {
438 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
439 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
440 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
441 if (nsKey == nullptr || nsInnerMap == nullptr) {
442 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
443 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
444 "Type mismatch in nameSpaces map"));
445 }
446 string requestedNamespace = nsKey->value();
David Zeuthen28edb102020-04-28 18:54:55 -0400447 set<string> requestedKeys;
David Zeuthen81603152020-02-11 22:04:24 -0500448 for (size_t m = 0; m < nsInnerMap->size(); m++) {
449 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
450 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
451 const cppbor::Simple* simple = innerMapValueItem->asSimple();
452 const cppbor::Bool* intentToRetainItem =
453 (simple != nullptr) ? simple->asBool() : nullptr;
454 if (nameItem == nullptr || intentToRetainItem == nullptr) {
455 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
456 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
457 "Type mismatch in value in nameSpaces map"));
458 }
David Zeuthen28edb102020-04-28 18:54:55 -0400459 requestedKeys.insert(nameItem->value());
David Zeuthen81603152020-02-11 22:04:24 -0500460 }
461 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
462 }
463 }
464
David Zeuthen28edb102020-04-28 18:54:55 -0400465 // Validate all the access control profiles in the requestData.
David Zeuthenef739512020-06-03 13:24:52 -0400466 bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
David Zeuthen81603152020-02-11 22:04:24 -0500467 for (const auto& profile : accessControlProfiles) {
468 if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
David Zeuthenef739512020-06-03 13:24:52 -0400469 LOG(ERROR) << "Error checking MAC for profile";
David Zeuthen81603152020-02-11 22:04:24 -0500470 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
471 IIdentityCredentialStore::STATUS_INVALID_DATA,
472 "Error checking MAC for profile"));
473 }
474 int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
475 if (profile.userAuthenticationRequired) {
David Zeuthena8ed82c2020-05-08 10:03:28 -0400476 if (!haveAuthToken ||
477 !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
David Zeuthen81603152020-02-11 22:04:24 -0500478 accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
479 }
480 } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
481 if (!readerCertificateChain ||
482 !checkReaderAuthentication(profile, readerCertificateChain.value())) {
483 accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
484 }
485 }
486 profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
487 }
488
489 deviceNameSpacesMap_ = cppbor::Map();
490 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
491
492 requestCountsRemaining_ = requestCounts;
493 currentNameSpace_ = "";
494
495 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900496 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500497
David Zeuthen28edb102020-04-28 18:54:55 -0400498 // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
499 expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
500
David Zeuthen81603152020-02-11 22:04:24 -0500501 numStartRetrievalCalls_ += 1;
502 return ndk::ScopedAStatus::ok();
503}
504
David Zeuthen28edb102020-04-28 18:54:55 -0400505size_t cborNumBytesForLength(size_t length) {
506 if (length < 24) {
507 return 0;
508 } else if (length <= 0xff) {
509 return 1;
510 } else if (length <= 0xffff) {
511 return 2;
512 } else if (length <= 0xffffffff) {
513 return 4;
514 }
515 return 8;
516}
517
518size_t cborNumBytesForTstr(const string& value) {
519 return 1 + cborNumBytesForLength(value.size()) + value.size();
520}
521
522size_t IdentityCredential::calcDeviceNameSpacesSize() {
523 /*
524 * This is how DeviceNameSpaces is defined:
525 *
526 * DeviceNameSpaces = {
527 * * NameSpace => DeviceSignedItems
528 * }
529 * DeviceSignedItems = {
530 * + DataItemName => DataItemValue
531 * }
532 *
533 * Namespace = tstr
534 * DataItemName = tstr
535 * DataItemValue = any
536 *
537 * This function will calculate its length using knowledge of how CBOR is
538 * encoded.
539 */
540 size_t ret = 0;
541 size_t numNamespacesWithValues = 0;
542 for (const RequestNamespace& rns : requestNamespaces_) {
543 vector<RequestDataItem> itemsToInclude;
544
545 for (const RequestDataItem& rdi : rns.items) {
546 // If we have a CBOR request message, skip if item isn't in it
547 if (itemsRequest_.size() > 0) {
548 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
549 if (it == requestedNameSpacesAndNames_.end()) {
550 continue;
551 }
552 const set<string>& dataItemNames = it->second;
553 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
554 continue;
555 }
556 }
557
558 // Access is granted if at least one of the profiles grants access.
559 //
560 // If an item is configured without any profiles, access is denied.
561 //
562 bool authorized = false;
563 for (auto id : rdi.accessControlProfileIds) {
564 auto it = profileIdToAccessCheckResult_.find(id);
565 if (it != profileIdToAccessCheckResult_.end()) {
566 int accessControlForProfile = it->second;
567 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
568 authorized = true;
569 break;
570 }
571 }
572 }
573 if (!authorized) {
574 continue;
575 }
576
577 itemsToInclude.push_back(rdi);
578 }
579
580 // If no entries are to be in the namespace, we don't include it...
581 if (itemsToInclude.size() == 0) {
582 continue;
583 }
584
585 // Key: NameSpace
586 ret += cborNumBytesForTstr(rns.namespaceName);
587
588 // Value: Open the DeviceSignedItems map
589 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
590
591 for (const RequestDataItem& item : itemsToInclude) {
592 // Key: DataItemName
593 ret += cborNumBytesForTstr(item.name);
594
595 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
596 // that.
597 ret += item.size;
598 }
599
600 numNamespacesWithValues++;
601 }
602
603 // Now that we now the nunber of namespaces with values, we know how many
604 // bytes the DeviceNamespaces map in the beginning is going to take up.
605 ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
606
607 return ret;
608}
609
David Zeuthen81603152020-02-11 22:04:24 -0500610ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
611 const string& nameSpace, const string& name, int32_t entrySize,
612 const vector<int32_t>& accessControlProfileIds) {
613 if (name.empty()) {
614 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
615 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
616 }
617 if (nameSpace.empty()) {
618 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
619 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
620 }
621
622 if (requestCountsRemaining_.size() == 0) {
623 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
624 IIdentityCredentialStore::STATUS_INVALID_DATA,
625 "No more name spaces left to go through"));
626 }
627
628 if (currentNameSpace_ == "") {
629 // First call.
630 currentNameSpace_ = nameSpace;
631 }
632
633 if (nameSpace == currentNameSpace_) {
634 // Same namespace.
635 if (requestCountsRemaining_[0] == 0) {
636 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
637 IIdentityCredentialStore::STATUS_INVALID_DATA,
638 "No more entries to be retrieved in current name space"));
639 }
640 requestCountsRemaining_[0] -= 1;
641 } else {
642 // New namespace.
643 if (requestCountsRemaining_[0] != 0) {
644 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
645 IIdentityCredentialStore::STATUS_INVALID_DATA,
646 "Moved to new name space but one or more entries need to be retrieved "
647 "in current name space"));
648 }
649 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
650 deviceNameSpacesMap_.add(currentNameSpace_,
651 std::move(currentNameSpaceDeviceNameSpacesMap_));
652 }
653 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
654
655 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
656 currentNameSpace_ = nameSpace;
657 }
658
659 // It's permissible to have an empty itemsRequest... but if non-empty you can
660 // only request what was specified in said itemsRequest. Enforce that.
661 if (itemsRequest_.size() > 0) {
662 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
663 if (it == requestedNameSpacesAndNames_.end()) {
664 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
665 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
666 "Name space was not requested in startRetrieval"));
667 }
David Zeuthen28edb102020-04-28 18:54:55 -0400668 const set<string>& dataItemNames = it->second;
669 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500670 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
671 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
672 "Data item name in name space was not requested in startRetrieval"));
673 }
674 }
675
676 // Enforce access control.
677 //
678 // Access is granted if at least one of the profiles grants access.
679 //
680 // If an item is configured without any profiles, access is denied.
681 //
682 int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
683 for (auto id : accessControlProfileIds) {
684 auto search = profileIdToAccessCheckResult_.find(id);
685 if (search == profileIdToAccessCheckResult_.end()) {
686 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
687 IIdentityCredentialStore::STATUS_INVALID_DATA,
688 "Requested entry with unvalidated profile id"));
689 }
690 int accessControlForProfile = search->second;
691 if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
692 accessControl = IIdentityCredentialStore::STATUS_OK;
693 break;
694 }
695 accessControl = accessControlForProfile;
696 }
697 if (accessControl != IIdentityCredentialStore::STATUS_OK) {
698 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
699 int(accessControl), "Access control check failed"));
700 }
701
702 entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
703
704 currentName_ = name;
705 entryRemainingBytes_ = entrySize;
706 entryValue_.resize(0);
707
708 return ndk::ScopedAStatus::ok();
709}
710
Jooyung Han17be89b2020-02-21 21:17:06 +0900711ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
712 vector<uint8_t>* outContent) {
David Zeuthen81603152020-02-11 22:04:24 -0500713 optional<vector<uint8_t>> content =
714 support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
715 if (!content) {
716 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
717 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
718 }
719
720 size_t chunkSize = content.value().size();
721
722 if (chunkSize > entryRemainingBytes_) {
723 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
724 << " is bigger than remaining space of size " << entryRemainingBytes_;
725 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
726 IIdentityCredentialStore::STATUS_INVALID_DATA,
727 "Retrieved chunk is bigger than remaining space"));
728 }
729
730 entryRemainingBytes_ -= chunkSize;
731 if (entryRemainingBytes_ > 0) {
732 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
733 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
734 IIdentityCredentialStore::STATUS_INVALID_DATA,
735 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
736 }
737 }
738
739 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
740
741 if (entryRemainingBytes_ == 0) {
742 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
743 if (entryValueItem == nullptr) {
744 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
745 IIdentityCredentialStore::STATUS_INVALID_DATA,
746 "Retrieved data which is invalid CBOR"));
747 }
748 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
749 }
750
Jooyung Han17be89b2020-02-21 21:17:06 +0900751 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500752 return ndk::ScopedAStatus::ok();
753}
754
Jooyung Han17be89b2020-02-21 21:17:06 +0900755ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
756 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500757 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
758 deviceNameSpacesMap_.add(currentNameSpace_,
759 std::move(currentNameSpaceDeviceNameSpacesMap_));
760 }
761 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
762
David Zeuthen28edb102020-04-28 18:54:55 -0400763 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
764 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
765 << "was expecting " << expectedDeviceNameSpacesSize_;
766 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
767 IIdentityCredentialStore::STATUS_INVALID_DATA,
768 StringPrintf(
769 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
770 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
771 .c_str()));
772 }
773
David Zeuthen81603152020-02-11 22:04:24 -0500774 // If there's no signing key or no sessionTranscript or no reader ephemeral
775 // public key, we return the empty MAC.
776 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500777 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
778 readerPublicKey_.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500779 cppbor::Array array;
780 array.add("DeviceAuthentication");
781 array.add(sessionTranscriptItem_->clone());
782 array.add(docType_);
783 array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
David Zeuthen2e4533e2020-06-20 17:04:41 -0400784 vector<uint8_t> deviceAuthenticationBytes = cppbor::Semantic(24, array.encode()).encode();
David Zeuthen81603152020-02-11 22:04:24 -0500785
786 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
787 optional<vector<uint8_t>> signingKey =
David Zeuthene35797f2020-02-27 14:25:54 -0500788 support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
David Zeuthen81603152020-02-11 22:04:24 -0500789 if (!signingKey) {
790 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
791 IIdentityCredentialStore::STATUS_INVALID_DATA,
792 "Error decrypting signingKeyBlob"));
793 }
794
795 optional<vector<uint8_t>> sharedSecret =
796 support::ecdh(readerPublicKey_, signingKey.value());
797 if (!sharedSecret) {
798 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
799 IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
800 }
801
David Zeuthen2e4533e2020-06-20 17:04:41 -0400802 // Mix-in SessionTranscriptBytes
803 vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
804 vector<uint8_t> sharedSecretWithSessionTranscriptBytes = sharedSecret.value();
805 std::copy(sessionTranscriptBytes.begin(), sessionTranscriptBytes.end(),
806 std::back_inserter(sharedSecretWithSessionTranscriptBytes));
807
David Zeuthen81603152020-02-11 22:04:24 -0500808 vector<uint8_t> salt = {0x00};
809 vector<uint8_t> info = {};
David Zeuthen2e4533e2020-06-20 17:04:41 -0400810 optional<vector<uint8_t>> derivedKey =
811 support::hkdf(sharedSecretWithSessionTranscriptBytes, salt, info, 32);
David Zeuthen81603152020-02-11 22:04:24 -0500812 if (!derivedKey) {
813 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
814 IIdentityCredentialStore::STATUS_FAILED,
815 "Error deriving key from shared secret"));
816 }
817
David Zeuthen2e4533e2020-06-20 17:04:41 -0400818 mac = support::coseMac0(derivedKey.value(), {}, // payload
819 deviceAuthenticationBytes); // detached content
David Zeuthen81603152020-02-11 22:04:24 -0500820 if (!mac) {
821 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
822 IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
823 }
824 }
825
Jooyung Han17be89b2020-02-21 21:17:06 +0900826 *outMac = mac.value_or(vector<uint8_t>({}));
827 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500828 return ndk::ScopedAStatus::ok();
829}
830
831ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900832 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen81603152020-02-11 22:04:24 -0500833 string serialDecimal = "0"; // TODO: set serial to something unique
834 string issuer = "Android Open Source Project";
835 string subject = "Android IdentityCredential Reference Implementation";
836 time_t validityNotBefore = time(nullptr);
837 time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
838
839 optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
840 if (!signingKeyPKCS8) {
841 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
842 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
843 }
844
845 optional<vector<uint8_t>> signingPublicKey =
846 support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
847 if (!signingPublicKey) {
848 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
849 IIdentityCredentialStore::STATUS_FAILED,
850 "Error getting public part of signingKey"));
851 }
852
853 optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
854 if (!signingKey) {
855 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
856 IIdentityCredentialStore::STATUS_FAILED,
857 "Error getting private part of signingKey"));
858 }
859
860 optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
861 signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
862 validityNotBefore, validityNotAfter);
863 if (!certificate) {
864 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
865 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
866 }
867
868 optional<vector<uint8_t>> nonce = support::getRandom(12);
869 if (!nonce) {
870 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
871 IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
872 }
873 vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
874 optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
875 storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
876 if (!encryptedSigningKey) {
877 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
878 IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
879 }
Jooyung Han17be89b2020-02-21 21:17:06 +0900880 *outSigningKeyBlob = encryptedSigningKey.value();
David Zeuthen81603152020-02-11 22:04:24 -0500881 *outSigningKeyCertificate = Certificate();
Jooyung Han17be89b2020-02-21 21:17:06 +0900882 outSigningKeyCertificate->encodedCertificate = certificate.value();
David Zeuthen81603152020-02-11 22:04:24 -0500883 return ndk::ScopedAStatus::ok();
884}
885
886} // namespace aidl::android::hardware::identity