/* * 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 "ReaderAuthTests" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "VtsIdentityTestUtils.h" namespace android::hardware::identity { using std::endl; using std::make_pair; using std::map; using std::optional; using std::pair; using std::string; using std::tie; using std::vector; using ::android::sp; using ::android::String16; using ::android::binder::Status; using ::android::hardware::keymaster::HardwareAuthToken; using ::android::hardware::keymaster::VerificationToken; class ReaderAuthTests : public testing::TestWithParam { public: virtual void SetUp() override { credentialStore_ = android::waitForDeclaredService( String16(GetParam().c_str())); ASSERT_NE(credentialStore_, nullptr); } void provisionData(); void retrieveData(const vector& readerPrivateKey, const vector>& readerCertChain, bool expectSuccess, bool leaveOutAccessibleToAllFromRequestMessage); // Set by provisionData vector readerPublicKey_; vector readerPrivateKey_; vector intermediateAPublicKey_; vector intermediateAPrivateKey_; vector intermediateBPublicKey_; vector intermediateBPrivateKey_; vector intermediateCPublicKey_; vector intermediateCPrivateKey_; vector cert_A_SelfSigned_; vector cert_B_SelfSigned_; vector cert_B_SignedBy_C_; vector cert_C_SelfSigned_; vector cert_reader_SelfSigned_; vector cert_reader_SignedBy_A_; vector cert_reader_SignedBy_B_; SecureAccessControlProfile sacp0_; SecureAccessControlProfile sacp1_; SecureAccessControlProfile sacp2_; SecureAccessControlProfile sacp3_; vector encContentAccessibleByA_; vector encContentAccessibleByAorB_; vector encContentAccessibleByB_; vector encContentAccessibleByC_; vector encContentAccessibleByAll_; vector encContentAccessibleByNone_; vector credentialData_; // Set by retrieveData() bool canGetAccessibleByA_; bool canGetAccessibleByAorB_; bool canGetAccessibleByB_; bool canGetAccessibleByC_; bool canGetAccessibleByAll_; bool canGetAccessibleByNone_; sp credentialStore_; }; pair, vector> generateReaderKey() { optional> keyPKCS8 = support::createEcKeyPair(); optional> publicKey = support::ecKeyPairGetPublicKey(keyPKCS8.value()); optional> privateKey = support::ecKeyPairGetPrivateKey(keyPKCS8.value()); return make_pair(publicKey.value(), privateKey.value()); } vector generateReaderCert(const vector& publicKey, const vector& signingKey) { time_t validityNotBefore = 0; time_t validityNotAfter = 0xffffffff; optional> cert = support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer", "Subject", validityNotBefore, validityNotAfter); return cert.value(); } void ReaderAuthTests::provisionData() { // Keys and certificates for intermediates. tie(intermediateAPublicKey_, intermediateAPrivateKey_) = generateReaderKey(); tie(intermediateBPublicKey_, intermediateBPrivateKey_) = generateReaderKey(); tie(intermediateCPublicKey_, intermediateCPrivateKey_) = generateReaderKey(); cert_A_SelfSigned_ = generateReaderCert(intermediateAPublicKey_, intermediateAPrivateKey_); cert_B_SelfSigned_ = generateReaderCert(intermediateBPublicKey_, intermediateBPrivateKey_); cert_B_SignedBy_C_ = generateReaderCert(intermediateBPublicKey_, intermediateCPrivateKey_); cert_C_SelfSigned_ = generateReaderCert(intermediateCPublicKey_, intermediateCPrivateKey_); // Key and self-signed certificate reader tie(readerPublicKey_, readerPrivateKey_) = generateReaderKey(); cert_reader_SelfSigned_ = generateReaderCert(readerPublicKey_, readerPrivateKey_); // Certificate for reader signed by intermediates cert_reader_SignedBy_A_ = generateReaderCert(readerPublicKey_, intermediateAPrivateKey_); cert_reader_SignedBy_B_ = generateReaderCert(readerPublicKey_, intermediateBPrivateKey_); string docType = "org.iso.18013-5.2019.mdl"; bool testCredential = true; sp wc; ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk()); vector attestationApplicationId = {}; vector attestationChallenge = {1}; vector certChain; ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge, &certChain) .isOk()); size_t proofOfProvisioningSize = 465 + cert_A_SelfSigned_.size() + cert_B_SelfSigned_.size() + cert_C_SelfSigned_.size(); ASSERT_TRUE(wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize).isOk()); // Not in v1 HAL, may fail wc->startPersonalization(4 /* numAccessControlProfiles */, {6} /* numDataElementsPerNamespace */); // AIDL expects certificates wrapped in the Certificate type... Certificate cert_A; Certificate cert_B; Certificate cert_C; cert_A.encodedCertificate = cert_A_SelfSigned_; cert_B.encodedCertificate = cert_B_SelfSigned_; cert_C.encodedCertificate = cert_C_SelfSigned_; // Access control profile 0: accessible by A ASSERT_TRUE(wc->addAccessControlProfile(0, cert_A, false, 0, 0, &sacp0_).isOk()); // Access control profile 1: accessible by B ASSERT_TRUE(wc->addAccessControlProfile(1, cert_B, false, 0, 0, &sacp1_).isOk()); // Access control profile 2: accessible by C ASSERT_TRUE(wc->addAccessControlProfile(2, cert_C, false, 0, 0, &sacp2_).isOk()); // Access control profile 3: open access ASSERT_TRUE(wc->addAccessControlProfile(3, {}, false, 0, 0, &sacp3_).isOk()); // Data Element: "Accessible by A" ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Accessible by A", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByA_).isOk()); // Data Element: "Accessible by A or B" ASSERT_TRUE(wc->beginAddEntry({0, 1}, "ns", "Accessible by A or B", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAorB_).isOk()); // Data Element: "Accessible by B" ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Accessible by B", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByB_).isOk()); // Data Element: "Accessible by C" ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by C", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByC_).isOk()); // Data Element: "Accessible by All" ASSERT_TRUE(wc->beginAddEntry({3}, "ns", "Accessible by All", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk()); // Data Element: "Accessible by None" ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk()); ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk()); vector proofOfProvisioningSignature; ASSERT_TRUE(wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature).isOk()); } RequestDataItem buildRequestDataItem(const string& name, size_t size, vector accessControlProfileIds) { RequestDataItem item; item.name = name; item.size = size; item.accessControlProfileIds = accessControlProfileIds; return item; } void ReaderAuthTests::retrieveData(const vector& readerPrivateKey, const vector>& readerCertChain, bool expectSuccess, bool leaveOutAccessibleToAllFromRequestMessage) { canGetAccessibleByA_ = false; canGetAccessibleByAorB_ = false; canGetAccessibleByB_ = false; canGetAccessibleByC_ = false; canGetAccessibleByAll_ = false; canGetAccessibleByNone_ = false; sp c; ASSERT_TRUE(credentialStore_ ->getCredential( CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, credentialData_, &c) .isOk()); optional> readerEKeyPair = support::createEcKeyPair(); optional> readerEPublicKey = support::ecKeyPairGetPublicKey(readerEKeyPair.value()); ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk()); vector eKeyPair; ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk()); optional> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair); // Calculate requestData field and sign it with the reader key. auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.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; if (leaveOutAccessibleToAllFromRequestMessage) { itemsRequestBytes = cppbor::Map("nameSpaces", cppbor::Map().add("ns", cppbor::Map() .add("Accessible by A", false) .add("Accessible by A or B", false) .add("Accessible by B", false) .add("Accessible by C", false) .add("Accessible by None", false))) .encode(); } else { itemsRequestBytes = cppbor::Map("nameSpaces", cppbor::Map().add("ns", cppbor::Map() .add("Accessible by A", false) .add("Accessible by A or B", false) .add("Accessible by B", false) .add("Accessible by C", false) .add("Accessible by All", false) .add("Accessible by None", false))) .encode(); } vector encodedReaderAuthentication = cppbor::Array() .add("ReaderAuthentication") .add(sessionTranscript.clone()) .add(cppbor::Semantic(24, itemsRequestBytes)) .encode(); vector encodedReaderAuthenticationBytes = cppbor::Semantic(24, encodedReaderAuthentication).encode(); optional> readerSignature = support::coseSignEcDsa(readerPrivateKey, // private key for reader {}, // content encodedReaderAuthenticationBytes, // detached content support::certificateChainJoin(readerCertChain)); ASSERT_TRUE(readerSignature); // Generate the key that will be used to sign AuthenticatedData. vector signingKeyBlob; Certificate signingKeyCertificate; ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); RequestNamespace rns; rns.namespaceName = "ns"; rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0})); rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1})); rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1})); rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2})); rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3})); rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {})); // OK to fail, not available in v1 HAL c->setRequestedNamespaces({rns}).isOk(); // 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(); // OK to fail, not available in v1 HAL c->setVerificationToken(verificationToken); Status status = c->startRetrieval( {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob, sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */}); if (expectSuccess) { ASSERT_TRUE(status.isOk()); } else { ASSERT_FALSE(status.isOk()); return; } vector decrypted; status = c->startRetrieveEntryValue("ns", "Accessible by A", 1, {0}); if (status.isOk()) { canGetAccessibleByA_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByA_, &decrypted).isOk()); } status = c->startRetrieveEntryValue("ns", "Accessible by A or B", 1, {0, 1}); if (status.isOk()) { canGetAccessibleByAorB_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAorB_, &decrypted).isOk()); } status = c->startRetrieveEntryValue("ns", "Accessible by B", 1, {1}); if (status.isOk()) { canGetAccessibleByB_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByB_, &decrypted).isOk()); } status = c->startRetrieveEntryValue("ns", "Accessible by C", 1, {2}); if (status.isOk()) { canGetAccessibleByC_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByC_, &decrypted).isOk()); } status = c->startRetrieveEntryValue("ns", "Accessible by All", 1, {3}); if (status.isOk()) { canGetAccessibleByAll_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk()); } status = c->startRetrieveEntryValue("ns", "Accessible by None", 1, {}); if (status.isOk()) { canGetAccessibleByNone_ = true; ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk()); } vector mac; vector deviceNameSpaces; ASSERT_TRUE(c->finishRetrieval(&mac, &deviceNameSpaces).isOk()); } TEST_P(ReaderAuthTests, presentingChain_Reader) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); EXPECT_FALSE(canGetAccessibleByA_); EXPECT_FALSE(canGetAccessibleByAorB_); EXPECT_FALSE(canGetAccessibleByB_); EXPECT_FALSE(canGetAccessibleByC_); EXPECT_TRUE(canGetAccessibleByAll_); EXPECT_FALSE(canGetAccessibleByNone_); } TEST_P(ReaderAuthTests, presentingChain_Reader_A) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SignedBy_A_, cert_A_SelfSigned_}, true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); EXPECT_TRUE(canGetAccessibleByA_); EXPECT_TRUE(canGetAccessibleByAorB_); EXPECT_FALSE(canGetAccessibleByB_); EXPECT_FALSE(canGetAccessibleByC_); EXPECT_TRUE(canGetAccessibleByAll_); EXPECT_FALSE(canGetAccessibleByNone_); } TEST_P(ReaderAuthTests, presentingChain_Reader_B) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SelfSigned_}, true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); EXPECT_FALSE(canGetAccessibleByA_); EXPECT_TRUE(canGetAccessibleByAorB_); EXPECT_TRUE(canGetAccessibleByB_); EXPECT_FALSE(canGetAccessibleByC_); EXPECT_TRUE(canGetAccessibleByAll_); EXPECT_FALSE(canGetAccessibleByNone_); } // This test proves that for the purpose of determining inclusion of an ACP certificate // in a presented reader chain, certificate equality is done by comparing public keys, // not bitwise comparison of the certificates. // // Specifically for this test, the ACP is configured with cert_B_SelfSigned_ and the // reader is presenting cert_B_SignedBy_C_. Both certificates have the same public // key - intermediateBPublicKey_ - but they are signed by different keys. // TEST_P(ReaderAuthTests, presentingChain_Reader_B_C) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_}, true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); EXPECT_FALSE(canGetAccessibleByA_); EXPECT_TRUE(canGetAccessibleByAorB_); EXPECT_TRUE(canGetAccessibleByB_); EXPECT_TRUE(canGetAccessibleByC_); EXPECT_TRUE(canGetAccessibleByAll_); EXPECT_FALSE(canGetAccessibleByNone_); } // This test presents a reader chain where the chain is invalid because // the 2nd certificate in the chain isn't signed by the 3rd one. // TEST_P(ReaderAuthTests, presentingInvalidChain) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SelfSigned_, cert_C_SelfSigned_}, false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); } // This tests presents a valid reader chain but where requestMessage isn't // signed by the private key corresponding to the public key in the top-level // certificate. // TEST_P(ReaderAuthTests, presentingMessageSignedNotByTopLevel) { provisionData(); retrieveData(intermediateBPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_}, false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); } // This test leaves out "Accessible by All" data element from the signed request // message (the CBOR from the reader) while still including this data element at // the API level. The call on the API level for said element will fail with // STATUS_NOT_IN_REQUEST_MESSAGE but this doesn't prevent the other elements // from being returned (if authorized, of course). // // This test verifies that. // TEST_P(ReaderAuthTests, limitedMessage) { provisionData(); retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */, true /* leaveOutAccessibleToAllFromRequestMessage */); EXPECT_FALSE(canGetAccessibleByA_); EXPECT_FALSE(canGetAccessibleByAorB_); EXPECT_FALSE(canGetAccessibleByB_); EXPECT_FALSE(canGetAccessibleByC_); EXPECT_FALSE(canGetAccessibleByAll_); EXPECT_FALSE(canGetAccessibleByNone_); } TEST_P(ReaderAuthTests, ephemeralKeyNotInSessionTranscript) { provisionData(); sp c; ASSERT_TRUE(credentialStore_ ->getCredential( CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, credentialData_, &c) .isOk()); optional> readerEKeyPair = support::createEcKeyPair(); optional> readerEPublicKey = support::ecKeyPairGetPublicKey(readerEKeyPair.value()); ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk()); vector eKeyPair; ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk()); optional> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair); // Calculate requestData field and sign it with the reader key. auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value()); ASSERT_TRUE(getXYSuccess); // Instead of include the X and Y coordinates (|ephX| and |ephY|), add NUL bytes instead. vector nulls(32); cppbor::Map deviceEngagement = cppbor::Map().add("ephX", nulls).add("ephY", nulls); 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; itemsRequestBytes = cppbor::Map("nameSpaces", cppbor::Map().add("ns", cppbor::Map() .add("Accessible by A", false) .add("Accessible by A or B", false) .add("Accessible by B", false) .add("Accessible by C", false) .add("Accessible by None", false))) .encode(); vector encodedReaderAuthentication = cppbor::Array() .add("ReaderAuthentication") .add(sessionTranscript.clone()) .add(cppbor::Semantic(24, itemsRequestBytes)) .encode(); vector encodedReaderAuthenticationBytes = cppbor::Semantic(24, encodedReaderAuthentication).encode(); vector> readerCertChain = {cert_reader_SelfSigned_}; optional> readerSignature = support::coseSignEcDsa(readerPrivateKey_, // private key for reader {}, // content encodedReaderAuthenticationBytes, // detached content support::certificateChainJoin(readerCertChain)); ASSERT_TRUE(readerSignature); // Generate the key that will be used to sign AuthenticatedData. vector signingKeyBlob; Certificate signingKeyCertificate; ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); RequestNamespace rns; rns.namespaceName = "ns"; rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0})); rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1})); rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1})); rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2})); rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3})); rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {})); // OK to fail, not available in v1 HAL c->setRequestedNamespaces({rns}).isOk(); // 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::TRUSTED_ENVIRONMENT; verificationToken.mac.clear(); // OK to fail, not available in v1 HAL c->setVerificationToken(verificationToken); // Finally check that STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND is returned. // This proves that the TA checked for X and Y coordinatets and didn't find // them. Status status = c->startRetrieval( {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob, sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */}); ASSERT_FALSE(status.isOk()); ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode()); ASSERT_EQ(IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, status.serviceSpecificErrorCode()); } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ReaderAuthTests); INSTANTIATE_TEST_SUITE_P( Identity, ReaderAuthTests, testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), android::PrintInstanceNameToString); } // namespace android::hardware::identity