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
Change-Id: I11b79dbd57b1830609c70301fea9c99f9e5080cb
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index d7f47e8..3b8fbd9 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -160,17 +160,10 @@
      *       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 @@
      *
      * 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 @@
      *
      *        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 @@
      *
      *  - 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 b7ad283..297fd1d 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -29,9 +29,27 @@
      * 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 @@
      *     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 @@
      *         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 381eb84..4e9e0e6 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -164,6 +164,7 @@
     }
 
     *outChallenge = challenge;
+    authChallenge_ = challenge;
     return ndk::ScopedAStatus::ok();
 }
 
@@ -223,7 +224,8 @@
         }
 
         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;
@@ -337,28 +339,6 @@
     //
     // 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<uint8_t>& bytesDE = encodedDE->value();
-
         auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
         if (!getXYSuccess) {
             return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -366,8 +346,10 @@
                     "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 "
@@ -474,9 +456,10 @@
     }
 
     // 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 8bc4b49..fea289b 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -65,6 +65,10 @@
                 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<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
     vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
@@ -165,6 +169,13 @@
                 "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 5b075c6..58473dc 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -10,6 +10,8 @@
         "VtsIdentityTestUtils.cpp",
         "VtsAttestationTests.cpp",
         "VtsAttestationParserSupport.cpp",
