diff options
author | David Zeuthen <zeuthen@google.com> | 2020-10-16 11:27:24 -0400 |
---|---|---|
committer | David Zeuthen <zeuthen@google.com> | 2021-01-23 13:35:57 -0500 |
commit | 49f2d2558ac417d090dfae9c78ab372d71e5140c (patch) | |
tree | be240ccdfb0fa1e45aa03648cb385ff7ce82d84f /identity/aidl/vts/EndToEndTests.cpp | |
parent | eafa06164d1e1bafbe20562d540ab5420bb0f825 (diff) |
Identity Credential changes for Android 12
- Add IIdentityCredential.deleteCredentialWithChallenge()
- Deprecate IIdentityCredential.deleteCredential()
- Add IIdentityCredential.proveOwership()
- Add IIdentityCredential.updateCredential()
- Add ProofOfBinding CBOR to AuthenticationKey X.509 certificate
- Document which API versions new methods/features appeared in.
- Mention need to declare android.hardware.identity_credential system
feature (w/ feature version number) and do this for the default
implementation.
Bug: 170146643
Test: atest VtsHalIdentityTargetTest
Change-Id: Ib47c7caa5f3d6fff6919f019eee44a735dba9cf8
Diffstat (limited to 'identity/aidl/vts/EndToEndTests.cpp')
-rw-r--r-- | identity/aidl/vts/EndToEndTests.cpp | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/identity/aidl/vts/EndToEndTests.cpp b/identity/aidl/vts/EndToEndTests.cpp new file mode 100644 index 0000000000..5798b4c28c --- /dev/null +++ b/identity/aidl/vts/EndToEndTests.cpp @@ -0,0 +1,535 @@ +/* + * Copyright (C) 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 "VtsHalIdentityEndToEndTest" + +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/hardware/identity/IIdentityCredentialStore.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <cppbor.h> +#include <cppbor_parse.h> +#include <gtest/gtest.h> +#include <future> +#include <map> +#include <tuple> + +#include "Util.h" + +namespace android::hardware::identity { + +using std::endl; +using std::make_tuple; +using std::map; +using std::optional; +using std::string; +using std::tuple; +using std::vector; + +using ::android::sp; +using ::android::String16; +using ::android::binder::Status; + +using ::android::hardware::keymaster::HardwareAuthToken; +using ::android::hardware::keymaster::VerificationToken; + +using test_utils::validateAttestationCertificate; + +class EndToEndTests : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>( + String16(GetParam().c_str())); + ASSERT_NE(credentialStore_, nullptr); + halApiVersion_ = credentialStore_->getInterfaceVersion(); + } + + sp<IIdentityCredentialStore> credentialStore_; + int halApiVersion_; +}; + +TEST_P(EndToEndTests, hardwareInformation) { + HardwareInformation info; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&info).isOk()); + ASSERT_GT(info.credentialStoreName.size(), 0); + ASSERT_GT(info.credentialStoreAuthorName.size(), 0); + ASSERT_GE(info.dataChunkSize, 256); +} + +tuple<bool, string, vector<uint8_t>, vector<uint8_t>, vector<uint8_t>> +extractFromTestCredentialData(const vector<uint8_t>& credentialData) { + string docType; + vector<uint8_t> storageKey; + vector<uint8_t> credentialPrivKey; + vector<uint8_t> sha256Pop; + + auto [item, _, message] = cppbor::parse(credentialData); + if (item == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + + const cppbor::Array* arrayItem = item->asArray(); + if (arrayItem == nullptr || arrayItem->size() != 3) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + + 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) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + + docType = docTypeItem->value(); + + vector<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey(); + 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) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + + auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value()); + if (dckItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + const cppbor::Array* dckArrayItem = dckItem->asArray(); + if (dckArrayItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + if (dckArrayItem->size() < 2) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr(); + const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr(); + if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + storageKey = storageKeyItem->value(); + credentialPrivKey = credentialPrivKeyItem->value(); + if (dckArrayItem->size() == 3) { + const cppbor::Bstr* sha256PopItem = (*dckArrayItem)[2]->asBstr(); + if (sha256PopItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey, sha256Pop); + } + sha256Pop = sha256PopItem->value(); + } + return make_tuple(true, docType, storageKey, credentialPrivKey, sha256Pop); +} + +TEST_P(EndToEndTests, createAndRetrieveCredential) { + // First, generate a key-pair for the reader since its public key will be + // part of the request data. + vector<uint8_t> readerKey; + optional<vector<uint8_t>> readerCertificate = + test_utils::generateReaderCertificate("1234", &readerKey); + ASSERT_TRUE(readerCertificate); + + // Make the portrait image really big (just shy of 256 KiB) to ensure that + // the chunking code gets exercised. + vector<uint8_t> portraitImage; + test_utils::setImageData(portraitImage); + + // Access control profiles: + const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication) + {0, readerCertificate.value(), false, 0}, + // Profile 1 (no authentication) + {1, {}, false, 0}}; + + // It doesn't matter since no user auth is needed in this particular test, + // but for good measure, clear out the tokens we pass to the HAL. + HardwareAuthToken authToken; + VerificationToken verificationToken; + authToken.challenge = 0; + authToken.userId = 0; + authToken.authenticatorId = 0; + authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE; + authToken.timestamp.milliSeconds = 0; + authToken.mac.clear(); + verificationToken.challenge = 0; + verificationToken.timestamp.milliSeconds = 0; + verificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE; + verificationToken.mac.clear(); + + // Here's the actual test data: + const vector<test_utils::TestEntryData> testEntries = { + {"PersonalData", "Last name", string("Turing"), vector<int32_t>{0, 1}}, + {"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}}, + {"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}}, + {"PersonalData", "Home address", string("Maida Vale, London, England"), + vector<int32_t>{0}}, + {"Image", "Portrait image", portraitImage, vector<int32_t>{0, 1}}, + }; + const vector<int32_t> testEntriesEntryCounts = {static_cast<int32_t>(testEntries.size() - 1), + 1u}; + HardwareInformation hwInfo; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); + + string cborPretty; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_, + true /* testCredential */)); + + string challenge = "attestationChallenge"; + test_utils::AttestationData attData(writableCredential, challenge, + {1} /* atteestationApplicationId */); + ASSERT_TRUE(attData.result.isOk()) + << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; + + validateAttestationCertificate(attData.attestationCertificate, attData.attestationChallenge, + attData.attestationApplicationId, true); + + // This is kinda of a hack but we need to give the size of + // ProofOfProvisioning that we'll expect to receive. + const int32_t expectedProofOfProvisioningSize = 262861 - 326 + readerCertificate.value().size(); + // OK to fail, not available in v1 HAL + writableCredential->setExpectedProofOfProvisioningSize(expectedProofOfProvisioningSize); + ASSERT_TRUE( + writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts) + .isOk()); + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::addAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + // Uses TestEntryData* pointer as key and values are the encrypted blobs. This + // is a little hacky but it works well enough. + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; + + for (const auto& entry : testEntries) { + ASSERT_TRUE(test_utils::addEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector<uint8_t> credentialData; + vector<uint8_t> proofOfProvisioningSignature; + ASSERT_TRUE( + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature) + .isOk()); + + // Validate the proofOfProvisioning which was returned + optional<vector<uint8_t>> proofOfProvisioning = + support::coseSignGetPayload(proofOfProvisioningSignature); + ASSERT_TRUE(proofOfProvisioning); + cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"}); + EXPECT_EQ( + "[\n" + " 'ProofOfProvisioning',\n" + " 'org.iso.18013-5.2019.mdl',\n" + " [\n" + " {\n" + " 'id' : 0,\n" + " 'readerCertificate' : <not printed>,\n" + " },\n" + " {\n" + " 'id' : 1,\n" + " },\n" + " ],\n" + " {\n" + " 'PersonalData' : [\n" + " {\n" + " 'name' : 'Last name',\n" + " 'value' : 'Turing',\n" + " 'accessControlProfiles' : [0, 1, ],\n" + " },\n" + " {\n" + " 'name' : 'Birth date',\n" + " 'value' : '19120623',\n" + " 'accessControlProfiles' : [0, 1, ],\n" + " },\n" + " {\n" + " 'name' : 'First name',\n" + " 'value' : 'Alan',\n" + " 'accessControlProfiles' : [0, 1, ],\n" + " },\n" + " {\n" + " 'name' : 'Home address',\n" + " 'value' : 'Maida Vale, London, England',\n" + " 'accessControlProfiles' : [0, ],\n" + " },\n" + " ],\n" + " 'Image' : [\n" + " {\n" + " 'name' : 'Portrait image',\n" + " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n" + " 'accessControlProfiles' : [0, 1, ],\n" + " },\n" + " ],\n" + " },\n" + " true,\n" + "]", + cborPretty); + + optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey( + attData.attestationCertificate[0].encodedCertificate); + ASSERT_TRUE(credentialPubKey); + EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature, + {}, // Additional data + credentialPubKey.value())); + writableCredential = nullptr; + + // Extract doctype, storage key, and credentialPrivKey from credentialData... this works + // only because we asked for a test-credential meaning that the HBK is all zeroes. + auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey, exSha256Pop] = + extractFromTestCredentialData(credentialData); + + ASSERT_TRUE(exSuccess); + ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl"); + // ... check that the public key derived from the private key matches what was + // in the certificate. + optional<vector<uint8_t>> exCredentialKeyPair = + support::ecPrivateKeyToKeyPair(exCredentialPrivKey); + ASSERT_TRUE(exCredentialKeyPair); + optional<vector<uint8_t>> exCredentialPubKey = + support::ecKeyPairGetPublicKey(exCredentialKeyPair.value()); + ASSERT_TRUE(exCredentialPubKey); + ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value()); + + // Starting with API version 3 (feature version 202101) we require SHA-256(ProofOfProvisioning) + // to be in CredentialKeys (which is stored encrypted in CredentialData). Check + // that it's there with the expected value. + if (halApiVersion_ >= 3) { + ASSERT_EQ(exSha256Pop, support::sha256(proofOfProvisioning.value())); + } + + // Now that the credential has been provisioned, read it back and check the + // correct data is returned. + sp<IIdentityCredential> credential; + ASSERT_TRUE(credentialStore_ + ->getCredential( + CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, + credentialData, &credential) + .isOk()); + ASSERT_NE(credential, nullptr); + + optional<vector<uint8_t>> readerEphemeralKeyPair = support::createEcKeyPair(); + ASSERT_TRUE(readerEphemeralKeyPair); + optional<vector<uint8_t>> readerEphemeralPublicKey = + support::ecKeyPairGetPublicKey(readerEphemeralKeyPair.value()); + ASSERT_TRUE(credential->setReaderEphemeralPublicKey(readerEphemeralPublicKey.value()).isOk()); + + vector<uint8_t> ephemeralKeyPair; + ASSERT_TRUE(credential->createEphemeralKeyPair(&ephemeralKeyPair).isOk()); + optional<vector<uint8_t>> ephemeralPublicKey = support::ecKeyPairGetPublicKey(ephemeralKeyPair); + + // Calculate requestData field and sign it with the reader key. + auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ephemeralPublicKey.value()); + ASSERT_TRUE(getXYSuccess); + cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY); + vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode(); + vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode(); + cppbor::Array sessionTranscript = cppbor::Array() + .add(cppbor::Semantic(24, deviceEngagementBytes)) + .add(cppbor::Semantic(24, eReaderPubBytes)); + vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode(); + + vector<uint8_t> itemsRequestBytes = + cppbor::Map("nameSpaces", + cppbor::Map() + .add("PersonalData", cppbor::Map() + .add("Last name", false) + .add("Birth date", false) + .add("First name", false) + .add("Home address", true)) + .add("Image", cppbor::Map().add("Portrait image", false))) + .encode(); + cborPretty = support::cborPrettyPrint(itemsRequestBytes, 32, {"EphemeralPublicKey"}); + EXPECT_EQ( + "{\n" + " 'nameSpaces' : {\n" + " 'PersonalData' : {\n" + " 'Last name' : false,\n" + " 'Birth date' : false,\n" + " 'First name' : false,\n" + " 'Home address' : true,\n" + " },\n" + " 'Image' : {\n" + " 'Portrait image' : false,\n" + " },\n" + " },\n" + "}", + cborPretty); + vector<uint8_t> encodedReaderAuthentication = + cppbor::Array() + .add("ReaderAuthentication") + .add(sessionTranscript.clone()) + .add(cppbor::Semantic(24, itemsRequestBytes)) + .encode(); + vector<uint8_t> encodedReaderAuthenticationBytes = + cppbor::Semantic(24, encodedReaderAuthentication).encode(); + optional<vector<uint8_t>> readerSignature = + support::coseSignEcDsa(readerKey, {}, // content + encodedReaderAuthenticationBytes, // detached content + readerCertificate.value()); + ASSERT_TRUE(readerSignature); + + // Generate the key that will be used to sign AuthenticatedData. + vector<uint8_t> signingKeyBlob; + Certificate signingKeyCertificate; + ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); + optional<vector<uint8_t>> signingPubKey = + support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate); + EXPECT_TRUE(signingPubKey); + test_utils::verifyAuthKeyCertificate(signingKeyCertificate.encodedCertificate); + + // Since we're using a test-credential we know storageKey meaning we can get the + // private key. Do this, derive the public key from it, and check this matches what + // is in the certificate... + const vector<uint8_t> exDocTypeVec(exDocType.begin(), exDocType.end()); + optional<vector<uint8_t>> exSigningPrivKey = + support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec); + ASSERT_TRUE(exSigningPrivKey); + optional<vector<uint8_t>> exSigningKeyPair = + support::ecPrivateKeyToKeyPair(exSigningPrivKey.value()); + ASSERT_TRUE(exSigningKeyPair); + optional<vector<uint8_t>> exSigningPubKey = + support::ecKeyPairGetPublicKey(exSigningKeyPair.value()); + ASSERT_TRUE(exSigningPubKey); + ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value()); + + vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries); + // OK to fail, not available in v1 HAL + credential->setRequestedNamespaces(requestedNamespaces); + // OK to fail, not available in v1 HAL + credential->setVerificationToken(verificationToken); + ASSERT_TRUE(credential + ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes, + signingKeyBlob, sessionTranscriptEncoded, + readerSignature.value(), testEntriesEntryCounts) + .isOk()); + + for (const auto& entry : testEntries) { + ASSERT_TRUE(credential + ->startRetrieveEntryValue(entry.nameSpace, entry.name, + entry.valueCbor.size(), entry.profileIds) + .isOk()); + + auto it = encryptedBlobs.find(&entry); + ASSERT_NE(it, encryptedBlobs.end()); + const vector<vector<uint8_t>>& encryptedChunks = it->second; + + vector<uint8_t> content; + for (const auto& encryptedChunk : encryptedChunks) { + vector<uint8_t> chunk; + ASSERT_TRUE(credential->retrieveEntryValue(encryptedChunk, &chunk).isOk()); + content.insert(content.end(), chunk.begin(), chunk.end()); + } + EXPECT_EQ(content, entry.valueCbor); + + // TODO: also use |exStorageKey| to decrypt data and check it's the same as whatt + // the HAL returns... + } + + vector<uint8_t> mac; + vector<uint8_t> deviceNameSpacesEncoded; + ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk()); + cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {}); + ASSERT_EQ( + "{\n" + " 'PersonalData' : {\n" + " 'Last name' : 'Turing',\n" + " 'Birth date' : '19120623',\n" + " 'First name' : 'Alan',\n" + " 'Home address' : 'Maida Vale, London, England',\n" + " },\n" + " 'Image' : {\n" + " 'Portrait image' : <bstr size=262134 " + "sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n" + " },\n" + "}", + cborPretty); + + string docType = "org.iso.18013-5.2019.mdl"; + optional<vector<uint8_t>> readerEphemeralPrivateKey = + support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value()); + optional<vector<uint8_t>> eMacKey = support::calcEMacKey( + readerEphemeralPrivateKey.value(), // Private Key + signingPubKey.value(), // Public Key + cppbor::Semantic(24, sessionTranscript.encode()).encode()); // SessionTranscriptBytes + optional<vector<uint8_t>> calculatedMac = + support::calcMac(sessionTranscript.encode(), // SessionTranscript + docType, // DocType + deviceNameSpacesEncoded, // DeviceNamespaces + eMacKey.value()); // EMacKey + ASSERT_TRUE(calculatedMac); + EXPECT_EQ(mac, calculatedMac); + + // Also perform an additional empty request. This is what mDL applications + // are envisioned to do - one call to get the data elements, another to get + // an empty DeviceSignedItems and corresponding MAC. + // + credential->setRequestedNamespaces({}); // OK to fail, not available in v1 HAL + ASSERT_TRUE(credential + ->startRetrieval( + secureProfiles.value(), authToken, {}, // itemsRequestBytes + signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature, + testEntriesEntryCounts) + .isOk()); + ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk()); + cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {}); + ASSERT_EQ("{}", cborPretty); + // Calculate DeviceAuthentication and MAC (MACing key hasn't changed) + calculatedMac = support::calcMac(sessionTranscript.encode(), // SessionTranscript + docType, // DocType + deviceNameSpacesEncoded, // DeviceNamespaces + eMacKey.value()); // EMacKey + ASSERT_TRUE(calculatedMac); + EXPECT_EQ(mac, calculatedMac); + + // Some mDL apps might send a request but with a single empty + // namespace. Check that too. + RequestNamespace emptyRequestNS; + emptyRequestNS.namespaceName = "PersonalData"; + credential->setRequestedNamespaces({emptyRequestNS}); // OK to fail, not available in v1 HAL + ASSERT_TRUE(credential + ->startRetrieval( + secureProfiles.value(), authToken, {}, // itemsRequestBytes + signingKeyBlob, sessionTranscriptEncoded, {}, // readerSignature, + testEntriesEntryCounts) + .isOk()); + ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk()); + cborPretty = support::cborPrettyPrint(deviceNameSpacesEncoded, 32, {}); + ASSERT_EQ("{}", cborPretty); + // Calculate DeviceAuthentication and MAC (MACing key hasn't changed) + calculatedMac = support::calcMac(sessionTranscript.encode(), // SessionTranscript + docType, // DocType + deviceNameSpacesEncoded, // DeviceNamespaces + eMacKey.value()); // EMacKey + ASSERT_TRUE(calculatedMac); + EXPECT_EQ(mac, calculatedMac); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EndToEndTests); +INSTANTIATE_TEST_SUITE_P( + Identity, EndToEndTests, + testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), + android::PrintInstanceNameToString); + +} // namespace android::hardware::identity + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::android::ProcessState::self()->setThreadPoolMaxThreadCount(1); + ::android::ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +} |