/* * 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 "VtsHalIdentityTargetTest" #include #include #include #include #include #include #include #include #include #include #include #include #include "VtsIdentityTestUtils.h" namespace android::hardware::identity { using std::endl; using std::map; using std::optional; using std::string; using std::vector; using ::android::sp; using ::android::String16; using ::android::binder::Status; using ::android::hardware::keymaster::HardwareAuthToken; class IdentityAidl : public testing::TestWithParam { public: virtual void SetUp() override { credentialStore_ = android::waitForDeclaredService( String16(GetParam().c_str())); ASSERT_NE(credentialStore_, nullptr); } sp credentialStore_; }; TEST_P(IdentityAidl, 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); } TEST_P(IdentityAidl, 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}}; HardwareAuthToken authToken; // 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_)); string challenge = "attestationChallenge"; test_utils::AttestationData attData(writableCredential, challenge, {}); ASSERT_TRUE(attData.result.isOk()) << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; ASSERT_EQ(binder::Status::EX_NONE, attData.result.exceptionCode()); ASSERT_EQ(IIdentityCredentialStore::STATUS_OK, attData.result.serviceSpecificErrorCode()); // TODO: set it to something random and check it's in the cert chain ASSERT_GE(attData.attestationCertificate.size(), 2); 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()); optional> 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' : ,\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; // 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::Semantic(24, deviceEngagementBytes)) .add(cppbor::Semantic(24, eReaderPubBytes)); vector sessionTranscriptBytes = 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 = 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 dataToSign = cppbor::Array() .add("ReaderAuthentication") .add(sessionTranscript.clone()) .add(cppbor::Semantic(24, itemsRequestBytes)) .encode(); optional> readerSignature = support::coseSignEcDsa(readerKey, {}, // content dataToSign, // 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()); ASSERT_TRUE(credential ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes, signingKeyBlob, sessionTranscriptBytes, 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); } vector mac; vector deviceNameSpacesBytes; ASSERT_TRUE(credential->finishRetrieval(&mac, &deviceNameSpacesBytes).isOk()); cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 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); // The data that is MACed is ["DeviceAuthentication", sessionTranscriptBytes, docType, // deviceNameSpacesBytes] so build up that structure cppbor::Array deviceAuthentication; deviceAuthentication.add("DeviceAuthentication"); deviceAuthentication.add(sessionTranscript.clone()); string docType = "org.iso.18013-5.2019.mdl"; deviceAuthentication.add(docType); deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes)); vector encodedDeviceAuthentication = deviceAuthentication.encode(); optional> signingPublicKey = support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate); EXPECT_TRUE(signingPublicKey); // Derive the key used for MACing. optional> readerEphemeralPrivateKey = support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value()); optional> sharedSecret = support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value()); ASSERT_TRUE(sharedSecret); vector salt = {0x00}; vector info = {}; optional> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32); ASSERT_TRUE(derivedKey); optional> calculatedMac = support::coseMac0(derivedKey.value(), {}, // payload encodedDeviceAuthentication); // detached content ASSERT_TRUE(calculatedMac); EXPECT_EQ(mac, calculatedMac); } INSTANTIATE_TEST_SUITE_P( Identity, IdentityAidl, testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), android::PrintInstanceNameToString); // INSTANTIATE_TEST_SUITE_P(Identity, IdentityAidl, // testing::Values("android.hardware.identity.IIdentityCredentialStore/default")); } // 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(); }