+        "UserAuthTests.cpp",
+        "ReaderAuthTests.cpp",
     ],
     shared_libs: [
         "android.hardware.keymaster@4.0",
@@ -18,6 +20,7 @@
         "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 0000000..680ba5b
--- /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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.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 <utility>
+
+#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<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+    }
+
+    void provisionData();
+    void retrieveData(const vector<uint8_t>& readerPrivateKey,
+                      const vector<vector<uint8_t>>& readerCertChain, bool expectSuccess,
+                      bool leaveOutAccessibleToAllFromRequestMessage);
+
+    // Set by provisionData
+    vector<uint8_t> readerPublicKey_;
+    vector<uint8_t> readerPrivateKey_;
+    vector<uint8_t> intermediateAPublicKey_;
+    vector<uint8_t> intermediateAPrivateKey_;
+    vector<uint8_t> intermediateBPublicKey_;
+    vector<uint8_t> intermediateBPrivateKey_;
+    vector<uint8_t> intermediateCPublicKey_;
+    vector<uint8_t> intermediateCPrivateKey_;
+
+    vector<uint8_t> cert_A_SelfSigned_;
+
+    vector<uint8_t> cert_B_SelfSigned_;
+
+    vector<uint8_t> cert_B_SignedBy_C_;
+
+    vector<uint8_t> cert_C_SelfSigned_;
+
+    vector<uint8_t> cert_reader_SelfSigned_;
+    vector<uint8_t> cert_reader_SignedBy_A_;
+    vector<uint8_t> cert_reader_SignedBy_B_;
+
+    SecureAccessControlProfile sacp0_;
+    SecureAccessControlProfile sacp1_;
+    SecureAccessControlProfile sacp2_;
+    SecureAccessControlProfile sacp3_;
+
+    vector<uint8_t> encContentAccessibleByA_;
+    vector<uint8_t> encContentAccessibleByAorB_;
+    vector<uint8_t> encContentAccessibleByB_;
+    vector<uint8_t> encContentAccessibleByC_;
+    vector<uint8_t> encContentAccessibleByAll_;
+    vector<uint8_t> encContentAccessibleByNone_;
+
+    vector<uint8_t> credentialData_;
+
+    // Set by retrieveData()
+    bool canGetAccessibleByA_;
+    bool canGetAccessibleByAorB_;
+    bool canGetAccessibleByB_;
+    bool canGetAccessibleByC_;
+    bool canGetAccessibleByAll_;
+    bool canGetAccessibleByNone_;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+};
+
+pair<vector<uint8_t>, vector<uint8_t>> generateReaderKey() {
+    optional<vector<uint8_t>> keyPKCS8 = support::createEcKeyPair();
+    optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPKCS8.value());
+    optional<vector<uint8_t>> privateKey = support::ecKeyPairGetPrivateKey(keyPKCS8.value());
+    return make_pair(publicKey.value(), privateKey.value());
+}
+
+vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
+                                   const vector<uint8_t>& signingKey) {
+    time_t validityNotBefore = 0;
+    time_t validityNotAfter = 0xffffffff;
+    optional<vector<uint8_t>> 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<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> 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<uint8_t> proofOfProvisioningSignature;
+    ASSERT_TRUE(wc->finishAddingEntries(&credentialData_, &proofOfProvisioningSignature).isOk());
+}
+
+RequestDataItem buildRequestDataItem(const string& name, size_t size,
+                                     vector<int32_t> accessControlProfileIds) {
+    RequestDataItem item;
+    item.name = name;
+    item.size = size;
+    item.accessControlProfileIds = accessControlProfileIds;
+    return item;
+}
+
+void ReaderAuthTests::retrieveData(const vector<uint8_t>& readerPrivateKey,
+                                   const vector<vector<uint8_t>>& readerCertChain,
+                                   bool expectSuccess,
+                                   bool leaveOutAccessibleToAllFromRequestMessage) {
+    canGetAccessibleByA_ = false;
+    canGetAccessibleByAorB_ = false;
+    canGetAccessibleByB_ = false;
+    canGetAccessibleByC_ = false;
+    canGetAccessibleByAll_ = false;
+    canGetAccessibleByNone_ = false;
+
+    sp<IIdentityCredential> c;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &c)
+                        .isOk());
+
+    optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+    optional<vector<uint8_t>> readerEPublicKey =
+            support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+    ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+    vector<uint8_t> eKeyPair;
+    ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
+    optional<vector<uint8_t>> 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<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+    vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+    cppbor::Array sessionTranscript = cppbor::Array()
+                                              .add(cppbor::Semantic(24, deviceEngagementBytes))
+                                              .add(cppbor::Semantic(24, eReaderPubBytes));
+    vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
+
+    vector<uint8_t> 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<uint8_t> dataToSign = cppbor::Array()
+                                         .add("ReaderAuthentication")
+                                         .add(sessionTranscript.clone())
+                                         .add(cppbor::Semantic(24, itemsRequestBytes))
+                                         .encode();
+
+    optional<vector<uint8_t>> 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<uint8_t> 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<uint8_t> 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<uint8_t> mac;
+    vector<uint8_t> 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<IIdentityCredential> c;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData_, &c)
+                        .isOk());
+
+    optional<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+    optional<vector<uint8_t>> readerEPublicKey =
+            support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+    ASSERT_TRUE(c->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+    vector<uint8_t> eKeyPair;
+    ASSERT_TRUE(c->createEphemeralKeyPair(&eKeyPair).isOk());
+    optional<vector<uint8_t>> 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<uint8_t> nulls(32);
+    cppbor::Map deviceEngagement = cppbor::Map().add("ephX", nulls).add("ephY", nulls);
+    vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+    vector<uint8_t> eReaderPubBytes = cppbor::Tstr("ignored").encode();
+    cppbor::Array sessionTranscript = cppbor::Array()
+                                              .add(cppbor::Semantic(24, deviceEngagementBytes))
+                                              .add(cppbor::Semantic(24, eReaderPubBytes));
+    vector<uint8_t> sessionTranscriptBytes = sessionTranscript.encode();
+
+    vector<uint8_t> 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<uint8_t> dataToSign = cppbor::Array()
+                                         .add("ReaderAuthentication")
+                                         .add(sessionTranscript.clone())
+                                         .add(cppbor::Semantic(24, itemsRequestBytes))
+                                         .encode();
+
+    vector<vector<uint8_t>> readerCertChain = {cert_reader_SelfSigned_};
+    optional<vector<uint8_t>> 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<uint8_t> 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 0000000..5b4c8f1
--- /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 <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <aidl/android/hardware/keymaster/VerificationToken.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 <utility>
+
+#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<string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+    }
+
+    void provisionData();
+    void setupRetrieveData();
+    pair<HardwareAuthToken, VerificationToken> 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<uint8_t> encContentUserAuthPerSession_;
+    vector<uint8_t> encContentUserAuthTimeout_;
+    vector<uint8_t> encContentAccessibleByAll_;
+    vector<uint8_t> encContentAccessibleByNone_;
+
+    vector<uint8_t> credentialData_;
+
+    // Set by setupRetrieveData().
+    int64_t authChallenge_;
+    cppbor::Map sessionTranscript_;
+    sp<IIdentityCredential> credential_;
+
+    // Set by retrieveData()
+    bool canGetUserAuthPerSession_;
+    bool canGetUserAuthTimeout_;
+    bool canGetAccessibleByAll_;
+    bool canGetAccessibleByNone_;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+};
+
+void UserAuthTests::provisionData() {
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    sp<IWritableIdentityCredential> wc;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &wc).isOk());
+
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge = {1};
+    vector<Certificate> 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<uint8_t> 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<uint8_t>, vector<uint8_t>> generateReaderKey();
+vector<uint8_t> generateReaderCert(const vector<uint8_t>& publicKey,
+                                   const vector<uint8_t>& signingKey);
+RequestDataItem buildRequestDataItem(const string& name, size_t size,
+                                     vector<int32_t> accessControlProfileIds);
+
+cppbor::Map calcSessionTranscript(const vector<uint8_t>& ePublicKey) {
+    auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ePublicKey);
+    cppbor::Map deviceEngagement = cppbor::Map().add("ephX", ephX).add("ephY", ephY);
+    vector<uint8_t> deviceEngagementBytes = deviceEngagement.encode();
+    vector<uint8_t> 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<vector<uint8_t>> readerEKeyPair = support::createEcKeyPair();
+    optional<vector<uint8_t>> readerEPublicKey =
+            support::ecKeyPairGetPublicKey(readerEKeyPair.value());
+    ASSERT_TRUE(credential_->setReaderEphemeralPublicKey(readerEPublicKey.value()).isOk());
+
+    vector<uint8_t> eKeyPair;
+    ASSERT_TRUE(credential_->createEphemeralKeyPair(&eKeyPair).isOk());
+    optional<vector<uint8_t>> 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<uint8_t> itemsRequestBytes;
+    vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> mac;
+    vector<uint8_t> deviceNameSpaces;
+    ASSERT_TRUE(credential_->finishRetrieval(&mac, &deviceNameSpaces).isOk());
+}
+
+pair<HardwareAuthToken, VerificationToken> 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<vector<uint8_t>> eKeyPairNew = support::createEcKeyPair();
+    optional<vector<uint8_t>> 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 00b5893..c7cdfc7 100644
--- a/identity/aidl/vts/VtsAttestationTests.cpp
+++ b/identity/aidl/vts/VtsAttestationTests.cpp
@@ -61,51 +61,6 @@
     sp<IIdentityCredentialStore> credentialStore_;
 };
 
