diff options
| author | Selene Huang <seleneh@google.com> | 2020-03-04 02:24:16 -0800 |
|---|---|---|
| committer | David Zeuthen <zeuthen@google.com> | 2020-04-27 13:03:20 -0400 |
| commit | d39b9fb604dc1592c13ba2db1fa7ac12416b94de (patch) | |
| tree | 8a7596e6cec11b49ddaba541a78a8cbf54139451 | |
| parent | d78626d962ad570d6d670859e7ab600dba118ea4 (diff) | |
Fix IC vts bugs and add tests for IC IWritableIdentityCredential.aidl interface.
Fixed following bugs in WritableIdentityCredential.cpp
- Do not allow startPersonalization to be called more than once per
aidl.
- Do not preceed with beginAddEntry if addAccessControlProfile and
startPersonalization profile count mismatch.
- Verify access control profile ids are unique.
- Do not let empty name space to mess up beginAddEntry.
- Do not allow beginAddEntry to add entries interleaving namespace
groupings. Enforce all entries must be added in namespace "groups"
per aidl.
- Fix counting error that allowed one entries to be added per name
space than startPersonalization limit.
- Do not approve finishAddingEntries if there are more profiles or
entries to be added than startPersonalization set accounting.
- Add testing utilities library for identity credential.
- Refactored end to end tests.
Bug: 154909726
Test: atest VtsHalIdentityTargetTest
Test: atest android.security.identity.cts
Merged-In: I51902681776c6230e49589fc75a8145e79d7d1a6
Change-Id: Ib7c108f67c61125edba6177dcac61cfbf58da671
| -rw-r--r-- | identity/aidl/default/WritableIdentityCredential.cpp | 39 | ||||
| -rw-r--r-- | identity/aidl/default/WritableIdentityCredential.h | 8 | ||||
| -rw-r--r-- | identity/aidl/vts/Android.bp | 6 | ||||
| -rw-r--r-- | identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp (renamed from identity/aidl/vts/VtsHalIdentityTargetTest.cpp) | 153 | ||||
| -rw-r--r-- | identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp | 649 | ||||
| -rw-r--r-- | identity/aidl/vts/VtsIdentityTestUtils.cpp | 179 | ||||
| -rw-r--r-- | identity/aidl/vts/VtsIdentityTestUtils.h | 118 | ||||
| -rw-r--r-- | identity/support/src/IdentityCredentialSupport.cpp | 7 |
8 files changed, 1036 insertions, 123 deletions
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp index 89f7f3569..553a3d832 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<int32_t>& 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<uint8_t>& hardwareBoundKey, const strin ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries( vector<int8_t>* outCredentialData, vector<int8_t>* 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 b18286286..cb91f7ba8 100644 --- a/identity/aidl/default/WritableIdentityCredential.h +++ b/identity/aidl/default/WritableIdentityCredential.h @@ -21,9 +21,11 @@ #include <android/hardware/identity/support/IdentityCredentialSupport.h> #include <cppbor.h> +#include <set> 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<uint8_t> storageKey_; + bool startPersonalizationCalled_; + bool firstEntry_; // These are set in getAttestationCertificate(). vector<uint8_t> credentialPrivKey_; @@ -79,6 +83,9 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { cppbor::Map signedDataNamespaces_; cppbor::Array signedDataCurrentNamespace_; + // This field is initialized in addAccessControlProfile + set<int32_t> accessControlProfileIds_; + // These fields are initialized during beginAddEntry() size_t entryRemainingBytes_; vector<uint8_t> entryAdditionalData_; @@ -86,6 +93,7 @@ class WritableIdentityCredential : public BnWritableIdentityCredential { string entryName_; vector<int32_t> entryAccessControlProfileIds_; vector<uint8_t> entryBytes_; + set<string> allNameSpaces_; }; } // namespace aidl::android::hardware::identity diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp index ef8beb4cd..e4780bf89 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 index ea37fdc7a..8a4e8a7fa 100644 --- a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp +++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp @@ -28,8 +28,11 @@ #include <future> #include <map> +#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<int32_t> profileIds) - : nameSpace(nameSpace), name(name), profileIds(profileIds) {} - - TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Tstr(((const char*)value.data())).encode(); - } - TestEntryData(string nameSpace, string name, const vector<uint8_t>& value, - vector<int32_t> profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Bstr(value).encode(); - } - TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds) - : TestEntryData(nameSpace, name, profileIds) { - valueCbor = cppbor::Bool(value).encode(); - } - TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds) - : TestEntryData(nameSpace, name, profileIds) { - if (value >= 0) { - valueCbor = cppbor::Uint(value).encode(); - } else { - valueCbor = cppbor::Nint(-value).encode(); - } - } - - string nameSpace; - string name; - vector<uint8_t> valueCbor; - vector<int32_t> profileIds; -}; - -struct TestProfile { - uint16_t id; - vector<uint8_t> readerCertificate; - bool userAuthenticationRequired; - uint64_t timeoutMillis; -}; - -// ---------------------------------------------------------------- - class IdentityAidl : public testing::TestWithParam<std::string> { 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<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair(); - ASSERT_TRUE(readerKeyPKCS8); - optional<vector<uint8_t>> readerPublicKey = - support::ecKeyPairGetPublicKey(readerKeyPKCS8.value()); - optional<vector<uint8_t>> 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<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate( - readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject, - validityNotBefore, validityNotAfter); + vector<uint8_t> readerKey; + optional<vector<uint8_t>> readerCertificate = + test_utils::GenerateReaderCertificate("1234", readerKey); ASSERT_TRUE(readerCertificate); // Make the portrait image really big (just shy of 256 KiB) to ensure that // the chunking code gets exercised. vector<uint8_t> portraitImage; - 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<TestProfile> testProfiles = {// Profile 0 (reader authentication) - {0, readerCertificate.value(), false, 0}, - // Profile 1 (no authentication) - {1, {}, false, 0}}; + const vector<test_utils::TestProfile> 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<TestEntryData> testEntries = { + const vector<test_utils::TestEntryData> testEntries = { {"PersonalData", "Last name", string("Turing"), vector<int32_t>{0, 1}}, {"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}}, {"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}}, @@ -155,67 +100,33 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { string cborPretty; sp<IWritableIdentityCredential> 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<uint8_t> attestationApplicationId = {}; - vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end()); - vector<Certificate> 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<SecureAccessControlProfile> 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<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); // Uses TestEntryData* pointer as key and values are the encrypted blobs. This // is a little hacky but it works well enough. - map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; for (const auto& entry : testEntries) { - vector<vector<uint8_t>> chunks = - support::chunkVector(entry.valueCbor, hwInfo.dataChunkSize); - - ASSERT_TRUE(writableCredential - ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name, - entry.valueCbor.size()) - .isOk()); - - vector<vector<uint8_t>> encryptedChunks; - for (const auto& chunk : chunks) { - vector<uint8_t> 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<uint8_t> credentialData; @@ -276,8 +187,8 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { "]", cborPretty); - optional<vector<uint8_t>> credentialPubKey = - support::certificateChainGetTopMostKey(attestationCertificates[0].encodedCertificate); + optional<vector<uint8_t>> 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<vector<uint8_t>> 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<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode(); diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp new file mode 100644 index 000000000..56b30af9a --- /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 <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/hardware/identity/IIdentityCredentialStore.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <cppbor.h> +#include <cppbor_parse.h> +#include <gtest/gtest.h> +#include <future> +#include <map> + +#include "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<string> { + public: + virtual void SetUp() override { + credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>( + String16(GetParam().c_str())); + ASSERT_NE(credentialStore_, nullptr); + } + + sp<IIdentityCredentialStore> credentialStore_; +}; + +TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) { + Status result; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + vector<uint8_t> attestationChallenge; + vector<Certificate> attestationCertificate; + vector<uint8_t> 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<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1"; + vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end()); + vector<Certificate> attestationCertificate; + vector<uint8_t> 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<IWritableIdentityCredential> 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<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // First call should go through + const vector<int32_t> 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<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify minimal number of profile count and entry count + const vector<int32_t> entryCounts = {1, 1}; + writableCredential->startPersonalization(1, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) { + Status result; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + const vector<int32_t> entryCounts = {0}; + writableCredential->startPersonalization(0, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) { + Status result; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify minimal number of profile count and entry count + const vector<int32_t> entryCounts = {1}; + writableCredential->startPersonalization(1, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) { + Status result; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Verify set a large number of profile count and entry count is ok + const vector<int32_t> entryCounts = {3000}; + writableCredential->startPersonalization(3500, entryCounts); + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; +} + +TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) { + Status result; + sp<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + // Enter mismatched entry and profile numbers + const vector<int32_t> entryCounts = {5, 6}; + writableCredential->startPersonalization(5, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> readerCertificate = test_utils::GenerateReaderCertificate("12345"); + ASSERT_TRUE(readerCertificate); + + const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication) + {1, readerCertificate.value(), false, 0}, + {2, readerCertificate.value(), true, 1}, + // Profile 4 (no authentication) + {4, {}, false, 0}}; + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + vector<uint8_t> credentialData; + vector<uint8_t> 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<IWritableIdentityCredential> writableCredential; + ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_)); + + const vector<int32_t> entryCounts = {3, 6}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + const vector<test_utils::TestProfile> 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<IWritableIdentityCredential> 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<int32_t> entryCounts = {1u}; + writableCredential->startPersonalization(1, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + const vector<test_utils::TestProfile> testProfiles = {{1, readerCertificate1.value(), true, 1}}; + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector<test_utils::TestEntryData> testEntries1 = { + {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}}, + }; + + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; + for (const auto& entry : testEntries1) { + ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector<uint8_t> credentialData; + vector<uint8_t> proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> 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' : <not printed>,\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<vector<uint8_t>> 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<IWritableIdentityCredential> 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<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional<vector<uint8_t>> readerCertificate2 = test_utils::GenerateReaderCertificate("1256"); + ASSERT_TRUE(readerCertificate2); + + const vector<test_utils::TestProfile> testProfiles = { + {1, readerCertificate1.value(), true, 1}, + {2, readerCertificate2.value(), true, 2}, + }; + const vector<int32_t> entryCounts = {1u, 3u, 1u, 1u, 2u}; + writableCredential->startPersonalization(testProfiles.size(), entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + vector<uint8_t> portraitImage1; + test_utils::SetImageData(portraitImage1); + + vector<uint8_t> portraitImage2; + test_utils::SetImageData(portraitImage2); + + const vector<test_utils::TestEntryData> testEntries1 = { + {"Name Space 1", "Last name", string("Turing"), vector<int32_t>{1, 2}}, + {"Name Space2", "Home address", string("Maida Vale, London, England"), + vector<int32_t>{1}}, + {"Name Space2", "Work address", string("Maida Vale2, London, England"), + vector<int32_t>{2}}, + {"Name Space2", "Trailer address", string("Maida, London, England"), + vector<int32_t>{1}}, + {"Image", "Portrait image", portraitImage1, vector<int32_t>{1}}, + {"Image2", "Work image", portraitImage2, vector<int32_t>{1, 2}}, + {"Name Space3", "xyzw", string("random stuff"), vector<int32_t>{1, 2}}, + {"Name Space3", "Something", string("Some string"), vector<int32_t>{2}}, + }; + + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; + for (const auto& entry : testEntries1) { + EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector<uint8_t> credentialData; + vector<uint8_t> proofOfProvisioningSignature; + result = + writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature); + + EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> 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' : <not printed>,\n" + " 'userAuthenticationRequired' : true,\n" + " 'timeoutMillis' : 1,\n" + " },\n" + " {\n" + " 'id' : 2,\n" + " 'readerCertificate' : <not printed>,\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' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n" + " 'accessControlProfiles' : [1, ],\n" + " },\n" + " ],\n" + " 'Image2' : [\n" + " {\n" + " 'name' : 'Work image',\n" + " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\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<vector<uint8_t>> 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<IWritableIdentityCredential> 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<int32_t> entryCounts = {2u, 2u}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional<vector<uint8_t>> readerCertificate2 = + test_utils::GenerateReaderCertificate("123456987987987987987987"); + ASSERT_TRUE(readerCertificate2); + + const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0}, + {1, readerCertificate2.value(), true, 1}, + {2, {}, false, 0}}; + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector<test_utils::TestEntryData> testEntries1 = { + // test empty name space + {"", "t name", string("Turing"), vector<int32_t>{2}}, + {"", "Birth", string("19120623"), vector<int32_t>{2}}, + {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}}, + {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}}, + }; + + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs; + for (const auto& entry : testEntries1) { + EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, true)); + } + + vector<uint8_t> credentialData; + vector<uint8_t> 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<IWritableIdentityCredential> 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<int32_t> entryCounts = {2u, 1u, 2u}; + writableCredential->startPersonalization(3, entryCounts); + ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() + << endl; + + optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456"); + ASSERT_TRUE(readerCertificate1); + + optional<vector<uint8_t>> readerCertificate2 = + test_utils::GenerateReaderCertificate("123456987987987987987987"); + ASSERT_TRUE(readerCertificate2); + + const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0}, + {1, readerCertificate2.value(), true, 1}, + {2, {}, false, 0}}; + + optional<vector<SecureAccessControlProfile>> secureProfiles = + test_utils::AddAccessControlProfiles(writableCredential, testProfiles); + ASSERT_TRUE(secureProfiles); + + const vector<test_utils::TestEntryData> testEntries1 = { + // test empty name space + {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}}, + {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}}, + }; + + map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> 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<int32_t>{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<test_utils::TestEntryData> testEntries3 = { + {"Name Space", "First name", string("Alan"), vector<int32_t>{0, 1}}, + {"Name Space", "Home address", string("Maida Vale, London, England"), + vector<int32_t>{0}}, + }; + + for (const auto& entry : testEntries3) { + EXPECT_FALSE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize, + encryptedBlobs, false)); + } + + vector<uint8_t> credentialData; + vector<uint8_t> 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 000000000..3aeebc66b --- /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 <aidl/Gtest.h> +#include <map> + +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<IWritableIdentityCredential>& writableCredential, + sp<IIdentityCredentialStore>& 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<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal) { + vector<uint8_t> privKey; + return GenerateReaderCertificate(serialDecimal, privKey); +} + +optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal, + vector<uint8_t>& readerPrivateKey) { + optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair(); + if (!readerKeyPKCS8) { + return {}; + } + + optional<vector<uint8_t>> readerPublicKey = + support::ecKeyPairGetPublicKey(readerKeyPKCS8.value()); + optional<vector<uint8_t>> 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<vector<SecureAccessControlProfile>> AddAccessControlProfiles( + sp<IWritableIdentityCredential>& writableCredential, + const vector<TestProfile>& testProfiles) { + Status result; + + vector<SecureAccessControlProfile> 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<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry, + int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs, + bool expectSuccess) { + Status result; + vector<vector<uint8_t>> 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<vector<uint8_t>> encryptedChunks; + for (const auto& chunk : chunks) { + vector<uint8_t> 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<Certificate>& inputCertificates) { + return (inputCertificates.size() >= 2); + // TODO: add parsing of the certificate and make sure it is genuine. +} + +void SetImageData(vector<uint8_t>& 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 000000000..043ccd690 --- /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 <android/hardware/identity/IIdentityCredentialStore.h> +#include <android/hardware/identity/support/IdentityCredentialSupport.h> +#include <cppbor.h> +#include <cppbor_parse.h> + +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<IWritableIdentityCredential>& writableCredential, string challenge, + vector<uint8_t> applicationId) + : attestationApplicationId(applicationId) { + // ASSERT_NE(writableCredential, nullptr); + + if (!challenge.empty()) { + attestationChallenge.assign(challenge.begin(), challenge.end()); + } + + result = writableCredential->getAttestationCertificate( + attestationApplicationId, attestationChallenge, &attestationCertificate); + } + + AttestationData() {} + + vector<uint8_t> attestationChallenge; + vector<uint8_t> attestationApplicationId; + vector<Certificate> attestationCertificate; + Status result; +}; + +struct TestEntryData { + TestEntryData(string nameSpace, string name, vector<int32_t> profileIds) + : nameSpace(nameSpace), name(name), profileIds(profileIds) {} + + TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Tstr(((const char*)value.data())).encode(); + } + TestEntryData(string nameSpace, string name, const vector<uint8_t>& value, + vector<int32_t> profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Bstr(value).encode(); + } + TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds) + : TestEntryData(nameSpace, name, profileIds) { + valueCbor = cppbor::Bool(value).encode(); + } + TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds) + : TestEntryData(nameSpace, name, profileIds) { + if (value >= 0) { + valueCbor = cppbor::Uint(value).encode(); + } else { + valueCbor = cppbor::Nint(-value).encode(); + } + } + + string nameSpace; + string name; + vector<uint8_t> valueCbor; + vector<int32_t> profileIds; +}; + +struct TestProfile { + uint16_t id; + vector<uint8_t> readerCertificate; + bool userAuthenticationRequired; + uint64_t timeoutMillis; +}; + +bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential, + sp<IIdentityCredentialStore>& credentialStore); + +optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal); + +optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal, + vector<uint8_t>& readerPrivateKey); + +optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles( + sp<IWritableIdentityCredential>& writableCredential, + const vector<TestProfile>& testProfiles); + +bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry, + int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs, + bool expectSuccess); + +bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates); + +void SetImageData(vector<uint8_t>& 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 bf6a5c3c4..dc49ddc99 100644 --- a/identity/support/src/IdentityCredentialSupport.cpp +++ b/identity/support/src/IdentityCredentialSupport.cpp @@ -958,12 +958,17 @@ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> createEcKeyPairAnd optional<vector<uint8_t>> 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"; |
