summaryrefslogtreecommitdiff
path: root/identity/aidl/default/IdentityCredential.cpp
diff options
context:
space:
mode:
authorDavid Zeuthen <zeuthen@google.com>2020-05-11 14:04:54 -0400
committerDavid Zeuthen <zeuthen@google.com>2021-01-05 18:30:59 -0500
commit630de2a93e48d8f9ed2a23806d46b7a7a6b46c74 (patch)
tree7af50ea784609a5f340dd82ae7c386aae610668c /identity/aidl/default/IdentityCredential.cpp
parent19086060541a2a812e76921d3d6a6bdb4f97c521 (diff)
Identity Credential: Switch default implementation to use libeic.
Introduce platform-neutral C library ("libeic") which can be used to implement an Identity Credential Trusted Application/Applet in Secure Hardware. The libeic library is intentionally low-level, has no dependencies (not even libc), uses very little run-time memory (less than 500 bytes during a provisioning or presentation session), and doesn't dynamically allocate any memory. Crypto routines are provided by the library user through a simple crypto interface defined in EicOps. Also provide an Android-side HAL implementation designed to communicate with libeic running in Secure Hardware outside Android. Abstract out communications between HAL and TA in a couple of SecureHardwareProxy* classes which mimic libeic 1:1. The default implementation of the HAL is a combination of the aforementioned HAL using libeic in-process backed by BoringSSL for the crypto bits. Test: atest VtsHalIdentityTargetTest Test: atest android.security.identity.cts Bug: 170146643 Change-Id: I3bf43fa7fd9362f94023052591801f2094a04607
Diffstat (limited to 'identity/aidl/default/IdentityCredential.cpp')
-rw-r--r--identity/aidl/default/IdentityCredential.cpp864
1 files changed, 0 insertions, 864 deletions
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
deleted file mode 100644
index dfcd4f557f..0000000000
--- a/identity/aidl/default/IdentityCredential.cpp
+++ /dev/null
@@ -1,864 +0,0 @@
-/*
- * Copyright 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "IdentityCredential"
-
-#include "IdentityCredential.h"
-#include "IdentityCredentialStore.h"
-#include "Util.h"
-
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <string.h>
-
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-
-#include <cppbor.h>
-#include <cppbor_parse.h>
-
-namespace aidl::android::hardware::identity {
-
-using ::aidl::android::hardware::keymaster::Timestamp;
-using ::android::base::StringPrintf;
-using ::std::optional;
-
-using namespace ::android::hardware::identity;
-
-int IdentityCredential::initialize() {
- if (credentialData_.size() == 0) {
- LOG(ERROR) << "CredentialData is empty";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- auto [item, _, message] = cppbor::parse(credentialData_);
- if (item == nullptr) {
- LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
-
- const cppbor::Array* arrayItem = item->asArray();
- if (arrayItem == nullptr || arrayItem->size() != 3) {
- LOG(ERROR) << "CredentialData is not an array with three elements";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
-
- const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr();
- const cppbor::Bool* testCredentialItem =
- ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool())
- : nullptr);
- const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr();
- if (docTypeItem == nullptr || testCredentialItem == nullptr ||
- encryptedCredentialKeysItem == nullptr) {
- LOG(ERROR) << "CredentialData unexpected item types";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
-
- docType_ = docTypeItem->value();
- testCredential_ = testCredentialItem->value();
-
- vector<uint8_t> hardwareBoundKey;
- if (testCredential_) {
- hardwareBoundKey = support::getTestHardwareBoundKey();
- } else {
- hardwareBoundKey = getHardwareBoundKey();
- }
-
- const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
- const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> decryptedCredentialKeys =
- support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
- if (!decryptedCredentialKeys) {
- LOG(ERROR) << "Error decrypting CredentialKeys";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
-
- auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
- if (dckItem == nullptr) {
- LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- const cppbor::Array* dckArrayItem = dckItem->asArray();
- if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
- LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
- const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
- if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
- LOG(ERROR) << "CredentialKeys unexpected item types";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- storageKey_ = storageKeyItem->value();
- credentialPrivKey_ = credentialPrivKeyItem->value();
-
- return IIdentityCredentialStore::STATUS_OK;
-}
-
-ndk::ScopedAStatus IdentityCredential::deleteCredential(
- vector<uint8_t>* outProofOfDeletionSignature) {
- cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
- vector<uint8_t> proofOfDeletion = array.encode();
-
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- proofOfDeletion, // payload
- {}, // additionalData
- {}); // certificateChain
- if (!signature) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
- }
-
- *outProofOfDeletionSignature = signature.value();
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
- optional<vector<uint8_t>> kp = support::createEcKeyPair();
- if (!kp) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
- }
-
- // Stash public key of this key-pair for later check in startRetrieval().
- optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
- if (!publicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of ephemeral key pair"));
- }
- ephemeralPublicKey_ = publicKey.value();
-
- *outKeyPair = kp.value();
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
- const vector<uint8_t>& publicKey) {
- readerPublicKey_ = publicKey;
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
- uint64_t challenge = 0;
- while (challenge == 0) {
- optional<vector<uint8_t>> bytes = support::getRandom(8);
- if (!bytes) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting random data for challenge"));
- }
-
- challenge = 0;
- for (size_t n = 0; n < bytes.value().size(); n++) {
- challenge |= ((bytes.value())[n] << (n * 8));
- }
- }
-
- *outChallenge = challenge;
- authChallenge_ = challenge;
- return ndk::ScopedAStatus::ok();
-}
-
-// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
-// ahead of time.
-bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& readerCertificateChain) {
- optional<vector<uint8_t>> acpPubKey =
- support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
- if (!acpPubKey) {
- LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
- return false;
- }
-
- optional<vector<vector<uint8_t>>> certificatesInChain =
- support::certificateChainSplit(readerCertificateChain);
- if (!certificatesInChain) {
- LOG(ERROR) << "Error splitting readerCertificateChain";
- return false;
- }
- for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
- optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
- if (!certPubKey) {
- LOG(ERROR)
- << "Error extracting public key from certificate in chain presented by reader";
- return false;
- }
- if (acpPubKey.value() == certPubKey.value()) {
- return true;
- }
- }
- return false;
-}
-
-bool checkUserAuthentication(const SecureAccessControlProfile& profile,
- const VerificationToken& verificationToken,
- const HardwareAuthToken& authToken, uint64_t authChallenge) {
- if (profile.secureUserId != authToken.userId) {
- LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
- << ") differs from userId in authToken (" << authToken.userId << ")";
- return false;
- }
-
- if (verificationToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "VerificationToken is not set";
- return false;
- }
- if (authToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "AuthToken is not set";
- return false;
- }
-
- if (profile.timeoutMillis == 0) {
- if (authToken.challenge == 0) {
- LOG(ERROR) << "No challenge in authToken";
- return false;
- }
-
- if (authToken.challenge != int64_t(authChallenge)) {
- LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
- << "doesn't match the challenge we created (" << authChallenge << ")";
- return false;
- }
- return true;
- }
-
- // Timeout-based user auth follows. The verification token conveys what the
- // time is right now in the environment which generated the auth token. This
- // is what makes it possible to do timeout-based checks.
- //
- const Timestamp now = verificationToken.timestamp;
- if (authToken.timestamp.milliSeconds > now.milliSeconds) {
- LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
- << ") is in the future (now: " << now.milliSeconds << ")";
- return false;
- }
- if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
- LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
- << profile.timeoutMillis << " = "
- << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
- << ") is in the past (now: " << now.milliSeconds << ")";
- return false;
- }
- return true;
-}
-
-ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
- const vector<RequestNamespace>& requestNamespaces) {
- requestNamespaces_ = requestNamespaces;
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::setVerificationToken(
- const VerificationToken& verificationToken) {
- verificationToken_ = verificationToken;
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::startRetrieval(
- const vector<SecureAccessControlProfile>& accessControlProfiles,
- const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
- const vector<uint8_t>& signingKeyBlob, const vector<uint8_t>& sessionTranscript,
- const vector<uint8_t>& readerSignature, const vector<int32_t>& requestCounts) {
- std::unique_ptr<cppbor::Item> sessionTranscriptItem;
- if (sessionTranscript.size() > 0) {
- auto [item, _, message] = cppbor::parse(sessionTranscript);
- if (item == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "SessionTranscript contains invalid CBOR"));
- }
- sessionTranscriptItem = std::move(item);
- }
- if (numStartRetrievalCalls_ > 0) {
- if (sessionTranscript_ != sessionTranscript) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
- "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
- }
- }
- sessionTranscript_ = sessionTranscript;
-
- // If there is a signature, validate that it was made with the top-most key in the
- // certificate chain embedded in the COSE_Sign1 structure.
- optional<vector<uint8_t>> readerCertificateChain;
- if (readerSignature.size() > 0) {
- readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
- if (!readerCertificateChain) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Unable to get reader certificate chain from COSE_Sign1"));
- }
-
- if (!support::certificateChainValidate(readerCertificateChain.value())) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Error validating reader certificate chain"));
- }
-
- optional<vector<uint8_t>> readerPublicKey =
- support::certificateChainGetTopMostKey(readerCertificateChain.value());
- if (!readerPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Unable to get public key from reader certificate chain"));
- }
-
- const vector<uint8_t>& itemsRequestBytes = itemsRequest;
- vector<uint8_t> encodedReaderAuthentication =
- cppbor::Array()
- .add("ReaderAuthentication")
- .add(std::move(sessionTranscriptItem))
- .add(cppbor::Semantic(24, itemsRequestBytes))
- .encode();
- vector<uint8_t> encodedReaderAuthenticationBytes =
- cppbor::Semantic(24, encodedReaderAuthentication).encode();
- if (!support::coseCheckEcDsaSignature(readerSignature,
- encodedReaderAuthenticationBytes, // detached content
- readerPublicKey.value())) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "readerSignature check failed"));
- }
- }
-
- // Here's where we would validate the passed-in |authToken| to assure ourselves
- // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
- //
- // However this involves calculating the MAC. However this requires access
- // to the key needed to a pre-shared key which we don't have...
- //
-
- // To prevent replay-attacks, we check that the public part of the ephemeral
- // key we previously created, is present in the DeviceEngagement part of
- // SessionTranscript as a COSE_Key, in uncompressed form.
- //
- // We do this by just searching for the X and Y coordinates.
- if (sessionTranscript.size() > 0) {
- auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
- if (!getXYSuccess) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "Error extracting X and Y from ePub"));
- }
- if (sessionTranscript.size() > 0 &&
- !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(),
- ePubX.size()) != nullptr &&
- memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(),
- ePubY.size()) != nullptr)) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
- "Did not find ephemeral public key's X and Y coordinates in "
- "SessionTranscript (make sure leading zeroes are not used)"));
- }
- }
-
- // itemsRequest: If non-empty, contains request data that may be signed by the
- // reader. The content can be defined in the way appropriate for the
- // credential, but there are three requirements that must be met to work with
- // this HAL:
- if (itemsRequest.size() > 0) {
- // 1. The content must be a CBOR-encoded structure.
- auto [item, _, message] = cppbor::parse(itemsRequest);
- if (item == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
- "Error decoding CBOR in itemsRequest"));
- }
-
- // 2. The CBOR structure must be a map.
- const cppbor::Map* map = item->asMap();
- if (map == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
- "itemsRequest is not a CBOR map"));
- }
-
- // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
- // the example below.
- //
- // NameSpaces = {
- // + NameSpace => DataElements ; Requested data elements for each NameSpace
- // }
- //
- // NameSpace = tstr
- //
- // DataElements = {
- // + DataElement => IntentToRetain
- // }
- //
- // DataElement = tstr
- // IntentToRetain = bool
- //
- // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
- // through 3.:
- //
- // {
- // 'docType' : 'org.iso.18013-5.2019',
- // 'nameSpaces' : {
- // 'org.iso.18013-5.2019' : {
- // 'Last name' : false,
- // 'Birth date' : false,
- // 'First name' : false,
- // 'Home address' : true
- // },
- // 'org.aamva.iso.18013-5.2019' : {
- // 'Real Id' : false
- // }
- // }
- // }
- //
- const cppbor::Map* nsMap = nullptr;
- for (size_t n = 0; n < map->size(); n++) {
- const auto& [keyItem, valueItem] = (*map)[n];
- if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
- valueItem->type() == cppbor::MAP) {
- nsMap = valueItem->asMap();
- break;
- }
- }
- if (nsMap == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
- "No nameSpaces map in top-most map"));
- }
-
- for (size_t n = 0; n < nsMap->size(); n++) {
- auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
- const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
- const cppbor::Map* nsInnerMap = nsValueItem->asMap();
- if (nsKey == nullptr || nsInnerMap == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
- "Type mismatch in nameSpaces map"));
- }
- string requestedNamespace = nsKey->value();
- set<string> requestedKeys;
- for (size_t m = 0; m < nsInnerMap->size(); m++) {
- const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
- const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
- const cppbor::Simple* simple = innerMapValueItem->asSimple();
- const cppbor::Bool* intentToRetainItem =
- (simple != nullptr) ? simple->asBool() : nullptr;
- if (nameItem == nullptr || intentToRetainItem == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
- "Type mismatch in value in nameSpaces map"));
- }
- requestedKeys.insert(nameItem->value());
- }
- requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
- }
- }
-
- // Validate all the access control profiles in the requestData.
- bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
- for (const auto& profile : accessControlProfiles) {
- if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
- LOG(ERROR) << "Error checking MAC for profile";
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error checking MAC for profile"));
- }
- int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
- if (profile.userAuthenticationRequired) {
- if (!haveAuthToken ||
- !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
- accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
- }
- } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
- if (!readerCertificateChain ||
- !checkReaderAuthentication(profile, readerCertificateChain.value())) {
- accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
- }
- }
- profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
- }
-
- deviceNameSpacesMap_ = cppbor::Map();
- currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
-
- requestCountsRemaining_ = requestCounts;
- currentNameSpace_ = "";
-
- itemsRequest_ = itemsRequest;
- signingKeyBlob_ = signingKeyBlob;
-
- // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
- expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
-
- numStartRetrievalCalls_ += 1;
- return ndk::ScopedAStatus::ok();
-}
-
-size_t cborNumBytesForLength(size_t length) {
- if (length < 24) {
- return 0;
- } else if (length <= 0xff) {
- return 1;
- } else if (length <= 0xffff) {
- return 2;
- } else if (length <= 0xffffffff) {
- return 4;
- }
- return 8;
-}
-
-size_t cborNumBytesForTstr(const string& value) {
- return 1 + cborNumBytesForLength(value.size()) + value.size();
-}
-
-size_t IdentityCredential::calcDeviceNameSpacesSize() {
- /*
- * This is how DeviceNameSpaces is defined:
- *
- * DeviceNameSpaces = {
- * * NameSpace => DeviceSignedItems
- * }
- * DeviceSignedItems = {
- * + DataItemName => DataItemValue
- * }
- *
- * Namespace = tstr
- * DataItemName = tstr
- * DataItemValue = any
- *
- * This function will calculate its length using knowledge of how CBOR is
- * encoded.
- */
- size_t ret = 0;
- size_t numNamespacesWithValues = 0;
- for (const RequestNamespace& rns : requestNamespaces_) {
- vector<RequestDataItem> itemsToInclude;
-
- for (const RequestDataItem& rdi : rns.items) {
- // If we have a CBOR request message, skip if item isn't in it
- if (itemsRequest_.size() > 0) {
- const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
- if (it == requestedNameSpacesAndNames_.end()) {
- continue;
- }
- const set<string>& dataItemNames = it->second;
- if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
- continue;
- }
- }
-
- // Access is granted if at least one of the profiles grants access.
- //
- // If an item is configured without any profiles, access is denied.
- //
- bool authorized = false;
- for (auto id : rdi.accessControlProfileIds) {
- auto it = profileIdToAccessCheckResult_.find(id);
- if (it != profileIdToAccessCheckResult_.end()) {
- int accessControlForProfile = it->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- authorized = true;
- break;
- }
- }
- }
- if (!authorized) {
- continue;
- }
-
- itemsToInclude.push_back(rdi);
- }
-
- // If no entries are to be in the namespace, we don't include it...
- if (itemsToInclude.size() == 0) {
- continue;
- }
-
- // Key: NameSpace
- ret += cborNumBytesForTstr(rns.namespaceName);
-
- // Value: Open the DeviceSignedItems map
- ret += 1 + cborNumBytesForLength(itemsToInclude.size());
-
- for (const RequestDataItem& item : itemsToInclude) {
- // Key: DataItemName
- ret += cborNumBytesForTstr(item.name);
-
- // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
- // that.
- ret += item.size;
- }
-
- numNamespacesWithValues++;
- }
-
- // Now that we now the nunber of namespaces with values, we know how many
- // bytes the DeviceNamespaces map in the beginning is going to take up.
- ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
-
- return ret;
-}
-
-ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
- const string& nameSpace, const string& name, int32_t entrySize,
- const vector<int32_t>& accessControlProfileIds) {
- if (name.empty()) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
- }
- if (nameSpace.empty()) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
- }
-
- if (requestCountsRemaining_.size() == 0) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "No more name spaces left to go through"));
- }
-
- if (currentNameSpace_ == "") {
- // First call.
- currentNameSpace_ = nameSpace;
- }
-
- if (nameSpace == currentNameSpace_) {
- // Same namespace.
- if (requestCountsRemaining_[0] == 0) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "No more entries to be retrieved in current name space"));
- }
- requestCountsRemaining_[0] -= 1;
- } else {
- // New namespace.
- if (requestCountsRemaining_[0] != 0) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Moved to new name space but one or more entries need to be retrieved "
- "in current name space"));
- }
- if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
- deviceNameSpacesMap_.add(currentNameSpace_,
- std::move(currentNameSpaceDeviceNameSpacesMap_));
- }
- currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
-
- requestCountsRemaining_.erase(requestCountsRemaining_.begin());
- currentNameSpace_ = nameSpace;
- }
-
- // It's permissible to have an empty itemsRequest... but if non-empty you can
- // only request what was specified in said itemsRequest. Enforce that.
- if (itemsRequest_.size() > 0) {
- const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
- if (it == requestedNameSpacesAndNames_.end()) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
- "Name space was not requested in startRetrieval"));
- }
- const set<string>& dataItemNames = it->second;
- if (dataItemNames.find(name) == dataItemNames.end()) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
- "Data item name in name space was not requested in startRetrieval"));
- }
- }
-
- // Enforce access control.
- //
- // Access is granted if at least one of the profiles grants access.
- //
- // If an item is configured without any profiles, access is denied.
- //
- int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
- for (auto id : accessControlProfileIds) {
- auto search = profileIdToAccessCheckResult_.find(id);
- if (search == profileIdToAccessCheckResult_.end()) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Requested entry with unvalidated profile id"));
- }
- int accessControlForProfile = search->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- accessControl = IIdentityCredentialStore::STATUS_OK;
- break;
- }
- accessControl = accessControlForProfile;
- }
- if (accessControl != IIdentityCredentialStore::STATUS_OK) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- int(accessControl), "Access control check failed"));
- }
-
- entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
-
- currentName_ = name;
- entryRemainingBytes_ = entrySize;
- entryValue_.resize(0);
-
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
- vector<uint8_t>* outContent) {
- optional<vector<uint8_t>> content =
- support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
- if (!content) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
- }
-
- size_t chunkSize = content.value().size();
-
- if (chunkSize > entryRemainingBytes_) {
- LOG(ERROR) << "Retrieved chunk of size " << chunkSize
- << " is bigger than remaining space of size " << entryRemainingBytes_;
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Retrieved chunk is bigger than remaining space"));
- }
-
- entryRemainingBytes_ -= chunkSize;
- if (entryRemainingBytes_ > 0) {
- if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
- }
- }
-
- entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
-
- if (entryRemainingBytes_ == 0) {
- auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
- if (entryValueItem == nullptr) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Retrieved data which is invalid CBOR"));
- }
- currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
- }
-
- *outContent = content.value();
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::finishRetrieval(vector<uint8_t>* outMac,
- vector<uint8_t>* outDeviceNameSpaces) {
- if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
- deviceNameSpacesMap_.add(currentNameSpace_,
- std::move(currentNameSpaceDeviceNameSpacesMap_));
- }
- vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
-
- if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
- LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
- << "was expecting " << expectedDeviceNameSpacesSize_;
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- StringPrintf(
- "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
- encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
- .c_str()));
- }
-
- // If there's no signing key or no sessionTranscript or no reader ephemeral
- // public key, we return the empty MAC.
- optional<vector<uint8_t>> mac;
- if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
- readerPublicKey_.size() > 0) {
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> signingKey =
- support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
- if (!signingKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error decrypting signingKeyBlob"));
- }
-
- vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
- optional<vector<uint8_t>> eMacKey =
- support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
- if (!eMacKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
- }
- mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
- eMacKey.value());
- if (!mac) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
- }
- }
-
- *outMac = mac.value_or(vector<uint8_t>({}));
- *outDeviceNameSpaces = encodedDeviceNameSpaces;
- return ndk::ScopedAStatus::ok();
-}
-
-ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
- vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
- string serialDecimal = "1";
- string issuer = "Android Identity Credential Key";
- string subject = "Android Identity Credential Authentication Key";
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
-
- optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
- if (!signingKeyPKCS8) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
- }
-
- optional<vector<uint8_t>> signingPublicKey =
- support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
- if (!signingPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of signingKey"));
- }
-
- optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
- if (!signingKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of signingKey"));
- }
-
- optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
- signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
- if (!certificate) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
- }
-
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
- }
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
- storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
- if (!encryptedSigningKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
- }
- *outSigningKeyBlob = encryptedSigningKey.value();
- *outSigningKeyCertificate = Certificate();
- outSigningKeyCertificate->encodedCertificate = certificate.value();
- return ndk::ScopedAStatus::ok();
-}
-
-} // namespace aidl::android::hardware::identity