From d16d0fa609a80a6c9136b28c9a92053828e749fd Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Wed, 3 Jun 2020 13:24:52 -0400 Subject: [PATCH] Update Identity Credential VTS tests. These updates are based on input/experiences implementing this HAL. There are no API changes. - Specify that the validity for credentialKey certificate shall be from current time and expire at the same time as the attestation batch certificate. - Require challenge passed to getAttestationCertificate() is non-empty. - Fix bug in VTS tests where the startPersonlization() result was not checked. - Remove verifyStartPersonalizationZero test since it cannot be completed. - Ensure secureUserId is non-zero if user authentication is needed. - Specify format for signingKeyBlob in generateSigningKeyPair() same way we do for credentialData in finishAddingEntries(). - Modify EndToEndTest to decrypt/unpack credentialData to obtain credentialPrivKey and storageKey and do cross-checks on these. - Modify EndToEndTest to decrypt/unpack signingKeyBlob to obtain signingKeyPriv and check it matches the public key in the returned certificate. - Add new VTS tests for user and reader authentication. - Relax unnecessary requirements about SessionTranscript structure - just require it has X and Y of the ephemeral key created earlier. - Allow calls in VTS tests to v2 HAL to fail - this should allow these VTS tests to pass on a compliant v1 HAL. Bug: 156911917 Bug: 158107945 Test: atest VtsHalIdentityTargetTest Test: atest android.security.identity.cts Merged-In: I11b79dbd57b1830609c70301fea9c99f9e5080cb Change-Id: I93003389012e69c6df23e1bcebeafde8281caf9c --- .../identity/IIdentityCredential.aidl | 29 +- .../identity/IWritableIdentityCredential.aidl | 24 +- identity/aidl/default/IdentityCredential.cpp | 35 +- .../default/WritableIdentityCredential.cpp | 11 + identity/aidl/vts/Android.bp | 3 + identity/aidl/vts/ReaderAuthTests.cpp | 596 ++++++++++++++++++ identity/aidl/vts/UserAuthTests.cpp | 473 ++++++++++++++ identity/aidl/vts/VtsAttestationTests.cpp | 45 -- .../aidl/vts/VtsHalIdentityEndToEndTest.cpp | 101 ++- .../VtsIWritableIdentityCredentialTests.cpp | 45 +- identity/aidl/vts/VtsIdentityTestUtils.cpp | 3 +- .../support/IdentityCredentialSupport.h | 5 + .../support/src/IdentityCredentialSupport.cpp | 36 ++ 13 files changed, 1286 insertions(+), 120 deletions(-) create mode 100644 identity/aidl/vts/ReaderAuthTests.cpp create mode 100644 identity/aidl/vts/UserAuthTests.cpp diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl index d7f47e82c0..3b8fbd9e1f 100644 --- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl +++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl @@ -160,17 +160,10 @@ interface IIdentityCredential { * ItemsRequestBytes * ] * - * SessionTranscript = [ - * DeviceEngagementBytes, - * EReaderKeyBytes - * ] + * SessionTranscript = any * - * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) - * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) * - * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader - * * The public key corresponding to the key used to made signature, can be found in the * 'x5chain' unprotected header element of the COSE_Sign1 structure (as as described * in 'draft-ietf-cose-x509-04'). There will be at least one certificate in said element @@ -184,8 +177,12 @@ interface IIdentityCredential { * * If the SessionTranscript CBOR is not empty, the X and Y coordinates of the public * part of the key-pair previously generated by createEphemeralKeyPair() must appear - * somewhere in the bytes of DeviceEngagement structure. Both X and Y should be in - * uncompressed form. If this is not satisfied, the call fails with + * somewhere in the bytes of the CBOR. Each of these coordinates must appear encoded + * with the most significant bits first and use the exact amount of bits indicated by + * the key size of the ephemeral keys. For example, if the ephemeral key is using the + * P-256 curve then the 32 bytes for the X coordinate encoded with the most significant + * bits first must appear somewhere in the CBOR and ditto for the 32 bytes for the Y + * coordinate. If this is not satisfied, the call fails with * STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND. * * @param accessControlProfiles @@ -298,13 +295,8 @@ interface IIdentityCredential { * * DocType = tstr * - * SessionTranscript = [ - * DeviceEngagementBytes, - * EReaderKeyBytes - * ] + * SessionTranscript = any * - * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) - * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) * * where @@ -356,8 +348,9 @@ interface IIdentityCredential { * * - subjectPublicKeyInfo: must contain attested public key. * - * @param out signingKeyBlob contains an encrypted copy of the newly-generated private - * signing key. + * @param out signingKeyBlob contains an AES-GCM-ENC(storageKey, R, signingKey, docType) + * where signingKey is an EC private key in uncompressed form. That is, the returned + * blob is an encrypted copy of the newly-generated private signing key. * * @return an X.509 certificate for the new signing key, signed by the credential key. */ diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl index b7ad283bf8..297fd1d8ec 100644 --- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl +++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl @@ -29,9 +29,27 @@ interface IWritableIdentityCredential { * Gets the certificate chain for credentialKey which can be used to prove the hardware * characteristics to an issuing authority. Must not be called more than once. * + * The following non-optional fields for the X.509 certificate shall be set as follows: + * + * - version: INTEGER 2 (means v3 certificate). + * + * - serialNumber: INTEGER 1 (fixed value: same on all certs). + * + * - signature: must be set to ECDSA. + * + * - subject: CN shall be set to "Android Identity Credential Key". + * + * - issuer: shall be set to "credentialStoreName (credentialStoreAuthorName)" using the + * values returned in HardwareInformation. + * + * - validity: should be from current time and expire at the same time as the + * attestation batch certificate used. + * + * - subjectPublicKeyInfo: must contain attested public key. + * * The certificate chain must be generated using Keymaster Attestation * (see https://source.android.com/security/keystore/attestation) with the - * following additional requirements: + * following additional requirements on the data in the attestation extension: * * - The attestationVersion field in the attestation extension must be at least 3. * @@ -109,7 +127,8 @@ interface IWritableIdentityCredential { * in Tag::ATTESTATION_APPLICATION_ID. This schema is described in * https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid * - * @param attestationChallenge a challenge set by the issuer to ensure freshness. + * @param attestationChallenge a challenge set by the issuer to ensure freshness. If + * this is empty, the call fails with STATUS_INVALID_DATA. * * @return the X.509 certificate chain for the credentialKey */ @@ -250,6 +269,7 @@ interface IWritableIdentityCredential { * CredentialKeys = [ * bstr, ; storageKey, a 128-bit AES key * bstr ; credentialPrivKey, the private key for credentialKey + * ; in uncompressed form * ] * * @param out proofOfProvisioningSignature proves to the IA that the credential was imported diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp index 8a00d221b7..f3c4bbfc28 100644 --- a/identity/aidl/default/IdentityCredential.cpp +++ b/identity/aidl/default/IdentityCredential.cpp @@ -164,6 +164,7 @@ ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge } *outChallenge = challenge; + authChallenge_ = challenge; return ndk::ScopedAStatus::ok(); } @@ -223,7 +224,8 @@ bool checkUserAuthentication(const SecureAccessControlProfile& profile, } if (authToken.challenge != int64_t(authChallenge)) { - LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created"; + LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") " + << "doesn't match the challenge we created (" << authChallenge << ")"; return false; } return true; @@ -341,28 +343,6 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( // // We do this by just searching for the X and Y coordinates. if (sessionTranscript.size() > 0) { - const cppbor::Array* array = sessionTranscriptItem_->asArray(); - if (array == nullptr || array->size() != 2) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, - "SessionTranscript is not an array with two items")); - } - const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic(); - if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, - "First item in SessionTranscript array is not a " - "semantic with value 24")); - } - const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr(); - if (encodedDE == nullptr) { - return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( - IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, - "Child of semantic in first item in SessionTranscript " - "array is not a bstr")); - } - const vector& bytesDE = encodedDE->value(); - auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_); if (!getXYSuccess) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( @@ -370,8 +350,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( "Error extracting X and Y from ePub")); } if (sessionTranscript.size() > 0 && - !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr && - memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) { + !(memmem(sessionTranscript.data(), sessionTranscript.size(), ePubX.data(), + ePubX.size()) != nullptr && + memmem(sessionTranscript.data(), sessionTranscript.size(), ePubY.data(), + ePubY.size()) != nullptr)) { return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, "Did not find ephemeral public key's X and Y coordinates in " @@ -478,9 +460,10 @@ ndk::ScopedAStatus IdentityCredential::startRetrieval( } // Validate all the access control profiles in the requestData. - bool haveAuthToken = (authToken.mac.size() > 0); + bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0)); for (const auto& profile : accessControlProfiles) { if (!secureAccessControlProfileCheckMac(profile, storageKey_)) { + LOG(ERROR) << "Error checking MAC for profile"; return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( IIdentityCredentialStore::STATUS_INVALID_DATA, "Error checking MAC for profile")); diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp index 7732c33a16..c218866ace 100644 --- a/identity/aidl/default/WritableIdentityCredential.cpp +++ b/identity/aidl/default/WritableIdentityCredential.cpp @@ -65,6 +65,10 @@ ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate( IIdentityCredentialStore::STATUS_FAILED, "Error attestation certificate previously generated")); } + if (attestationChallenge.empty()) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty")); + } vector challenge(attestationChallenge.begin(), attestationChallenge.end()); vector appId(attestationApplicationId.begin(), attestationApplicationId.end()); @@ -165,6 +169,13 @@ ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile( "userAuthenticationRequired is false but timeout is non-zero")); } + // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero. + if (userAuthenticationRequired && secureUserId == 0) { + return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage( + IIdentityCredentialStore::STATUS_INVALID_DATA, + "userAuthenticationRequired is true but secureUserId is zero")); + } + profile.id = id; profile.readerCertificate = readerCertificate; profile.userAuthenticationRequired = userAuthenticationRequired; diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp index 5b075c6e67..58473dc2a2 100644 --- a/identity/aidl/vts/Android.bp +++ b/identity/aidl/vts/Android.bp @@ -10,6 +10,8 @@ cc_test { "VtsIdentityTestUtils.cpp", "VtsAttestationTests.cpp", "VtsAttestationParserSupport.cpp", + "UserAuthTests.cpp", + "ReaderAuthTests.cpp", ], shared_libs: [ "android.hardware.keymaster@4.0", @@ -18,6 +20,7 @@ cc_test { "libkeymaster_portable", "libsoft_attestation_cert", "libpuresoftkeymasterdevice", + "android.hardware.keymaster-ndk_platform", ], static_libs: [ "libcppbor", diff --git a/identity/aidl/vts/ReaderAuthTests.cpp b/identity/aidl/vts/ReaderAuthTests.cpp new file mode 100644 index 0000000000..680ba5b7f9 --- /dev/null +++ b/identity/aidl/vts/ReaderAuthTests.cpp @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "ReaderAuthTests" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VtsIdentityTestUtils.h" + +namespace android::hardware::identity { + +using std::endl; +using std::make_pair; +using std::map; +using std::optional; +using std::pair; +using std::string; +using std::tie; +using std::vector; + +using ::android::sp; +using ::android::String16; +using ::android::binder::Status; + +using ::android::hardware::keymaster::HardwareAuthToken; +using ::android::hardware::keymaster::VerificationToken; + +class ReaderAuthTests : public testing::TestWithParam { + public: + virtual void SetUp() override { + credentialStore_ = android::waitForDeclaredService( + String16(GetParam().c_str())); + ASSERT_NE(credentialStore_, nullptr); + } + + void provisionData(); + void retrieveData(const vector& readerPrivateKey, + const vector>& readerCertChain, bool expectSuccess, + bool leaveOutAccessibleToAllFromRequestMessage); + + // Set by provisionData + vector readerPublicKey_; + vector readerPrivateKey_; + vector intermediateAPublicKey_; + vector intermediateAPrivateKey_; + vector intermediateBPublicKey_; + vector intermediateBPrivateKey_; + vector intermediateCPublicKey_; + vector intermediateCPrivateKey_; + + vector cert_A_SelfSigned_; + + vector cert_B_SelfSigned_; + + vector cert_B_SignedBy_C_; + + vector cert_C_SelfSigned_; + + vector cert_reader_SelfSigned_; + vector cert_reader_SignedBy_A_; + vector cert_reader_SignedBy_B_; + + SecureAccessControlProfile sacp0_; + SecureAccessControlProfile sacp1_; + SecureAccessControlProfile sacp2_; + SecureAccessControlProfile sacp3_; + + vector encContentAccessibleByA_; + vector encContentAccessibleByAorB_; + vector encContentAccessibleByB_; + vector encContentAccessibleByC_; + vector encContentAccessibleByAll_; + vector encContentAccessibleByNone_; + + vector credentialData_; + + // Set by retrieveData() + bool canGetAccessibleByA_; + bool canGetAccessibleByAorB_; + bool canGetAccessibleByB_; + bool canGetAccessibleByC_; + bool canGetAccessibleByAll_; + bool canGetAccessibleByNone_; + + sp credentialStore_; +}; + +pair, vector> generateReaderKey() { + optional> keyPKCS8 = support::createEcKeyPair(); + optional> publicKey = support::ecKeyPairGetPublicKey(keyPKCS8.value()); + optional> privateKey = support::ecKeyPairGetPrivateKey(keyPKCS8.value()); + return make_pair(publicKey.value(), privateKey.value()); +} + +vector generateReaderCert(const vector& publicKey, + const vector& signingKey) { + time_t validityNotBefore = 0; + time_t validityNotAfter = 0xffffffff; + optional> cert = + support::ecPublicKeyGenerateCertificate(publicKey, signingKey, "24601", "Issuer", + "Subject", validityNotBefore, validityNotAfter); + return cert.value(); +} + +void ReaderAuthTests::provisionData() { + // Keys and certificates for intermediates. + tie(intermediateAPublicKey_, intermediateAPrivateKey_) = generateReaderKey(); + tie(intermediateBPublicKey_, intermediateBPrivateKey_) = generateReaderKey(); + tie(intermediateCPublicKey_, intermediateCPrivateKey_) = generateReaderKey(); + + cert_A_SelfSigned_ = generateReaderCert(intermediateAPublicKey_, intermediateAPrivateKey_); + + cert_B_SelfSigned_ = generateReaderCert(intermediateBPublicKey_, intermediateBPrivateKey_); + + cert_B_SignedBy_C_ = generateReaderCert(intermediateBPublicKey_, intermediateCPrivateKey_); + + cert_C_SelfSigned_ = generateReaderCert(intermediateCPublicKey_, intermediateCPrivateKey_); + + // Key and self-signed certificate reader + tie(readerPublicKey_, readerPrivateKey_) = generateReaderKey(); + cert_reader_SelfSigned_ = generateReaderCert(readerPublicKey_, readerPrivateKey_); + + // Certificate for reader signed by intermediates + cert_reader_SignedBy_A_ = generateReaderCert(readerPublicKey_, intermediateAPrivateKey_); + cert_reader_SignedBy_B_ = generateReaderCert(readerPublicKey_, intermediateBPrivateKey_); + + string docType = "org.iso.18013-5.2019.mdl"; + bool testCredential = true; + sp wc; + ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk()); + + vector attestationApplicationId = {}; + vector attestationChallenge = {1}; + vector certChain; + ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge, + &certChain) + .isOk()); + + size_t proofOfProvisioningSize = + 465 + cert_A_SelfSigned_.size() + cert_B_SelfSigned_.size() + cert_C_SelfSigned_.size(); + ASSERT_TRUE(wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize).isOk()); + + // Not in v1 HAL, may fail + wc->startPersonalization(4 /* numAccessControlProfiles */, + {6} /* numDataElementsPerNamespace */); + + // AIDL expects certificates wrapped in the Certificate type... + Certificate cert_A; + Certificate cert_B; + Certificate cert_C; + cert_A.encodedCertificate = cert_A_SelfSigned_; + cert_B.encodedCertificate = cert_B_SelfSigned_; + cert_C.encodedCertificate = cert_C_SelfSigned_; + + // Access control profile 0: accessible by A + ASSERT_TRUE(wc->addAccessControlProfile(0, cert_A, false, 0, 0, &sacp0_).isOk()); + + // Access control profile 1: accessible by B + ASSERT_TRUE(wc->addAccessControlProfile(1, cert_B, false, 0, 0, &sacp1_).isOk()); + + // Access control profile 2: accessible by C + ASSERT_TRUE(wc->addAccessControlProfile(2, cert_C, false, 0, 0, &sacp2_).isOk()); + + // Access control profile 3: open access + ASSERT_TRUE(wc->addAccessControlProfile(3, {}, false, 0, 0, &sacp3_).isOk()); + + // Data Element: "Accessible by A" + ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "Accessible by A", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByA_).isOk()); + + // Data Element: "Accessible by A or B" + ASSERT_TRUE(wc->beginAddEntry({0, 1}, "ns", "Accessible by A or B", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAorB_).isOk()); + + // Data Element: "Accessible by B" + ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "Accessible by B", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByB_).isOk()); + + // Data Element: "Accessible by C" + ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by C", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByC_).isOk()); + + // Data Element: "Accessible by All" + ASSERT_TRUE(wc->beginAddEntry({3}, "ns", "Accessible by All", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk()); + + // Data Element: "Accessible by None" + ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk()); + + vector proofOfProvisioningSignature; + ASSERT_TRUE(wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature).isOk()); +} + +RequestDataItem buildRequestDataItem(const string& name, size_t size, + vector accessControlProfileIds) { + RequestDataItem item; + item.name = name; + item.size = size; + item.accessControlProfileIds = accessControlProfileIds; + return item; +} + +void ReaderAuthTests::retrieveData(const vector& readerPrivateKey, + const vector>& readerCertChain, + bool expectSuccess, + bool leaveOutAccessibleToAllFromRequestMessage) { + canGetAccessibleByA_ = false; + canGetAccessibleByAorB_ = false; + canGetAccessibleByB_ = false; + canGetAccessibleByC_ = false; + canGetAccessibleByAll_ = false; + canGetAccessibleByNone_ = false; + + sp c; + ASSERT_TRUE(credentialStore_ + ->getCredential( + CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, + credentialData_, &c) + .isOk()); + + optional> readerEKeyPair = support::createEcKeyPair(); + optional> readerEPublicKey = + support::ecKeyPairGetPublicKey(readerEKeyPair.value()); + ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk()); + + vector eKeyPair; + ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk()); + optional> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair); + + // Calculate requestData field and sign it with the reader key. + auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value()); + ASSERT_TRUE(getXYSuccess); + cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY); + vector deviceEngagementBytes = deviceEngagement.encode(); + vector eReaderPubBytes = cppbor::Tstr("ignored").encode(); + cppbor::Array sessionTranscript = cppbor::Array() + .add(cppbor::Semantic(24, deviceEngagementBytes)) + .add(cppbor::Semantic(24, eReaderPubBytes)); + vector sessionTranscriptBytes = sessionTranscript.encode(); + + vector itemsRequestBytes; + if (leaveOutAccessibleToAllFromRequestMessage) { + itemsRequestBytes = + cppbor::Map("nameSpaces", + cppbor::Map().add("ns", cppbor::Map() + .add("Accessible by A", false) + .add("Accessible by A or B", false) + .add("Accessible by B", false) + .add("Accessible by C", false) + .add("Accessible by None", false))) + .encode(); + } else { + itemsRequestBytes = + cppbor::Map("nameSpaces", + cppbor::Map().add("ns", cppbor::Map() + .add("Accessible by A", false) + .add("Accessible by A or B", false) + .add("Accessible by B", false) + .add("Accessible by C", false) + .add("Accessible by All", false) + .add("Accessible by None", false))) + .encode(); + } + vector dataToSign = cppbor::Array() + .add("ReaderAuthentication") + .add(sessionTranscript.clone()) + .add(cppbor::Semantic(24, itemsRequestBytes)) + .encode(); + + optional> readerSignature = + support::coseSignEcDsa(readerPrivateKey, // private key for reader + {}, // content + dataToSign, // detached content + support::certificateChainJoin(readerCertChain)); + ASSERT_TRUE(readerSignature); + + // Generate the key that will be used to sign AuthenticatedData. + vector signingKeyBlob; + Certificate signingKeyCertificate; + ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); + + RequestNamespace rns; + rns.namespaceName = "ns"; + rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0})); + rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1})); + rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1})); + rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2})); + rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3})); + rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {})); + // OK to fail, not available in v1 HAL + c->setRequestedNamespaces({rns}).isOk(); + + // It doesn't matter since no user auth is needed in this particular test, + // but for good measure, clear out the tokens we pass to the HAL. + HardwareAuthToken authToken; + VerificationToken verificationToken; + authToken.challenge = 0; + authToken.userId = 0; + authToken.authenticatorId = 0; + authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE; + authToken.timestamp.milliSeconds = 0; + authToken.mac.clear(); + verificationToken.challenge = 0; + verificationToken.timestamp.milliSeconds = 0; + verificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE; + verificationToken.mac.clear(); + // OK to fail, not available in v1 HAL + c->setVerificationToken(verificationToken); + + Status status = c->startRetrieval( + {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob, + sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */}); + if (expectSuccess) { + ASSERT_TRUE(status.isOk()); + } else { + ASSERT_FALSE(status.isOk()); + return; + } + + vector decrypted; + + status = c->startRetrieveEntryValue("ns", "Accessible by A", 1, {0}); + if (status.isOk()) { + canGetAccessibleByA_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByA_, &decrypted).isOk()); + } + + status = c->startRetrieveEntryValue("ns", "Accessible by A or B", 1, {0, 1}); + if (status.isOk()) { + canGetAccessibleByAorB_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAorB_, &decrypted).isOk()); + } + + status = c->startRetrieveEntryValue("ns", "Accessible by B", 1, {1}); + if (status.isOk()) { + canGetAccessibleByB_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByB_, &decrypted).isOk()); + } + + status = c->startRetrieveEntryValue("ns", "Accessible by C", 1, {2}); + if (status.isOk()) { + canGetAccessibleByC_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByC_, &decrypted).isOk()); + } + + status = c->startRetrieveEntryValue("ns", "Accessible by All", 1, {3}); + if (status.isOk()) { + canGetAccessibleByAll_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk()); + } + + status = c->startRetrieveEntryValue("ns", "Accessible by None", 1, {}); + if (status.isOk()) { + canGetAccessibleByNone_ = true; + ASSERT_TRUE(c->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk()); + } + + vector mac; + vector deviceNameSpaces; + ASSERT_TRUE(c->finishRetrieval(&mac, &deviceNameSpaces).isOk()); +} + +TEST_P(ReaderAuthTests, presentingChain_Reader) { + provisionData(); + retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */, + false /* leaveOutAccessibleToAllFromRequestMessage */); + EXPECT_FALSE(canGetAccessibleByA_); + EXPECT_FALSE(canGetAccessibleByAorB_); + EXPECT_FALSE(canGetAccessibleByB_); + EXPECT_FALSE(canGetAccessibleByC_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(ReaderAuthTests, presentingChain_Reader_A) { + provisionData(); + retrieveData(readerPrivateKey_, {cert_reader_SignedBy_A_, cert_A_SelfSigned_}, + true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); + EXPECT_TRUE(canGetAccessibleByA_); + EXPECT_TRUE(canGetAccessibleByAorB_); + EXPECT_FALSE(canGetAccessibleByB_); + EXPECT_FALSE(canGetAccessibleByC_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(ReaderAuthTests, presentingChain_Reader_B) { + provisionData(); + retrieveData(readerPrivateKey_, {cert_reader_SignedBy_B_, cert_B_SelfSigned_}, + true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); + EXPECT_FALSE(canGetAccessibleByA_); + EXPECT_TRUE(canGetAccessibleByAorB_); + EXPECT_TRUE(canGetAccessibleByB_); + EXPECT_FALSE(canGetAccessibleByC_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +// This test proves that for the purpose of determining inclusion of an ACP certificate +// in a presented reader chain, certificate equality is done by comparing public keys, +// not bitwise comparison of the certificates. +// +// Specifically for this test, the ACP is configured with cert_B_SelfSigned_ and the +// reader is presenting cert_B_SignedBy_C_. Both certificates have the same public +// key - intermediateBPublicKey_ - but they are signed by different keys. +// +TEST_P(ReaderAuthTests, presentingChain_Reader_B_C) { + provisionData(); + retrieveData(readerPrivateKey_, + {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_}, + true /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); + EXPECT_FALSE(canGetAccessibleByA_); + EXPECT_TRUE(canGetAccessibleByAorB_); + EXPECT_TRUE(canGetAccessibleByB_); + EXPECT_TRUE(canGetAccessibleByC_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +// This test presents a reader chain where the chain is invalid because +// the 2nd certificate in the chain isn't signed by the 3rd one. +// +TEST_P(ReaderAuthTests, presentingInvalidChain) { + provisionData(); + retrieveData(readerPrivateKey_, + {cert_reader_SignedBy_B_, cert_B_SelfSigned_, cert_C_SelfSigned_}, + false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); +} + +// This tests presents a valid reader chain but where requestMessage isn't +// signed by the private key corresponding to the public key in the top-level +// certificate. +// +TEST_P(ReaderAuthTests, presentingMessageSignedNotByTopLevel) { + provisionData(); + retrieveData(intermediateBPrivateKey_, + {cert_reader_SignedBy_B_, cert_B_SignedBy_C_, cert_C_SelfSigned_}, + false /* expectSuccess */, false /* leaveOutAccessibleToAllFromRequestMessage */); +} + +// This test leaves out "Accessible by All" data element from the signed request +// message (the CBOR from the reader) while still including this data element at +// the API level. The call on the API level for said element will fail with +// STATUS_NOT_IN_REQUEST_MESSAGE but this doesn't prevent the other elements +// from being returned (if authorized, of course). +// +// This test verifies that. +// +TEST_P(ReaderAuthTests, limitedMessage) { + provisionData(); + retrieveData(readerPrivateKey_, {cert_reader_SelfSigned_}, true /* expectSuccess */, + true /* leaveOutAccessibleToAllFromRequestMessage */); + EXPECT_FALSE(canGetAccessibleByA_); + EXPECT_FALSE(canGetAccessibleByAorB_); + EXPECT_FALSE(canGetAccessibleByB_); + EXPECT_FALSE(canGetAccessibleByC_); + EXPECT_FALSE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(ReaderAuthTests, ephemeralKeyNotInSessionTranscript) { + provisionData(); + + sp c; + ASSERT_TRUE(credentialStore_ + ->getCredential( + CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, + credentialData_, &c) + .isOk()); + + optional> readerEKeyPair = support::createEcKeyPair(); + optional> readerEPublicKey = + support::ecKeyPairGetPublicKey(readerEKeyPair.value()); + ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk()); + + vector eKeyPair; + ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk()); + optional> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair); + + // Calculate requestData field and sign it with the reader key. + auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey.value()); + ASSERT_TRUE(getXYSuccess); + // Instead of include the X and Y coordinates (|ephX| and |ephY|), add NUL bytes instead. + vector nulls(32); + cppbor::Map deviceEngagement = cppbor::Map().add("ephX", nulls).add("ephY", nulls); + vector deviceEngagementBytes = deviceEngagement.encode(); + vector eReaderPubBytes = cppbor::Tstr("ignored").encode(); + cppbor::Array sessionTranscript = cppbor::Array() + .add(cppbor::Semantic(24, deviceEngagementBytes)) + .add(cppbor::Semantic(24, eReaderPubBytes)); + vector sessionTranscriptBytes = sessionTranscript.encode(); + + vector itemsRequestBytes; + itemsRequestBytes = + cppbor::Map("nameSpaces", + cppbor::Map().add("ns", cppbor::Map() + .add("Accessible by A", false) + .add("Accessible by A or B", false) + .add("Accessible by B", false) + .add("Accessible by C", false) + .add("Accessible by None", false))) + .encode(); + vector dataToSign = cppbor::Array() + .add("ReaderAuthentication") + .add(sessionTranscript.clone()) + .add(cppbor::Semantic(24, itemsRequestBytes)) + .encode(); + + vector> readerCertChain = {cert_reader_SelfSigned_}; + optional> readerSignature = + support::coseSignEcDsa(readerPrivateKey_, // private key for reader + {}, // content + dataToSign, // detached content + support::certificateChainJoin(readerCertChain)); + ASSERT_TRUE(readerSignature); + + // Generate the key that will be used to sign AuthenticatedData. + vector signingKeyBlob; + Certificate signingKeyCertificate; + ASSERT_TRUE(c->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); + + RequestNamespace rns; + rns.namespaceName = "ns"; + rns.items.push_back(buildRequestDataItem("Accessible by A", 1, {0})); + rns.items.push_back(buildRequestDataItem("Accessible by A or B", 1, {0, 1})); + rns.items.push_back(buildRequestDataItem("Accessible by B", 1, {1})); + rns.items.push_back(buildRequestDataItem("Accessible by C", 1, {2})); + rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {3})); + rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {})); + // OK to fail, not available in v1 HAL + c->setRequestedNamespaces({rns}).isOk(); + + // It doesn't matter since no user auth is needed in this particular test, + // but for good measure, clear out the tokens we pass to the HAL. + HardwareAuthToken authToken; + VerificationToken verificationToken; + authToken.challenge = 0; + authToken.userId = 0; + authToken.authenticatorId = 0; + authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE; + authToken.timestamp.milliSeconds = 0; + authToken.mac.clear(); + verificationToken.challenge = 0; + verificationToken.timestamp.milliSeconds = 0; + verificationToken.securityLevel = + ::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT; + verificationToken.mac.clear(); + // OK to fail, not available in v1 HAL + c->setVerificationToken(verificationToken); + + // Finally check that STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND is returned. + // This proves that the TA checked for X and Y coordinatets and didn't find + // them. + Status status = c->startRetrieval( + {sacp0_, sacp1_, sacp2_, sacp3_}, authToken, itemsRequestBytes, signingKeyBlob, + sessionTranscriptBytes, readerSignature.value(), {6 /* numDataElementsPerNamespace */}); + ASSERT_FALSE(status.isOk()); + ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, status.exceptionCode()); + ASSERT_EQ(IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, + status.serviceSpecificErrorCode()); +} + +INSTANTIATE_TEST_SUITE_P( + Identity, ReaderAuthTests, + testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), + android::PrintInstanceNameToString); + +} // namespace android::hardware::identity diff --git a/identity/aidl/vts/UserAuthTests.cpp b/identity/aidl/vts/UserAuthTests.cpp new file mode 100644 index 0000000000..5b4c8f12ef --- /dev/null +++ b/identity/aidl/vts/UserAuthTests.cpp @@ -0,0 +1,473 @@ +/* + * 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 "UserAuthTests" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VtsIdentityTestUtils.h" + +namespace android::hardware::identity { + +using std::endl; +using std::make_pair; +using std::map; +using std::optional; +using std::pair; +using std::string; +using std::tie; +using std::vector; + +using ::android::sp; +using ::android::String16; +using ::android::binder::Status; + +using ::android::hardware::keymaster::HardwareAuthToken; +using ::android::hardware::keymaster::VerificationToken; + +class UserAuthTests : public testing::TestWithParam { + public: + virtual void SetUp() override { + credentialStore_ = android::waitForDeclaredService( + String16(GetParam().c_str())); + ASSERT_NE(credentialStore_, nullptr); + } + + void provisionData(); + void setupRetrieveData(); + pair mintTokens(uint64_t challengeForAuthToken, + int64_t ageOfAuthTokenMilliSeconds); + void retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken, + bool expectSuccess, bool useSessionTranscript); + + // Set by provisionData + SecureAccessControlProfile sacp0_; + SecureAccessControlProfile sacp1_; + SecureAccessControlProfile sacp2_; + + vector encContentUserAuthPerSession_; + vector encContentUserAuthTimeout_; + vector encContentAccessibleByAll_; + vector encContentAccessibleByNone_; + + vector credentialData_; + + // Set by setupRetrieveData(). + int64_t authChallenge_; + cppbor::Map sessionTranscript_; + sp credential_; + + // Set by retrieveData() + bool canGetUserAuthPerSession_; + bool canGetUserAuthTimeout_; + bool canGetAccessibleByAll_; + bool canGetAccessibleByNone_; + + sp credentialStore_; +}; + +void UserAuthTests::provisionData() { + string docType = "org.iso.18013-5.2019.mdl"; + bool testCredential = true; + sp wc; + ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk()); + + vector attestationApplicationId = {}; + vector attestationChallenge = {1}; + vector certChain; + ASSERT_TRUE(wc->getAttestationCertificate(attestationApplicationId, attestationChallenge, + &certChain) + .isOk()); + + size_t proofOfProvisioningSize = 381; + // Not in v1 HAL, may fail + wc->setExpectedProofOfProvisioningSize(proofOfProvisioningSize); + + ASSERT_TRUE(wc->startPersonalization(3 /* numAccessControlProfiles */, + {4} /* numDataElementsPerNamespace */) + .isOk()); + + // Access control profile 0: user auth every session (timeout = 0) + ASSERT_TRUE(wc->addAccessControlProfile(0, {}, true, 0, 65 /* secureUserId */, &sacp0_).isOk()); + + // Access control profile 1: user auth, 60 seconds timeout + ASSERT_TRUE( + wc->addAccessControlProfile(1, {}, true, 60000, 65 /* secureUserId */, &sacp1_).isOk()); + + // Access control profile 2: open access + ASSERT_TRUE(wc->addAccessControlProfile(2, {}, false, 0, 0, &sacp2_).isOk()); + + // Data Element: "UserAuth Per Session" + ASSERT_TRUE(wc->beginAddEntry({0}, "ns", "UserAuth Per Session", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthPerSession_).isOk()); + + // Data Element: "UserAuth Timeout" + ASSERT_TRUE(wc->beginAddEntry({1}, "ns", "UserAuth Timeout", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentUserAuthTimeout_).isOk()); + + // Data Element: "Accessible by All" + ASSERT_TRUE(wc->beginAddEntry({2}, "ns", "Accessible by All", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByAll_).isOk()); + + // Data Element: "Accessible by None" + ASSERT_TRUE(wc->beginAddEntry({}, "ns", "Accessible by None", 1).isOk()); + ASSERT_TRUE(wc->addEntryValue({9}, &encContentAccessibleByNone_).isOk()); + + vector proofOfProvisioningSignature; + Status status = wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature); + EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage(); +} + +// From ReaderAuthTest.cpp - TODO: consolidate with VtsIdentityTestUtils.h +pair, vector> generateReaderKey(); +vector generateReaderCert(const vector& publicKey, + const vector& signingKey); +RequestDataItem buildRequestDataItem(const string& name, size_t size, + vector accessControlProfileIds); + +cppbor::Map calcSessionTranscript(const vector& ePublicKey) { + auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey); + cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY); + vector deviceEngagementBytes = deviceEngagement.encode(); + vector eReaderPubBytes = cppbor::Tstr("ignored").encode(); + // Let SessionTranscript be a map here (it's an array in EndToEndTest) just + // to check that the implementation can deal with either. + cppbor::Map sessionTranscript; + sessionTranscript.add(42, cppbor::Semantic(24, deviceEngagementBytes)); + sessionTranscript.add(43, cppbor::Semantic(24, eReaderPubBytes)); + return sessionTranscript; +} + +void UserAuthTests::setupRetrieveData() { + ASSERT_TRUE(credentialStore_ + ->getCredential( + CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256, + credentialData_, &credential_) + .isOk()); + + optional> readerEKeyPair = support::createEcKeyPair(); + optional> readerEPublicKey = + support::ecKeyPairGetPublicKey(readerEKeyPair.value()); + ASSERT_TRUE(credential_->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk()); + + vector eKeyPair; + ASSERT_TRUE(credential_->createEphemeralKeyPair(&eKeyPair).isOk()); + optional> ePublicKey = support::ecKeyPairGetPublicKey(eKeyPair); + sessionTranscript_ = calcSessionTranscript(ePublicKey.value()); + + Status status = credential_->createAuthChallenge(&authChallenge_); + EXPECT_TRUE(status.isOk()) << status.exceptionCode() << ": " << status.exceptionMessage(); +} + +void UserAuthTests::retrieveData(HardwareAuthToken authToken, VerificationToken verificationToken, + bool expectSuccess, bool useSessionTranscript) { + canGetUserAuthPerSession_ = false; + canGetUserAuthTimeout_ = false; + canGetAccessibleByAll_ = false; + canGetAccessibleByNone_ = false; + + vector itemsRequestBytes; + vector sessionTranscriptBytes; + if (useSessionTranscript) { + sessionTranscriptBytes = sessionTranscript_.encode(); + + itemsRequestBytes = + cppbor::Map("nameSpaces", + cppbor::Map().add("ns", cppbor::Map() + .add("UserAuth Per Session", false) + .add("UserAuth Timeout", false) + .add("Accessible by All", false) + .add("Accessible by None", false))) + .encode(); + vector dataToSign = cppbor::Array() + .add("ReaderAuthentication") + .add(sessionTranscript_.clone()) + .add(cppbor::Semantic(24, itemsRequestBytes)) + .encode(); + } + + // Generate the key that will be used to sign AuthenticatedData. + vector signingKeyBlob; + Certificate signingKeyCertificate; + ASSERT_TRUE( + credential_->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); + + RequestNamespace rns; + rns.namespaceName = "ns"; + rns.items.push_back(buildRequestDataItem("UserAuth Per Session", 1, {0})); + rns.items.push_back(buildRequestDataItem("UserAuth Timeout", 1, {1})); + rns.items.push_back(buildRequestDataItem("Accessible by All", 1, {2})); + rns.items.push_back(buildRequestDataItem("Accessible by None", 1, {})); + // OK to fail, not available in v1 HAL + credential_->setRequestedNamespaces({rns}).isOk(); + + // OK to fail, not available in v1 HAL + credential_->setVerificationToken(verificationToken); + + Status status = credential_->startRetrieval({sacp0_, sacp1_, sacp2_}, authToken, + itemsRequestBytes, signingKeyBlob, + sessionTranscriptBytes, {} /* readerSignature */, + {4 /* numDataElementsPerNamespace */}); + if (expectSuccess) { + ASSERT_TRUE(status.isOk()); + } else { + ASSERT_FALSE(status.isOk()); + return; + } + + vector decrypted; + + status = credential_->startRetrieveEntryValue("ns", "UserAuth Per Session", 1, {0}); + if (status.isOk()) { + canGetUserAuthPerSession_ = true; + ASSERT_TRUE( + credential_->retrieveEntryValue(encContentUserAuthPerSession_, &decrypted).isOk()); + } + + status = credential_->startRetrieveEntryValue("ns", "UserAuth Timeout", 1, {1}); + if (status.isOk()) { + canGetUserAuthTimeout_ = true; + ASSERT_TRUE(credential_->retrieveEntryValue(encContentUserAuthTimeout_, &decrypted).isOk()); + } + + status = credential_->startRetrieveEntryValue("ns", "Accessible by All", 1, {2}); + if (status.isOk()) { + canGetAccessibleByAll_ = true; + ASSERT_TRUE(credential_->retrieveEntryValue(encContentAccessibleByAll_, &decrypted).isOk()); + } + + status = credential_->startRetrieveEntryValue("ns", "Accessible by None", 1, {}); + if (status.isOk()) { + canGetAccessibleByNone_ = true; + ASSERT_TRUE( + credential_->retrieveEntryValue(encContentAccessibleByNone_, &decrypted).isOk()); + } + + vector mac; + vector deviceNameSpaces; + ASSERT_TRUE(credential_->finishRetrieval(&mac, &deviceNameSpaces).isOk()); +} + +pair UserAuthTests::mintTokens( + uint64_t challengeForAuthToken, int64_t ageOfAuthTokenMilliSeconds) { + HardwareAuthToken authToken; + VerificationToken verificationToken; + + uint64_t epochMilliseconds = 1000ULL * 1000ULL * 1000ULL * 1000ULL; + + authToken.challenge = challengeForAuthToken; + authToken.userId = 65; + authToken.authenticatorId = 0; + authToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE; + authToken.timestamp.milliSeconds = epochMilliseconds - ageOfAuthTokenMilliSeconds; + authToken.mac.clear(); + verificationToken.challenge = authChallenge_; + verificationToken.timestamp.milliSeconds = epochMilliseconds; + verificationToken.securityLevel = + ::android::hardware::keymaster::SecurityLevel::TRUSTED_ENVIRONMENT; + verificationToken.mac.clear(); + return make_pair(authToken, verificationToken); +} + +TEST_P(UserAuthTests, GoodChallenge) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(authChallenge_, // challengeForAuthToken + 0); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_TRUE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, OtherChallenge) { + provisionData(); + setupRetrieveData(); + uint64_t otherChallenge = authChallenge_ ^ 0x12345678; + auto [authToken, verificationToken] = mintTokens(otherChallenge, // challengeForAuthToken + 0); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, NoChallenge) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 0); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, AuthTokenAgeZero) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 0); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, AuthTokenFromTheFuture) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + -1 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_FALSE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, AuthTokenInsideTimeout) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 30 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +TEST_P(UserAuthTests, AuthTokenOutsideTimeout) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 61 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_FALSE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +// The API works even when there's no SessionTranscript / itemsRequest. +// Verify that. +TEST_P(UserAuthTests, NoSessionTranscript) { + provisionData(); + setupRetrieveData(); + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 1 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + false /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +// This test verifies that it's possible to do multiple requests as long +// as the sessionTranscript doesn't change. +// +TEST_P(UserAuthTests, MultipleRequestsSameSessionTranscript) { + provisionData(); + setupRetrieveData(); + + // First we try with a stale authToken + // + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 61 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_FALSE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); + + // Then we get a new authToken and try again. + tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken + 5 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_TRUE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); +} + +// Like MultipleRequestsSameSessionTranscript but we change the sessionTranscript +// between the two calls. This test verifies that change is detected and the +// second request fails. +// +TEST_P(UserAuthTests, MultipleRequestsSessionTranscriptChanges) { + provisionData(); + setupRetrieveData(); + + // First we try with a stale authToken + // + auto [authToken, verificationToken] = mintTokens(0, // challengeForAuthToken + 61 * 1000); // ageOfAuthTokenMilliSeconds + retrieveData(authToken, verificationToken, true /* expectSuccess */, + true /* useSessionTranscript */); + EXPECT_FALSE(canGetUserAuthPerSession_); + EXPECT_FALSE(canGetUserAuthTimeout_); + EXPECT_TRUE(canGetAccessibleByAll_); + EXPECT_FALSE(canGetAccessibleByNone_); + + // Then we get a new authToken and try again. + tie(authToken, verificationToken) = mintTokens(0, // challengeForAuthToken + 5 * 1000); // ageOfAuthTokenMilliSeconds + + // Change sessionTranscript... + optional> eKeyPairNew = support::createEcKeyPair(); + optional> ePublicKeyNew = support::ecKeyPairGetPublicKey(eKeyPairNew.value()); + sessionTranscript_ = calcSessionTranscript(ePublicKeyNew.value()); + + // ... and expect failure. + retrieveData(authToken, verificationToken, false /* expectSuccess */, + true /* useSessionTranscript */); +} + +INSTANTIATE_TEST_SUITE_P( + Identity, UserAuthTests, + testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)), + android::PrintInstanceNameToString); + +} // namespace android::hardware::identity diff --git a/identity/aidl/vts/VtsAttestationTests.cpp b/identity/aidl/vts/VtsAttestationTests.cpp index 00b5893d47..c7cdfc7714 100644 --- a/identity/aidl/vts/VtsAttestationTests.cpp +++ b/identity/aidl/vts/VtsAttestationTests.cpp @@ -61,51 +61,6 @@ class VtsAttestationTests : public testing::TestWithParam { sp credentialStore_; }; -TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeEmptyId) { - Status result; - - HardwareInformation hwInfo; - ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); - - sp writableCredential; - ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_)); - - vector attestationChallenge; - vector attestationCertificate; - vector attestationApplicationId = {}; - result = writableCredential->getAttestationCertificate( - attestationApplicationId, attestationChallenge, &attestationCertificate); - - ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() - << endl; - - EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge, - attestationApplicationId, hwInfo)); -} - -TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeNonemptyId) { - Status result; - - HardwareInformation hwInfo; - ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk()); - - sp writableCredential; - ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_)); - - vector attestationChallenge; - vector attestationCertificate; - string applicationId = "Attestation Verification"; - vector attestationApplicationId = {applicationId.begin(), applicationId.end()}; - - result = writableCredential->getAttestationCertificate( - attestationApplicationId, attestationChallenge, &attestationCertificate); - - ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() - << endl; - EXPECT_TRUE(validateAttestationCertificate(attestationCertificate, attestationChallenge, - attestationApplicationId, hwInfo)); -} - TEST_P(VtsAttestationTests, verifyAttestationWithNonemptyChallengeEmptyId) { Status result; diff --git a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp index 464ab0c6d0..a0c4416115 100644 --- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp +++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp @@ -27,15 +27,18 @@ #include #include #include +#include #include "VtsIdentityTestUtils.h" namespace android::hardware::identity { using std::endl; +using std::make_tuple; using std::map; using std::optional; using std::string; +using std::tuple; using std::vector; using ::android::sp; @@ -66,6 +69,61 @@ TEST_P(IdentityAidl, hardwareInformation) { ASSERT_GE(info.dataChunkSize, 256); } +tuple, vector> extractFromTestCredentialData( + const vector& credentialData) { + string docType; + vector storageKey; + vector credentialPrivKey; + + auto [item, _, message] = cppbor::parse(credentialData); + if (item == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + + const cppbor::Array* arrayItem = item->asArray(); + if (arrayItem == nullptr || arrayItem->size() != 3) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + + const cppbor::Tstr* docTypeItem = (*arrayItem)[0]->asTstr(); + const cppbor::Bool* testCredentialItem = + ((*arrayItem)[1]->asSimple() != nullptr ? ((*arrayItem)[1]->asSimple()->asBool()) + : nullptr); + const cppbor::Bstr* encryptedCredentialKeysItem = (*arrayItem)[2]->asBstr(); + if (docTypeItem == nullptr || testCredentialItem == nullptr || + encryptedCredentialKeysItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + + docType = docTypeItem->value(); + + vector hardwareBoundKey = support::getTestHardwareBoundKey(); + const vector& encryptedCredentialKeys = encryptedCredentialKeysItem->value(); + const vector docTypeVec(docType.begin(), docType.end()); + optional> decryptedCredentialKeys = + support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec); + if (!decryptedCredentialKeys) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + + auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value()); + if (dckItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + const cppbor::Array* dckArrayItem = dckItem->asArray(); + if (dckArrayItem == nullptr || dckArrayItem->size() != 2) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr(); + const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr(); + if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) { + return make_tuple(false, docType, storageKey, credentialPrivKey); + } + storageKey = storageKeyItem->value(); + credentialPrivKey = credentialPrivKeyItem->value(); + return make_tuple(true, docType, storageKey, credentialPrivKey); +} + TEST_P(IdentityAidl, createAndRetrieveCredential) { // First, generate a key-pair for the reader since its public key will be // part of the request data. @@ -155,6 +213,7 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature) .isOk()); + // Validate the proofOfProvisioning which was returned optional> proofOfProvisioning = support::coseSignGetPayload(proofOfProvisioningSignature); ASSERT_TRUE(proofOfProvisioning); @@ -215,6 +274,22 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { credentialPubKey.value())); writableCredential = nullptr; + // Extract doctype, storage key, and credentialPrivKey from credentialData... this works + // only because we asked for a test-credential meaning that the HBK is all zeroes. + auto [exSuccess, exDocType, exStorageKey, exCredentialPrivKey] = + extractFromTestCredentialData(credentialData); + ASSERT_TRUE(exSuccess); + ASSERT_EQ(exDocType, "org.iso.18013-5.2019.mdl"); + // ... check that the public key derived from the private key matches what was + // in the certificate. + optional> exCredentialKeyPair = + support::ecPrivateKeyToKeyPair(exCredentialPrivKey); + ASSERT_TRUE(exCredentialKeyPair); + optional> exCredentialPubKey = + support::ecKeyPairGetPublicKey(exCredentialKeyPair.value()); + ASSERT_TRUE(exCredentialPubKey); + ASSERT_EQ(exCredentialPubKey.value(), credentialPubKey.value()); + // Now that the credential has been provisioned, read it back and check the // correct data is returned. sp credential; @@ -287,6 +362,24 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { vector signingKeyBlob; Certificate signingKeyCertificate; ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk()); + optional> signingPubKey = + support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate); + EXPECT_TRUE(signingPubKey); + + // Since we're using a test-credential we know storageKey meaning we can get the + // private key. Do this, derive the public key from it, and check this matches what + // is in the certificate... + const vector exDocTypeVec(exDocType.begin(), exDocType.end()); + optional> exSigningPrivKey = + support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec); + ASSERT_TRUE(exSigningPrivKey); + optional> exSigningKeyPair = + support::ecPrivateKeyToKeyPair(exSigningPrivKey.value()); + ASSERT_TRUE(exSigningKeyPair); + optional> exSigningPubKey = + support::ecKeyPairGetPublicKey(exSigningKeyPair.value()); + ASSERT_TRUE(exSigningPubKey); + ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value()); vector requestedNamespaces = test_utils::buildRequestNamespaces(testEntries); // OK to fail, not available in v1 HAL @@ -316,6 +409,9 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { content.insert(content.end(), chunk.begin(), chunk.end()); } EXPECT_EQ(content, entry.valueCbor); + + // TODO: also use |exStorageKey| to decrypt data and check it's the same as whatt + // the HAL returns... } vector mac; @@ -346,15 +442,12 @@ TEST_P(IdentityAidl, createAndRetrieveCredential) { deviceAuthentication.add(docType); deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes)); vector encodedDeviceAuthentication = deviceAuthentication.encode(); - optional> signingPublicKey = - support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate); - EXPECT_TRUE(signingPublicKey); // Derive the key used for MACing. optional> readerEphemeralPrivateKey = support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value()); optional> sharedSecret = - support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value()); + support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value()); ASSERT_TRUE(sharedSecret); vector salt = {0x00}; vector info = {}; diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp index 8b0c050226..b572b0fbec 100644 --- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp +++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp @@ -69,11 +69,10 @@ TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) { result = writableCredential->getAttestationCertificate( attestationApplicationId, attestationChallenge, &attestationCertificate); - EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() - << endl; - - EXPECT_TRUE(test_utils::validateAttestationCertificate( - attestationCertificate, attestationChallenge, attestationApplicationId, hwInfo)); + 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, verifyAttestationSuccessWithChallenge) { @@ -130,6 +129,7 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalization) { // First call should go through const vector entryCounts = {2, 4}; + writableCredential->setExpectedProofOfProvisioningSize(123456); result = writableCredential->startPersonalization(5, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; @@ -151,18 +151,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) { // 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); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(1, entryCounts); EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; } @@ -174,7 +164,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) { // Verify minimal number of profile count and entry count const vector entryCounts = {1}; - writableCredential->startPersonalization(1, entryCounts); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(1, entryCounts); EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; } @@ -186,7 +177,8 @@ TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) { // Verify set a large number of profile count and entry count is ok const vector entryCounts = {3000}; - writableCredential->startPersonalization(3500, entryCounts); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(25, entryCounts); EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; } @@ -198,7 +190,8 @@ TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) { // Enter mismatched entry and profile numbers const vector entryCounts = {5, 6}; - writableCredential->startPersonalization(5, entryCounts); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(5, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; @@ -234,7 +227,8 @@ TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) { ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_)); const vector entryCounts = {3, 6}; - writableCredential->startPersonalization(3, entryCounts); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(3, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; @@ -251,9 +245,10 @@ TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) { SecureAccessControlProfile profile; Certificate cert; cert.encodedCertificate = testProfile.readerCertificate; + int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0; result = writableCredential->addAccessControlProfile( testProfile.id, cert, testProfile.userAuthenticationRequired, - testProfile.timeoutMillis, 0, &profile); + testProfile.timeoutMillis, secureUserId, &profile); if (expectOk) { expectOk = false; @@ -554,7 +549,7 @@ TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) { ; // OK to fail, not available in v1 HAL writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize); - writableCredential->startPersonalization(3, entryCounts); + result = writableCredential->startPersonalization(3, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; @@ -608,7 +603,8 @@ TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) { // 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); + writableCredential->setExpectedProofOfProvisioningSize(123456); + result = writableCredential->startPersonalization(3, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; @@ -674,6 +670,7 @@ TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) { ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_)); const vector entryCounts = {1}; + writableCredential->setExpectedProofOfProvisioningSize(123456); Status result = writableCredential->startPersonalization(1, entryCounts); ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage() << endl; diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp index aaebcbe3c9..b6ed80f4b1 100644 --- a/identity/aidl/vts/VtsIdentityTestUtils.cpp +++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp @@ -96,9 +96,10 @@ optional> addAccessControlProfiles( SecureAccessControlProfile profile; Certificate cert; cert.encodedCertificate = testProfile.readerCertificate; + int64_t secureUserId = testProfile.userAuthenticationRequired ? 66 : 0; result = writableCredential->addAccessControlProfile( testProfile.id, cert, testProfile.userAuthenticationRequired, - testProfile.timeoutMillis, 0, &profile); + testProfile.timeoutMillis, secureUserId, &profile); // Don't use assert so all errors can be outputed. Then return // instead of exit even on errors so caller can decide. diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h index 507e914fa3..0f27a7229a 100644 --- a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h +++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h @@ -134,6 +134,11 @@ optional> ecKeyPairGetPublicKey(const vector& keyPair); // optional> ecKeyPairGetPrivateKey(const vector& keyPair); +// Creates a PKCS#8 encoded key-pair from a private key (which must be uncompressed, +// e.g. 32 bytes). The public key is derived from the given private key.. +// +optional> ecPrivateKeyToKeyPair(const vector& privateKey); + // For an EC key |keyPair| encoded in PKCS#8 format, creates a PKCS#12 structure // with the key-pair (not using a password to encrypt the data). The public key // in the created structure is included as a certificate, using the given fields diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp index dc49ddc992..e9d5d6c7cc 100644 --- a/identity/support/src/IdentityCredentialSupport.cpp +++ b/identity/support/src/IdentityCredentialSupport.cpp @@ -1047,6 +1047,42 @@ optional> ecKeyPairGetPrivateKey(const vector& keyPair) return privateKey; } +optional> ecPrivateKeyToKeyPair(const vector& privateKey) { + auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr)); + if (bn.get() == nullptr) { + LOG(ERROR) << "Error creating BIGNUM"; + return {}; + } + + auto ecKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + if (EC_KEY_set_private_key(ecKey.get(), bn.get()) != 1) { + LOG(ERROR) << "Error setting private key from BIGNUM"; + return {}; + } + + auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + LOG(ERROR) << "Memory allocation failed"; + return {}; + } + + if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { + LOG(ERROR) << "Error getting private key"; + return {}; + } + + int size = i2d_PrivateKey(pkey.get(), nullptr); + if (size == 0) { + LOG(ERROR) << "Error generating public key encoding"; + return {}; + } + vector keyPair; + keyPair.resize(size); + unsigned char* p = keyPair.data(); + i2d_PrivateKey(pkey.get(), &p); + return keyPair; +} + optional> ecKeyPairGetPkcs12(const vector& keyPair, const string& name, const string& serialDecimal, const string& issuer, const string& subject, time_t validityNotBefore,