diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp index 89f7f35696..553a3d832b 100644 --- a/identity/aidl/default/WritableIdentityCredential.cpp +++ b/identity/aidl/default/WritableIdentityCredential.cpp @@ -44,6 +44,8 @@ bool WritableIdentityCredential::initialize() { return false; } storageKey_ = random.value(); + startPersonalizationCalled_ = false; + firstEntry_ = true; return true; } @@ -105,6 +107,12 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( ndk::ScopedAStatus WritableIdentityCredential::startPersonalization( int32_t accessControlProfileCount, const vector& entryCounts) { + if (startPersonalizationCalled_) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already")); + } + + startPersonalizationCalled_ = true; numAccessControlProfileRemaining_ = accessControlProfileCount; remainingEntryCounts_ = entryCounts; entryNameSpace_ = ""; @@ -128,6 +136,13 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "numAccessControlProfileRemaining_ is 0 and expected non-zero")); } + if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Access Control Profile id must be unique")); + } + accessControlProfileIds_.insert(id); + // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also // be zero. if (!userAuthenticationRequired && timeoutMillis != 0) { @@ -184,12 +199,20 @@ ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry( } // Handle initial beginEntry() call. - if (entryNameSpace_ == "") { + if (firstEntry_) { + firstEntry_ = false; entryNameSpace_ = nameSpace; + allNameSpaces_.insert(nameSpace); } // If the namespace changed... if (nameSpace != entryNameSpace_) { + if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "Name space cannot be added in interleaving fashion")); + } + // Then check that all entries in the previous namespace have been added.. if (remainingEntryCounts_[0] != 0) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( @@ -197,6 +220,8 @@ ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry( "New namespace but a non-zero number of entries remain to be added")); } remainingEntryCounts_.erase(remainingEntryCounts_.begin()); + remainingEntryCounts_[0] -= 1; + allNameSpaces_.insert(nameSpace); if (signedDataCurrentNamespace_.size() > 0) { signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_)); @@ -330,6 +355,18 @@ bool generateCredentialData(const vector& hardwareBoundKey, const strin ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( vector* outCredentialData, vector* outProofOfProvisioningSignature) { + if (numAccessControlProfileRemaining_ != 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "numAccessControlProfileRemaining_ is not 0 and expected zero")); + } + + if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "More entry spaces remain than startPersonalization configured")); + } + if (signedDataCurrentNamespace_.size() > 0) { signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_)); } diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h index b182862862..cb91f7ba86 100644 --- a/identity/aidl/default/WritableIdentityCredential.h +++ b/identity/aidl/default/WritableIdentityCredential.h @@ -21,9 +21,11 @@ #include #include +#include namespace aidl::android::hardware::identity { +using ::std::set; using ::std::string; using ::std::vector; @@ -66,6 +68,8 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { // This is set in initialize(). vector storageKey_; + bool startPersonalizationCalled_; + bool firstEntry_; // These are set in getAttestationCertificate(). vector credentialPrivKey_; @@ -79,6 +83,9 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { cppbor::Map signedDataNamespaces_; cppbor::Array signedDataCurrentNamespace_; + // This field is initialized in addAccessControlProfile + set accessControlProfileIds_; + // These fields are initialized during beginAddEntry() size_t entryRemainingBytes_; vector entryAdditionalData_; @@ -86,6 +93,7 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { string entryName_; vector entryAccessControlProfileIds_; vector entryBytes_; + set allNameSpaces_; }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp index ef8beb4cdf..e4780bf89c 100644 --- a/identity/aidl/vts/Android.bp +++ b/identity/aidl/vts/Android.bp @@ -4,7 +4,11 @@ cc_test { "VtsHalTargetTestDefaults", "use_libaidlvintf_gtest_helper_static", ], - srcs: ["VtsHalIdentityTargetTest.cpp"], + srcs: [ + "VtsHalIdentityEndToEndTest.cpp", + "VtsIWritableIdentityCredentialTests.cpp", + "VtsIdentityTestUtils.cpp", + ], shared_libs: [ "libbinder", "libcrypto", diff --git a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp similarity index 69% rename from identity/aidl/vts/VtsHalIdentityTargetTest.cpp rename to identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp index ea37fdc7a5..8a4e8a7fa1 100644 --- a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp +++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp @@ -28,8 +28,11 @@ #include #include +#include "VtsIdentityTestUtils.h" + namespace android::hardware::identity { +using std::endl; using std::map; using std::optional; using std::string; @@ -41,51 +44,6 @@ using ::android::binder::Status; using ::android::hardware::keymaster::HardwareAuthToken; -// --------------------------------------------------------------------------- -// Test Data. -// --------------------------------------------------------------------------- - -struct TestEntryData { - TestEntryData(string nameSpace, string name, vector profileIds) - : nameSpace(nameSpace), name(name), profileIds(profileIds) {} - - TestEntryData(string nameSpace, string name, const string& value, vector profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Tstr(((const char*)value.data())).encode(); - } - TestEntryData(string nameSpace, string name, const vector& value, - vector profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Bstr(value).encode(); - } - TestEntryData(string nameSpace, string name, bool value, vector profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Bool(value).encode(); - } - TestEntryData(string nameSpace, string name, int64_t value, vector profileIds) - : TestEntryData(nameSpace, name, profileIds) { - if (value >= 0) { - valueCbor = cppbor::Uint(value).encode(); - } else { - valueCbor = cppbor::Nint(-value).encode(); - } - } - - string nameSpace; - string name; - vector valueCbor; - vector profileIds; -}; - -struct TestProfile { - uint16_t id; - vector readerCertificate; - bool userAuthenticationRequired; - uint64_t timeoutMillis; -}; - -// ---------------------------------------------------------------- - class IdentityAidl : public testing::TestWithParam { public: virtual void SetUp() override { @@ -108,39 +66,26 @@ TEST_P(IdentityAidl, hardwareInformation) { TEST_P(IdentityAidl, createAndRetrieveCredential) { // First, generate a key-pair for the reader since its public key will be // part of the request data. - optional> readerKeyPKCS8 = support::createEcKeyPair(); - ASSERT_TRUE(readerKeyPKCS8); - optional> readerPublicKey = - support::ecKeyPairGetPublicKey(readerKeyPKCS8.value()); - optional> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value()); - string serialDecimal = "1234"; - string issuer = "Android Open Source Project"; - string subject = "Android IdentityCredential VTS Test"; - time_t validityNotBefore = time(nullptr); - time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600; - optional> readerCertificate = support::ecPublicKeyGenerateCertificate( - readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject, - validityNotBefore, validityNotAfter); + 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; - portraitImage.resize(256 * 1024 - 10); - for (size_t n = 0; n < portraitImage.size(); n++) { - portraitImage[n] = (uint8_t)n; - } + 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}}; + 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 = { + 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}}, @@ -155,67 +100,33 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { string cborPretty; sp writableCredential; - string docType = "org.iso.18013-5.2019.mdl"; - bool testCredential = true; - ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &writableCredential) - .isOk()); - ASSERT_NE(writableCredential, nullptr); + 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 - vector attestationApplicationId = {}; - vector attestationChallenge(challenge.begin(), challenge.end()); - vector attestationCertificates; - ASSERT_TRUE(writableCredential - ->getAttestationCertificate(attestationApplicationId, attestationChallenge, - &attestationCertificates) - .isOk()); - ASSERT_GE(attestationCertificates.size(), 2); + ASSERT_GE(attData.attestationCertificate.size(), 2); ASSERT_TRUE( writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts) .isOk()); - vector returnedSecureProfiles; - for (const auto& testProfile : testProfiles) { - SecureAccessControlProfile profile; - Certificate cert; - cert.encodedCertificate = testProfile.readerCertificate; - ASSERT_TRUE(writableCredential - ->addAccessControlProfile(testProfile.id, cert, - testProfile.userAuthenticationRequired, - testProfile.timeoutMillis, - 0, // secureUserId - &profile) - .isOk()); - ASSERT_EQ(testProfile.id, profile.id); - ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate); - ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired); - ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis); - ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size()); - returnedSecureProfiles.push_back(profile); - } + 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; + map>> encryptedBlobs; for (const auto& entry : testEntries) { - vector> chunks = - support::chunkVector(entry.valueCbor, hwInfo.dataChunkSize); - - ASSERT_TRUE(writableCredential - ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name, - entry.valueCbor.size()) - .isOk()); - - vector> encryptedChunks; - for (const auto& chunk : chunks) { - vector encryptedChunk; - ASSERT_TRUE(writableCredential->addEntryValue(chunk, &encryptedChunk).isOk()); - encryptedChunks.push_back(encryptedChunk); - } - encryptedBlobs[&entry] = encryptedChunks; + ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); } vector credentialData; @@ -276,8 +187,8 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { "]", cborPretty); - optional> credentialPubKey = - support::certificateChainGetTopMostKey(attestationCertificates[0].encodedCertificate); + optional> credentialPubKey = support::certificateChainGetTopMostKey( + attData.attestationCertificate[0].encodedCertificate); ASSERT_TRUE(credentialPubKey); EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature, {}, // Additional data @@ -347,8 +258,8 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { .add(cppbor::Semantic(24, itemsRequestBytes)) .encode(); optional> readerSignature = - support::coseSignEcDsa(readerKey.value(), {}, // content - dataToSign, // detached content + support::coseSignEcDsa(readerKey, {}, // content + dataToSign, // detached content readerCertificate.value()); ASSERT_TRUE(readerSignature); @@ -358,7 +269,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); ASSERT_TRUE(credential - ->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes, + ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes, signingKeyBlob, sessionTranscriptBytes, readerSignature.value(), testEntriesEntryCounts) .isOk()); @@ -405,6 +316,8 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { 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(); diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp new file mode 100644 index 0000000000..56b30af9a4 --- /dev/null +++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp @@ -0,0 +1,649 @@ +/* + * 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 "VtsIWritableIdentityCredentialTests" + +#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; + +class IdentityCredentialTests : public testing::TestWithParam { + public: + virtual void SetUp() override { + credentialStore_ = android::waitForDeclaredService( + String16(GetParam().c_str())); + ASSERT_NE(credentialStore_, nullptr); + } + + sp credentialStore_; +}; + +TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + vector attestationChallenge; + vector attestationCertificate; + vector attestationApplicationId = {}; + result = writableCredential->getAttestationCertificate( + attestationApplicationId, attestationChallenge, &attestationCertificate); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate)); +} + +TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1"; + vector attestationChallenge(challenge.begin(), challenge.end()); + vector attestationCertificate; + vector attestationApplicationId = {}; + + result = writableCredential->getAttestationCertificate( + attestationApplicationId, attestationChallenge, &attestationCertificate); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate)); +} + +TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge1"; + test_utils::AttestationData attData(writableCredential, challenge, {}); + ASSERT_TRUE(test_utils::ValidateAttestationCertificate(attData.attestationCertificate)); + + string challenge2 = "NotSoRandomChallenge2"; + test_utils::AttestationData attData2(writableCredential, challenge2, {}); + EXPECT_FALSE(attData2.result.isOk()) << attData2.result.exceptionCode() << "; " + << attData2.result.exceptionMessage() << endl; + EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, attData2.result.exceptionCode()); + EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, attData2.result.serviceSpecificErrorCode()); +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalization) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // First call should go through + const vector entryCounts = {2, 4}; + result = writableCredential->startPersonalization(5, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + // Call personalization again to check if repeat call is allowed. + result = writableCredential->startPersonalization(7, entryCounts); + + // Second call to startPersonalization should have failed. + EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode()); + EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode()); +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify minimal number of profile count and entry count + const vector entryCounts = {1, 1}; + writableCredential->startPersonalization(1, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + const vector entryCounts = {0}; + writableCredential->startPersonalization(0, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify minimal number of profile count and entry count + const vector entryCounts = {1}; + writableCredential->startPersonalization(1, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify set a large number of profile count and entry count is ok + const vector entryCounts = {3000}; + writableCredential->startPersonalization(3500, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Enter mismatched entry and profile numbers + const vector entryCounts = {5, 6}; + writableCredential->startPersonalization(5, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> readerCertificate = test_utils::GenerateReaderCertificate("12345"); + ASSERT_TRUE(readerCertificate); + + const vector testProfiles = {// Profile 0 (reader authentication) + {1, readerCertificate.value(), false, 0}, + {2, readerCertificate.value(), true, 1}, + // Profile 4 (no authentication) + {4, {}, false, 0}}; + + optional> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + vector credentialData; + vector proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + // finishAddingEntries should fail because the number of addAccessControlProfile mismatched with + // startPersonalization, and begintest_utils::AddEntry was not called. + EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode()); + EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode()); +} + +TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) { + Status result; + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + const vector entryCounts = {3, 6}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + const vector testProfiles = {// first profile should go though + {1, {}, true, 2}, + // same id, different + // authentication requirement + {1, {}, true, 1}, + // same id, different certificate + {1, {}, false, 0}}; + + bool expectOk = true; + for (const auto& testProfile : testProfiles) { + SecureAccessControlProfile profile; + Certificate cert; + cert.encodedCertificate = testProfile.readerCertificate; + result = writableCredential->addAccessControlProfile( + testProfile.id, cert, testProfile.userAuthenticationRequired, + testProfile.timeoutMillis, 0, &profile); + + if (expectOk) { + expectOk = false; + // for profile should be allowed though as there are no duplications + // yet. + ASSERT_TRUE(result.isOk()) + << result.exceptionCode() << "; " << result.exceptionMessage() + << "test profile id = " << testProfile.id << endl; + + ASSERT_EQ(testProfile.id, profile.id); + ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate); + ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired); + ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis); + ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size()); + } else { + // should not allow duplicate id profiles. + ASSERT_FALSE(result.isOk()) + << result.exceptionCode() << "; " << result.exceptionMessage() + << ". Test profile id = " << testProfile.id + << ", timeout=" << testProfile.timeoutMillis << endl; + ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode()); + ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, + result.serviceSpecificErrorCode()); + } + } +} + +TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) { + Status result; + + HardwareInformation hwInfo; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); + + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge1"; + test_utils::AttestationData attData(writableCredential, challenge, {}); + EXPECT_TRUE(attData.result.isOk()) + << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; + + const vector entryCounts = {1u}; + writableCredential->startPersonalization(1, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + const vector testProfiles = {{1, readerCertificate1.value(), true, 1}}; + + optional> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector testEntries1 = { + {"Name Space", "Last name", string("Turing"), vector{0, 1}}, + }; + + map>> encryptedBlobs; + for (const auto& entry : testEntries1) { + ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector credentialData; + vector proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> proofOfProvisioning = + support::coseSignGetPayload(proofOfProvisioningSignature); + ASSERT_TRUE(proofOfProvisioning); + string cborPretty = + support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"}); + EXPECT_EQ( + "[\n" + " 'ProofOfProvisioning',\n" + " 'org.iso.18013-5.2019.mdl',\n" + " [\n" + " {\n" + " 'id' : 1,\n" + " 'readerCertificate' : ,\n" + " 'userAuthenticationRequired' : true,\n" + " 'timeoutMillis' : 1,\n" + " },\n" + " ],\n" + " {\n" + " 'Name Space' : [\n" + " {\n" + " 'name' : 'Last name',\n" + " 'value' : 'Turing',\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())); +} + +TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) { + Status result; + + HardwareInformation hwInfo; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); + + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge"; + test_utils::AttestationData attData(writableCredential, challenge, {}); + EXPECT_TRUE(attData.result.isOk()) + << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; + + optional> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional> readerCertificate2 = test_utils::GenerateReaderCertificate("1256"); + ASSERT_TRUE(readerCertificate2); + + const vector testProfiles = { + {1, readerCertificate1.value(), true, 1}, + {2, readerCertificate2.value(), true, 2}, + }; + const vector entryCounts = {1u, 3u, 1u, 1u, 2u}; + writableCredential->startPersonalization(testProfiles.size(), entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + vector portraitImage1; + test_utils::SetImageData(portraitImage1); + + vector portraitImage2; + test_utils::SetImageData(portraitImage2); + + const vector testEntries1 = { + {"Name Space 1", "Last name", string("Turing"), vector{1, 2}}, + {"Name Space2", "Home address", string("Maida Vale, London, England"), + vector{1}}, + {"Name Space2", "Work address", string("Maida Vale2, London, England"), + vector{2}}, + {"Name Space2", "Trailer address", string("Maida, London, England"), + vector{1}}, + {"Image", "Portrait image", portraitImage1, vector{1}}, + {"Image2", "Work image", portraitImage2, vector{1, 2}}, + {"Name Space3", "xyzw", string("random stuff"), vector{1, 2}}, + {"Name Space3", "Something", string("Some string"), vector{2}}, + }; + + map>> encryptedBlobs; + for (const auto& entry : testEntries1) { + EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector credentialData; + vector proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> proofOfProvisioning = + support::coseSignGetPayload(proofOfProvisioningSignature); + ASSERT_TRUE(proofOfProvisioning); + string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), + 32, // + {"readerCertificate"}); + EXPECT_EQ( + "[\n" + " 'ProofOfProvisioning',\n" + " 'org.iso.18013-5.2019.mdl',\n" + " [\n" + " {\n" + " 'id' : 1,\n" + " 'readerCertificate' : ,\n" + " 'userAuthenticationRequired' : true,\n" + " 'timeoutMillis' : 1,\n" + " },\n" + " {\n" + " 'id' : 2,\n" + " 'readerCertificate' : ,\n" + " 'userAuthenticationRequired' : true,\n" + " 'timeoutMillis' : 2,\n" + " },\n" + " ],\n" + " {\n" + " 'Name Space 1' : [\n" + " {\n" + " 'name' : 'Last name',\n" + " 'value' : 'Turing',\n" + " 'accessControlProfiles' : [1, 2, ],\n" + " },\n" + " ],\n" + " 'Name Space2' : [\n" + " {\n" + " 'name' : 'Home address',\n" + " 'value' : 'Maida Vale, London, England',\n" + " 'accessControlProfiles' : [1, ],\n" + " },\n" + " {\n" + " 'name' : 'Work address',\n" + " 'value' : 'Maida Vale2, London, England',\n" + " 'accessControlProfiles' : [2, ],\n" + " },\n" + " {\n" + " 'name' : 'Trailer address',\n" + " 'value' : 'Maida, London, England',\n" + " 'accessControlProfiles' : [1, ],\n" + " },\n" + " ],\n" + " 'Image' : [\n" + " {\n" + " 'name' : 'Portrait image',\n" + " 'value' : ,\n" + " 'accessControlProfiles' : [1, ],\n" + " },\n" + " ],\n" + " 'Image2' : [\n" + " {\n" + " 'name' : 'Work image',\n" + " 'value' : ,\n" + " 'accessControlProfiles' : [1, 2, ],\n" + " },\n" + " ],\n" + " 'Name Space3' : [\n" + " {\n" + " 'name' : 'xyzw',\n" + " 'value' : 'random stuff',\n" + " 'accessControlProfiles' : [1, 2, ],\n" + " },\n" + " {\n" + " 'name' : 'Something',\n" + " 'value' : 'Some string',\n" + " 'accessControlProfiles' : [2, ],\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())); +} + +TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) { + Status result; + + HardwareInformation hwInfo; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); + + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge"; + test_utils::AttestationData attData(writableCredential, challenge, {}); + ASSERT_TRUE(attData.result.isOk()) + << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; + + const vector entryCounts = {2u, 2u}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional> readerCertificate2 = + test_utils::GenerateReaderCertificate("123456987987987987987987"); + ASSERT_TRUE(readerCertificate2); + + const vector testProfiles = {{0, readerCertificate1.value(), false, 0}, + {1, readerCertificate2.value(), true, 1}, + {2, {}, false, 0}}; + + optional> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector testEntries1 = { + // test empty name space + {"", "t name", string("Turing"), vector{2}}, + {"", "Birth", string("19120623"), vector{2}}, + {"Name Space", "Last name", string("Turing"), vector{0, 1}}, + {"Name Space", "Birth date", string("19120623"), vector{0, 1}}, + }; + + map>> encryptedBlobs; + for (const auto& entry : testEntries1) { + EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector credentialData; + vector proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) { + Status result; + + HardwareInformation hwInfo; + ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); + + sp writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge"; + test_utils::AttestationData attData(writableCredential, challenge, {}); + ASSERT_TRUE(attData.result.isOk()) + << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl; + + // Enter mismatched entry and profile numbers. + // Technically the 2nd name space of "Name Space" occurs intermittently, 2 + // before "Image" and 2 after image, which is not correct. All of same name + // space should occur together. Let's see if this fails. + const vector entryCounts = {2u, 1u, 2u}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional> readerCertificate2 = + test_utils::GenerateReaderCertificate("123456987987987987987987"); + ASSERT_TRUE(readerCertificate2); + + const vector testProfiles = {{0, readerCertificate1.value(), false, 0}, + {1, readerCertificate2.value(), true, 1}, + {2, {}, false, 0}}; + + optional> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector testEntries1 = { + // test empty name space + {"Name Space", "Last name", string("Turing"), vector{0, 1}}, + {"Name Space", "Birth date", string("19120623"), vector{0, 1}}, + }; + + map>> encryptedBlobs; + for (const auto& entry : testEntries1) { + EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + const test_utils::TestEntryData testEntry2 = {"Image", "Portrait image", string("asdfs"), + vector{0, 1}}; + + EXPECT_TRUE(test_utils::AddEntry(writableCredential, testEntry2, hwInfo.dataChunkSize, + encryptedBlobs, true)); + + // We expect this to fail because the namespace is out of order, all "Name Space" + // should have been called together + const vector testEntries3 = { + {"Name Space", "First name", string("Alan"), vector{0, 1}}, + {"Name Space", "Home address", string("Maida Vale, London, England"), + vector{0}}, + }; + + for (const auto& entry : testEntries3) { + EXPECT_FALSE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, false)); + } + + vector credentialData; + vector proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + // should fail because test_utils::AddEntry should have failed earlier. + EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode()); + EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode()); +} + +INSTANTIATE_TEST_SUITE_P( + Identity, IdentityCredentialTests, + testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), + android::PrintInstanceNameToString); + +} // namespace android::hardware::identity diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp new file mode 100644 index 0000000000..3aeebc66b6 --- /dev/null +++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp @@ -0,0 +1,179 @@ +/* + * Copyright 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. + */ + +#include "VtsIdentityTestUtils.h" + +#include +#include + +namespace android::hardware::identity::test_utils { + +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; + +bool SetupWritableCredential(sp& writableCredential, + sp& credentialStore) { + if (credentialStore == nullptr) { + return false; + } + + string docType = "org.iso.18013-5.2019.mdl"; + bool testCredential = true; + Status result = credentialStore->createCredential(docType, testCredential, &writableCredential); + + if (result.isOk() && writableCredential != nullptr) { + return true; + } else { + return false; + } +} + +optional> GenerateReaderCertificate(string serialDecimal) { + vector privKey; + return GenerateReaderCertificate(serialDecimal, privKey); +} + +optional> GenerateReaderCertificate(string serialDecimal, + vector& readerPrivateKey) { + optional> readerKeyPKCS8 = support::createEcKeyPair(); + if (!readerKeyPKCS8) { + return {}; + } + + optional> readerPublicKey = + support::ecKeyPairGetPublicKey(readerKeyPKCS8.value()); + optional> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value()); + if (!readerPublicKey || !readerKey) { + return {}; + } + + readerPrivateKey = readerKey.value(); + + string issuer = "Android Open Source Project"; + string subject = "Android IdentityCredential VTS Test"; + time_t validityNotBefore = time(nullptr); + time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600; + + return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(), + serialDecimal, issuer, subject, + validityNotBefore, validityNotAfter); +} + +optional> AddAccessControlProfiles( + sp& writableCredential, + const vector& testProfiles) { + Status result; + + vector secureProfiles; + + for (const auto& testProfile : testProfiles) { + SecureAccessControlProfile profile; + Certificate cert; + cert.encodedCertificate = testProfile.readerCertificate; + result = writableCredential->addAccessControlProfile( + testProfile.id, cert, testProfile.userAuthenticationRequired, + testProfile.timeoutMillis, 0, &profile); + + // Don't use assert so all errors can be outputed. Then return + // instead of exit even on errors so caller can decide. + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << "test profile id = " << testProfile.id << endl; + EXPECT_EQ(testProfile.id, profile.id); + EXPECT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate); + EXPECT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired); + EXPECT_EQ(testProfile.timeoutMillis, profile.timeoutMillis); + EXPECT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size()); + + if (!result.isOk() || testProfile.id != profile.id || + testProfile.readerCertificate != profile.readerCertificate.encodedCertificate || + testProfile.userAuthenticationRequired != profile.userAuthenticationRequired || + testProfile.timeoutMillis != profile.timeoutMillis || + support::kAesGcmTagSize + support::kAesGcmIvSize != profile.mac.size()) { + return {}; + } + + secureProfiles.push_back(profile); + } + + return secureProfiles; +} + +// Most test expects this function to pass. So we will print out additional +// value if failed so more debug data can be provided. +bool AddEntry(sp& writableCredential, const TestEntryData& entry, + int dataChunkSize, map>>& encryptedBlobs, + bool expectSuccess) { + Status result; + vector> chunks = support::chunkVector(entry.valueCbor, dataChunkSize); + + result = writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name, + entry.valueCbor.size()); + + if (expectSuccess) { + EXPECT_TRUE(result.isOk()) + << result.exceptionCode() << "; " << result.exceptionMessage() << endl + << "entry name = " << entry.name << ", name space=" << entry.nameSpace << endl; + } + + if (!result.isOk()) { + return false; + } + + vector> encryptedChunks; + for (const auto& chunk : chunks) { + vector encryptedContent; + result = writableCredential->addEntryValue(chunk, &encryptedContent); + if (expectSuccess) { + EXPECT_TRUE(result.isOk()) + << result.exceptionCode() << "; " << result.exceptionMessage() << endl + << "entry name = " << entry.name << ", name space = " << entry.nameSpace + << endl; + + EXPECT_GT(encryptedContent.size(), 0u) << "entry name = " << entry.name + << ", name space = " << entry.nameSpace << endl; + } + + if (!result.isOk() || encryptedContent.size() <= 0u) { + return false; + } + + encryptedChunks.push_back(encryptedContent); + } + + encryptedBlobs[&entry] = encryptedChunks; + return true; +} + +bool ValidateAttestationCertificate(vector& inputCertificates) { + return (inputCertificates.size() >= 2); + // TODO: add parsing of the certificate and make sure it is genuine. +} + +void SetImageData(vector& image) { + image.resize(256 * 1024 - 10); + for (size_t n = 0; n < image.size(); n++) { + image[n] = (uint8_t)n; + } +} + +} // namespace android::hardware::identity::test_utils diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/VtsIdentityTestUtils.h new file mode 100644 index 0000000000..043ccd6905 --- /dev/null +++ b/identity/aidl/vts/VtsIdentityTestUtils.h @@ -0,0 +1,118 @@ +/* + * Copyright 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. + */ + +#ifndef VTS_IDENTITY_TEST_UTILS_H +#define VTS_IDENTITY_TEST_UTILS_H + +#include +#include +#include +#include + +namespace android::hardware::identity::test_utils { + +using ::std::map; +using ::std::optional; +using ::std::string; +using ::std::vector; + +using ::android::sp; +using ::android::binder::Status; + +struct AttestationData { + AttestationData(sp& writableCredential, string challenge, + vector applicationId) + : attestationApplicationId(applicationId) { + // ASSERT_NE(writableCredential, nullptr); + + if (!challenge.empty()) { + attestationChallenge.assign(challenge.begin(), challenge.end()); + } + + result = writableCredential->getAttestationCertificate( + attestationApplicationId, attestationChallenge, &attestationCertificate); + } + + AttestationData() {} + + vector attestationChallenge; + vector attestationApplicationId; + vector attestationCertificate; + Status result; +}; + +struct TestEntryData { + TestEntryData(string nameSpace, string name, vector profileIds) + : nameSpace(nameSpace), name(name), profileIds(profileIds) {} + + TestEntryData(string nameSpace, string name, const string& value, vector profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Tstr(((const char*)value.data())).encode(); + } + TestEntryData(string nameSpace, string name, const vector& value, + vector profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Bstr(value).encode(); + } + TestEntryData(string nameSpace, string name, bool value, vector profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Bool(value).encode(); + } + TestEntryData(string nameSpace, string name, int64_t value, vector profileIds) + : TestEntryData(nameSpace, name, profileIds) { + if (value >= 0) { + valueCbor = cppbor::Uint(value).encode(); + } else { + valueCbor = cppbor::Nint(-value).encode(); + } + } + + string nameSpace; + string name; + vector valueCbor; + vector profileIds; +}; + +struct TestProfile { + uint16_t id; + vector readerCertificate; + bool userAuthenticationRequired; + uint64_t timeoutMillis; +}; + +bool SetupWritableCredential(sp& writableCredential, + sp& credentialStore); + +optional> GenerateReaderCertificate(string serialDecimal); + +optional> GenerateReaderCertificate(string serialDecimal, + vector& readerPrivateKey); + +optional> AddAccessControlProfiles( + sp& writableCredential, + const vector& testProfiles); + +bool AddEntry(sp& writableCredential, const TestEntryData& entry, + int dataChunkSize, map>>& encryptedBlobs, + bool expectSuccess); + +bool ValidateAttestationCertificate(vector& inputCertificates); + +void SetImageData(vector& image); + +} // namespace android::hardware::identity::test_utils + +#endif // VTS_IDENTITY_TEST_UTILS_H diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp index bf6a5c3c45..dc49ddc992 100644 --- a/identity/support/src/IdentityCredentialSupport.cpp +++ b/identity/support/src/IdentityCredentialSupport.cpp @@ -958,12 +958,17 @@ optional, vector>>> createEcKeyPairAnd optional> createEcKeyPair() { auto ec_key = EC_KEY_Ptr(EC_KEY_new()); auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); - auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); if (ec_key.get() == nullptr || pkey.get() == nullptr) { LOG(ERROR) << "Memory allocation failed"; return {}; } + auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (group.get() == nullptr) { + LOG(ERROR) << "Error creating EC group by curve name"; + return {}; + } + if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 || EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) { LOG(ERROR) << "Error generating key";