blob: 270fcfa8d0c5dd58cbf69713bc9d98e2a768cc8c [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"
David Zeuthen81603152020-02-11 22:04:24 -050021
22#include <android/hardware/identity/support/IdentityCredentialSupport.h>
23
24#include <string.h>
25
26#include <android-base/logging.h>
David Zeuthen28edb102020-04-28 18:54:55 -040027#include <android-base/stringprintf.h>
David Zeuthen81603152020-02-11 22:04:24 -050028
29#include <cppbor.h>
30#include <cppbor_parse.h>
31
David Zeuthen630de2a2020-05-11 14:04:54 -040032#include "FakeSecureHardwareProxy.h"
33
David Zeuthen81603152020-02-11 22:04:24 -050034namespace aidl::android::hardware::identity {
35
36using ::aidl::android::hardware::keymaster::Timestamp;
David Zeuthen28edb102020-04-28 18:54:55 -040037using ::android::base::StringPrintf;
David Zeuthen81603152020-02-11 22:04:24 -050038using ::std::optional;
39
40using namespace ::android::hardware::identity;
41
42int IdentityCredential::initialize() {
David Zeuthen2e4533e2020-06-20 17:04:41 -040043 if (credentialData_.size() == 0) {
44 LOG(ERROR) << "CredentialData is empty";
45 return IIdentityCredentialStore::STATUS_INVALID_DATA;
46 }
David Zeuthen81603152020-02-11 22:04:24 -050047 auto [item, _, message] = cppbor::parse(credentialData_);
48 if (item == nullptr) {
49 LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
50 return IIdentityCredentialStore::STATUS_INVALID_DATA;
51 }
52
53 const cppbor::Array* arrayItem = item->asArray();
54 if (arrayItem == nullptr || arrayItem->size() != 3) {
55 LOG(ERROR) << "CredentialData is not an array with three elements";
56 return IIdentityCredentialStore::STATUS_INVALID_DATA;
57 }
58
59 const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
60 const cppbor::Bool* testCredentialItem =
61 ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
62 : nullptr);
63 const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
64 if (docTypeItem == nullptr || testCredentialItem == nullptr ||
65 encryptedCredentialKeysItem == nullptr) {
66 LOG(ERROR) << "CredentialData unexpected item types";
67 return IIdentityCredentialStore::STATUS_INVALID_DATA;
68 }
69
70 docType_ = docTypeItem->value();
71 testCredential_ = testCredentialItem->value();
72
David Zeuthen81603152020-02-11 22:04:24 -050073 const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
David Zeuthen630de2a2020-05-11 14:04:54 -040074
75 if (encryptedCredentialKeys.size() != 80) {
76 LOG(ERROR) << "Unexpected size for encrypted CredentialKeys";
David Zeuthen81603152020-02-11 22:04:24 -050077 return IIdentityCredentialStore::STATUS_INVALID_DATA;
78 }
79
David Zeuthen630de2a2020-05-11 14:04:54 -040080 if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) {
81 LOG(ERROR) << "hwProxy->initialize failed";
82 return false;
David Zeuthen81603152020-02-11 22:04:24 -050083 }
David Zeuthen81603152020-02-11 22:04:24 -050084
85 return IIdentityCredentialStore::STATUS_OK;
86}
87
88ndk::ScopedAStatus IdentityCredential::deleteCredential(
Jooyung Han17be89b2020-02-21 21:17:06 +090089 vector<uint8_t>* outProofOfDeletionSignature) {
David Zeuthen81603152020-02-11 22:04:24 -050090 cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
David Zeuthen630de2a2020-05-11 14:04:54 -040091 vector<uint8_t> proofOfDeletionCbor = array.encode();
92 vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
David Zeuthen81603152020-02-11 22:04:24 -050093
David Zeuthen630de2a2020-05-11 14:04:54 -040094 optional<vector<uint8_t>> signatureOfToBeSigned =
95 hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size());
96 if (!signatureOfToBeSigned) {
97 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
98 IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
99 }
100
101 optional<vector<uint8_t>> signature =
102 support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
103 proofOfDeletionCbor, // data
104 {}); // certificateChain
David Zeuthen81603152020-02-11 22:04:24 -0500105 if (!signature) {
106 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
107 IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
108 }
109
Jooyung Han17be89b2020-02-21 21:17:06 +0900110 *outProofOfDeletionSignature = signature.value();
David Zeuthen81603152020-02-11 22:04:24 -0500111 return ndk::ScopedAStatus::ok();
112}
113
Jooyung Han17be89b2020-02-21 21:17:06 +0900114ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400115 optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
116 if (!ephemeralPriv) {
David Zeuthen81603152020-02-11 22:04:24 -0500117 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
David Zeuthen630de2a2020-05-11 14:04:54 -0400118 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key"));
119 }
120 optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
121 if (!keyPair) {
122 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
123 IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
David Zeuthen81603152020-02-11 22:04:24 -0500124 }
125
126 // Stash public key of this key-pair for later check in startRetrieval().
David Zeuthen630de2a2020-05-11 14:04:54 -0400127 optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
David Zeuthen81603152020-02-11 22:04:24 -0500128 if (!publicKey) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400129 LOG(ERROR) << "Error getting public part of ephemeral key pair";
David Zeuthen81603152020-02-11 22:04:24 -0500130 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
131 IIdentityCredentialStore::STATUS_FAILED,
132 "Error getting public part of ephemeral key pair"));
133 }
134 ephemeralPublicKey_ = publicKey.value();
135
David Zeuthen630de2a2020-05-11 14:04:54 -0400136 *outKeyPair = keyPair.value();
David Zeuthen81603152020-02-11 22:04:24 -0500137 return ndk::ScopedAStatus::ok();
138}
139
140ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
Jooyung Han17be89b2020-02-21 21:17:06 +0900141 const vector<uint8_t>& publicKey) {
142 readerPublicKey_ = publicKey;
David Zeuthen81603152020-02-11 22:04:24 -0500143 return ndk::ScopedAStatus::ok();
144}
145
146ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400147 optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
148 if (!challenge) {
149 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
150 IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
David Zeuthen81603152020-02-11 22:04:24 -0500151 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400152 *outChallenge = challenge.value();
David Zeuthen81603152020-02-11 22:04:24 -0500153 return ndk::ScopedAStatus::ok();
154}
155
David Zeuthen28edb102020-04-28 18:54:55 -0400156ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
157 const vector<RequestNamespace>& requestNamespaces) {
158 requestNamespaces_ = requestNamespaces;
159 return ndk::ScopedAStatus::ok();
160}
161
David Zeuthena8ed82c2020-05-08 10:03:28 -0400162ndk::ScopedAStatus IdentityCredential::setVerificationToken(
163 const VerificationToken& verificationToken) {
164 verificationToken_ = verificationToken;
165 return ndk::ScopedAStatus::ok();
166}
167
David Zeuthen81603152020-02-11 22:04:24 -0500168ndk::ScopedAStatus IdentityCredential::startRetrieval(
169 const vector<SecureAccessControlProfile>& accessControlProfiles,
Jooyung Han17be89b2020-02-21 21:17:06 +0900170 const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
171 const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
172 const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
David Zeuthen34abaae2020-10-26 20:26:36 -0400173 std::unique_ptr<cppbor::Item> sessionTranscriptItem;
David Zeuthen81603152020-02-11 22:04:24 -0500174 if (sessionTranscript.size() > 0) {
175 auto [item, _, message] = cppbor::parse(sessionTranscript);
176 if (item == nullptr) {
177 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
178 IIdentityCredentialStore::STATUS_INVALID_DATA,
179 "SessionTranscript contains invalid CBOR"));
180 }
David Zeuthen34abaae2020-10-26 20:26:36 -0400181 sessionTranscriptItem = std::move(item);
David Zeuthen81603152020-02-11 22:04:24 -0500182 }
183 if (numStartRetrievalCalls_ > 0) {
184 if (sessionTranscript_ != sessionTranscript) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400185 LOG(ERROR) << "Session Transcript changed";
David Zeuthen81603152020-02-11 22:04:24 -0500186 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
187 IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
188 "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
189 }
190 }
191 sessionTranscript_ = sessionTranscript;
192
David Zeuthen630de2a2020-05-11 14:04:54 -0400193 // This resets various state in the TA...
194 if (!hwProxy_->startRetrieveEntries()) {
195 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
196 IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
197 }
198
199 optional<vector<uint8_t>> signatureOfToBeSigned;
200 if (readerSignature.size() > 0) {
201 signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
202 if (!signatureOfToBeSigned) {
203 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
204 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
205 "Error extracting signatureOfToBeSigned from COSE_Sign1"));
206 }
207 }
208
209 // Feed the auth token to secure hardware.
210 if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId,
211 int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
212 authToken.mac, verificationToken_.challenge,
213 verificationToken_.timestamp.milliSeconds,
214 int(verificationToken_.securityLevel), verificationToken_.mac)) {
215 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
216 IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
217 }
218
219 // We'll be feeding ACPs interleaved with certificates from the reader
220 // certificate chain...
221 vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
222
223 // ... and we'll use those ACPs to build up a 32-bit mask indicating which
224 // of the possible 32 ACPs grants access.
225 uint32_t accessControlProfileMask = 0;
226
David Zeuthen81603152020-02-11 22:04:24 -0500227 // If there is a signature, validate that it was made with the top-most key in the
228 // certificate chain embedded in the COSE_Sign1 structure.
229 optional<vector<uint8_t>> readerCertificateChain;
230 if (readerSignature.size() > 0) {
231 readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
232 if (!readerCertificateChain) {
233 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
234 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
235 "Unable to get reader certificate chain from COSE_Sign1"));
236 }
237
David Zeuthen630de2a2020-05-11 14:04:54 -0400238 // First, feed all the reader certificates to the secure hardware. We start
239 // at the end..
240 optional<vector<vector<uint8_t>>> splitCerts =
241 support::certificateChainSplit(readerCertificateChain.value());
242 if (!splitCerts || splitCerts.value().size() == 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500243 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
244 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
David Zeuthen630de2a2020-05-11 14:04:54 -0400245 "Error splitting certificate chain from COSE_Sign1"));
246 }
247 for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
248 const vector<uint8_t>& x509Cert = splitCerts.value()[n];
249 if (!hwProxy_->pushReaderCert(x509Cert)) {
250 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
251 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
252 StringPrintf("Error validating reader certificate %zd", n).c_str()));
253 }
254
255 // If we have ACPs for that particular certificate, send them to the
256 // TA right now...
257 //
258 // Remember in this case certificate equality is done by comparing public keys,
259 // not bitwise comparison of the certificates.
260 //
261 optional<vector<uint8_t>> x509CertPubKey =
262 support::certificateChainGetTopMostKey(x509Cert);
263 if (!x509CertPubKey) {
264 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
265 IIdentityCredentialStore::STATUS_FAILED,
266 StringPrintf("Error getting public key from reader certificate %zd", n)
267 .c_str()));
268 }
269 vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
270 while (it != remainingAcps.end()) {
271 const SecureAccessControlProfile& profile = *it;
272 if (profile.readerCertificate.encodedCertificate.size() == 0) {
273 ++it;
274 continue;
275 }
276 optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey(
277 profile.readerCertificate.encodedCertificate);
278 if (!profilePubKey) {
279 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
280 IIdentityCredentialStore::STATUS_FAILED,
281 "Error getting public key from profile"));
282 }
283 if (profilePubKey.value() == x509CertPubKey.value()) {
284 optional<bool> res = hwProxy_->validateAccessControlProfile(
285 profile.id, profile.readerCertificate.encodedCertificate,
286 profile.userAuthenticationRequired, profile.timeoutMillis,
287 profile.secureUserId, profile.mac);
288 if (!res) {
289 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
290 IIdentityCredentialStore::STATUS_INVALID_DATA,
291 "Error validating access control profile"));
292 }
293 if (res.value()) {
294 accessControlProfileMask |= (1 << profile.id);
295 }
296 it = remainingAcps.erase(it);
297 } else {
298 ++it;
299 }
300 }
David Zeuthen81603152020-02-11 22:04:24 -0500301 }
302
David Zeuthen630de2a2020-05-11 14:04:54 -0400303 // ... then pass the request message and have the TA check it's signed by the
304 // key in last certificate we pushed.
305 if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) {
306 optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
307 if (!tbsSignature) {
308 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
309 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
310 "Error extracting toBeSigned from COSE_Sign1"));
311 }
312 optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
313 if (!coseSignAlg) {
314 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
315 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
316 "Error extracting signature algorithm from COSE_Sign1"));
317 }
318 if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
319 coseSignAlg.value(), tbsSignature.value())) {
320 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
321 IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
322 "readerMessage is not signed by top-level certificate"));
323 }
David Zeuthen81603152020-02-11 22:04:24 -0500324 }
325 }
326
David Zeuthen630de2a2020-05-11 14:04:54 -0400327 // Feed remaining access control profiles...
328 for (const SecureAccessControlProfile& profile : remainingAcps) {
329 optional<bool> res = hwProxy_->validateAccessControlProfile(
330 profile.id, profile.readerCertificate.encodedCertificate,
331 profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId,
332 profile.mac);
333 if (!res) {
334 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
335 IIdentityCredentialStore::STATUS_INVALID_DATA,
336 "Error validating access control profile"));
337 }
338 if (res.value()) {
339 accessControlProfileMask |= (1 << profile.id);
340 }
341 }
David Zeuthen81603152020-02-11 22:04:24 -0500342
David Zeuthen630de2a2020-05-11 14:04:54 -0400343 // TODO: move this check to the TA
344#if 1
David Zeuthen81603152020-02-11 22:04:24 -0500345 // To prevent replay-attacks, we check that the public part of the ephemeral
346 // key we previously created, is present in the DeviceEngagement part of
347 // SessionTranscript as a COSE_Key, in uncompressed form.
348 //
349 // We do this by just searching for the X and Y coordinates.
350 if (sessionTranscript.size() > 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500351 auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
352 if (!getXYSuccess) {
353 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
354 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
355 "Error extracting X and Y from ePub"));
356 }
357 if (sessionTranscript.size() > 0 &&
David Zeuthenef739512020-06-03 13:24:52 -0400358 !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
359 ePubX.size()) != nullptr &&
360 memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
361 ePubY.size()) != nullptr)) {
David Zeuthen81603152020-02-11 22:04:24 -0500362 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
363 IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
364 "Did not find ephemeral public key's X and Y coordinates in "
365 "SessionTranscript (make sure leading zeroes are not used)"));
366 }
367 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400368#endif
David Zeuthen81603152020-02-11 22:04:24 -0500369
370 // itemsRequest: If non-empty, contains request data that may be signed by the
371 // reader. The content can be defined in the way appropriate for the
372 // credential, but there are three requirements that must be met to work with
373 // this HAL:
374 if (itemsRequest.size() > 0) {
375 // 1. The content must be a CBOR-encoded structure.
376 auto [item, _, message] = cppbor::parse(itemsRequest);
377 if (item == nullptr) {
378 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
379 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
380 "Error decoding CBOR in itemsRequest"));
381 }
382
383 // 2. The CBOR structure must be a map.
384 const cppbor::Map* map = item->asMap();
385 if (map == nullptr) {
386 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
387 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
388 "itemsRequest is not a CBOR map"));
389 }
390
391 // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
392 // the example below.
393 //
394 // NameSpaces = {
395 // + NameSpace => DataElements ; Requested data elements for each NameSpace
396 // }
397 //
398 // NameSpace = tstr
399 //
400 // DataElements = {
401 // + DataElement => IntentToRetain
402 // }
403 //
404 // DataElement = tstr
405 // IntentToRetain = bool
406 //
407 // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
408 // through 3.:
409 //
410 // {
411 // 'docType' : 'org.iso.18013-5.2019',
412 // 'nameSpaces' : {
413 // 'org.iso.18013-5.2019' : {
414 // 'Last name' : false,
415 // 'Birth date' : false,
416 // 'First name' : false,
417 // 'Home address' : true
418 // },
419 // 'org.aamva.iso.18013-5.2019' : {
420 // 'Real Id' : false
421 // }
422 // }
423 // }
424 //
425 const cppbor::Map* nsMap = nullptr;
426 for (size_t n = 0; n < map->size(); n++) {
427 const auto& [keyItem, valueItem] = (*map)[n];
428 if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
429 valueItem->type() == cppbor::MAP) {
430 nsMap = valueItem->asMap();
431 break;
432 }
433 }
434 if (nsMap == nullptr) {
435 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
436 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
437 "No nameSpaces map in top-most map"));
438 }
439
440 for (size_t n = 0; n < nsMap->size(); n++) {
441 auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
442 const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
443 const cppbor::Map* nsInnerMap = nsValueItem->asMap();
444 if (nsKey == nullptr || nsInnerMap == nullptr) {
445 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
446 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
447 "Type mismatch in nameSpaces map"));
448 }
449 string requestedNamespace = nsKey->value();
David Zeuthen28edb102020-04-28 18:54:55 -0400450 set<string> requestedKeys;
David Zeuthen81603152020-02-11 22:04:24 -0500451 for (size_t m = 0; m < nsInnerMap->size(); m++) {
452 const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
453 const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
454 const cppbor::Simple* simple = innerMapValueItem->asSimple();
455 const cppbor::Bool* intentToRetainItem =
456 (simple != nullptr) ? simple->asBool() : nullptr;
457 if (nameItem == nullptr || intentToRetainItem == nullptr) {
458 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
459 IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
460 "Type mismatch in value in nameSpaces map"));
461 }
David Zeuthen28edb102020-04-28 18:54:55 -0400462 requestedKeys.insert(nameItem->value());
David Zeuthen81603152020-02-11 22:04:24 -0500463 }
464 requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
465 }
466 }
467
David Zeuthen81603152020-02-11 22:04:24 -0500468 deviceNameSpacesMap_ = cppbor::Map();
469 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
470
471 requestCountsRemaining_ = requestCounts;
472 currentNameSpace_ = "";
473
474 itemsRequest_ = itemsRequest;
Jooyung Han17be89b2020-02-21 21:17:06 +0900475 signingKeyBlob_ = signingKeyBlob;
David Zeuthen81603152020-02-11 22:04:24 -0500476
David Zeuthen630de2a2020-05-11 14:04:54 -0400477 // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
478 calcDeviceNameSpacesSize(accessControlProfileMask);
479
480 // Count the number of non-empty namespaces
481 size_t numNamespacesWithValues = 0;
482 for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
483 if (expectedNumEntriesPerNamespace_[n] > 0) {
484 numNamespacesWithValues += 1;
485 }
486 }
487
488 // Finally, pass info so the HMAC key can be derived and the TA can start
489 // creating the DeviceNameSpaces CBOR...
490 if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) {
491 // We expect the reader ephemeral public key to be same size and curve
492 // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
493 // won't work. So its length should be 65 bytes and it should be
494 // starting with 0x04.
495 if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
496 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
497 IIdentityCredentialStore::STATUS_FAILED,
498 "Reader public key is not in expected format"));
499 }
500 vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
501 if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
502 numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
503 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
504 IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
505 }
506 }
David Zeuthen28edb102020-04-28 18:54:55 -0400507
David Zeuthen81603152020-02-11 22:04:24 -0500508 numStartRetrievalCalls_ += 1;
509 return ndk::ScopedAStatus::ok();
510}
511
David Zeuthen28edb102020-04-28 18:54:55 -0400512size_t cborNumBytesForLength(size_t length) {
513 if (length < 24) {
514 return 0;
515 } else if (length <= 0xff) {
516 return 1;
517 } else if (length <= 0xffff) {
518 return 2;
519 } else if (length <= 0xffffffff) {
520 return 4;
521 }
522 return 8;
523}
524
525size_t cborNumBytesForTstr(const string& value) {
526 return 1 + cborNumBytesForLength(value.size()) + value.size();
527}
528
David Zeuthen630de2a2020-05-11 14:04:54 -0400529void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) {
David Zeuthen28edb102020-04-28 18:54:55 -0400530 /*
531 * This is how DeviceNameSpaces is defined:
532 *
533 * DeviceNameSpaces = {
534 * * NameSpace => DeviceSignedItems
535 * }
536 * DeviceSignedItems = {
537 * + DataItemName => DataItemValue
538 * }
539 *
540 * Namespace = tstr
541 * DataItemName = tstr
542 * DataItemValue = any
543 *
544 * This function will calculate its length using knowledge of how CBOR is
545 * encoded.
546 */
547 size_t ret = 0;
David Zeuthen630de2a2020-05-11 14:04:54 -0400548 vector<unsigned int> numEntriesPerNamespace;
David Zeuthen28edb102020-04-28 18:54:55 -0400549 for (const RequestNamespace& rns : requestNamespaces_) {
550 vector<RequestDataItem> itemsToInclude;
551
552 for (const RequestDataItem& rdi : rns.items) {
553 // If we have a CBOR request message, skip if item isn't in it
554 if (itemsRequest_.size() > 0) {
555 const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
556 if (it == requestedNameSpacesAndNames_.end()) {
557 continue;
558 }
559 const set<string>& dataItemNames = it->second;
560 if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
561 continue;
562 }
563 }
564
565 // Access is granted if at least one of the profiles grants access.
566 //
567 // If an item is configured without any profiles, access is denied.
568 //
569 bool authorized = false;
570 for (auto id : rdi.accessControlProfileIds) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400571 if (accessControlProfileMask & (1 << id)) {
572 authorized = true;
573 break;
David Zeuthen28edb102020-04-28 18:54:55 -0400574 }
575 }
576 if (!authorized) {
577 continue;
578 }
579
580 itemsToInclude.push_back(rdi);
581 }
582
David Zeuthen630de2a2020-05-11 14:04:54 -0400583 numEntriesPerNamespace.push_back(itemsToInclude.size());
584
585 // If no entries are to be in the namespace, we don't include it in
586 // the CBOR...
David Zeuthen28edb102020-04-28 18:54:55 -0400587 if (itemsToInclude.size() == 0) {
588 continue;
589 }
590
591 // Key: NameSpace
592 ret += cborNumBytesForTstr(rns.namespaceName);
593
594 // Value: Open the DeviceSignedItems map
595 ret += 1 + cborNumBytesForLength(itemsToInclude.size());
596
597 for (const RequestDataItem& item : itemsToInclude) {
598 // Key: DataItemName
599 ret += cborNumBytesForTstr(item.name);
600
601 // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
602 // that.
603 ret += item.size;
604 }
David Zeuthen28edb102020-04-28 18:54:55 -0400605 }
606
David Zeuthen630de2a2020-05-11 14:04:54 -0400607 // Now that we know the number of namespaces with values, we know how many
David Zeuthen28edb102020-04-28 18:54:55 -0400608 // bytes the DeviceNamespaces map in the beginning is going to take up.
David Zeuthen630de2a2020-05-11 14:04:54 -0400609 ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
David Zeuthen28edb102020-04-28 18:54:55 -0400610
David Zeuthen630de2a2020-05-11 14:04:54 -0400611 expectedDeviceNameSpacesSize_ = ret;
612 expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
David Zeuthen28edb102020-04-28 18:54:55 -0400613}
614
David Zeuthen81603152020-02-11 22:04:24 -0500615ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
616 const string& nameSpace, const string& name, int32_t entrySize,
617 const vector<int32_t>& accessControlProfileIds) {
618 if (name.empty()) {
619 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
620 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
621 }
622 if (nameSpace.empty()) {
623 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
624 IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
625 }
626
627 if (requestCountsRemaining_.size() == 0) {
628 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
629 IIdentityCredentialStore::STATUS_INVALID_DATA,
630 "No more name spaces left to go through"));
631 }
632
David Zeuthen630de2a2020-05-11 14:04:54 -0400633 bool newNamespace;
David Zeuthen81603152020-02-11 22:04:24 -0500634 if (currentNameSpace_ == "") {
635 // First call.
636 currentNameSpace_ = nameSpace;
David Zeuthen630de2a2020-05-11 14:04:54 -0400637 newNamespace = true;
David Zeuthen81603152020-02-11 22:04:24 -0500638 }
639
640 if (nameSpace == currentNameSpace_) {
641 // Same namespace.
642 if (requestCountsRemaining_[0] == 0) {
643 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
644 IIdentityCredentialStore::STATUS_INVALID_DATA,
645 "No more entries to be retrieved in current name space"));
646 }
647 requestCountsRemaining_[0] -= 1;
648 } else {
649 // New namespace.
650 if (requestCountsRemaining_[0] != 0) {
651 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
652 IIdentityCredentialStore::STATUS_INVALID_DATA,
653 "Moved to new name space but one or more entries need to be retrieved "
654 "in current name space"));
655 }
656 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
657 deviceNameSpacesMap_.add(currentNameSpace_,
658 std::move(currentNameSpaceDeviceNameSpacesMap_));
659 }
660 currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
661
662 requestCountsRemaining_.erase(requestCountsRemaining_.begin());
663 currentNameSpace_ = nameSpace;
David Zeuthen630de2a2020-05-11 14:04:54 -0400664 newNamespace = true;
David Zeuthen81603152020-02-11 22:04:24 -0500665 }
666
667 // It's permissible to have an empty itemsRequest... but if non-empty you can
668 // only request what was specified in said itemsRequest. Enforce that.
669 if (itemsRequest_.size() > 0) {
670 const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
671 if (it == requestedNameSpacesAndNames_.end()) {
672 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
673 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
674 "Name space was not requested in startRetrieval"));
675 }
David Zeuthen28edb102020-04-28 18:54:55 -0400676 const set<string>& dataItemNames = it->second;
677 if (dataItemNames.find(name) == dataItemNames.end()) {
David Zeuthen81603152020-02-11 22:04:24 -0500678 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
679 IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
680 "Data item name in name space was not requested in startRetrieval"));
681 }
682 }
683
David Zeuthen630de2a2020-05-11 14:04:54 -0400684 unsigned int newNamespaceNumEntries = 0;
685 if (newNamespace) {
686 if (expectedNumEntriesPerNamespace_.size() == 0) {
David Zeuthen81603152020-02-11 22:04:24 -0500687 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
688 IIdentityCredentialStore::STATUS_INVALID_DATA,
David Zeuthen630de2a2020-05-11 14:04:54 -0400689 "No more populated name spaces left to go through"));
David Zeuthen81603152020-02-11 22:04:24 -0500690 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400691 newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
692 expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin());
David Zeuthen81603152020-02-11 22:04:24 -0500693 }
694
David Zeuthen630de2a2020-05-11 14:04:54 -0400695 // Access control is enforced in the secure hardware.
696 //
697 // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
698 // consolidate).
699 //
700 AccessCheckResult res = hwProxy_->startRetrieveEntryValue(
701 nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds);
702 switch (res) {
703 case AccessCheckResult::kOk:
704 /* Do nothing. */
705 break;
706 case AccessCheckResult::kFailed:
707 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
708 IIdentityCredentialStore::STATUS_FAILED,
709 "Access control check failed (failed)"));
710 break;
711 case AccessCheckResult::kNoAccessControlProfiles:
712 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
713 IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
714 "Access control check failed (no access control profiles)"));
715 break;
716 case AccessCheckResult::kUserAuthenticationFailed:
717 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
718 IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
719 "Access control check failed (user auth)"));
720 break;
721 case AccessCheckResult::kReaderAuthenticationFailed:
722 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
723 IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
724 "Access control check failed (reader auth)"));
725 break;
726 }
David Zeuthen81603152020-02-11 22:04:24 -0500727
728 currentName_ = name;
David Zeuthen630de2a2020-05-11 14:04:54 -0400729 currentAccessControlProfileIds_ = accessControlProfileIds;
David Zeuthen81603152020-02-11 22:04:24 -0500730 entryRemainingBytes_ = entrySize;
731 entryValue_.resize(0);
732
733 return ndk::ScopedAStatus::ok();
734}
735
Jooyung Han17be89b2020-02-21 21:17:06 +0900736ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
737 vector<uint8_t>* outContent) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400738 optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
739 encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
David Zeuthen81603152020-02-11 22:04:24 -0500740 if (!content) {
741 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
742 IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
743 }
744
745 size_t chunkSize = content.value().size();
746
747 if (chunkSize > entryRemainingBytes_) {
748 LOG(ERROR) << "Retrieved chunk of size " << chunkSize
749 << " is bigger than remaining space of size " << entryRemainingBytes_;
750 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
751 IIdentityCredentialStore::STATUS_INVALID_DATA,
752 "Retrieved chunk is bigger than remaining space"));
753 }
754
755 entryRemainingBytes_ -= chunkSize;
756 if (entryRemainingBytes_ > 0) {
757 if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
758 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
759 IIdentityCredentialStore::STATUS_INVALID_DATA,
760 "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
761 }
762 }
763
764 entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
765
766 if (entryRemainingBytes_ == 0) {
767 auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
768 if (entryValueItem == nullptr) {
769 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
770 IIdentityCredentialStore::STATUS_INVALID_DATA,
771 "Retrieved data which is invalid CBOR"));
772 }
773 currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
774 }
775
Jooyung Han17be89b2020-02-21 21:17:06 +0900776 *outContent = content.value();
David Zeuthen81603152020-02-11 22:04:24 -0500777 return ndk::ScopedAStatus::ok();
778}
779
Jooyung Han17be89b2020-02-21 21:17:06 +0900780ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
781 vector<uint8_t>* outDeviceNameSpaces) {
David Zeuthen81603152020-02-11 22:04:24 -0500782 if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
783 deviceNameSpacesMap_.add(currentNameSpace_,
784 std::move(currentNameSpaceDeviceNameSpacesMap_));
785 }
786 vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
787
David Zeuthen28edb102020-04-28 18:54:55 -0400788 if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
789 LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
790 << "was expecting " << expectedDeviceNameSpacesSize_;
791 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
792 IIdentityCredentialStore::STATUS_INVALID_DATA,
793 StringPrintf(
794 "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
795 encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
796 .c_str()));
797 }
798
David Zeuthen81603152020-02-11 22:04:24 -0500799 // If there's no signing key or no sessionTranscript or no reader ephemeral
800 // public key, we return the empty MAC.
801 optional<vector<uint8_t>> mac;
David Zeuthene35797f2020-02-27 14:25:54 -0500802 if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
803 readerPublicKey_.size() > 0) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400804 optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
805 if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
David Zeuthen81603152020-02-11 22:04:24 -0500806 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
807 IIdentityCredentialStore::STATUS_INVALID_DATA,
David Zeuthen630de2a2020-05-11 14:04:54 -0400808 "Error generating digestToBeMaced"));
David Zeuthen81603152020-02-11 22:04:24 -0500809 }
David Zeuthen630de2a2020-05-11 14:04:54 -0400810 // Now construct COSE_Mac0 from the returned MAC...
811 mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
David Zeuthen81603152020-02-11 22:04:24 -0500812 }
813
Jooyung Han17be89b2020-02-21 21:17:06 +0900814 *outMac = mac.value_or(vector<uint8_t>({}));
815 *outDeviceNameSpaces = encodedDeviceNameSpaces;
David Zeuthen81603152020-02-11 22:04:24 -0500816 return ndk::ScopedAStatus::ok();
817}
818
819ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
Jooyung Han17be89b2020-02-21 21:17:06 +0900820 vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
David Zeuthen630de2a2020-05-11 14:04:54 -0400821 time_t now = time(NULL);
822 optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
823 hwProxy_->generateSigningKeyPair(docType_, now);
824 if (!pair) {
David Zeuthen81603152020-02-11 22:04:24 -0500825 return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
826 IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
827 }
828
David Zeuthen81603152020-02-11 22:04:24 -0500829 *outSigningKeyCertificate = Certificate();
David Zeuthen630de2a2020-05-11 14:04:54 -0400830 outSigningKeyCertificate->encodedCertificate = pair->first;
831
832 *outSigningKeyBlob = pair->second;
David Zeuthen81603152020-02-11 22:04:24 -0500833 return ndk::ScopedAStatus::ok();
834}
835
836} // namespace aidl::android::hardware::identity