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
diff --git a/identity/aidl/vts/EndToEndTests.cpp b/identity/aidl/vts/EndToEndTests.cpp
new file mode 100644
index 0000000..5798b4c
--- /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();
+}