-TEST_P(VtsAttestationTests, verifyAttestationWithEmptyChallengeEmptyId) {
-    Status result;
-
-    HardwareInformation hwInfo;
-    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
-
-    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);
-
-    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<IWritableIdentityCredential> writableCredential;
-    ASSERT_TRUE(setupWritableCredential(writableCredential, credentialStore_));
-
-    vector<uint8_t> attestationChallenge;
-    vector<Certificate> attestationCertificate;
-    string applicationId = "Attestation Verification";
-    vector<uint8_t> 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 464ab0c..a0c4416 100644
--- a/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -27,15 +27,18 @@
 #include <gtest/gtest.h>
 #include <future>
 #include <map>
+#include <tuple>
 
 #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 @@
     ASSERT_GE(info.dataChunkSize, 256);
 }
 
+tuple<bool, string, vector<uint8_t>, vector<uint8_t>> extractFromTestCredentialData(
+        const vector<uint8_t>& credentialData) {
+    string docType;
+    vector<uint8_t> storageKey;
+    vector<uint8_t> 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<uint8_t> hardwareBoundKey = support::getTestHardwareBoundKey();
+    const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
+    const vector<uint8_t> docTypeVec(docType.begin(), docType.end());
+    optional<vector<uint8_t>> 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 @@
             writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature)
                     .isOk());
 
+    // Validate the proofOfProvisioning which was returned
     optional<vector<uint8_t>> proofOfProvisioning =
             support::coseSignGetPayload(proofOfProvisioningSignature);
     ASSERT_TRUE(proofOfProvisioning);
@@ -215,6 +274,22 @@
                                                  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<vector<uint8_t>> exCredentialKeyPair =
