/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 { public: virtual void SetUp() override { credentialStore_ = android::waitForDeclaredService( String16(GetParam().c_str())); ASSERT_NE(credentialStore_, nullptr); halApiVersion_ = credentialStore_->getInterfaceVersion(); } sp 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, vector, vector> extractFromTestCredentialData(const vector& credentialData) { string docType; vector storageKey; vector credentialPrivKey; vector 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 hardwareBoundKey = support::getTestHardwareBoundKey(); const vector& encryptedCredentialKeys = encryptedCredentialKeysItem->value(); const vector docTypeVec(docType.begin(), docType.end()); optional> 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 readerKey; optional> 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 portraitImage; test_utils::setImageData(portraitImage); // Access control profiles: const vector 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 testEntries = { {"PersonalData", "Last name", string("Turing"), vector{0, 1}}, {"PersonalData", "Birth date", string("19120623"), vector{0, 1}}, {"PersonalData", "First name", string("Alan"), vector{0, 1}}, {"PersonalData", "Home address", string("Maida Vale, London, England"), vector{0}}, {"Image", "Portrait image", portraitImage, vector{0, 1}}, }; const vector testEntriesEntryCounts = {static_cast(testEntries.size() - 1), 1u}; HardwareInformation hwInfo; ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); string cborPretty; sp 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> 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>> encryptedBlobs; for (const auto& entry : testEntries) { ASSERT_TRUE(test_utils::addEntry(writableCredential, entry, hwInfo.dataChunkSize, encryptedBlobs, true)); } vector credentialData; vector proofOfProvisioningSignature; ASSERT_TRUE( writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature) .isOk()); // Validate the proofOfProvisioning which was returned optional> 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' : ,\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' : ,\n" " 'accessControlProfiles' : [0, 1, ],\n" " },\n" " ],\n" " },\n" " true,\n" "]", cborPretty); optional> 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> exCredentialKeyPair = support::ecPrivateKeyToKeyPair(exCredentialPrivKey); ASSERT_TRUE(exCredentialKeyPair); optional> 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 credential; ASSERT_TRUE(credentialStore_ ->getCredential( CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, credentialData, &credential) .isOk()); ASSERT_NE(credential, nullptr); optional> readerEphemeralKeyPair = support::createEcKeyPair(); ASSERT_TRUE(readerEphemeralKeyPair); optional> readerEphemeralPublicKey = support::ecKeyPairGetPublicKey(readerEphemeralKeyPair.value()); ASSERT_TRUE(credential->setReaderEphemeralPublicKey(readerEphemeralPublicKey.value()).isOk()); vector ephemeralKeyPair; ASSERT_TRUE(credential->createEphemeralKeyPair(&ephemeralKeyPair).isOk()); optional> 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 deviceEngagementBytes = deviceEngagement.encode(); vector eReaderPubBytes = cppbor::Tstr("ignored").encode(); cppbor::Array sessionTranscript = cppbor::Array() .add(cppbor::SemanticTag(24, deviceEngagementBytes)) .add(cppbor::SemanticTag(24, eReaderPubBytes)); vector sessionTranscriptEncoded = sessionTranscript.encode(); vector 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 encodedReaderAuthentication = cppbor::Array() .add("ReaderAuthentication") .add(sessionTranscript.clone()) .add(cppbor::SemanticTag(24, itemsRequestBytes)) .encode(); vector encodedReaderAuthenticationBytes = cppbor::SemanticTag(24, encodedReaderAuthentication).encode(); optional> readerSignature = support::coseSignEcDsa(readerKey, {}, // content encodedReaderAuthenticationBytes, // detached content readerCertificate.value()); ASSERT_TRUE(readerSignature); // Generate the key that will be used to sign AuthenticatedData. vector signingKeyBlob; Certificate signingKeyCertificate; ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); optional> 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 exDocTypeVec(exDocType.begin(), exDocType.end()); optional> exSigningPrivKey = support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec); ASSERT_TRUE(exSigningPrivKey); optional> exSigningKeyPair = support::ecPrivateKeyToKeyPair(exSigningPrivKey.value()); ASSERT_TRUE(exSigningKeyPair); optional> exSigningPubKey = support::ecKeyPairGetPublicKey(exSigningKeyPair.value()); ASSERT_TRUE(exSigningPubKey); ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value()); vector 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>& encryptedChunks = it->second; vector content; for (const auto& encryptedChunk : encryptedChunks) { vector 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 mac; vector ecdsaSignature; vector 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' : ,\n" " },\n" "}", cborPretty); string docType = "org.iso.18013-5.2019.mdl"; optional> readerEphemeralPrivateKey = support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value()); optional> eMacKey = support::calcEMacKey(readerEphemeralPrivateKey.value(), // Private Key signingPubKey.value(), // Public Key cppbor::SemanticTag(24, sessionTranscript.encode()) .encode()); // SessionTranscriptBytes optional> 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 encodedDeviceAuthentication = cppbor::Array() .add("DeviceAuthentication") .add(sessionTranscript.clone()) .add(docType) .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded)) .encode(); vector 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 encodedDeviceAuthentication = cppbor::Array() .add("DeviceAuthentication") .add(sessionTranscript.clone()) .add(docType) .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded)) .encode(); vector 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 encodedDeviceAuthentication = cppbor::Array() .add("DeviceAuthentication") .add(sessionTranscript.clone()) .add(docType) .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded)) .encode(); vector 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 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 testEntries = { {"PersonalData", "Last name", string("Turing"), vector{0}}, {"PersonalData", "Birth date", string("19120623"), vector{0}}, {"PersonalData", "First name", string("Alan"), vector{0}}, }; const vector testEntriesEntryCounts = {3}; HardwareInformation hwInfo; ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); string cborPretty; sp 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> 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>> encryptedBlobs; for (const auto& entry : testEntries) { ASSERT_TRUE(test_utils::addEntry(writableCredential, entry, hwInfo.dataChunkSize, encryptedBlobs, true)); } vector credentialData; vector proofOfProvisioningSignature; ASSERT_TRUE( writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature) .isOk()); // Validate the proofOfProvisioning which was returned optional> 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> 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> exCredentialKeyPair = support::ecPrivateKeyToKeyPair(exCredentialPrivKey); ASSERT_TRUE(exCredentialKeyPair); optional> exCredentialPubKey = support::ecKeyPairGetPublicKey(exCredentialKeyPair.value()); ASSERT_TRUE(exCredentialPubKey); ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value()); sp 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{1, 2, 3})) .add(cppbor::Bstr(vector{9, 8, 7, 6}))); vector sessionTranscriptEncoded = sessionTranscript.encode(); // Generate the key that will be used to sign AuthenticatedData. vector signingKeyBlob; Certificate signingKeyCertificate; ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); optional> signingPubKey = support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate); EXPECT_TRUE(signingPubKey); test_utils::verifyAuthKeyCertificate(signingKeyCertificate.encodedCertificate); vector 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>& encryptedChunks = it->second; vector content; for (const auto& encryptedChunk : encryptedChunks) { vector chunk; ASSERT_TRUE(credential->retrieveEntryValue(encryptedChunk, &chunk).isOk()); content.insert(content.end(), chunk.begin(), chunk.end()); } EXPECT_EQ(content, entry.valueCbor); } vector mac; vector ecdsaSignature; vector 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 encodedDeviceAuthentication = cppbor::Array() .add("DeviceAuthentication") .add(sessionTranscript.clone()) .add(docType) .add(cppbor::SemanticTag(24, deviceNameSpacesEncoded)) .encode(); vector 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(); }