diff options
author | David Zeuthen <zeuthen@google.com> | 2020-02-11 22:04:24 -0500 |
---|---|---|
committer | David Zeuthen <zeuthen@google.com> | 2020-02-14 13:48:55 -0500 |
commit | 81603155a9582941cf558e840ffff0c90edaf330 (patch) | |
tree | 8951666994a7169ddc89e4a4c2a81207db974d89 /identity/aidl/default | |
parent | 25c2de29b9d41c6e1e2c12ba18cb0680023b9092 (diff) | |
download | platform_hardware_interfaces-81603155a9582941cf558e840ffff0c90edaf330.tar.gz platform_hardware_interfaces-81603155a9582941cf558e840ffff0c90edaf330.tar.bz2 platform_hardware_interfaces-81603155a9582941cf558e840ffff0c90edaf330.zip |
Port IdentityCredential HAL to AIDL.
This includes add a partial types-only HAL for KeyMaster for
HardwareAuthToken.
Bug: 111446262
Test: atest android.security.identity.cts
Test: VtsHalIdentityTargetTest
Test: android.hardware.identity-support-lib-test
Change-Id: I7a6254d33200bfd62269aed1957cbb2a84b16272
Diffstat (limited to 'identity/aidl/default')
-rw-r--r-- | identity/aidl/default/Android.bp | 29 | ||||
-rw-r--r-- | identity/aidl/default/IdentityCredential.cpp | 768 | ||||
-rw-r--r-- | identity/aidl/default/IdentityCredential.h | 109 | ||||
-rw-r--r-- | identity/aidl/default/IdentityCredentialStore.cpp | 74 | ||||
-rw-r--r-- | identity/aidl/default/IdentityCredentialStore.h | 48 | ||||
-rw-r--r-- | identity/aidl/default/Util.cpp | 117 | ||||
-rw-r--r-- | identity/aidl/default/Util.h | 58 | ||||
-rw-r--r-- | identity/aidl/default/WritableIdentityCredential.cpp | 421 | ||||
-rw-r--r-- | identity/aidl/default/WritableIdentityCredential.h | 90 | ||||
-rw-r--r-- | identity/aidl/default/identity-default.rc | 3 | ||||
-rw-r--r-- | identity/aidl/default/identity-default.xml | 9 | ||||
-rw-r--r-- | identity/aidl/default/service.cpp | 39 |
12 files changed, 1765 insertions, 0 deletions
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp new file mode 100644 index 0000000000..2eb0faa034 --- /dev/null +++ b/identity/aidl/default/Android.bp @@ -0,0 +1,29 @@ +cc_binary { + name: "android.hardware.identity-service.example", + relative_install_path: "hw", + init_rc: ["identity-default.rc"], + vintf_fragments: ["identity-default.xml"], + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcppbor", + "libcrypto", + "liblog", + "libutils", + "android.hardware.identity-support-lib", + "android.hardware.identity-ndk_platform", + "android.hardware.keymaster-ndk_platform", + ], + srcs: [ + "IdentityCredential.cpp", + "IdentityCredentialStore.cpp", + "WritableIdentityCredential.cpp", + "Util.cpp", + "service.cpp", + ], +} diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp new file mode 100644 index 0000000000..d5b3a0ffee --- /dev/null +++ b/identity/aidl/default/IdentityCredential.cpp @@ -0,0 +1,768 @@ +/* + * 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 <cppbor.h> +#include <cppbor_parse.h> + +namespace aidl::android::hardware::identity { + +using ::aidl::android::hardware::keymaster::Timestamp; +using ::std::optional; + +using namespace ::android::hardware::identity; + +int IdentityCredential::initialize() { + 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<int8_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 = byteStringToSigned(signature.value()); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<int8_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 = byteStringToSigned(kp.value()); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey( + const vector<int8_t>& publicKey) { + readerPublicKey_ = byteStringToUnsigned(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; + 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( + byteStringToUnsigned(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; +} + +Timestamp clockGetTime() { + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + Timestamp ts; + ts.milliSeconds = time.tv_sec * 1000 + time.tv_nsec / 1000000; + return ts; +} + +bool checkUserAuthentication(const SecureAccessControlProfile& profile, + 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 (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 doesn't match the challenge we created"; + return false; + } + return true; + } + + // Note that the Epoch for timestamps in HardwareAuthToken is at the + // discretion of the vendor: + // + // "[...] since some starting point (generally the most recent device + // boot) which all of the applications within one secure environment + // must agree upon." + // + // Therefore, if this software implementation is used on a device which isn't + // the emulator then the assumption that the epoch is the same as used in + // clockGetTime above will not hold. This is OK as this software + // implementation should never be used on a real device. + // + Timestamp now = clockGetTime(); + 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::startRetrieval( + const vector<SecureAccessControlProfile>& accessControlProfiles, + const HardwareAuthToken& authToken, const vector<int8_t>& itemsRequestS, + const vector<int8_t>& sessionTranscriptS, const vector<int8_t>& readerSignatureS, + const vector<int32_t>& requestCounts) { + auto sessionTranscript = byteStringToUnsigned(sessionTranscriptS); + auto itemsRequest = byteStringToUnsigned(itemsRequestS); + auto readerSignature = byteStringToUnsigned(readerSignatureS); + + 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> dataThatWasSigned = cppbor::Array() + .add("ReaderAuthentication") + .add(sessionTranscriptItem_->clone()) + .add(cppbor::Semantic(24, itemsRequestBytes)) + .encode(); + if (!support::coseCheckEcDsaSignature(readerSignature, + dataThatWasSigned, // 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) { + const cppbor::Array* array = sessionTranscriptItem_->asArray(); + if (array == nullptr || array->size() != 2) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + "SessionTranscript is not an array with two items")); + } + const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic(); + if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + "First item in SessionTranscript array is not a " + "semantic with value 24")); + } + const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr(); + if (encodedDE == nullptr) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + "Child of semantic in first item in SessionTranscript " + "array is not a bstr")); + } + const vector<uint8_t>& bytesDE = encodedDE->value(); + + 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(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr && + memmem(bytesDE.data(), bytesDE.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(); + vector<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.push_back(nameItem->value()); + } + requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys; + } + } + + // Finally, validate all the access control profiles in the requestData. + bool haveAuthToken = (authToken.mac.size() > 0); + for (const auto& profile : accessControlProfiles) { + if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { + 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, 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; + + numStartRetrievalCalls_ += 1; + return ndk::ScopedAStatus::ok(); +} + +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 auto& dataItemNames = it->second; + if (std::find(dataItemNames.begin(), dataItemNames.end(), 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<int8_t>& encryptedContentS, + vector<int8_t>* outContent) { + auto encryptedContent = byteStringToUnsigned(encryptedContentS); + + 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 = byteStringToSigned(content.value()); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredential::finishRetrieval(const vector<int8_t>& signingKeyBlobS, + vector<int8_t>* outMac, + vector<int8_t>* outDeviceNameSpaces) { + auto signingKeyBlob = byteStringToUnsigned(signingKeyBlobS); + + if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) { + deviceNameSpacesMap_.add(currentNameSpace_, + std::move(currentNameSpaceDeviceNameSpacesMap_)); + } + vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode(); + + // 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) { + cppbor::Array array; + array.add("DeviceAuthentication"); + array.add(sessionTranscriptItem_->clone()); + array.add(docType_); + array.add(cppbor::Semantic(24, encodedDeviceNameSpaces)); + vector<uint8_t> encodedDeviceAuthentication = array.encode(); + + 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")); + } + + optional<vector<uint8_t>> sharedSecret = + support::ecdh(readerPublicKey_, signingKey.value()); + if (!sharedSecret) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH")); + } + + vector<uint8_t> salt = {0x00}; + vector<uint8_t> info = {}; + optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32); + if (!derivedKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error deriving key from shared secret")); + } + + mac = support::coseMac0(derivedKey.value(), {}, // payload + encodedDeviceAuthentication); // additionalData + if (!mac) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error MACing data")); + } + } + + *outMac = byteStringToSigned(mac.value_or(vector<uint8_t>({}))); + *outDeviceNameSpaces = byteStringToSigned(encodedDeviceNameSpaces); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair( + vector<int8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) { + string serialDecimal = "0"; // TODO: set serial to something unique + string issuer = "Android Open Source Project"; + string subject = "Android IdentityCredential Reference Implementation"; + 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 = byteStringToSigned(encryptedSigningKey.value()); + *outSigningKeyCertificate = Certificate(); + outSigningKeyCertificate->encodedCertificate = byteStringToSigned(certificate.value()); + return ndk::ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/IdentityCredential.h new file mode 100644 index 0000000000..49ed0d45b8 --- /dev/null +++ b/identity/aidl/default/IdentityCredential.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H +#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H + +#include <aidl/android/hardware/identity/BnIdentityCredential.h> +#include <aidl/android/hardware/keymaster/HardwareAuthToken.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include <cppbor/cppbor.h> + +namespace aidl::android::hardware::identity { + +using ::aidl::android::hardware::keymaster::HardwareAuthToken; +using ::std::map; +using ::std::string; +using ::std::vector; + +using MapStringToVectorOfStrings = map<string, vector<string>>; + +class IdentityCredential : public BnIdentityCredential { + public: + IdentityCredential(const vector<uint8_t>& credentialData) + : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {} + + // Parses and decrypts credentialData_, return a status code from + // IIdentityCredentialStore. Must be called right after construction. + int initialize(); + + // Methods from IIdentityCredential follow. + ndk::ScopedAStatus deleteCredential(vector<int8_t>* outProofOfDeletionSignature) override; + ndk::ScopedAStatus createEphemeralKeyPair(vector<int8_t>* outKeyPair) override; + ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<int8_t>& publicKey) override; + ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override; + ndk::ScopedAStatus startRetrieval( + const vector<SecureAccessControlProfile>& accessControlProfiles, + const HardwareAuthToken& authToken, const vector<int8_t>& itemsRequest, + const vector<int8_t>& sessionTranscript, const vector<int8_t>& readerSignature, + const vector<int32_t>& requestCounts) override; + ndk::ScopedAStatus startRetrieveEntryValue( + const string& nameSpace, const string& name, int32_t entrySize, + const vector<int32_t>& accessControlProfileIds) override; + ndk::ScopedAStatus retrieveEntryValue(const vector<int8_t>& encryptedContent, + vector<int8_t>* outContent) override; + ndk::ScopedAStatus finishRetrieval(const vector<int8_t>& signingKeyBlob, vector<int8_t>* outMac, + vector<int8_t>* outDeviceNameSpaces) override; + ndk::ScopedAStatus generateSigningKeyPair(vector<int8_t>* outSigningKeyBlob, + Certificate* outSigningKeyCertificate) override; + + private: + // Set by constructor + vector<uint8_t> credentialData_; + int numStartRetrievalCalls_; + + // Set by initialize() + string docType_; + bool testCredential_; + vector<uint8_t> storageKey_; + vector<uint8_t> credentialPrivKey_; + + // Set by createEphemeralKeyPair() + vector<uint8_t> ephemeralPublicKey_; + + // Set by setReaderEphemeralPublicKey() + vector<uint8_t> readerPublicKey_; + + // Set by createAuthChallenge() + uint64_t authChallenge_; + + // Set at startRetrieval() time. + map<int32_t, int> profileIdToAccessCheckResult_; + vector<uint8_t> sessionTranscript_; + std::unique_ptr<cppbor::Item> sessionTranscriptItem_; + vector<uint8_t> itemsRequest_; + vector<int32_t> requestCountsRemaining_; + MapStringToVectorOfStrings requestedNameSpacesAndNames_; + cppbor::Map deviceNameSpacesMap_; + cppbor::Map currentNameSpaceDeviceNameSpacesMap_; + + // Set at startRetrieveEntryValue() time. + string currentNameSpace_; + string currentName_; + size_t entryRemainingBytes_; + vector<uint8_t> entryValue_; + vector<uint8_t> entryAdditionalData_; +}; + +} // namespace aidl::android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/IdentityCredentialStore.cpp new file mode 100644 index 0000000000..1efb4b4937 --- /dev/null +++ b/identity/aidl/default/IdentityCredentialStore.cpp @@ -0,0 +1,74 @@ +/* + * 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 "IdentityCredentialStore" + +#include <android-base/logging.h> + +#include "IdentityCredential.h" +#include "IdentityCredentialStore.h" +#include "WritableIdentityCredential.h" + +namespace aidl::android::hardware::identity { + +ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation( + HardwareInformation* hardwareInformation) { + HardwareInformation hw; + hw.credentialStoreName = "Identity Credential Reference Implementation"; + hw.credentialStoreAuthorName = "Google"; + hw.dataChunkSize = kGcmChunkSize; + hw.isDirectAccess = false; + hw.supportedDocTypes = {}; + *hardwareInformation = hw; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredentialStore::createCredential( + const string& docType, bool testCredential, + shared_ptr<IWritableIdentityCredential>* outWritableCredential) { + shared_ptr<WritableIdentityCredential> wc = + ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential); + if (!wc->initialize()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error initializing WritableIdentityCredential")); + } + *outWritableCredential = wc; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus IdentityCredentialStore::getCredential( + CipherSuite cipherSuite, const vector<int8_t>& credentialData, + shared_ptr<IIdentityCredential>* outCredential) { + // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right now. + if (cipherSuite != CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED, + "Unsupported cipher suite")); + } + + vector<uint8_t> data = vector<uint8_t>(credentialData.begin(), credentialData.end()); + shared_ptr<IdentityCredential> credential = ndk::SharedRefBase::make<IdentityCredential>(data); + auto ret = credential->initialize(); + if (ret != IIdentityCredentialStore::STATUS_OK) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + int(ret), "Error initializing IdentityCredential")); + } + *outCredential = credential; + return ndk::ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/IdentityCredentialStore.h new file mode 100644 index 0000000000..a2051130b0 --- /dev/null +++ b/identity/aidl/default/IdentityCredentialStore.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H +#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H + +#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h> + +namespace aidl::android::hardware::identity { + +using ::std::shared_ptr; +using ::std::string; +using ::std::vector; + +class IdentityCredentialStore : public BnIdentityCredentialStore { + public: + IdentityCredentialStore() {} + + // The GCM chunk size used by this implementation is 64 KiB. + static constexpr size_t kGcmChunkSize = 64 * 1024; + + // Methods from IIdentityCredentialStore follow. + ndk::ScopedAStatus getHardwareInformation(HardwareInformation* hardwareInformation) override; + + ndk::ScopedAStatus createCredential( + const string& docType, bool testCredential, + shared_ptr<IWritableIdentityCredential>* outWritableCredential) override; + + ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<int8_t>& credentialData, + shared_ptr<IIdentityCredential>* outCredential) override; +}; + +} // namespace aidl::android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp new file mode 100644 index 0000000000..a0f86bedcd --- /dev/null +++ b/identity/aidl/default/Util.cpp @@ -0,0 +1,117 @@ +/* + * 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 "Util" + +#include "Util.h" + +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <string.h> + +#include <android-base/logging.h> + +#include <cppbor.h> +#include <cppbor_parse.h> + +namespace aidl::android::hardware::identity { + +using namespace ::android::hardware::identity; + +// This is not a very random HBK but that's OK because this is the SW +// implementation where it can't be kept secret. +vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + +const vector<uint8_t>& getHardwareBoundKey() { + return hardwareBoundKey; +} + +vector<uint8_t> byteStringToUnsigned(const vector<int8_t>& value) { + return vector<uint8_t>(value.begin(), value.end()); +} + +vector<int8_t> byteStringToSigned(const vector<uint8_t>& value) { + return vector<int8_t>(value.begin(), value.end()); +} + +vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) { + cppbor::Map map; + map.add("id", profile.id); + + if (profile.readerCertificate.encodedCertificate.size() > 0) { + map.add("readerCertificate", + cppbor::Bstr(byteStringToUnsigned(profile.readerCertificate.encodedCertificate))); + } + + if (profile.userAuthenticationRequired) { + map.add("userAuthenticationRequired", profile.userAuthenticationRequired); + map.add("timeoutMillis", profile.timeoutMillis); + map.add("secureUserId", profile.secureUserId); + } + + return map.encode(); +} + +optional<vector<uint8_t>> secureAccessControlProfileCalcMac( + const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) { + vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile); + + optional<vector<uint8_t>> nonce = support::getRandom(12); + if (!nonce) { + return {}; + } + optional<vector<uint8_t>> macO = + support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData); + if (!macO) { + return {}; + } + return macO.value(); +} + +bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, + const vector<uint8_t>& storageKey) { + vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile); + + if (profile.mac.size() < support::kAesGcmIvSize) { + return false; + } + vector<uint8_t> nonce = + vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize); + optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData); + if (!mac) { + return false; + } + if (mac.value() != byteStringToUnsigned(profile.mac)) { + return false; + } + return true; +} + +vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name, + const vector<int32_t> accessControlProfileIds) { + cppbor::Map map; + map.add("Namespace", nameSpace); + map.add("Name", name); + + cppbor::Array acpIds; + for (auto id : accessControlProfileIds) { + acpIds.add(id); + } + map.add("AccessControlProfileIds", std::move(acpIds)); + return map.encode(); +} + +} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h new file mode 100644 index 0000000000..ee41ad1aac --- /dev/null +++ b/identity/aidl/default/Util.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_UTIL_H +#define ANDROID_HARDWARE_IDENTITY_UTIL_H + +#include <aidl/android/hardware/identity/BnIdentityCredential.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <map> +#include <optional> +#include <set> +#include <string> +#include <vector> + +#include <cppbor/cppbor.h> + +namespace aidl::android::hardware::identity { + +using ::std::optional; +using ::std::string; +using ::std::vector; + +// Returns the hardware-bound AES-128 key. +const vector<uint8_t>& getHardwareBoundKey(); + +// Calculates the MAC for |profile| using |storageKey|. +optional<vector<uint8_t>> secureAccessControlProfileCalcMac( + const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey); + +// Checks authenticity of the MAC in |profile| using |storageKey|. +bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile, + const vector<uint8_t>& storageKey); + +// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method. +vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name, + const vector<int32_t> accessControlProfileIds); + +vector<uint8_t> byteStringToUnsigned(const vector<int8_t>& value); + +vector<int8_t> byteStringToSigned(const vector<uint8_t>& value); + +} // namespace aidl::android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp new file mode 100644 index 0000000000..ba2062d7c0 --- /dev/null +++ b/identity/aidl/default/WritableIdentityCredential.cpp @@ -0,0 +1,421 @@ +/* + * 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 "WritableIdentityCredential" + +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <android-base/logging.h> + +#include <cppbor/cppbor.h> +#include <cppbor/cppbor_parse.h> + +#include "IdentityCredentialStore.h" +#include "Util.h" +#include "WritableIdentityCredential.h" + +namespace aidl::android::hardware::identity { + +using ::std::optional; +using namespace ::android::hardware::identity; + +bool WritableIdentityCredential::initialize() { + optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); + if (!keyPair) { + LOG(ERROR) << "Error creating credentialKey"; + return false; + } + + optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); + if (!pubKey) { + LOG(ERROR) << "Error getting public part of credentialKey"; + return false; + } + credentialPubKey_ = pubKey.value(); + + optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); + if (!privKey) { + LOG(ERROR) << "Error getting private part of credentialKey"; + return false; + } + credentialPrivKey_ = privKey.value(); + + optional<vector<uint8_t>> random = support::getRandom(16); + if (!random) { + LOG(ERROR) << "Error creating storageKey"; + return false; + } + storageKey_ = random.value(); + + return true; +} + +// TODO: use |attestationApplicationId| and |attestationChallenge| and also +// ensure the returned certificate chain satisfy the requirements listed in +// the docs for IWritableIdentityCredential::getAttestationCertificate() +// +ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( + const vector<int8_t>& /*attestationApplicationId*/, + const vector<int8_t>& /*attestationChallenge*/, vector<Certificate>* outCertificateChain) { + // For now, we dynamically generate an attestion key on each and every + // request and use that to sign CredentialKey. In a real implementation this + // would look very differently. + optional<vector<uint8_t>> attestationKeyPair = support::createEcKeyPair(); + if (!attestationKeyPair) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error creating attestationKey")); + } + + optional<vector<uint8_t>> attestationPubKey = + support::ecKeyPairGetPublicKey(attestationKeyPair.value()); + if (!attestationPubKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error getting public part of attestationKey")); + } + + optional<vector<uint8_t>> attestationPrivKey = + support::ecKeyPairGetPrivateKey(attestationKeyPair.value()); + if (!attestationPrivKey) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error getting private part of attestationKey")); + } + + string serialDecimal; + string issuer; + string subject; + time_t validityNotBefore = time(nullptr); + time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600; + + // First create a certificate for |credentialPubKey| which is signed by + // |attestationPrivKey|. + // + serialDecimal = "0"; // TODO: set serial to |attestationChallenge| + issuer = "Android Open Source Project"; + subject = "Android IdentityCredential CredentialKey"; + optional<vector<uint8_t>> credentialPubKeyCertificate = support::ecPublicKeyGenerateCertificate( + credentialPubKey_, attestationPrivKey.value(), serialDecimal, issuer, subject, + validityNotBefore, validityNotAfter); + if (!credentialPubKeyCertificate) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error creating certificate for credentialPubKey")); + } + + // This is followed by a certificate for |attestationPubKey| self-signed by + // |attestationPrivKey|. + serialDecimal = "0"; // TODO: set serial + issuer = "Android Open Source Project"; + subject = "Android IdentityCredential AttestationKey"; + optional<vector<uint8_t>> attestationKeyCertificate = support::ecPublicKeyGenerateCertificate( + attestationPubKey.value(), attestationPrivKey.value(), serialDecimal, issuer, subject, + validityNotBefore, validityNotAfter); + if (!attestationKeyCertificate) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, + "Error creating certificate for attestationPubKey")); + } + + // Concatenate the certificates to form the chain. + vector<uint8_t> certificateChain; + certificateChain.insert(certificateChain.end(), credentialPubKeyCertificate.value().begin(), + credentialPubKeyCertificate.value().end()); + certificateChain.insert(certificateChain.end(), attestationKeyCertificate.value().begin(), + attestationKeyCertificate.value().end()); + + optional<vector<vector<uint8_t>>> splitCertChain = + support::certificateChainSplit(certificateChain); + if (!splitCertChain) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error splitting certificate chain")); + } + *outCertificateChain = vector<Certificate>(); + for (const vector<uint8_t>& cert : splitCertChain.value()) { + Certificate c = Certificate(); + c.encodedCertificate = byteStringToSigned(cert); + outCertificateChain->push_back(std::move(c)); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( + int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) { + numAccessControlProfileRemaining_ = accessControlProfileCount; + remainingEntryCounts_ = entryCounts; + entryNameSpace_ = ""; + + signedDataAccessControlProfiles_ = cppbor::Array(); + signedDataNamespaces_ = cppbor::Map(); + signedDataCurrentNamespace_ = cppbor::Array(); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( + int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired, + int64_t timeoutMillis, int64_t secureUserId, + SecureAccessControlProfile* outSecureAccessControlProfile) { + SecureAccessControlProfile profile; + + if (numAccessControlProfileRemaining_ == 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "numAccessControlProfileRemaining_ is 0 and expected non-zero")); + } + + // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also + // be zero. + if (!userAuthenticationRequired && timeoutMillis != 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "userAuthenticationRequired is false but timeout is non-zero")); + } + + profile.id = id; + profile.readerCertificate = readerCertificate; + profile.userAuthenticationRequired = userAuthenticationRequired; + profile.timeoutMillis = timeoutMillis; + profile.secureUserId = secureUserId; + optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_); + if (!mac) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile")); + } + profile.mac = byteStringToSigned(mac.value()); + + cppbor::Map profileMap; + profileMap.add("id", profile.id); + if (profile.readerCertificate.encodedCertificate.size() > 0) { + profileMap.add( + "readerCertificate", + cppbor::Bstr(byteStringToUnsigned(profile.readerCertificate.encodedCertificate))); + } + if (profile.userAuthenticationRequired) { + profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired); + profileMap.add("timeoutMillis", profile.timeoutMillis); + } + signedDataAccessControlProfiles_.add(std::move(profileMap)); + + numAccessControlProfileRemaining_--; + + *outSecureAccessControlProfile = profile; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry( + const vector<int32_t>& accessControlProfileIds, const string& nameSpace, const string& name, + int32_t entrySize) { + if (numAccessControlProfileRemaining_ != 0) { + LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_ + << " and expected zero"; + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "numAccessControlProfileRemaining_ is not zero")); + } + + if (remainingEntryCounts_.size() == 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, "No more namespaces to add to")); + } + + // Handle initial beginEntry() call. + if (entryNameSpace_ == "") { + entryNameSpace_ = nameSpace; + } + + // If the namespace changed... + if (nameSpace != entryNameSpace_) { + // Then check that all entries in the previous namespace have been added.. + if (remainingEntryCounts_[0] != 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "New namespace but a non-zero number of entries remain to be added")); + } + remainingEntryCounts_.erase(remainingEntryCounts_.begin()); + + if (signedDataCurrentNamespace_.size() > 0) { + signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_)); + signedDataCurrentNamespace_ = cppbor::Array(); + } + } else { + // Same namespace... + if (remainingEntryCounts_[0] == 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Same namespace but no entries remain to be added")); + } + remainingEntryCounts_[0] -= 1; + } + + entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds); + + entryRemainingBytes_ = entrySize; + entryNameSpace_ = nameSpace; + entryName_ = name; + entryAccessControlProfileIds_ = accessControlProfileIds; + entryBytes_.resize(0); + // LOG(INFO) << "name=" << name << " entrySize=" << entrySize; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<int8_t>& contentS, + vector<int8_t>* outEncryptedContent) { + auto content = byteStringToUnsigned(contentS); + size_t contentSize = content.size(); + + if (contentSize > IdentityCredentialStore::kGcmChunkSize) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Passed in chunk of is bigger than kGcmChunkSize")); + } + if (contentSize > entryRemainingBytes_) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Passed in chunk is bigger than remaining space")); + } + + entryBytes_.insert(entryBytes_.end(), content.begin(), content.end()); + entryRemainingBytes_ -= contentSize; + if (entryRemainingBytes_ > 0) { + if (contentSize != IdentityCredentialStore::kGcmChunkSize) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Retrieved non-final chunk which isn't kGcmChunkSize")); + } + } + + optional<vector<uint8_t>> nonce = support::getRandom(12); + if (!nonce) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce")); + } + optional<vector<uint8_t>> encryptedContent = + support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_); + if (!encryptedContent) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content")); + } + + if (entryRemainingBytes_ == 0) { + // TODO: ideally do do this without parsing the data (but still validate data is valid + // CBOR). + auto [item, _, message] = cppbor::parse(entryBytes_); + if (item == nullptr) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, "Data is not valid CBOR")); + } + cppbor::Map entryMap; + entryMap.add("name", entryName_); + entryMap.add("value", std::move(item)); + cppbor::Array profileIdArray; + for (auto id : entryAccessControlProfileIds_) { + profileIdArray.add(id); + } + entryMap.add("accessControlProfiles", std::move(profileIdArray)); + signedDataCurrentNamespace_.add(std::move(entryMap)); + } + + *outEncryptedContent = byteStringToSigned(encryptedContent.value()); + return ndk::ScopedAStatus::ok(); +} + +// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and +// |credentialPrivKey|. +static bool generateCredentialKeys(const vector<uint8_t>& storageKey, + const vector<uint8_t>& credentialPrivKey, + vector<uint8_t>& credentialKeys) { + if (storageKey.size() != 16) { + LOG(ERROR) << "Size of storageKey is not 16"; + return false; + } + + cppbor::Array array; + array.add(cppbor::Bstr(storageKey)); + array.add(cppbor::Bstr(credentialPrivKey)); + credentialKeys = array.encode(); + return true; +} + +// Writes CBOR-encoded structure to |credentialData| containing |docType|, +// |testCredential| and |credentialKeys|. The latter element will be stored in +// encrypted form, using |hardwareBoundKey| as the encryption key. +bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType, + bool testCredential, const vector<uint8_t>& credentialKeys, + vector<uint8_t>& credentialData) { + optional<vector<uint8_t>> nonce = support::getRandom(12); + if (!nonce) { + LOG(ERROR) << "Error getting random"; + return false; + } + vector<uint8_t> docTypeAsVec(docType.begin(), docType.end()); + optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm( + hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec); + if (!credentialBlob) { + LOG(ERROR) << "Error encrypting CredentialKeys blob"; + return false; + } + + cppbor::Array array; + array.add(docType); + array.add(testCredential); + array.add(cppbor::Bstr(credentialBlob.value())); + credentialData = array.encode(); + return true; +} + +ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( + vector<int8_t>* outCredentialData, vector<int8_t>* outProofOfProvisioningSignature) { + if (signedDataCurrentNamespace_.size() > 0) { + signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_)); + } + cppbor::Array popArray; + popArray.add("ProofOfProvisioning") + .add(docType_) + .add(std::move(signedDataAccessControlProfiles_)) + .add(std::move(signedDataNamespaces_)) + .add(testCredential_); + vector<uint8_t> encodedCbor = popArray.encode(); + + optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_, + encodedCbor, // payload + {}, // additionalData + {}); // certificateChain + if (!signature) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error signing data")); + } + + vector<uint8_t> credentialKeys; + if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys")); + } + + vector<uint8_t> credentialData; + if (!generateCredentialData( + testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(), + docType_, testCredential_, credentialKeys, credentialData)) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData")); + } + + *outCredentialData = byteStringToSigned(credentialData); + *outProofOfProvisioningSignature = byteStringToSigned(signature.value()); + return ndk::ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::identity diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h new file mode 100644 index 0000000000..b380f897a1 --- /dev/null +++ b/identity/aidl/default/WritableIdentityCredential.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H +#define ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H + +#include <aidl/android/hardware/identity/BnWritableIdentityCredential.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> + +#include <cppbor.h> + +namespace aidl::android::hardware::identity { + +using ::std::string; +using ::std::vector; + +class WritableIdentityCredential : public BnWritableIdentityCredential { + public: + WritableIdentityCredential(const string& docType, bool testCredential) + : docType_(docType), testCredential_(testCredential) {} + + // Creates the Credential Key. Returns false on failure. Must be called + // right after construction. + bool initialize(); + + // Methods from IWritableIdentityCredential follow. + ndk::ScopedAStatus getAttestationCertificate(const vector<int8_t>& attestationApplicationId, + const vector<int8_t>& attestationChallenge, + vector<Certificate>* outCertificateChain) override; + + ndk::ScopedAStatus startPersonalization(int32_t accessControlProfileCount, + const vector<int32_t>& entryCounts) override; + + ndk::ScopedAStatus addAccessControlProfile( + int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired, + int64_t timeoutMillis, int64_t secureUserId, + SecureAccessControlProfile* outSecureAccessControlProfile) override; + + ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds, + const string& nameSpace, const string& name, + int32_t entrySize) override; + + ndk::ScopedAStatus addEntryValue(const vector<int8_t>& content, + vector<int8_t>* outEncryptedContent) override; + + ndk::ScopedAStatus finishAddingEntries( + vector<int8_t>* outCredentialData, + vector<int8_t>* outProofOfProvisioningSignature) override; + + // private: + string docType_; + bool testCredential_; + + // These are set in initialize(). + vector<uint8_t> storageKey_; + vector<uint8_t> credentialPrivKey_; + vector<uint8_t> credentialPubKey_; + + // These fields are initialized during startPersonalization() + size_t numAccessControlProfileRemaining_; + vector<int32_t> remainingEntryCounts_; + cppbor::Array signedDataAccessControlProfiles_; + cppbor::Map signedDataNamespaces_; + cppbor::Array signedDataCurrentNamespace_; + + // These fields are initialized during beginAddEntry() + size_t entryRemainingBytes_; + vector<uint8_t> entryAdditionalData_; + string entryNameSpace_; + string entryName_; + vector<int32_t> entryAccessControlProfileIds_; + vector<uint8_t> entryBytes_; +}; + +} // namespace aidl::android::hardware::identity + +#endif // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H diff --git a/identity/aidl/default/identity-default.rc b/identity/aidl/default/identity-default.rc new file mode 100644 index 0000000000..d3b62c1042 --- /dev/null +++ b/identity/aidl/default/identity-default.rc @@ -0,0 +1,3 @@ +service vendor.identity-default /vendor/bin/hw/android.hardware.identity-service.example + class hal + user nobody diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml new file mode 100644 index 0000000000..a47d354ce5 --- /dev/null +++ b/identity/aidl/default/identity-default.xml @@ -0,0 +1,9 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.identity</name> + <interface> + <name>IIdentityCredentialStore</name> + <instance>default</instance> + </interface> + </hal> +</manifest> diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp new file mode 100644 index 0000000000..f05c615001 --- /dev/null +++ b/identity/aidl/default/service.cpp @@ -0,0 +1,39 @@ +/* + * 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 "android.hardware.identity-service" + +#include <android-base/logging.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "IdentityCredentialStore.h" + +using aidl::android::hardware::identity::IdentityCredentialStore; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + std::shared_ptr<IdentityCredentialStore> store = + ndk::SharedRefBase::make<IdentityCredentialStore>(); + + const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default"; + LOG(INFO) << "instance: " << instance; + binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} |