+            support::ecPrivateKeyToKeyPair(exCredentialPrivKey);
+    ASSERT_TRUE(exCredentialKeyPair);
+    optional<vector<uint8_t>> 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<IIdentityCredential> credential;
@@ -287,6 +362,24 @@
     vector<uint8_t> signingKeyBlob;
     Certificate signingKeyCertificate;
     ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+    optional<vector<uint8_t>> 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<uint8_t> exDocTypeVec(exDocType.begin(), exDocType.end());
+    optional<vector<uint8_t>> exSigningPrivKey =
+            support::decryptAes128Gcm(exStorageKey, signingKeyBlob, exDocTypeVec);
+    ASSERT_TRUE(exSigningPrivKey);
+    optional<vector<uint8_t>> exSigningKeyPair =
+            support::ecPrivateKeyToKeyPair(exSigningPrivKey.value());
+    ASSERT_TRUE(exSigningKeyPair);
+    optional<vector<uint8_t>> exSigningPubKey =
+            support::ecKeyPairGetPublicKey(exSigningKeyPair.value());
+    ASSERT_TRUE(exSigningPubKey);
+    ASSERT_EQ(exSigningPubKey.value(), signingPubKey.value());
 
     vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
     // OK to fail, not available in v1 HAL
@@ -316,6 +409,9 @@
             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<uint8_t> mac;
@@ -346,15 +442,12 @@
     deviceAuthentication.add(docType);
     deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
     vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
-    optional<vector<uint8_t>> signingPublicKey =
-            support::certificateChainGetTopMostKey(signingKeyCertificate.encodedCertificate);
-    EXPECT_TRUE(signingPublicKey);
 
     // Derive the key used for MACing.
     optional<vector<uint8_t>> readerEphemeralPrivateKey =
             support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
     optional<vector<uint8_t>> sharedSecret =
-            support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value());
+            support::ecdh(signingPubKey.value(), readerEphemeralPrivateKey.value());
     ASSERT_TRUE(sharedSecret);
     vector<uint8_t> salt = {0x00};
     vector<uint8_t> info = {};
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 8b0c050..b572b0f 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -69,11 +69,10 @@
     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 @@
 
     // First call should go through
     const vector<int32_t> entryCounts = {2, 4};
+    writableCredential->setExpectedProofOfProvisioningSize(123456);
     result = writableCredential->startPersonalization(5, entryCounts);
     ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
                                << endl;
@@ -151,18 +151,8 @@
 
     // 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);
+    writableCredential->setExpectedProofOfProvisioningSize(123456);
+    result = writableCredential->startPersonalization(1, entryCounts);
     EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
                                << endl;
 }
@@ -174,7 +164,8 @@
 
     // Verify minimal number of profile count and entry count
     const vector<int32_t> 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 @@
 
     // Verify set a large number of profile count and entry count is ok
     const vector<int32_t> 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 @@
 
     // Enter mismatched entry and profile numbers
     const vector<int32_t> 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 @@
     ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
 
     const vector<int32_t> 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 @@
         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 @@
     ;
     // 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 @@
     // 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);
+    writableCredential->setExpectedProofOfProvisioningSize(123456);
+    result = writableCredential->startPersonalization(3, entryCounts);
     ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
                                << endl;
 
@@ -674,6 +670,7 @@
     ASSERT_TRUE(test_utils::setupWritableCredential(writableCredential, credentialStore_));
 
     const vector<int32_t> 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 aaebcbe..b6ed80f 100644
--- a/identity/aidl/vts/VtsIdentityTestUtils.cpp
+++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp
@@ -96,9 +96,10 @@
         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 507e914..0f27a72 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<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& 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<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& 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 dc49ddc..e9d5d6c 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -1047,6 +1047,42 @@
     return privateKey;
 }
 
+optional<vector<uint8_t>> ecPrivateKeyToKeyPair(const vector<uint8_t>& 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<uint8_t> keyPair;
+    keyPair.resize(size);
+    unsigned char* p = keyPair.data();
+    i2d_PrivateKey(pkey.get(), &p);
+    return keyPair;
+}
+
 optional<vector<uint8_t>> ecKeyPairGetPkcs12(const vector<uint8_t>& keyPair, const string& name,
                                              const string& serialDecimal, const string& issuer,
                                              const string& subject, time_t validityNotBefore,