/*
 * 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 = cppbor::prettyPrint(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::SemanticTag(24, deviceEngagementBytes))
                                              .add(cppbor::SemanticTag(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 = cppbor::prettyPrint(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::SemanticTag(24, itemsRequestBytes))
                    .encode();
    vector<uint8_t> encodedReaderAuthenticationBytes =
            cppbor::SemanticTag(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> ecdsaSignature;
    vector<uint8_t> deviceNameSpacesEncoded;
    // API version 5 (feature version 202301) returns both MAC and ECDSA signature.
    if (halApiVersion_ >= 5) {
        ASSERT_TRUE(credential
                            ->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
                                                           &ecdsaSignature)
                            .isOk());
        ASSERT_GT(ecdsaSignature.size(), 0);
    } else {
        ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
    }
    cborPretty = cppbor::prettyPrint(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::SemanticTag(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);

    if (ecdsaSignature.size() > 0) {
        vector<uint8_t> encodedDeviceAuthentication =
                cppbor::Array()
                        .add("DeviceAuthentication")
                        .add(sessionTranscript.clone())
                        .add(docType)
                        .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
                        .encode();
        vector<uint8_t> deviceAuthenticationBytes =
                cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
        EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
                                                     deviceAuthenticationBytes,  // Detached content
                                                     signingPubKey.value()));
    }

    // 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());
    // API version 5 (feature version 202301) returns both MAC and ECDSA signature.
    if (halApiVersion_ >= 5) {
        ASSERT_TRUE(credential
                            ->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
                                                           &ecdsaSignature)
                            .isOk());
        ASSERT_GT(ecdsaSignature.size(), 0);
    } else {
        ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
    }
    cborPretty = cppbor::prettyPrint(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);

    if (ecdsaSignature.size() > 0) {
        vector<uint8_t> encodedDeviceAuthentication =
                cppbor::Array()
                        .add("DeviceAuthentication")
                        .add(sessionTranscript.clone())
                        .add(docType)
                        .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
                        .encode();
        vector<uint8_t> deviceAuthenticationBytes =
                cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
        EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
                                                     deviceAuthenticationBytes,  // Detached content
                                                     signingPubKey.value()));
    }

    // 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());
    // API version 5 (feature version 202301) returns both MAC and ECDSA signature.
    if (halApiVersion_ >= 5) {
        ASSERT_TRUE(credential
                            ->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
                                                           &ecdsaSignature)
                            .isOk());
        ASSERT_GT(ecdsaSignature.size(), 0);
    } else {
        ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesEncoded).isOk());
    }
    cborPretty = cppbor::prettyPrint(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);

    if (ecdsaSignature.size() > 0) {
        vector<uint8_t> encodedDeviceAuthentication =
                cppbor::Array()
                        .add("DeviceAuthentication")
                        .add(sessionTranscript.clone())
                        .add(docType)
                        .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
                        .encode();
        vector<uint8_t> deviceAuthenticationBytes =
                cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
        EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
                                                     deviceAuthenticationBytes,  // Detached content
                                                     signingPubKey.value()));
    }
}

TEST_P(EndToEndTests, noSessionEncryption) {
    if (halApiVersion_ < 5) {
        GTEST_SKIP() << "Need HAL API version 5, have " << halApiVersion_;
    }

    const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (no authentication)
                                                          {0, {}, false, 0}};

    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}},
            {"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0}},
            {"PersonalData", "First name", string("Alan"), vector<int32_t>{0}},
    };
    const vector<int32_t> testEntriesEntryCounts = {3};
    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;

    // 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 = 230;
    // 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 = cppbor::prettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
    EXPECT_EQ(
            "[\n"
            "  'ProofOfProvisioning',\n"
            "  'org.iso.18013-5.2019.mdl',\n"
            "  [\n"
            "    {\n"
            "      'id' : 0,\n"
            "    },\n"
            "  ],\n"
            "  {\n"
            "    'PersonalData' : [\n"
            "      {\n"
            "        'name' : 'Last name',\n"
            "        'value' : 'Turing',\n"
            "        'accessControlProfiles' : [0, ],\n"
            "      },\n"
            "      {\n"
            "        'name' : 'Birth date',\n"
            "        'value' : '19120623',\n"
            "        'accessControlProfiles' : [0, ],\n"
            "      },\n"
            "      {\n"
            "        'name' : 'First name',\n"
            "        'value' : 'Alan',\n"
            "        'accessControlProfiles' : [0, ],\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());

    sp<IIdentityCredential> credential;
    ASSERT_TRUE(credentialStore_
                        ->getCredential(
                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
                                credentialData, &credential)
                        .isOk());
    ASSERT_NE(credential, nullptr);

    // Calculate sessionTranscript, make something that resembles what you'd use for
    // an over-the-Internet presentation not using mdoc session encryption.
    cppbor::Array sessionTranscript =
            cppbor::Array()
                    .add(cppbor::Null())  // DeviceEngagementBytes isn't used.
                    .add(cppbor::Null())  // EReaderKeyBytes isn't used.
                    .add(cppbor::Array()  // Proprietary handover structure follows.
                                 .add(cppbor::Tstr("TestHandover"))
                                 .add(cppbor::Bstr(vector<uint8_t>{1, 2, 3}))
                                 .add(cppbor::Bstr(vector<uint8_t>{9, 8, 7, 6})));
    vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode();

    // 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);

    vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
    ASSERT_TRUE(credential->setRequestedNamespaces(requestedNamespaces).isOk());
    ASSERT_TRUE(credential->setVerificationToken(verificationToken).isOk());
    Status status = credential->startRetrieval(
            secureProfiles.value(), authToken, {} /* itemsRequestBytes*/, signingKeyBlob,
            sessionTranscriptEncoded, {} /* readerSignature */, testEntriesEntryCounts);
    ASSERT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();

    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);
    }

    vector<uint8_t> mac;
    vector<uint8_t> ecdsaSignature;
    vector<uint8_t> deviceNameSpacesEncoded;
    status = credential->finishRetrievalWithSignature(&mac, &deviceNameSpacesEncoded,
                                                      &ecdsaSignature);
    ASSERT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage();
    // MACing should NOT work since we're not using session encryption
    ASSERT_EQ(0, mac.size());

    // ECDSA signatures should work, however. Check this.
    ASSERT_GT(ecdsaSignature.size(), 0);

    cborPretty = cppbor::prettyPrint(deviceNameSpacesEncoded, 32, {});
    ASSERT_EQ(
            "{\n"
            "  'PersonalData' : {\n"
            "    'Last name' : 'Turing',\n"
            "    'Birth date' : '19120623',\n"
            "    'First name' : 'Alan',\n"
            "  },\n"
            "}",
            cborPretty);

    string docType = "org.iso.18013-5.2019.mdl";

    vector<uint8_t> encodedDeviceAuthentication =
            cppbor::Array()
                    .add("DeviceAuthentication")
                    .add(sessionTranscript.clone())
                    .add(docType)
                    .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded))
                    .encode();
    vector<uint8_t> deviceAuthenticationBytes =
            cppbor::SemanticTag(24, encodedDeviceAuthentication).encode();
    EXPECT_TRUE(support::coseCheckEcDsaSignature(ecdsaSignature,
                                                 deviceAuthenticationBytes,  // Detached content
                                                 signingPubKey.value()));
}

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();
}
