Merge "Convert VtsHalDrmV1_*TargetTest to be parameterized test"
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index 2c5e5cc..2948ecb 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -234,7 +234,7 @@
                  {
                          .prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED),
                          .access = VehiclePropertyAccess::READ,
-                         .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
+                         .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                          .minSampleRate = 1.0f,
                          .maxSampleRate = 10.0f,
                  },
diff --git a/current.txt b/current.txt
index 331a87d..853b8f8 100644
--- a/current.txt
+++ b/current.txt
@@ -615,13 +615,17 @@
 ce8dbe76eb9ee94b46ef98f725be992e760a5751073d4f4912484026541371f3 android.hardware.health@2.1::IHealth
 26f04510a0b57aba5167c5c0a7c2f077c2acbb98b81902a072517829fd9fd67f android.hardware.health@2.1::IHealthInfoCallback
 db47f4ceceb1f06c656f39caa70c557b0f8471ef59fd58611bea667ffca20101 android.hardware.health@2.1::types
+0589e410f519e36514e7ece18f283f022df0f70efd2c12821d822f67f74aba98 android.hardware.identity@1.0::types
+bbeee9604128ede83ee755b67e73b5ad29e6e1dbac9ec41fea6ffe2745b0c50a android.hardware.identity@1.0::IIdentityCredential
+96ce8aad80f4c476f25261f790d357c117e79e18474c7dadd850dac704bbe65e android.hardware.identity@1.0::IIdentityCredentialStore
+6e1e28a96c90ba78d47257faea3f3bb4e6360affbbfa5822f0dc31211f9266ff android.hardware.identity@1.0::IWritableIdentityCredential
 c228aaa27f66c48e147159a4f4996c5273191fece1b08de31bd171c61334855e android.hardware.keymaster@4.1::IKeymasterDevice
 adb0efdf1462e9b2e742c0dcadd598666aac551f178be06e755bfcdf5797abd0 android.hardware.keymaster@4.1::IOperation
 7a04ea5595ed418ca3e91c28b8bd7353dd988be9be7b0c8c9e64fb4b77bd4523 android.hardware.keymaster@4.1::types
 9e59fffceed0dd72a9799e04505db5f777bbbea1af0695ba4107ef6d967c6fda android.hardware.neuralnetworks@1.3::IDevice
 258825966435b3ed08832055bb736d81516013e405f161d9ccde9a90cfcdde83 android.hardware.neuralnetworks@1.3::IPreparedModel
 94e803236398bed1febb11cc21051bc42ec003700139b099d6c479e02a7ca3c3 android.hardware.neuralnetworks@1.3::IPreparedModelCallback
-618a628f8c94d6f6e4cb401b69fa50ccb8b82191ea434e3a071252289b4f312c android.hardware.neuralnetworks@1.3::types
+f3c1e7298da628a755b452cd3325e8d0fe867a2debb873069baab6a27434a72d android.hardware.neuralnetworks@1.3::types
 3e01d4446cd69fd1c48f8572efd97487bc179564b32bd795800b97bbe10be37b android.hardware.wifi@1.4::IWifi
 a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardware.wifi.supplicant@1.3::ISupplicant
 44445b8a03d7b9e68b2fbd954672c18a8fce9e32851b0692f4f4ab3407f86ecb android.hardware.wifi.supplicant@1.3::ISupplicantStaIface
diff --git a/drm/1.2/vts/functional/drm_hal_common.cpp b/drm/1.2/vts/functional/drm_hal_common.cpp
index 1f439f7..02aa3a9 100644
--- a/drm/1.2/vts/functional/drm_hal_common.cpp
+++ b/drm/1.2/vts/functional/drm_hal_common.cpp
@@ -132,7 +132,7 @@
     contentConfigurations = vendorModule->getContentConfigurations();
 
     // If drm scheme not installed skip subsequent tests
-    if (!drmFactory->isCryptoSchemeSupported(getVendorUUID())) {
+    if (drmFactory.get() == nullptr || !drmFactory->isCryptoSchemeSupported(getVendorUUID())) {
         vendorModule->setInstalled(false);
         return;
     }
diff --git a/drm/1.2/vts/functional/drm_hal_test.cpp b/drm/1.2/vts/functional/drm_hal_test.cpp
index 99c834d..54c5751 100644
--- a/drm/1.2/vts/functional/drm_hal_test.cpp
+++ b/drm/1.2/vts/functional/drm_hal_test.cpp
@@ -53,6 +53,7 @@
  * Ensure drm factory supports module UUID Scheme
  */
 TEST_P(DrmHalTest, VendorUuidSupported) {
+    RETURN_IF_SKIPPED;
     auto res = drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kSwSecureCrypto);
     ALOGI("kVideoMp4 = %s res %d", kVideoMp4, (bool)res);
     EXPECT_TRUE(res);
@@ -62,6 +63,7 @@
  * Ensure drm factory doesn't support an invalid scheme UUID
  */
 TEST_P(DrmHalTest, InvalidPluginNotSupported) {
+    RETURN_IF_SKIPPED;
     const uint8_t kInvalidUUID[16] = {
         0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
         0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
@@ -72,6 +74,7 @@
  * Ensure drm factory doesn't support an empty UUID
  */
 TEST_P(DrmHalTest, EmptyPluginUUIDNotSupported) {
+    RETURN_IF_SKIPPED;
     hidl_array<uint8_t, 16> emptyUUID;
     memset(emptyUUID.data(), 0, 16);
     EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(emptyUUID, kVideoMp4, kSwSecureCrypto));
@@ -81,6 +84,7 @@
  * Ensure drm factory doesn't support an invalid mime type
  */
 TEST_P(DrmHalTest, BadMimeNotSupported) {
+    RETURN_IF_SKIPPED;
     EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kBadMime, kSwSecureCrypto));
 }
 
@@ -134,6 +138,7 @@
  * A get key request should fail if no sessionId is provided
  */
 TEST_P(DrmHalTest, GetKeyRequestNoSession) {
+    RETURN_IF_SKIPPED;
     SessionId invalidSessionId;
     hidl_vec<uint8_t> initData;
     KeyedVector optionalParameters;
@@ -151,6 +156,7 @@
  * invalid mime type
  */
 TEST_P(DrmHalTest, GetKeyRequestBadMime) {
+    RETURN_IF_SKIPPED;
     auto sessionId = openSession();
     hidl_vec<uint8_t> initData;
     KeyedVector optionalParameters;
@@ -187,6 +193,7 @@
  * Test drm plugin offline key support
  */
 TEST_P(DrmHalTest, OfflineLicenseTest) {
+    RETURN_IF_SKIPPED;
     auto sessionId = openSession();
     hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
 
@@ -226,6 +233,7 @@
  * Test drm plugin offline key state
  */
 TEST_P(DrmHalTest, OfflineLicenseStateTest) {
+    RETURN_IF_SKIPPED;
     auto sessionId = openSession();
     DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent(KeyType::OFFLINE);
     hidl_vec<uint8_t> keySetId = loadKeys(sessionId, content, KeyType::OFFLINE);
@@ -250,6 +258,7 @@
  * Negative offline license test. Remove empty keySetId
  */
 TEST_P(DrmHalTest, RemoveEmptyKeySetId) {
+    RETURN_IF_SKIPPED;
     KeySetId emptyKeySetId;
     Status err = drmPlugin->removeOfflineLicense(emptyKeySetId);
     EXPECT_EQ(Status::BAD_VALUE, err);
@@ -259,6 +268,7 @@
  * Negative offline license test. Get empty keySetId state
  */
 TEST_P(DrmHalTest, GetEmptyKeySetIdState) {
+    RETURN_IF_SKIPPED;
     KeySetId emptyKeySetId;
     auto res = drmPlugin->getOfflineLicenseState(emptyKeySetId, checkKeySetIdState<Status::BAD_VALUE, OfflineLicenseState::UNKNOWN>);
     EXPECT_OK(res);
@@ -268,6 +278,7 @@
  * Test that the plugin returns valid connected and max HDCP levels
  */
 TEST_P(DrmHalTest, GetHdcpLevels) {
+    RETURN_IF_SKIPPED;
     auto res = drmPlugin->getHdcpLevels_1_2(
             [&](StatusV1_2 status, const HdcpLevel &connectedLevel,
                 const HdcpLevel &maxLevel) {
@@ -422,6 +433,7 @@
  * Ensure clearkey drm factory doesn't support security level higher than supported
  */
 TEST_P(DrmHalClearkeyTest, BadLevelNotSupported) {
+    RETURN_IF_SKIPPED;
     const SecurityLevel kHwSecureAll = SecurityLevel::HW_SECURE_ALL;
     EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kHwSecureAll));
 }
@@ -430,6 +442,7 @@
  * Test resource contention during attempt to generate key request
  */
 TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) {
+    RETURN_IF_SKIPPED;
     Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorResourceContention);
     EXPECT_EQ(Status::OK, status);
     auto sessionId = openSession();
@@ -451,6 +464,7 @@
  * Test clearkey plugin offline key with mock error
  */
 TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) {
+    RETURN_IF_SKIPPED;
     auto sessionId = openSession();
     hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE);
     Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorInvalidState);
@@ -472,6 +486,7 @@
  * Test SessionLostState is triggered on error
  */
 TEST_P(DrmHalClearkeyTest, SessionLostState) {
+    RETURN_IF_SKIPPED;
     sp<DrmHalPluginListener> listener = new DrmHalPluginListener();
     auto res = drmPlugin->setListener(listener);
     EXPECT_OK(res);
@@ -492,6 +507,7 @@
  * Negative decrypt test. Decrypt with invalid key.
  */
 TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) {
+    RETURN_IF_SKIPPED;
     vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
     const Pattern noPattern = {0, 0};
     const uint32_t kClearBytes = 512;
@@ -529,6 +545,7 @@
  * Negative decrypt test. Decrypt with a key exceeds AES_BLOCK_SIZE.
  */
 TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) {
+    RETURN_IF_SKIPPED;
     vector<uint8_t> iv(AES_BLOCK_SIZE, 0);
     const Pattern noPattern = {0, 0};
     const uint32_t kClearBytes = 512;
diff --git a/identity/1.0/Android.bp b/identity/1.0/Android.bp
new file mode 100644
index 0000000..a5cea90
--- /dev/null
+++ b/identity/1.0/Android.bp
@@ -0,0 +1,25 @@
+// This file is autogenerated by hidl-gen -Landroidbp.
+
+hidl_interface {
+    name: "android.hardware.identity@1.0",
+    root: "android.hardware",
+    vndk: {
+        enabled: true,
+    },
+    srcs: [
+        "types.hal",
+        "IIdentityCredential.hal",
+        "IIdentityCredentialStore.hal",
+        "IWritableIdentityCredential.hal",
+    ],
+    interfaces: [
+        "android.hidl.base@1.0",
+        "android.hardware.keymaster@4.0",
+    ],
+    types: [
+        "AuditLogEntry",
+        "ResultCode",
+        "SecureAccessControlProfile",
+    ],
+    gen_java: false,
+}
diff --git a/identity/1.0/IIdentityCredential.hal b/identity/1.0/IIdentityCredential.hal
new file mode 100644
index 0000000..75f6e18
--- /dev/null
+++ b/identity/1.0/IIdentityCredential.hal
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2020 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.
+ */
+
+package android.hardware.identity@1.0;
+
+import android.hardware.keymaster@4.0::HardwareAuthToken;
+
+interface IIdentityCredential {
+    /**
+     * Delete a credential.
+     *
+     * This method returns a COSE_Sign1 data structure signed by CredentialKey
+     * with payload set to the ProofOfDeletion CBOR below:
+     *
+     *     ProofOfDeletion = [
+     *          "ProofOfDeletion",            ; tstr
+     *          tstr,                         ; DocType
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *     ]
+     *
+     * After this method has been called, the persistent storage used for credentialData should
+     * be deleted.
+     *
+     * @return proofOfDeletionSignature is a COSE_Sign1 signature described above.
+     */
+    deleteCredential()
+        generates(Result result, vec<uint8_t> proofOfDeletionSignature);
+
+    /**
+     * Creates an ephemeral EC key pair, for use in establishing a seceure session with a reader.
+     * This method returns the private key so the caller can perform an ECDH key agreement operation
+     * with the reader.  The reason for generating the key pair in the secure environment is so that
+     * the secure environment knows what public key to expect to find in the session transcript.
+     *
+     * This method may only be called once per instance. If called more than once, FAILED
+     * will be returned.
+     *
+     * @return result is OK on success or FAILED if an error occurred.
+     *
+     * @return keyPair contains the unencrypted key-pair in PKCS#8 format.
+     */
+    createEphemeralKeyPair() generates (Result result, vec<uint8_t> keyPair);
+
+    /**
+     * Sets the public part of the reader's ephemeral key pair.
+     *
+     * This method may only be called once per instance. If called more than once, FAILED
+     * will be returned.
+     *
+     * @param publicKey contains the reader's ephemeral public key, in uncompressed form.
+     *
+     * @return result is OK on success or FAILED if an error occurred.
+     */
+    setReaderEphemeralPublicKey(vec<uint8_t> publicKey) generates (Result result);
+
+    /**
+     * Creates a challenge value to be used for proving successful user authentication. This
+     * is included in the authToken passed to the startRetrieval() method.
+     *
+     * This method may only be called once per instance. If called more than once, FAILED
+     * will be returned.
+     *
+     * @return result is OK on success or FAILED if an error occurred.
+     *
+     * @return challenge on success, is a non-zero number.
+     */
+    createAuthChallenge() generates (Result result, uint64_t challenge);
+
+    /**
+     * Start an entry retrieval process.
+     *
+     * This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
+     * createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
+     * multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
+     * finishRetrieval().This whole process is called a "credential presentation".
+     *
+     * It is permissible to perform multiple credential presentations using the same instance (e.g.
+     * startRetrieval(), then multiple calls of startRetrieveEntryValue(), retrieveEntryValue(),
+     * then finally finishRetrieval()) but if this is done, the sessionTranscript parameter
+     * must be identical for each startRetrieval() invocation. If this is not the case, this call
+     * fails with the SESSION_TRANSCRIPT_MISMATCH error.
+     *
+     * If the provided authToken is not valid this method fails with INVALID_AUTH_TOKEN.
+     *
+     * Each of the provided accessControlProfiles is checked in this call. If they are not
+     * all valid, the call fails with INVALID_DATA.
+     *
+     * For the itemsRequest parameter, the content can be defined in the way appropriate for
+     * the credential, but there are three requirements that must be met to work with this HAL:
+     *
+     *  1. The content must be a CBOR-encoded structure.
+     *  2. The CBOR structure must be a map.
+     *  3. The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
+     *     the example below.
+     *
+     * If these requirements are not met the startRetrieval() call fails with
+     * INVALID_ITEMS_REQUEST_MESSAGE.
+     *
+     * Here's an example of ItemsRequest CBOR which conforms to this requirement:
+     *
+     *   ItemsRequest = {
+     *     ? "docType" : DocType,
+     *       "nameSpaces" : NameSpaces,
+     *     ? "requestInfo" : {* tstr => any}   ; Additional info the reader wants to provide
+     *   }
+     *
+     *   DocType = tstr
+     *
+     *   NameSpaces = {
+     *     + NameSpace => DataElements    ; Requested data elements for each NameSpace
+     *   }
+     *
+     *   NameSpace = tstr
+     *
+     *   DataElements = {
+     *     + DataElement => IntentToRetain
+     *   }
+     *
+     *   DataElement = tstr
+     *   IntentToRetain = bool
+     *
+     * For the readerSignature parameter, this can either be empty or if non-empty it
+     * must be a COSE_Sign1 structure with an ECDSA signature over the content of the
+     * CBOR conforming to the following CDDL:
+     *
+     *     ReaderAuthentication = [
+     *       "ReaderAuthentication",
+     *       SessionTranscript,
+     *       ItemsRequestBytes
+     *     ]
+     *
+     *     SessionTranscript = [
+     *       DeviceEngagementBytes,
+     *       EReaderKeyBytes
+     *     ]
+     *
+     *     DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *     EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+     *
+     * 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
+     * and there may be more (and if so, each certificate must be signed by its successor).
+     * This is checked and if the check fails the call fails with READER_SIGNATURE_CHECK_FAILED.
+     *
+     * The SessionTranscript CBOR is conveyed in the sessionTranscript parameter. It
+     * is permissible for this to be empty in which case the readerSignature parameter
+     * must also be empty. If this is not the case, the call fails with FAILED.
+     *
+     * 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
+     * EPHEMERAL_PUBLIC_KEY_NOT_FOUND.
+     *
+     * @param accessControlProfiles
+     *   Access control profiles that are required to retrieve the entries that are going to be
+     *   requested with IIdentityCredential.retrieveEntryValue(). See above.
+     *
+     * @param authToken
+     *   The authentication token that proves the user was authenticated, as required
+     *   by one or more of the provided accessControlProfiles. See above.
+     *
+     * @param itemsRequest
+     *   If non-empty, contains request data that is signed by the reader. See above.
+     *
+     * @param sessionTranscript
+     *   Either empty or the CBOR of the SessionTranscript. See above.
+     *
+     * @param readerSignature
+     *   readerSignature is either empty or contains a CBOR_Sign1 structure. See above.
+     *
+     * @param requestCounts
+     *   requestCounts specifies the number of data items that are going to be requested, per
+     *   namespace.  The number of elements in the vector must be the number of namespaces for which
+     *   data items will be requested in retrieveEntryValue() calls, and the values of the elments
+     *   must be the number of items from each namespace, in order, that will be requested in
+     *   retrieveEntryValue() calls.
+     *   Note that it's the caller's responsibility to ensure that the counts correspond to the
+     *   retrieveEntryValue() calls that will be made, and that every retrieveEntryValue() call
+     *   will succeed (i.e. that the access control profile checks will succeed).  This means that
+     *   it's the responsibility of the caller to determine which access control checks will fail
+     *   and remove the corresponding requests from the counts.
+     *
+     * @return result is OK on success. If an error occurs one of the values described above
+     *   will be returned.
+     */
+    startRetrieval(vec<SecureAccessControlProfile> accessControlProfiles,
+                   HardwareAuthToken authToken,
+                   vec<uint8_t> itemsRequest,
+                   vec<uint8_t> sessionTranscript,
+                   vec<uint8_t> readerSignature,
+                   vec<uint16_t> requestCounts) generates(Result result);
+
+    /**
+     * Starts retrieving an entry, subject to access control requirements.  Entries must be
+     * retrieved in namespace groups as specified in the requestCounts parameter.
+     *
+     * If the requestData parameter as passed to startRetrieval() was non-empty
+     * this method must only be called with entries specified in that field. If this
+     * requirement is not met, the call fails with NOT_IN_REQUEST_MESSAGE.
+     *
+     * If nameSpace or name is empty this call fails with INVALID_DATA.
+     *
+     * Each access control profile for the entry is checked. If user authentication
+     * is required and the supplied auth token doesn't provide it the call fails
+     * with USER_AUTHENTICATION_FAILED. If reader authentication is required and
+     * a suitable reader certificate chain isn't presented, the call fails with
+     * READER_AUTHENTICATION_FAILED.
+     *
+     * It is permissible to keep retrieving values if an access control check fails.
+     *
+     * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+     *
+     * @param name is the name of the element.
+     *
+     * @param entrySize is the size of the entry value, if it's a text string or a byte string.
+     *     It must be zero if the entry value is an integer or boolean. If this requirement
+     *     is not met the call fails with INVALID_DATA.
+     *
+     * @param accessControlProfileIds specifies the set of access control profiles that can
+     *     authorize access to the provisioned element. If an identifier of a profile
+     *     is given and this profile wasn't passed to startRetrieval() this call fails
+     *     with INVALID_DATA.
+     *
+     * @return result is OK on success. Otherwise one of INVALID_DATA, FAILED,
+     *     USER_AUTHENTICATION_FAILED, READER_AUTHENTICATION_FAILED.
+     */
+    startRetrieveEntryValue(string nameSpace, string name, uint32_t entrySize,
+                            vec<uint16_t> accessControlProfileIds)
+        generates (Result result);
+
+
+    /**
+     * Retrieves an entry value, or part of one, if the entry value is larger than gcmChunkSize.
+     * May only be called after startRetrieveEntry().
+     *
+     * If the passed in data is not authentic, can't be decrypted, is of the wrong size, or can't
+     * be decoded, this call fails with INVALID_DATA.
+     *
+     * @param encryptedContent contains the encrypted and MACed content.
+     *
+     * @return result is OK on success, INVALID_DATA, or FAILED if an error occurred.
+     *
+     * @return content is the entry value as CBOR, or part of the entry value in the case the
+     *    content exceeds gcmChunkSize in length.
+     */
+    retrieveEntryValue(vec<uint8_t> encryptedContent)
+        generates (Result result, vec<uint8_t> content);
+
+
+    /**
+     * End retrieval of data, optionally returning a message authentication code over the
+     * returned data.
+     *
+     * If signingKeyBlob or the sessionTranscript parameter passed to startRetrieval() is
+     * empty then the returned MAC will be empty.
+     *
+     * @param signingKeyBlob is either empty or a signingKeyBlob (see generateSigningKeyPair(),
+     *    below) containing the signing key to use to sign the data retrieved. If this
+     *    is not in the right format the call fails with INVALID_DATA.
+     *
+     * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+     *
+     * @return mac is empty if signingKeyBlob or the sessionTranscript passed to
+     *    startRetrieval() is empty. Otherwise it is a COSE_Mac0 with empty payload
+     *    and the detached content is set to DeviceAuthentication as defined below.
+     *    The key used for the MAC operation is EMacKey and is derived as follows:
+     *
+     *     KDF(ECDH(SDeviceKey.Priv, EReaderKey.Pub))
+     *
+     *    where SDeviceKey.Priv is the key identified by signingKeyBlob. The KDF
+     *    and ECDH functions shall be the same as the ciphersuite selected and
+     *    passed to IIdentityStore.getCredential(). The EMacKey shall be derived
+     *    using a salt of 0x00.
+     *
+     *        DeviceAuthentication = [
+     *            "DeviceAuthentication",
+     *            SessionTranscript,
+     *            DocType,
+     *            DeviceNameSpaceBytes,
+     *        ]
+     *
+     *        DocType = tstr
+     *
+     *        SessionTranscript = [
+     *            DeviceEngagementBytes,
+     *            EReaderKeyBytes
+     *        ]
+     *
+     *        DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *        EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *        DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+     *
+     *    where
+     *
+     *        DeviceNameSpaces = {
+     *            * NameSpace => DeviceSignedItems
+     *        }
+     *        DeviceSignedItems = {
+     *            + DataItemName => DataItemValue
+     *        }
+     *
+     *        Namespace = tstr
+     *        DataItemName = tstr
+     *        DataItemValue = any
+     *
+     *
+     * @return deviceNameSpaces the bytes of DeviceNameSpaces.
+     */
+    finishRetrieval(vec<uint8_t> signingKeyBlob)
+        generates(Result result, vec<uint8_t> mac, vec<uint8_t> deviceNameSpaces);
+
+
+    /**
+     * Generate a key pair to be used for signing session data and retrieved data items.
+     *
+     * @return result is OK on success or FAILED if an error occurred.
+     *
+     * @return signingKeyBlob contains an encrypted copy of the newly-generated private signing key.
+     *
+     * @return signingKeyCertificate contains an X.509 certificate for the new signing key, signed
+     *     by the credential key.
+     */
+    generateSigningKeyPair()
+        generates(Result result, vec<uint8_t> signingKeyBlob,
+                  vec<uint8_t> signingKeyCertificate);
+};
diff --git a/identity/1.0/IIdentityCredentialStore.hal b/identity/1.0/IIdentityCredentialStore.hal
new file mode 100644
index 0000000..118ca6f
--- /dev/null
+++ b/identity/1.0/IIdentityCredentialStore.hal
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2020 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.
+ */
+
+package android.hardware.identity@1.0;
+
+import IWritableIdentityCredential;
+import IIdentityCredential;
+
+/**
+ * IIdentityCredentialStore provides an interface to a secure store for user identity documents.
+ * This HAL is deliberately fairly general and abstract.  To the extent possible, specification of
+ * the message formats and semantics of communication with credential verification devices and
+ * issuing authorities (IAs) is out of scope for this HAL.  It provides the interface with secure
+ * storage but a credential-specific Android application will be required to implement the
+ * presentation and verification protocols and processes appropriate for the specific credential
+ * type.
+ *
+ * The design of this HAL makes few assumptions about the underlying secure hardware.  In particular
+ * it does not assume that the secure hardware has any storage beyond that needed for a persistent,
+ * hardware-bound AES-128 key.  However, its design allows the use of secure hardware that does have
+ * storage, specifically to enable "direct access".  Most often credentials will be accessed through
+ * this HAL and through the Android layers above it but that requires that the Android device be
+ * powered up and fully functional.  It is desirable to allow identity credential usage when the
+ * Android device's battery is too low to boot the Android operating system, so direct access to the
+ * secure hardware via NFC may allow data retrieval, if the secure hardware chooses to implement it.
+ * Definition of how data is retrieved in low power mode is explicitly out of scope for this HAL
+ * specification; it's up to the relevant identity credential standards to define.
+ *
+ * The 'default' HAL instance is explicitly not for direct access and the 'direct_access' HAL
+ * instance - if available - is for direct access. Applications are expected to provision their
+ * credential to both instances (and the contents may differ), not just one of them.
+ *
+ * Multiple credentials can be created.  Each credential comprises:
+ *
+ * - A document type, which is a UTF-8 string of at most 256 bytes.
+ *
+ * - A set of namespaces, which serve to disambiguate value names.  Namespaces are UTF-8 strings of
+ *   up to 256 bytes in length (most should be much shorter).  It is recommended that namespaces be
+ *   structured as reverse domain names so that IANA effectively serves as the namespace registrar.
+ *
+ * - For each namespase, a set of name/value pairs, each with an associated set of access control
+ *   profile IDs.  Names are UTF-8 strings of up to 256 bytes in length (most should be much
+ *   shorter).  Values stored must be encoed as valid CBOR (https://tools.ietf.org/html/rfc7049) and
+ *   the encoeded size is is limited to at most 512 KiB.
+ *
+ * - A set of access control profiles, each with a profile ID and a specification of the
+ *   conditions which satisfy the profile's requirements.
+ *
+ * - An asymmetric key pair which is used to authenticate the credential to the IA, called the
+ *   CredentialKey. This key is attested to by the secure hardware using Android Keystore
+ *   attestation (https://source.android.com/security/keystore/attestation). See
+ *   getAttestationCertificate() in the IWritableIdentityCredential for more information.
+ *
+ * - A set of zero or more named reader authentication public keys, which are used to authenticate
+ *   an authorized reader to the credential.
+ *
+ * - A set of named signing keys, which are used to sign collections of values and session
+ *   transcripts.
+ *
+ * Cryptographic notation:
+ *
+ * Throughout this HAL, cryptographic operations are specified in detail.  To avoid repeating the
+ * definition of the notation, it's specified here.  It is assumed that the reader is familiar with
+ * standard cryptographic algorithms and constructs.
+ *
+ *     AES-GCM-ENC(K, N, D, A) represents AES-GCM encryption with key 'K', nonce 'N', additional
+ *         authenticated data 'A' and data 'D'.  The nonce is usually specified as 'R', meaning 12
+ *         random bytes.  The nonce is always 12 bytes and is prepended to the ciphertext. The GCM
+ *         tag is 16 bytes, appended to the ciphertext.  AES-GCM-DEC with the same argument notation
+ *         represents the corresponding decryption operation.
+ *
+ *    ECDSA(K, D) represents ECDSA signing of data 'D' with key 'K'.
+ *
+ *    || represents concatenation
+ *
+ *    {} represents an empty input; 0 bytes of data.
+ *
+ *    HBK represents a device-unique, hardware-bound AES-128 key which exists only in secure
+ *        hardware, except for "test" credential stores (see createCredential(), below).  For test
+ *        stores, an all-zero value is used in place of the HBK.
+ *
+ * Data encoding notation:
+ *
+ * Various fields need to be encoded as precisely-specified byte arrays.  Where existing standards
+ * define appropriate encodings, those are used.  For example, X.509 certificates.  Where new
+ * encodings are needed, CBOR is used.  CBOR maps are described in CDDL notation
+ * (https://tools.ietf.org/html/draft-ietf-cbor-cddl-06).
+ */
+interface IIdentityCredentialStore {
+
+    /**
+     * Returns information about hardware.
+     *
+     * The isDirectAccess output parameter indicates whether this credential store
+     * implementation is for direct access. Credentials provisioned in credential
+     * stores with this set to true, should use reader authentication on all data elements.
+     *
+     * @return result is OK on success, FAILED if an error occurred.
+     *
+     * @return credentialStoreName the name of the credential store implementation.
+     *
+     * @return credentialStoreAuthorName the name of the credential store author.
+     *
+     * @return dataChunkSize the maximum size of data chunks.
+     *
+     * @return isDirectAccess whether the provisioned credential is available through
+     *     direct access.
+     *
+     * @return supportedDocTypes if empty, then any document type is supported, otherwise
+     *     only the document types returned are supported.
+     */
+    getHardwareInformation()
+        generates(Result result,
+                  string credentialStoreName,
+                  string credentialStoreAuthorName,
+                  uint32_t dataChunkSize,
+                  bool isDirectAccess,
+                  vec<string> supportedDocTypes);
+
+    /**
+     * createCredential creates a new Credential.  When a Credential is created, two cryptographic
+     * keys are created: StorageKey, an AES key used to secure the externalized Credential
+     * contents, and CredentialKeyPair, an EC key pair used to authenticate the store to the IA.  In
+     * addition, all of the Credential data content is imported and a certificate for the
+     * CredentialKeyPair and a signature produced with the CredentialKeyPair are created.  These
+     * latter values may be checked by an issuing authority to verify that the data was imported
+     * into secure hardware and that it was imported unmodified.
+     *
+     * @param docType is an optional name (may be an empty string) that identifies the type of
+     *     credential being created, e.g. "org.iso.18013-5.2019.mdl" (the doc type of the ISO
+     *     driving license standard).
+     *
+     * @param testCredential indicates if this is a test store.  Test credentials must use an
+     *     all-zeros hardware-bound key (HBK) and must set the test bit in the
+     *     personalizationReceipt (see finishAddingEntries(), in IWritableIdentityCredential).
+     *
+     * @return result is OK on success, FAILED if an error occurred.
+     *
+     * @return writableCredential is an IWritableIdentityCredential HIDL interface that provides
+     *     operations to provision a credential.
+     */
+    createCredential(string docType, bool testCredential)
+        generates(Result result, IWritableIdentityCredential writableCredential);
+
+    /**
+     * getCredential retrieves an IIdentityCredential HIDL interface which allows use of a stored
+     * Credential.
+     *
+     * The cipher suite used to communicate with the remote verifier must also be specified. Currently
+     * only a single cipher-suite is supported and the details of this are as follow:
+     *
+     *  - ECDHE with HKDF-SHA-256 for key agreement.
+     *  - AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
+     *    for every message).
+     *  - ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
+     *    man-in-the-middle attacks), signing keys are not ephemeral.
+     *
+     * Support for other cipher suites may be added in a future version of this HAL.
+     *
+     * @param credentialData is a CBOR-encoded structure containing metadata about the credential
+     *     and an encrypted byte array that contains data used to secure the credential.  See the
+     *     return argument of the same name in finishAddingEntries(), in IWritableIdentityCredential.
+     *
+     * @return result is OK on success or INVALID_DATA if the passed in credentialData
+     *     cannot be decoded or decrypted.
+     *
+     * @return credential is an IIdentityCredential HIDL interface that provides operations on the
+     *     Credential.
+     */
+    getCredential(vec<uint8_t> credentialData)
+        generates (Result result, IIdentityCredential credential);
+};
diff --git a/identity/1.0/IWritableIdentityCredential.hal b/identity/1.0/IWritableIdentityCredential.hal
new file mode 100644
index 0000000..b1ce00d
--- /dev/null
+++ b/identity/1.0/IWritableIdentityCredential.hal
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2020 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.
+ */
+
+package android.hardware.identity@1.0;
+
+/**
+ * IWritableIdentityCredential is used to personalize a new identity credential.  Credentials cannot
+ * be updated or modified after creation; any changes require deletion and re-creation.
+ */
+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 certificate chain must be generated using Keymaster Attestation
+     * (see https://source.android.com/security/keystore/attestation) and must also
+     * have the Tag::IDENTITY_CREDENTIAL_KEY tag from KeyMaster 4.1 set. This tag indicates
+     * that this key is an Identity Credential key (which can only sign/MAC very
+     * specific messages) and not an Android Keystore key (which can be used to sign/MAC
+     * anything).
+     *
+     * @param attestationChallenge a challenge set by the issuer to ensure freshness.
+     *
+     * @return result is OK on success, FAILED if an error occurred.
+     *
+     * @return certificate is the X.509 certificate chain for the credentialKey
+     */
+    getAttestationCertificate(vec<uint8_t> attestationChallenge)
+        generates(Result result, vec<uint8_t> certificate);
+
+    /**
+     * Start the personalization process.
+     *
+     * startPersonalization must not be called more than once.
+     *
+     * @param accessControlProfileCount specifies the number of access control profiles that will be
+     *     provisioned with addAccessControlProfile().
+     *
+     * @param entryCounts specifies the number of data entries that will be provisioned with
+     *     beginAddEntry() and addEntry(). Each item in the array specifies how many entries
+     *     will be added for each name space.
+     *
+     * @return result is OK on success, FAILED if an error occurred.
+     *
+     */
+    startPersonalization(uint16_t accessControlProfileCount, vec<uint16_t> entryCounts)
+        generates(Result result);
+
+    /**
+     * Add an access control profile, which defines the requirements or retrieval of one or more
+     * entries.  If both readerCertificate and userAuthenticationRequired are empty/false,
+     * associated entries are open access, requiring no authentication to read (though the caller
+     * is free to require other authentication above this HAL).
+     *
+     * This method must be called exactly as many times as specified in the startPersonalization()
+     * accessControlProfileCount parameter. If this is requirement is not met, the method fails
+     * with INVALID_DATA.
+     *
+     * @param id a numeric identifier that must be unique within the context of a Credential and may
+     *     be used to reference the profile. If this is not satisfied the call fails with
+     *     INVALID_DATA.
+     *
+     * @param readerCertificate if non-empty, specifies a X.509 certificate (or chain of certificates)
+     *     that must be used to authenticate requests (see the readerSignature parameter in
+     *     IIdentityCredential.startRetrieval).
+     *
+     * @param userAuthenticationRequired if true, specifies that the user is required to
+     *     authenticate to allow requests.  Required authentication freshness is specified by
+     *     timeout below.
+     *
+     * @param timeoutMillis specifies the amount of time, in milliseconds, for which a user
+     *     authentication (see userAuthenticationRequired above) is valid, if
+     *     userAuthenticationRequired is true. If the timout is zero then authentication is
+     *     required for each reader session. If userAuthenticationRequired is false, the timeout
+     *     must be zero. If this requirement is not met the call fails with INVALID_DATA.
+     *
+     * @param secureUserId must be non-zero if userAuthenticationRequired is true. It is not
+     *     related to any Android user ID or UID, but is created in the Gatekeeper application
+     *     in the secure environment. If this requirement is not met the call fails with
+     *     INVALID_DATA.
+     *
+     * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+     *
+     * @return secureAccessControlProfile is a structure with the passed-in data and MAC created
+     *     with storageKey for authenticating the data at a later point in time.
+     */
+    addAccessControlProfile(uint16_t id, vec<uint8_t> readerCertificate,
+                            bool userAuthenticationRequired, uint64_t timeoutMillis,
+                            uint64_t secureUserId)
+        generates(Result result, SecureAccessControlProfile secureAccessControlProfile);
+
+    /**
+     * Begins the process of adding an entry to the credential.  All access control profiles must be
+     * added before calling this method.  Entries must be added in namespace "groups", meaning all
+     * entries of one namespace must be added before adding entries from another namespace.
+     *
+     * This method must be called exactly as many times as the sum of the items in the entryCounts
+     * parameter specified in the startPersonalization(), and must be followed by one or more calls
+     * to addEntryValue(). If this requirement is not met the method fails with INVALID_DATA.
+     *
+     * @param accessControlProfileIds specifies the set of access control profiles that can
+     *     authorize access to the provisioned element.
+     *
+     * @param nameSpace is the namespace of the element, e.g. "org.iso.18013"
+     *
+     * @param name is the name of the element.
+     *
+     * @param entrySize is the size of the entry value. If this requirement
+     *     is not met this method fails with INVALID_DATA.
+     *
+     * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+     */
+    beginAddEntry(vec<uint16_t> accessControlProfileIds, string nameSpace,
+                  string name, uint32_t entrySize)
+        generates(Result result);
+
+    /**
+     * Continues the process of adding an entry, providing a value or part of a value.
+     *
+     * In the common case, this method will be called only once per entry added.  In the case of
+     * values that are larger than the secure environment's GCM chunk size
+     * (see IIdentityCredentialStore.getHardwareInformation()), the caller must provide the
+     * value in chunks.  All chunks must be exactly gcmChunkSize except the last and the sum of all
+     * chunk sizes must equal the value of the beginAddEntry() entrySize argument. If this
+     * requirement is not met the call fails with INVALID_DATA.
+     *
+     * @param content is the entry value, encoded as CBOR. In the case the content exceeds gcmChunkSize,
+     *     this may be partial content up to gcmChunkSize bytes long.
+     *
+     * @return result is OK on success, INVALID_DATA or FAILED if an error occurred.
+     *
+     * @return encryptedContent contains the encrypted and MACed content.  For directly-available
+     *     credentials the contents are implementation-defined but must not exceed 32 bytes in
+     *     length.
+     *
+     *     For other credentials, encryptedContent contains:
+     *
+     *         AES-GCM-ENC(storageKey, R, Data, AdditionalData)
+     *
+     *     where:
+     *
+     *         Data = any   ; value
+     *
+     *         AdditionalData = {
+     *             "Namespace" : tstr,
+     *             "Name" : tstr,
+     *             "AccessControlProfileIds" : [ + uint ],
+     *         }
+     */
+    addEntryValue(vec<uint8_t> content)
+        generates(Result result, vec<uint8_t> encryptedContent);
+
+    /**
+     * Finishes adding entries and returns a signature that an issuing authority may use to validate
+     * that all data was provisioned correctly.
+     *
+     * After this method is called, the IWritableIdentityCredential is no longer usable.
+     *
+     * @return result is OK on success or FAILED if an error occurred.
+     *
+     * @return credentialData is a CBOR-encoded structure (in CDDL notation):
+     *
+     *         CredentialData = [
+     *              tstr,   ; docType, an optional name that identifies the type of credential
+     *              bool,   ; testCredential, indicates if this is a test credential
+     *              bstr    ; an opaque byte vector with encrypted data, see below
+     *         ]
+     *
+     *     The last element is an opaque byte vector which contains encrypted copies of the
+     *     secrets used to secure the new credential's data and to authenticate the credential to
+     *     the issuing authority.  It contains:
+     *
+     *         AES-GCM-ENC(HBK, R, CredentialKeys, docType)
+     *
+     *     where HBK is a unique hardware-bound key that has never existed outside of the secure
+     *     environment (except it's all zeroes if testCredential is True) and CredentialKeys is
+     *     the CBOR-encoded structure (in CDDL notation):
+     *
+     *         CredentialKeys = [
+     *              bstr,   ; storageKey, a 128-bit AES key
+     *              bstr    ; credentialPrivKey, the private key for credentialKey
+     *         ]
+     *
+     * @return proofOfProvisioningSignature proves to the IA that the credential was imported into the
+     *     secure hardware without alteration or error.  When the final addEntry() call is made
+     *     (when the number of provisioned entries equals the sum of the items in
+     *     startPersonalization() entryCounts parameter), it a COSE_Sign1 structure
+     *     signed by CredentialKey with payload set to the ProofOfProvisioning CBOR below:
+     *
+     *          ProofOfProvisioning = [
+     *              "ProofOfProvisioning",
+     *              tstr,                         ; DocType
+     *              [ * AccessControlProfile ],
+     *              ProvisionedData,
+     *              bool                          ; true if this is a test credential, should
+     *                                            ; always be false.
+     *          ]
+     *
+     *          AccessControlProfile = {
+     *              "id" : uint,
+     *              ? "readerCertificate" : bstr,
+     *              ? (
+     *                  "userAuthenticationRequired" : bool,
+     *                  "timeoutMillis" : uint,
+     *              )
+     *          }
+     *
+     *          ProvisionedData = {
+     *              * Namespace => [ + Entry ]
+     *          },
+     *
+     *          Namespace = tstr
+     *
+     *          Entry = {
+     *              "name" : tstr,
+     *              "value" : any,
+     *              "accessControlProfiles" : [ * uint ],
+     *          }
+     */
+    finishAddingEntries()
+        generates(Result result, vec<uint8_t> credentialData,
+                  vec<uint8_t> proofOfProvisioningSignature);
+};
diff --git a/identity/1.0/default/Android.bp b/identity/1.0/default/Android.bp
new file mode 100644
index 0000000..d2b2966
--- /dev/null
+++ b/identity/1.0/default/Android.bp
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+cc_binary {
+    name: "android.hardware.identity@1.0-service.example",
+    init_rc: ["android.hardware.identity@1.0-service.example.rc"],
+    vendor: true,
+    relative_install_path: "hw",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    srcs: [
+        "service.cpp",
+        "IdentityCredential.cpp",
+        "IdentityCredentialStore.cpp",
+        "WritableIdentityCredential.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.identity@1.0",
+        "android.hardware.identity-support-lib",
+        "android.hardware.keymaster@4.0",
+        "libcppbor",
+        "libcrypto",
+        "libbase",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+    ],
+}
diff --git a/identity/1.0/default/IdentityCredential.cpp b/identity/1.0/default/IdentityCredential.cpp
new file mode 100644
index 0000000..b0a5e56
--- /dev/null
+++ b/identity/1.0/default/IdentityCredential.cpp
@@ -0,0 +1,773 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredential"
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::android::hardware::keymaster::V4_0::Timestamp;
+using ::std::optional;
+
+Return<void> IdentityCredential::deleteCredential(deleteCredential_cb _hidl_cb) {
+    cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+    vector<uint8_t> proofOfDeletion = array.encode();
+
+    optional<vector<uint8_t>> proofOfDeletionSignature =
+            support::coseSignEcDsa(credentialPrivKey_,
+                                   proofOfDeletion,  // payload
+                                   {},               // additionalData
+                                   {});              // certificateChain
+    if (!proofOfDeletionSignature) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error signing data"), {});
+        return Void();
+    }
+
+    _hidl_cb(support::resultOK(), proofOfDeletionSignature.value());
+    return Void();
+}
+
+Return<void> IdentityCredential::createEphemeralKeyPair(createEphemeralKeyPair_cb _hidl_cb) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    if (!keyPair) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error creating ephemeral key pair"), {});
+        return Void();
+    }
+
+    // Stash public key of this key-pair for later check in startRetrieval().
+    optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    if (!publicKey) {
+        _hidl_cb(support::result(ResultCode::FAILED,
+                                 "Error getting public part of ephemeral key pair"),
+                 {});
+        return Void();
+    }
+    ephemeralPublicKey_ = publicKey.value();
+
+    _hidl_cb(support::resultOK(), keyPair.value());
+    return Void();
+}
+
+Return<void> IdentityCredential::setReaderEphemeralPublicKey(
+        const hidl_vec<uint8_t>& publicKey, setReaderEphemeralPublicKey_cb _hidl_cb) {
+    readerPublicKey_ = publicKey;
+    _hidl_cb(support::resultOK());
+    return Void();
+}
+
+ResultCode IdentityCredential::initialize() {
+    auto [item, _, message] = cppbor::parse(credentialData_);
+    if (item == nullptr) {
+        LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+        return ResultCode::INVALID_DATA;
+    }
+
+    const cppbor::Array* arrayItem = item->asArray();
+    if (arrayItem == nullptr || arrayItem->size() != 3) {
+        LOG(ERROR) << "CredentialData is not an array with three elements";
+        return ResultCode::INVALID_DATA;
+    }
+
+    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) {
+        LOG(ERROR) << "CredentialData unexpected item types";
+        return ResultCode::INVALID_DATA;
+    }
+
+    docType_ = docTypeItem->value();
+    testCredential_ = testCredentialItem->value();
+
+    vector<uint8_t> hardwareBoundKey;
+    if (testCredential_) {
+        hardwareBoundKey = support::getTestHardwareBoundKey();
+    } else {
+        hardwareBoundKey = support::getHardwareBoundKey();
+    }
+
+    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) {
+        LOG(ERROR) << "Error decrypting CredentialKeys";
+        return ResultCode::INVALID_DATA;
+    }
+
+    auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+    if (dckItem == nullptr) {
+        LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
+        return ResultCode::INVALID_DATA;
+    }
+    const cppbor::Array* dckArrayItem = dckItem->asArray();
+    if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
+        LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
+        return ResultCode::INVALID_DATA;
+    }
+    const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
+    const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
+    if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
+        LOG(ERROR) << "CredentialKeys unexpected item types";
+        return ResultCode::INVALID_DATA;
+    }
+    storageKey_ = storageKeyItem->value();
+    credentialPrivKey_ = credentialPrivKeyItem->value();
+
+    return ResultCode::OK;
+}
+
+Return<void> IdentityCredential::createAuthChallenge(createAuthChallenge_cb _hidl_cb) {
+    uint64_t challenge = 0;
+    while (challenge == 0) {
+        optional<vector<uint8_t>> bytes = support::getRandom(8);
+        if (!bytes) {
+            _hidl_cb(support::result(ResultCode::FAILED, "Error getting random data for challenge"),
+                     0);
+            return Void();
+        }
+
+        challenge = 0;
+        for (size_t n = 0; n < bytes.value().size(); n++) {
+            challenge |= ((bytes.value())[n] << (n * 8));
+        }
+    }
+
+    authChallenge_ = challenge;
+    _hidl_cb(support::resultOK(), challenge);
+    return Void();
+}
+
+// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
+// ahead of time.
+bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
+                               const vector<uint8_t>& readerCertificateChain) {
+    optional<vector<uint8_t>> acpPubKey =
+            support::certificateChainGetTopMostKey(profile.readerCertificate);
+    if (!acpPubKey) {
+        LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
+        return false;
+    }
+
+    optional<vector<vector<uint8_t>>> certificatesInChain =
+            support::certificateChainSplit(readerCertificateChain);
+    if (!certificatesInChain) {
+        LOG(ERROR) << "Error splitting readerCertificateChain";
+        return false;
+    }
+    for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
+        optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
+        if (!certPubKey) {
+            LOG(ERROR)
+                    << "Error extracting public key from certificate in chain presented by reader";
+            return false;
+        }
+        if (acpPubKey.value() == certPubKey.value()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+Timestamp clockGetTime() {
+    struct timespec time;
+    clock_gettime(CLOCK_MONOTONIC, &time);
+    return time.tv_sec * 1000 + time.tv_nsec / 1000000;
+}
+
+bool checkUserAuthentication(const SecureAccessControlProfile& profile,
+                             const HardwareAuthToken& authToken, uint64_t authChallenge) {
+    if (profile.secureUserId != authToken.userId) {
+        LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
+                   << ") differs from userId in authToken (" << authToken.userId << ")";
+        return false;
+    }
+
+    if (profile.timeoutMillis == 0) {
+        if (authToken.challenge == 0) {
+            LOG(ERROR) << "No challenge in authToken";
+            return false;
+        }
+
+        if (authToken.challenge != authChallenge) {
+            LOG(ERROR) << "Challenge in authToken doesn't match the challenge we created";
+            return false;
+        }
+        return true;
+    }
+
+    // Note that the Epoch for timestamps in HardwareAuthToken is at the
+    // discretion of the vendor:
+    //
+    //   "[...] since some starting point (generally the most recent device
+    //    boot) which all of the applications within one secure environment
+    //    must agree upon."
+    //
+    // Therefore, if this software implementation is used on a device which isn't
+    // the emulator then the assumption that the epoch is the same as used in
+    // clockGetTime above will not hold. This is OK as this software
+    // implementation should never be used on a real device.
+    //
+    Timestamp now = clockGetTime();
+    if (authToken.timestamp > now) {
+        LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp
+                   << ") is in the future (now: " << now << ")";
+        return false;
+    }
+    if (now > authToken.timestamp + profile.timeoutMillis) {
+        LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp << " + "
+                   << profile.timeoutMillis << " = "
+                   << (authToken.timestamp + profile.timeoutMillis)
+                   << ") is in the past (now: " << now << ")";
+        return false;
+    }
+
+    return true;
+}
+
+Return<void> IdentityCredential::startRetrieval(
+        const hidl_vec<SecureAccessControlProfile>& accessControlProfiles,
+        const HardwareAuthToken& authToken, const hidl_vec<uint8_t>& itemsRequest,
+        const hidl_vec<uint8_t>& sessionTranscript, const hidl_vec<uint8_t>& readerSignature,
+        const hidl_vec<uint16_t>& requestCounts, startRetrieval_cb _hidl_cb) {
+    if (sessionTranscript.size() > 0) {
+        auto [item, _, message] = cppbor::parse(sessionTranscript);
+        if (item == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "SessionTranscript contains invalid CBOR"));
+            return Void();
+        }
+        sessionTranscriptItem_ = std::move(item);
+    }
+    if (numStartRetrievalCalls_ > 0) {
+        if (sessionTranscript_ != vector<uint8_t>(sessionTranscript)) {
+            _hidl_cb(support::result(
+                    ResultCode::SESSION_TRANSCRIPT_MISMATCH,
+                    "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
+            return Void();
+        }
+    }
+    sessionTranscript_ = sessionTranscript;
+
+    // If there is a signature, validate that it was made with the top-most key in the
+    // certificate chain embedded in the COSE_Sign1 structure.
+    optional<vector<uint8_t>> readerCertificateChain;
+    if (readerSignature.size() > 0) {
+        readerCertificateChain = support::coseSignGetX5Chain(readerSignature);
+        if (!readerCertificateChain) {
+            _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+                                     "Unable to get reader certificate chain from COSE_Sign1"));
+            return Void();
+        }
+
+        if (!support::certificateChainValidate(readerCertificateChain.value())) {
+            _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+                                     "Error validating reader certificate chain"));
+            return Void();
+        }
+
+        optional<vector<uint8_t>> readerPublicKey =
+                support::certificateChainGetTopMostKey(readerCertificateChain.value());
+        if (!readerPublicKey) {
+            _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+                                     "Unable to get public key from reader certificate chain"));
+            return Void();
+        }
+
+        const vector<uint8_t>& itemsRequestBytes = itemsRequest;
+        vector<uint8_t> dataThatWasSigned = cppbor::Array()
+                                                    .add("ReaderAuthentication")
+                                                    .add(sessionTranscriptItem_->clone())
+                                                    .add(cppbor::Semantic(24, itemsRequestBytes))
+                                                    .encode();
+        if (!support::coseCheckEcDsaSignature(readerSignature,
+                                              dataThatWasSigned,  // detached content
+                                              readerPublicKey.value())) {
+            _hidl_cb(support::result(ResultCode::READER_SIGNATURE_CHECK_FAILED,
+                                     "readerSignature check failed"));
+            return Void();
+        }
+    }
+
+    // Here's where we would validate the passed-in |authToken| to assure ourselves
+    // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
+    //
+    // However this involves calculating the MAC. However this requires access
+    // to the key needed to a pre-shared key which we don't have...
+    //
+
+    // To prevent replay-attacks, we check that the public part of the ephemeral
+    // key we previously created, is present in the DeviceEngagement part of
+    // SessionTranscript as a COSE_Key, in uncompressed form.
+    //
+    // 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) {
+            _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                                     "SessionTranscript is not an array with two items"));
+            return Void();
+        }
+        const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
+        if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
+            _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                                     "First item in SessionTranscript array is not a "
+                                     "semantic with value 24"));
+            return Void();
+        }
+        const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
+        if (encodedDE == nullptr) {
+            _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                                     "Child of semantic in first item in SessionTranscript "
+                                     "array is not a bstr"));
+            return Void();
+        }
+        const vector<uint8_t>& bytesDE = encodedDE->value();
+
+        auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+        if (!getXYSuccess) {
+            _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                                     "Error extracting X and Y from ePub"));
+            return Void();
+        }
+        if (sessionTranscript.size() > 0 &&
+            !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
+              memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
+            _hidl_cb(support::result(ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                                     "Did not find ephemeral public key's X and Y coordinates in "
+                                     "SessionTranscript (make sure leading zeroes are not used)"));
+            return Void();
+        }
+    }
+
+    // itemsRequest: If non-empty, contains request data that may be signed by the
+    // reader.  The content can be defined in the way appropriate for the
+    // credential, but there are three requirements that must be met to work with
+    // this HAL:
+    if (itemsRequest.size() > 0) {
+        // 1. The content must be a CBOR-encoded structure.
+        auto [item, _, message] = cppbor::parse(itemsRequest);
+        if (item == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+                                     "Error decoding CBOR in itemsRequest: %s", message.c_str()));
+            return Void();
+        }
+
+        // 2. The CBOR structure must be a map.
+        const cppbor::Map* map = item->asMap();
+        if (map == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+                                     "itemsRequest is not a CBOR map"));
+            return Void();
+        }
+
+        // 3. The map must contain a key "nameSpaces" whose value contains a map, as described in
+        //    the example below.
+        //
+        //   NameSpaces = {
+        //     + NameSpace => DataElements ; Requested data elements for each NameSpace
+        //   }
+        //
+        //   NameSpace = tstr
+        //
+        //   DataElements = {
+        //     + DataElement => IntentToRetain
+        //   }
+        //
+        //   DataElement = tstr
+        //   IntentToRetain = bool
+        //
+        // Here's an example of an |itemsRequest| CBOR value satisfying above requirements 1.
+        // through 3.:
+        //
+        //    {
+        //        'docType' : 'org.iso.18013-5.2019',
+        //        'nameSpaces' : {
+        //            'org.iso.18013-5.2019' : {
+        //                'Last name' : false,
+        //                'Birth date' : false,
+        //                'First name' : false,
+        //                'Home address' : true
+        //            },
+        //            'org.aamva.iso.18013-5.2019' : {
+        //                'Real Id' : false
+        //            }
+        //        }
+        //    }
+        //
+        const cppbor::Map* nsMap = nullptr;
+        for (size_t n = 0; n < map->size(); n++) {
+            const auto& [keyItem, valueItem] = (*map)[n];
+            if (keyItem->type() == cppbor::TSTR && keyItem->asTstr()->value() == "nameSpaces" &&
+                valueItem->type() == cppbor::MAP) {
+                nsMap = valueItem->asMap();
+                break;
+            }
+        }
+        if (nsMap == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+                                     "No nameSpaces map in top-most map"));
+            return Void();
+        }
+
+        for (size_t n = 0; n < nsMap->size(); n++) {
+            auto [nsKeyItem, nsValueItem] = (*nsMap)[n];
+            const cppbor::Tstr* nsKey = nsKeyItem->asTstr();
+            const cppbor::Map* nsInnerMap = nsValueItem->asMap();
+            if (nsKey == nullptr || nsInnerMap == nullptr) {
+                _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+                                         "Type mismatch in nameSpaces map"));
+                return Void();
+            }
+            string requestedNamespace = nsKey->value();
+            vector<string> requestedKeys;
+            for (size_t m = 0; m < nsInnerMap->size(); m++) {
+                const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
+                const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
+                const cppbor::Simple* simple = innerMapValueItem->asSimple();
+                const cppbor::Bool* intentToRetainItem =
+                        (simple != nullptr) ? simple->asBool() : nullptr;
+                if (nameItem == nullptr || intentToRetainItem == nullptr) {
+                    _hidl_cb(support::result(ResultCode::INVALID_ITEMS_REQUEST_MESSAGE,
+                                             "Type mismatch in value in nameSpaces map"));
+                    return Void();
+                }
+                requestedKeys.push_back(nameItem->value());
+            }
+            requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
+        }
+    }
+
+    // Finally, validate all the access control profiles in the requestData.
+    bool haveAuthToken = (authToken.mac.size() > 0);
+    for (const auto& profile : accessControlProfiles) {
+        if (!support::secureAccessControlProfileCheckMac(profile, storageKey_)) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Error checking MAC for profile with id %d", int(profile.id)));
+            return Void();
+        }
+        ResultCode accessControlCheck = ResultCode::OK;
+        if (profile.userAuthenticationRequired) {
+            if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
+                accessControlCheck = ResultCode::USER_AUTHENTICATION_FAILED;
+            }
+        } else if (profile.readerCertificate.size() > 0) {
+            if (!readerCertificateChain ||
+                !checkReaderAuthentication(profile, readerCertificateChain.value())) {
+                accessControlCheck = ResultCode::READER_AUTHENTICATION_FAILED;
+            }
+        }
+        profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
+    }
+
+    deviceNameSpacesMap_ = cppbor::Map();
+    currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+    requestCountsRemaining_ = requestCounts;
+    currentNameSpace_ = "";
+
+    itemsRequest_ = itemsRequest;
+
+    numStartRetrievalCalls_ += 1;
+    _hidl_cb(support::resultOK());
+    return Void();
+}
+
+Return<void> IdentityCredential::startRetrieveEntryValue(
+        const hidl_string& nameSpace, const hidl_string& name, uint32_t entrySize,
+        const hidl_vec<uint16_t>& accessControlProfileIds, startRetrieveEntryValue_cb _hidl_cb) {
+    if (name.empty()) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA, "Name cannot be empty"));
+        return Void();
+    }
+    if (nameSpace.empty()) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA, "Name space cannot be empty"));
+        return Void();
+    }
+
+    if (requestCountsRemaining_.size() == 0) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "No more name spaces left to go through"));
+        return Void();
+    }
+
+    if (currentNameSpace_ == "") {
+        // First call.
+        currentNameSpace_ = nameSpace;
+    }
+
+    if (nameSpace == currentNameSpace_) {
+        // Same namespace.
+        if (requestCountsRemaining_[0] == 0) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "No more entries to be retrieved in current name space"));
+            return Void();
+        }
+        requestCountsRemaining_[0] -= 1;
+    } else {
+        // New namespace.
+        if (requestCountsRemaining_[0] != 0) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Moved to new name space but %d entries need to be retrieved "
+                                     "in current name space",
+                                     int(requestCountsRemaining_[0])));
+            return Void();
+        }
+        if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+            deviceNameSpacesMap_.add(currentNameSpace_,
+                                     std::move(currentNameSpaceDeviceNameSpacesMap_));
+        }
+        currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+        requestCountsRemaining_.erase(requestCountsRemaining_.begin());
+        currentNameSpace_ = nameSpace;
+    }
+
+    // It's permissible to have an empty itemsRequest... but if non-empty you can
+    // only request what was specified in said itemsRequest. Enforce that.
+    if (itemsRequest_.size() > 0) {
+        const auto& it = requestedNameSpacesAndNames_.find(nameSpace);
+        if (it == requestedNameSpacesAndNames_.end()) {
+            _hidl_cb(support::result(ResultCode::NOT_IN_REQUEST_MESSAGE,
+                                     "Name space '%s' was not requested in startRetrieval",
+                                     nameSpace.c_str()));
+            return Void();
+        }
+        const auto& dataItemNames = it->second;
+        if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
+            _hidl_cb(support::result(
+                    ResultCode::NOT_IN_REQUEST_MESSAGE,
+                    "Data item name '%s' in name space '%s' was not requested in startRetrieval",
+                    name.c_str(), nameSpace.c_str()));
+            return Void();
+        }
+    }
+
+    // Enforce access control.
+    //
+    // Access is granted if at least one of the profiles grants access.
+    //
+    // If an item is configured without any profiles, access is denied.
+    //
+    ResultCode accessControl = ResultCode::NO_ACCESS_CONTROL_PROFILES;
+    for (auto id : accessControlProfileIds) {
+        auto search = profileIdToAccessCheckResult_.find(id);
+        if (search == profileIdToAccessCheckResult_.end()) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Requested entry with unvalidated profile id %d", (int(id))));
+            return Void();
+        }
+        ResultCode accessControlForProfile = search->second;
+        if (accessControlForProfile == ResultCode::OK) {
+            accessControl = ResultCode::OK;
+            break;
+        }
+        accessControl = accessControlForProfile;
+    }
+    if (accessControl != ResultCode::OK) {
+        _hidl_cb(support::result(accessControl, "Access control check failed"));
+        return Void();
+    }
+
+    entryAdditionalData_ =
+            support::entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+    currentName_ = name;
+    entryRemainingBytes_ = entrySize;
+    entryValue_.resize(0);
+
+    _hidl_cb(support::resultOK());
+    return Void();
+}
+
+Return<void> IdentityCredential::retrieveEntryValue(const hidl_vec<uint8_t>& encryptedContent,
+                                                    retrieveEntryValue_cb _hidl_cb) {
+    optional<vector<uint8_t>> content =
+            support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+    if (!content) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA, "Error decrypting data"), {});
+        return Void();
+    }
+
+    size_t chunkSize = content.value().size();
+
+    if (chunkSize > entryRemainingBytes_) {
+        LOG(ERROR) << "Retrieved chunk of size " << chunkSize
+                   << " is bigger than remaining space of size " << entryRemainingBytes_;
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "Retrieved chunk of size %zd is bigger than remaining space "
+                                 "of size %zd",
+                                 chunkSize, entryRemainingBytes_),
+                 {});
+        return Void();
+    }
+
+    entryRemainingBytes_ -= chunkSize;
+    if (entryRemainingBytes_ > 0) {
+        if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Retrieved non-final chunk of size %zd but expected "
+                                     "kGcmChunkSize which is %zd",
+                                     chunkSize, IdentityCredentialStore::kGcmChunkSize),
+                     {});
+            return Void();
+        }
+    }
+
+    entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
+
+    if (entryRemainingBytes_ == 0) {
+        auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
+        if (entryValueItem == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA, "Retrieved data invalid CBOR"), {});
+            return Void();
+        }
+        currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
+    }
+
+    _hidl_cb(support::resultOK(), content.value());
+    return Void();
+}
+
+Return<void> IdentityCredential::finishRetrieval(const hidl_vec<uint8_t>& signingKeyBlob,
+                                                 finishRetrieval_cb _hidl_cb) {
+    if (currentNameSpaceDeviceNameSpacesMap_.size() > 0) {
+        deviceNameSpacesMap_.add(currentNameSpace_,
+                                 std::move(currentNameSpaceDeviceNameSpacesMap_));
+    }
+    vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
+
+    // If there's no signing key or no sessionTranscript or no reader ephemeral
+    // public key, we return the empty MAC.
+    optional<vector<uint8_t>> mac;
+    if (signingKeyBlob.size() > 0 && sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0) {
+        cppbor::Array array;
+        array.add("DeviceAuthentication");
+        array.add(sessionTranscriptItem_->clone());
+        array.add(docType_);
+        array.add(cppbor::Semantic(24, encodedDeviceNameSpaces));
+        vector<uint8_t> encodedDeviceAuthentication = array.encode();
+
+        vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
+        optional<vector<uint8_t>> signingKey =
+                support::decryptAes128Gcm(storageKey_, signingKeyBlob, docTypeAsBlob);
+        if (!signingKey) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA, "Error decrypting signingKeyBlob"),
+                     {}, {});
+            return Void();
+        }
+
+        optional<vector<uint8_t>> sharedSecret =
+                support::ecdh(readerPublicKey_, signingKey.value());
+        if (!sharedSecret) {
+            _hidl_cb(support::result(ResultCode::FAILED, "Error doing ECDH"), {}, {});
+            return Void();
+        }
+
+        vector<uint8_t> salt = {0x00};
+        vector<uint8_t> info = {};
+        optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+        if (!derivedKey) {
+            _hidl_cb(support::result(ResultCode::FAILED, "Error deriving key from shared secret"),
+                     {}, {});
+            return Void();
+        }
+
+        mac = support::coseMac0(derivedKey.value(), {},        // payload
+                                encodedDeviceAuthentication);  // additionalData
+        if (!mac) {
+            _hidl_cb(support::result(ResultCode::FAILED, "Error MACing data"), {}, {});
+            return Void();
+        }
+    }
+
+    _hidl_cb(support::resultOK(), mac.value_or(vector<uint8_t>({})), encodedDeviceNameSpaces);
+    return Void();
+}
+
+Return<void> IdentityCredential::generateSigningKeyPair(generateSigningKeyPair_cb _hidl_cb) {
+    string serialDecimal = "0";  // TODO: set serial to something unique
+    string issuer = "Android Open Source Project";
+    string subject = "Android IdentityCredential Reference Implementation";
+    time_t validityNotBefore = time(nullptr);
+    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+    optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
+    if (!signingKeyPKCS8) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error creating signingKey"), {}, {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> signingPublicKey =
+            support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
+    if (!signingPublicKey) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error getting public part of signingKey"), {},
+                 {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
+    if (!signingKey) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error getting private part of signingKey"),
+                 {}, {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
+            signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    if (!certificate) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error creating signingKey"), {}, {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> nonce = support::getRandom(12);
+    if (!nonce) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error getting random"), {}, {});
+        return Void();
+    }
+    vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
+    optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
+            storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
+    if (!encryptedSigningKey) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error encrypting signingKey"), {}, {});
+        return Void();
+    }
+    _hidl_cb(support::resultOK(), encryptedSigningKey.value(), certificate.value());
+    return Void();
+}
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
diff --git a/identity/1.0/default/IdentityCredential.h b/identity/1.0/default/IdentityCredential.h
new file mode 100644
index 0000000..eb8787b
--- /dev/null
+++ b/identity/1.0/default/IdentityCredential.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
+
+#include <android/hardware/identity/1.0/IIdentityCredential.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor/cppbor.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::map;
+using ::std::string;
+using ::std::vector;
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+
+using MapStringToVectorOfStrings = map<string, vector<string>>;
+
+class IdentityCredential : public IIdentityCredential {
+  public:
+    IdentityCredential(const hidl_vec<uint8_t>& credentialData)
+        : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {}
+
+    // Parses and decrypts credentialData_, return false on failure. Must be
+    // called right after construction.
+    ResultCode initialize();
+
+    // Methods from ::android::hardware::identity::IIdentityCredential follow.
+
+    Return<void> deleteCredential(deleteCredential_cb _hidl_cb) override;
+    Return<void> createEphemeralKeyPair(createEphemeralKeyPair_cb _hidl_cb) override;
+
+    Return<void> setReaderEphemeralPublicKey(const hidl_vec<uint8_t>& publicKey,
+                                             setReaderEphemeralPublicKey_cb _hidl_cb) override;
+
+    Return<void> createAuthChallenge(createAuthChallenge_cb _hidl_cb) override;
+
+    Return<void> startRetrieval(const hidl_vec<SecureAccessControlProfile>& accessControlProfiles,
+                                const HardwareAuthToken& authToken,
+                                const hidl_vec<uint8_t>& itemsRequest,
+                                const hidl_vec<uint8_t>& sessionTranscript,
+                                const hidl_vec<uint8_t>& readerSignature,
+                                const hidl_vec<uint16_t>& requestCounts,
+                                startRetrieval_cb _hidl_cb) override;
+    Return<void> startRetrieveEntryValue(const hidl_string& nameSpace, const hidl_string& name,
+                                         uint32_t entrySize,
+                                         const hidl_vec<uint16_t>& accessControlProfileIds,
+                                         startRetrieveEntryValue_cb _hidl_cb) override;
+    Return<void> retrieveEntryValue(const hidl_vec<uint8_t>& encryptedContent,
+                                    retrieveEntryValue_cb _hidl_cb) override;
+    Return<void> finishRetrieval(const hidl_vec<uint8_t>& signingKeyBlob,
+                                 finishRetrieval_cb _hidl_cb) override;
+
+    Return<void> generateSigningKeyPair(generateSigningKeyPair_cb _hidl_cb) override;
+
+  private:
+    // Set by constructor
+    vector<uint8_t> credentialData_;
+    int numStartRetrievalCalls_;
+
+    // Set by initialize()
+    string docType_;
+    bool testCredential_;
+    vector<uint8_t> storageKey_;
+    vector<uint8_t> credentialPrivKey_;
+
+    // Set by createEphemeralKeyPair()
+    vector<uint8_t> ephemeralPublicKey_;
+
+    // Set by setReaderEphemeralPublicKey()
+    vector<uint8_t> readerPublicKey_;
+
+    // Set by createAuthChallenge()
+    uint64_t authChallenge_;
+
+    // Set at startRetrieval() time.
+    map<uint16_t, ResultCode> profileIdToAccessCheckResult_;
+    vector<uint8_t> sessionTranscript_;
+    std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
+    vector<uint8_t> itemsRequest_;
+    vector<uint16_t> requestCountsRemaining_;
+    MapStringToVectorOfStrings requestedNameSpacesAndNames_;
+    cppbor::Map deviceNameSpacesMap_;
+    cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
+
+    // Set at startRetrieveEntryValue() time.
+    string currentNameSpace_;
+    string currentName_;
+    size_t entryRemainingBytes_;
+    vector<uint8_t> entryValue_;
+    vector<uint8_t> entryAdditionalData_;
+};
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
diff --git a/identity/1.0/default/IdentityCredentialStore.cpp b/identity/1.0/default/IdentityCredentialStore.cpp
new file mode 100644
index 0000000..9eb1e70
--- /dev/null
+++ b/identity/1.0/default/IdentityCredentialStore.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredentialStore"
+
+#include "IdentityCredentialStore.h"
+#include "IdentityCredential.h"
+#include "WritableIdentityCredential.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+// Methods from ::android::hardware::identity::IIdentityCredentialStore follow.
+
+Return<void> IdentityCredentialStore::getHardwareInformation(getHardwareInformation_cb _hidl_cb) {
+    _hidl_cb(support::resultOK(), "IdentityCredential Reference Implementation", "Google",
+             kGcmChunkSize, false /* isDirectAccess */, {} /* supportedDocTypes */);
+    return Void();
+}
+
+Return<void> IdentityCredentialStore::createCredential(const hidl_string& docType,
+                                                       bool testCredential,
+                                                       createCredential_cb _hidl_cb) {
+    auto writable_credential = new WritableIdentityCredential(docType, testCredential);
+    if (!writable_credential->initialize()) {
+        _hidl_cb(support::result(ResultCode::FAILED,
+                                 "Error initializing WritableIdentityCredential"),
+                 writable_credential);
+        return Void();
+    }
+    _hidl_cb(support::resultOK(), writable_credential);
+    return Void();
+}
+
+Return<void> IdentityCredentialStore::getCredential(const hidl_vec<uint8_t>& credentialData,
+                                                    getCredential_cb _hidl_cb) {
+    auto credential = new IdentityCredential(credentialData);
+    // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right now.
+    auto ret = credential->initialize();
+    if (ret != ResultCode::OK) {
+        _hidl_cb(support::result(ret, "Error initializing IdentityCredential"), credential);
+        return Void();
+    }
+    _hidl_cb(support::resultOK(), credential);
+    return Void();
+}
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
diff --git a/identity/1.0/default/IdentityCredentialStore.h b/identity/1.0/default/IdentityCredentialStore.h
new file mode 100644
index 0000000..ad75360
--- /dev/null
+++ b/identity/1.0/default/IdentityCredentialStore.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+#define ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+
+class IdentityCredentialStore : public IIdentityCredentialStore {
+  public:
+    IdentityCredentialStore() {}
+
+    // The GCM chunk size used by this implementation is 64 KiB.
+    static constexpr size_t kGcmChunkSize = 64 * 1024;
+
+    // Methods from ::android::hardware::identity::IIdentityCredentialStore follow.
+    Return<void> getHardwareInformation(getHardwareInformation_cb _hidl_cb) override;
+    Return<void> createCredential(const hidl_string& docType, bool testCredential,
+                                  createCredential_cb _hidl_cb) override;
+    Return<void> getCredential(const hidl_vec<uint8_t>& credentialData,
+                               getCredential_cb _hidl_cb) override;
+};
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
diff --git a/identity/1.0/default/OWNERS b/identity/1.0/default/OWNERS
new file mode 100644
index 0000000..6969910
--- /dev/null
+++ b/identity/1.0/default/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+zeuthen@google.com
diff --git a/identity/1.0/default/WritableIdentityCredential.cpp b/identity/1.0/default/WritableIdentityCredential.cpp
new file mode 100644
index 0000000..548b4c0
--- /dev/null
+++ b/identity/1.0/default/WritableIdentityCredential.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "WritableIdentityCredential"
+
+#include "WritableIdentityCredential.h"
+#include "IdentityCredentialStore.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor/cppbor.h>
+#include <cppbor/cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::optional;
+
+// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
+// |credentialPrivKey|.
+static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
+                                   const vector<uint8_t>& credentialPrivKey,
+                                   vector<uint8_t>& credentialKeys) {
+    if (storageKey.size() != 16) {
+        LOG(ERROR) << "Size of storageKey is not 16";
+        return false;
+    }
+
+    cppbor::Array array;
+    array.add(cppbor::Bstr(storageKey));
+    array.add(cppbor::Bstr(credentialPrivKey));
+    credentialKeys = array.encode();
+    return true;
+}
+
+// Writes CBOR-encoded structure to |credentialData| containing |docType|,
+// |testCredential| and |credentialKeys|. The latter element will be stored in
+// encrypted form, using |hardwareBoundKey| as the encryption key.
+bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
+                            bool testCredential, const vector<uint8_t>& credentialKeys,
+                            vector<uint8_t>& credentialData) {
+    optional<vector<uint8_t>> nonce = support::getRandom(12);
+    if (!nonce) {
+        LOG(ERROR) << "Error getting random";
+        return false;
+    }
+    vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
+    optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
+            hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
+    if (!credentialBlob) {
+        LOG(ERROR) << "Error encrypting CredentialKeys blob";
+        return false;
+    }
+
+    cppbor::Array array;
+    array.add(docType);
+    array.add(testCredential);
+    array.add(cppbor::Bstr(credentialBlob.value()));
+    credentialData = array.encode();
+    return true;
+}
+
+bool WritableIdentityCredential::initialize() {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    if (!keyPair) {
+        LOG(ERROR) << "Error creating credentialKey";
+        return false;
+    }
+
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    if (!pubKey) {
+        LOG(ERROR) << "Error getting public part of credentialKey";
+        return false;
+    }
+    credentialPubKey_ = pubKey.value();
+
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    if (!privKey) {
+        LOG(ERROR) << "Error getting private part of credentialKey";
+        return false;
+    }
+    credentialPrivKey_ = privKey.value();
+
+    optional<vector<uint8_t>> random = support::getRandom(16);
+    if (!random) {
+        LOG(ERROR) << "Error creating storageKey";
+        return false;
+    }
+    storageKey_ = random.value();
+
+    return true;
+}
+
+Return<void> WritableIdentityCredential::getAttestationCertificate(
+        const hidl_vec<uint8_t>& /* attestationChallenge */,
+        getAttestationCertificate_cb _hidl_cb) {
+    // For now, we dynamically generate an attestion key on each and every
+    // request and use that to sign CredentialKey. In a real implementation this
+    // would look very differently.
+    optional<vector<uint8_t>> attestationKeyPair = support::createEcKeyPair();
+    if (!attestationKeyPair) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error creating attestationKey"), {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> attestationPubKey =
+            support::ecKeyPairGetPublicKey(attestationKeyPair.value());
+    if (!attestationPubKey) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error getting public part of attestationKey"),
+                 {});
+        return Void();
+    }
+
+    optional<vector<uint8_t>> attestationPrivKey =
+            support::ecKeyPairGetPrivateKey(attestationKeyPair.value());
+    if (!attestationPrivKey) {
+        _hidl_cb(
+                support::result(ResultCode::FAILED, "Error getting private part of attestationKey"),
+                {});
+        return Void();
+    }
+
+    string serialDecimal;
+    string issuer;
+    string subject;
+    time_t validityNotBefore = time(nullptr);
+    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+    // First create a certificate for |credentialPubKey| which is signed by
+    // |attestationPrivKey|.
+    //
+    serialDecimal = "0";  // TODO: set serial to |attestationChallenge|
+    issuer = "Android Open Source Project";
+    subject = "Android IdentityCredential CredentialKey";
+    optional<vector<uint8_t>> credentialPubKeyCertificate = support::ecPublicKeyGenerateCertificate(
+            credentialPubKey_, attestationPrivKey.value(), serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    if (!credentialPubKeyCertificate) {
+        _hidl_cb(support::result(ResultCode::FAILED,
+                                 "Error creating certificate for credentialPubKey"),
+                 {});
+        return Void();
+    }
+
+    // This is followed by a certificate for |attestationPubKey| self-signed by
+    // |attestationPrivKey|.
+    serialDecimal = "0";  // TODO: set serial
+    issuer = "Android Open Source Project";
+    subject = "Android IdentityCredential AttestationKey";
+    optional<vector<uint8_t>> attestationKeyCertificate = support::ecPublicKeyGenerateCertificate(
+            attestationPubKey.value(), attestationPrivKey.value(), serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    if (!attestationKeyCertificate) {
+        _hidl_cb(support::result(ResultCode::FAILED,
+                                 "Error creating certificate for attestationPubKey"),
+                 {});
+        return Void();
+    }
+
+    // Concatenate the certificates to form the chain.
+    vector<uint8_t> certificateChain;
+    certificateChain.insert(certificateChain.end(), credentialPubKeyCertificate.value().begin(),
+                            credentialPubKeyCertificate.value().end());
+    certificateChain.insert(certificateChain.end(), attestationKeyCertificate.value().begin(),
+                            attestationKeyCertificate.value().end());
+
+    _hidl_cb(support::resultOK(), certificateChain);
+    return Void();
+}
+
+Return<void> WritableIdentityCredential::startPersonalization(uint16_t accessControlProfileCount,
+                                                              const hidl_vec<uint16_t>& entryCounts,
+                                                              startPersonalization_cb _hidl_cb) {
+    numAccessControlProfileRemaining_ = accessControlProfileCount;
+    remainingEntryCounts_ = entryCounts;
+    entryNameSpace_ = "";
+
+    signedDataAccessControlProfiles_ = cppbor::Array();
+    signedDataNamespaces_ = cppbor::Map();
+    signedDataCurrentNamespace_ = cppbor::Array();
+
+    _hidl_cb(support::resultOK());
+    return Void();
+}
+
+Return<void> WritableIdentityCredential::addAccessControlProfile(
+        uint16_t id, const hidl_vec<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+        uint64_t timeoutMillis, uint64_t secureUserId, addAccessControlProfile_cb _hidl_cb) {
+    SecureAccessControlProfile profile;
+
+    if (numAccessControlProfileRemaining_ == 0) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "numAccessControlProfileRemaining_ is 0 and expected non-zero"),
+                 profile);
+        return Void();
+    }
+
+    // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
+    // be zero.
+    if (!userAuthenticationRequired && timeoutMillis != 0) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "userAuthenticationRequired is false but timeout is non-zero"),
+                 profile);
+        return Void();
+    }
+
+    profile.id = id;
+    profile.readerCertificate = readerCertificate;
+    profile.userAuthenticationRequired = userAuthenticationRequired;
+    profile.timeoutMillis = timeoutMillis;
+    profile.secureUserId = secureUserId;
+    optional<vector<uint8_t>> mac =
+            support::secureAccessControlProfileCalcMac(profile, storageKey_);
+    if (!mac) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error calculating MAC for profile"), profile);
+        return Void();
+    }
+    profile.mac = mac.value();
+
+    cppbor::Map profileMap;
+    profileMap.add("id", profile.id);
+    if (profile.readerCertificate.size() > 0) {
+        profileMap.add("readerCertificate", cppbor::Bstr(profile.readerCertificate));
+    }
+    if (profile.userAuthenticationRequired) {
+        profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired);
+        profileMap.add("timeoutMillis", profile.timeoutMillis);
+    }
+    signedDataAccessControlProfiles_.add(std::move(profileMap));
+
+    numAccessControlProfileRemaining_--;
+
+    _hidl_cb(support::resultOK(), profile);
+    return Void();
+}
+
+Return<void> WritableIdentityCredential::beginAddEntry(
+        const hidl_vec<uint16_t>& accessControlProfileIds, const hidl_string& nameSpace,
+        const hidl_string& name, uint32_t entrySize, beginAddEntry_cb _hidl_cb) {
+    if (numAccessControlProfileRemaining_ != 0) {
+        LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_
+                   << " and expected zero";
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "numAccessControlProfileRemaining_ is %zd and expected zero",
+                                 numAccessControlProfileRemaining_));
+        return Void();
+    }
+
+    if (remainingEntryCounts_.size() == 0) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA, "No more namespaces to add to"));
+        return Void();
+    }
+
+    // Handle initial beginEntry() call.
+    if (entryNameSpace_ == "") {
+        entryNameSpace_ = nameSpace;
+    }
+
+    // If the namespace changed...
+    if (nameSpace != entryNameSpace_) {
+        // Then check that all entries in the previous namespace have been added..
+        if (remainingEntryCounts_[0] != 0) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "New namespace but %d entries remain to be added",
+                                     int(remainingEntryCounts_[0])));
+            return Void();
+        }
+        remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+
+        if (signedDataCurrentNamespace_.size() > 0) {
+            signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
+            signedDataCurrentNamespace_ = cppbor::Array();
+        }
+    } else {
+        // Same namespace...
+        if (remainingEntryCounts_[0] == 0) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Same namespace but no entries remain to be added"));
+            return Void();
+        }
+        remainingEntryCounts_[0] -= 1;
+    }
+
+    entryAdditionalData_ =
+            support::entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+    entryRemainingBytes_ = entrySize;
+    entryNameSpace_ = nameSpace;
+    entryName_ = name;
+    entryAccessControlProfileIds_ = accessControlProfileIds;
+    entryBytes_.resize(0);
+    // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+    _hidl_cb(support::resultOK());
+    return Void();
+}
+
+Return<void> WritableIdentityCredential::addEntryValue(const hidl_vec<uint8_t>& content,
+                                                       addEntryValue_cb _hidl_cb) {
+    size_t contentSize = content.size();
+
+    if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
+        _hidl_cb(support::result(
+                         ResultCode::INVALID_DATA,
+                         "Passed in chunk of size %zd is bigger than kGcmChunkSize which is %zd",
+                         contentSize, IdentityCredentialStore::kGcmChunkSize),
+                 {});
+        return Void();
+    }
+    if (contentSize > entryRemainingBytes_) {
+        _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                 "Passed in chunk of size %zd is bigger than remaining space "
+                                 "of size %zd",
+                                 contentSize, entryRemainingBytes_),
+                 {});
+        return Void();
+    }
+
+    entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
+    entryRemainingBytes_ -= contentSize;
+    if (entryRemainingBytes_ > 0) {
+        if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA,
+                                     "Retrieved non-final chunk of size %zd but expected "
+                                     "kGcmChunkSize which is %zd",
+                                     contentSize, IdentityCredentialStore::kGcmChunkSize),
+                     {});
+            return Void();
+        }
+    }
+
+    optional<vector<uint8_t>> nonce = support::getRandom(12);
+    if (!nonce) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error getting nonce"), {});
+        return Void();
+    }
+    optional<vector<uint8_t>> encryptedContent =
+            support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+    if (!encryptedContent) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error encrypting content"), {});
+        return Void();
+    }
+
+    if (entryRemainingBytes_ == 0) {
+        // TODO: ideally do do this without parsing the data (but still validate data is valid
+        // CBOR).
+        auto [item, _, message] = cppbor::parse(entryBytes_);
+        if (item == nullptr) {
+            _hidl_cb(support::result(ResultCode::INVALID_DATA, "Data is not valid CBOR"), {});
+            return Void();
+        }
+        cppbor::Map entryMap;
+        entryMap.add("name", entryName_);
+        entryMap.add("value", std::move(item));
+        cppbor::Array profileIdArray;
+        for (auto id : entryAccessControlProfileIds_) {
+            profileIdArray.add(id);
+        }
+        entryMap.add("accessControlProfiles", std::move(profileIdArray));
+        signedDataCurrentNamespace_.add(std::move(entryMap));
+    }
+
+    _hidl_cb(support::resultOK(), encryptedContent.value());
+    return Void();
+}
+
+Return<void> WritableIdentityCredential::finishAddingEntries(finishAddingEntries_cb _hidl_cb) {
+    if (signedDataCurrentNamespace_.size() > 0) {
+        signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
+    }
+    cppbor::Array popArray;
+    popArray.add("ProofOfProvisioning")
+            .add(docType_)
+            .add(std::move(signedDataAccessControlProfiles_))
+            .add(std::move(signedDataNamespaces_))
+            .add(testCredential_);
+    vector<uint8_t> encodedCbor = popArray.encode();
+
+    optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
+                                                                 encodedCbor,  // payload
+                                                                 {},           // additionalData
+                                                                 {});          // certificateChain
+    if (!signature) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error signing data"), {}, {});
+        return Void();
+    }
+
+    vector<uint8_t> credentialKeys;
+    if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error generating CredentialKeys"), {}, {});
+        return Void();
+    }
+
+    vector<uint8_t> credentialData;
+    if (!generateCredentialData(testCredential_ ? support::getTestHardwareBoundKey()
+                                                : support::getHardwareBoundKey(),
+                                docType_, testCredential_, credentialKeys, credentialData)) {
+        _hidl_cb(support::result(ResultCode::FAILED, "Error generating CredentialData"), {}, {});
+        return Void();
+    }
+
+    _hidl_cb(support::resultOK(), credentialData, signature.value());
+    return Void();
+}
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
diff --git a/identity/1.0/default/WritableIdentityCredential.h b/identity/1.0/default/WritableIdentityCredential.h
new file mode 100644
index 0000000..9f4e303
--- /dev/null
+++ b/identity/1.0/default/WritableIdentityCredential.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+#define ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
+
+#include <android/hardware/identity/1.0/IWritableIdentityCredential.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace implementation {
+
+using ::std::string;
+using ::std::vector;
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+class WritableIdentityCredential : public IWritableIdentityCredential {
+  public:
+    WritableIdentityCredential(const hidl_string& docType, bool testCredential)
+        : docType_(docType), testCredential_(testCredential) {}
+
+    // Creates the Credential Key. Returns false on failure. Must be called
+    // right after construction.
+    bool initialize();
+
+    // Methods from ::android::hardware::identity::IWritableIdentityCredential
+    // follow.
+    Return<void> getAttestationCertificate(const hidl_vec<uint8_t>& attestationChallenge,
+                                           getAttestationCertificate_cb _hidl_cb) override;
+
+    Return<void> startPersonalization(uint16_t accessControlProfileCount,
+                                      const hidl_vec<uint16_t>& entryCounts,
+                                      startPersonalization_cb _hidl_cb) override;
+
+    Return<void> addAccessControlProfile(uint16_t id, const hidl_vec<uint8_t>& readerCertificate,
+                                         bool userAuthenticationRequired, uint64_t timeoutMillis,
+                                         uint64_t secureUserId,
+                                         addAccessControlProfile_cb _hidl_cb) override;
+
+    Return<void> beginAddEntry(const hidl_vec<uint16_t>& accessControlProfileIds,
+                               const hidl_string& nameSpace, const hidl_string& name,
+                               uint32_t entrySize, beginAddEntry_cb _hidl_cb) override;
+
+    Return<void> addEntryValue(const hidl_vec<uint8_t>& content,
+                               addEntryValue_cb _hidl_cb) override;
+
+    Return<void> finishAddingEntries(finishAddingEntries_cb _hidl_cb) override;
+
+  private:
+    string docType_;
+    bool testCredential_;
+
+    // These are set in initialize().
+    vector<uint8_t> storageKey_;
+    vector<uint8_t> credentialPrivKey_;
+    vector<uint8_t> credentialPubKey_;
+
+    // These fields are initialized during startPersonalization()
+    size_t numAccessControlProfileRemaining_;
+    vector<uint16_t> remainingEntryCounts_;
+    cppbor::Array signedDataAccessControlProfiles_;
+    cppbor::Map signedDataNamespaces_;
+    cppbor::Array signedDataCurrentNamespace_;
+
+    // These fields are initialized during beginAddEntry()
+    size_t entryRemainingBytes_;
+    vector<uint8_t> entryAdditionalData_;
+    string entryNameSpace_;
+    string entryName_;
+    vector<uint16_t> entryAccessControlProfileIds_;
+    vector<uint8_t> entryBytes_;
+};
+
+}  // namespace implementation
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
diff --git a/identity/1.0/default/android.hardware.identity@1.0-service.example.rc b/identity/1.0/default/android.hardware.identity@1.0-service.example.rc
new file mode 100644
index 0000000..1eb7319
--- /dev/null
+++ b/identity/1.0/default/android.hardware.identity@1.0-service.example.rc
@@ -0,0 +1,3 @@
+service vendor.identity-1-0 /vendor/bin/hw/android.hardware.identity@1.0-service.example
+    class hal
+    user nobody
diff --git a/identity/1.0/default/service.cpp b/identity/1.0/default/service.cpp
new file mode 100644
index 0000000..839e803
--- /dev/null
+++ b/identity/1.0/default/service.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "android.hardware.identity@1.0-service"
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include "IdentityCredentialStore.h"
+
+using android::hardware::joinRpcThreadpool;
+using android::hardware::identity::implementation::IdentityCredentialStore;
+
+int main(int /* argc */, char* argv[]) {
+    ::android::hardware::configureRpcThreadpool(1, true /*willJoinThreadpool*/);
+
+    ::android::base::InitLogging(argv, &android::base::StderrLogger);
+
+    auto identity_store = new IdentityCredentialStore();
+    auto status = identity_store->registerAsService();
+    if (status != android::OK) {
+        LOG(FATAL) << "Could not register service for IdentityCredentialStore 1.0 (" << status
+                   << ")";
+    }
+    joinRpcThreadpool();
+    return -1;  // Should never get here.
+}
diff --git a/identity/1.0/types.hal b/identity/1.0/types.hal
new file mode 100644
index 0000000..5aedfea
--- /dev/null
+++ b/identity/1.0/types.hal
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package android.hardware.identity@1.0;
+
+/**
+ * The ResultCode enumeration is used to convey the status of an operation.
+ */
+enum ResultCode : int32_t {
+    /**
+     * Success.
+     */
+    OK = 0,
+
+    /**
+     * The operation failed. This is used as a generic catch-all for errors that don't belong
+     * in other categories, including memory/resource allocation failures and I/O errors.
+     */
+    FAILED = 1,
+
+    /**
+     * The passed data was invalid. This is a generic catch all for errors that don't belong
+     * in other categories related to parameter validation.
+     */
+    INVALID_DATA = 2,
+
+    /**
+     * The authToken parameter passed to IIdentityCredential.startRetrieval() is not valid.
+     */
+    INVALID_AUTH_TOKEN = 3,
+
+    /**
+     * The itemsRequest parameter passed to IIdentityCredential.startRetrieval() does not meet
+     * the requirements described in the documentation for that method.
+     */
+    INVALID_ITEMS_REQUEST_MESSAGE = 4,
+
+    /**
+     * The readerSignature parameter in IIdentityCredential.startRetrieval() is invalid,
+     * doesn't contain an embedded certificate chain, or the signature failed to
+     * validate.
+     */
+    READER_SIGNATURE_CHECK_FAILED = 5,
+
+    /**
+     * The sessionTranscript passed to startRetrieval() did not contain the ephmeral public
+     * key returned by createEphemeralPublicKey().
+     */
+    EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 6,
+
+    /**
+     * An access condition related to user authentication was not satisfied.
+     */
+    USER_AUTHENTICATION_FAILED = 7,
+
+    /**
+     * An access condition related to reader authentication was not satisfied.
+     */
+    READER_AUTHENTICATION_FAILED = 8,
+
+    /**
+    * The request data element has no access control profiles associated so it cannot be accessed.
+    */
+    NO_ACCESS_CONTROL_PROFILES = 9,
+
+    /**
+     * The requested data element is not in the provided non-empty itemsRequest message.
+     */
+    NOT_IN_REQUEST_MESSAGE = 10,
+
+    /**
+     * The passed-in sessionTranscript doesn't match the previously passed-in sessionTranscript.
+     */
+    SESSION_TRANSCRIPT_MISMATCH = 11,
+};
+
+/**
+ * A result has a ResultCode and corresponding textual message.
+ */
+struct Result {
+    /**
+     * The result code.
+     *
+     * Implementations must not use values not defined in the ResultCode enumeration.
+     */
+    ResultCode code;
+
+    /**
+     * A human-readable message in English conveying more detail about a failure.
+     *
+     * If code is ResultCode::OK this field must be set to the empty string.
+     */
+    string message;
+};
+
+struct SecureAccessControlProfile {
+    /**
+     * id is a numeric identifier that must be unique within the context of a Credential and may be
+     * used to reference the profile.
+     */
+    uint16_t id;
+
+    /**
+     * readerCertificate, if non-empty, specifies a single X.509 certificate (not a chain
+     * of certificates) that must be used to authenticate requests. For details about how
+     * this is done, see the readerSignature paremter of IIdentityCredential.startRetrieval.
+     */
+    vec<uint8_t> readerCertificate;
+
+    /**
+     * if true, the user is required to authenticate to allow requests.  Required authentication
+     * fressness is specified by timeout below.
+     *
+     */
+    bool userAuthenticationRequired;
+
+    /**
+     * Timeout specifies the amount of time, in milliseconds, for which a user authentication (see
+     * above) is valid, if userAuthenticationRequired is set to true.  If userAuthenticationRequired
+     * is true and timout is zero then authentication is required for each reader session.
+     *
+     * If userAuthenticationRequired is false, timeout must be zero.
+     */
+    uint64_t timeoutMillis;
+
+    /**
+     * secureUserId must be non-zero if userAuthenticationRequired is true.
+     * It is not related to any Android user ID or UID, but is created in the
+     * Gatekeeper application in the secure environment.
+     */
+    uint64_t secureUserId;
+
+    /**
+     * The mac is used to authenticate the access control profile.  It contains:
+     *
+     *      AES-GCM-ENC(storageKey, R, {}, AccessControlProfile)
+     *
+     *  where AccessControlProfile is the CBOR map:
+     *
+     *      AccessControlProfile = {
+     *          "id": uint,
+     *          ? "readerCertificate" : bstr,
+     *          ? (
+     *              "userAuthenticationRequired" : bool,
+     *              "timeoutMillis" : uint,
+     *              "secureUserId" : uint
+     *          )
+     *      }
+     */
+    vec<uint8_t> mac;
+};
diff --git a/identity/1.0/vts/OWNERS b/identity/1.0/vts/OWNERS
new file mode 100644
index 0000000..6969910
--- /dev/null
+++ b/identity/1.0/vts/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+zeuthen@google.com
diff --git a/identity/1.0/vts/functional/Android.bp b/identity/1.0/vts/functional/Android.bp
new file mode 100644
index 0000000..03b49de
--- /dev/null
+++ b/identity/1.0/vts/functional/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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.
+//
+
+cc_test {
+    name: "VtsHalIdentityCredentialTargetTest",
+    defaults: ["VtsHalTargetTestDefaults"],
+    srcs: [
+        "VtsHalIdentityCredentialTargetTest.cpp",
+    ],
+    static_libs: [
+        "android.hardware.identity@1.0",
+        "android.hardware.identity-support-lib",
+        "android.hardware.keymaster@4.0",
+        "libcppbor",
+    ],
+    shared_libs: [
+        "libcrypto",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts-core",
+    ],
+}
diff --git a/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp b/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp
new file mode 100644
index 0000000..903e912
--- /dev/null
+++ b/identity/1.0/vts/functional/VtsHalIdentityCredentialTargetTest.cpp
@@ -0,0 +1,527 @@
+/*
+ * 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 "IdentityCredentialHidlHalTest"
+
+#include <map>
+
+#include <android-base/logging.h>
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace test {
+
+using ::android::hardware::identity::V1_0::IIdentityCredential;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+using ::android::hardware::keymaster::V4_0::HardwareAuthToken;
+
+// ---------------------------------------------------------------------------
+// Test Data.
+// ---------------------------------------------------------------------------
+
+struct TestEntryData {
+    TestEntryData(string nameSpace, string name, vector<uint16_t> profileIds)
+        : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+    TestEntryData(string nameSpace, string name, const string& value, vector<uint16_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+    }
+    TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+                  vector<uint16_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bstr(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, bool value, vector<uint16_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bool(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, int64_t value, vector<uint16_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        if (value >= 0) {
+            valueCbor = cppbor::Uint(value).encode();
+        } else {
+            valueCbor = cppbor::Nint(-value).encode();
+        }
+    }
+
+    string nameSpace;
+    string name;
+    vector<uint8_t> valueCbor;
+    vector<uint16_t> profileIds;
+};
+
+struct TestProfile {
+    uint16_t id;
+    hidl_vec<uint8_t> readerCertificate;
+    bool userAuthenticationRequired;
+    uint64_t timeoutMillis;
+};
+
+/************************************
+ *   TEST DATA FOR AUTHENTICATION
+ ************************************/
+// Test authentication token for user authentication
+
+class IdentityCredentialStoreHidlTest : public ::testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        string serviceName = GetParam();
+        ASSERT_FALSE(serviceName.empty());
+        credentialStore_ = IIdentityCredentialStore::getService(serviceName);
+        ASSERT_NE(credentialStore_, nullptr);
+
+        credentialStore_->getHardwareInformation(
+                [&](const Result& result, const hidl_string& credentialStoreName,
+                    const hidl_string& credentialStoreAuthorName, uint32_t chunkSize,
+                    bool /* isDirectAccess */,
+                    const hidl_vec<hidl_string> /* supportedDocTypes */) {
+                    EXPECT_EQ("", result.message);
+                    ASSERT_EQ(ResultCode::OK, result.code);
+                    ASSERT_GT(credentialStoreName.size(), 0u);
+                    ASSERT_GT(credentialStoreAuthorName.size(), 0u);
+                    ASSERT_GE(chunkSize, 256u);  // Chunk sizes < APDU buffer won't be supported
+                    dataChunkSize_ = chunkSize;
+                });
+    }
+    virtual void TearDown() override {}
+
+    uint32_t dataChunkSize_ = 0;
+
+    sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityCredentialStoreHidlTest, HardwareConfiguration) {
+    credentialStore_->getHardwareInformation(
+            [&](const Result& result, const hidl_string& credentialStoreName,
+                const hidl_string& credentialStoreAuthorName, uint32_t chunkSize,
+                bool /* isDirectAccess */, const hidl_vec<hidl_string> /* supportedDocTypes */) {
+                EXPECT_EQ("", result.message);
+                ASSERT_EQ(ResultCode::OK, result.code);
+                ASSERT_GT(credentialStoreName.size(), 0u);
+                ASSERT_GT(credentialStoreAuthorName.size(), 0u);
+                ASSERT_GE(chunkSize, 256u);  // Chunk sizes < APDU buffer won't be supported
+            });
+}
+
+TEST_P(IdentityCredentialStoreHidlTest, createAndRetrieveCredential) {
+    // First, generate a key-pair for the reader since its public key will be
+    // part of the request data.
+    optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+    ASSERT_TRUE(readerKeyPKCS8);
+    optional<vector<uint8_t>> readerPublicKey =
+            support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+    optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+    string serialDecimal = "1234";
+    string issuer = "Android Open Source Project";
+    string subject = "Android IdentityCredential VTS Test";
+    time_t validityNotBefore = time(nullptr);
+    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+    optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
+            readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    ASSERT_TRUE(readerCertificate);
+
+    // Make the portrait image really big (just shy of 256 KiB) to ensure that
+    // the chunking code gets exercised.
+    vector<uint8_t> portraitImage;
+    portraitImage.resize(256 * 1024 - 10);
+    for (size_t n = 0; n < portraitImage.size(); n++) {
+        portraitImage[n] = (uint8_t)n;
+    }
+
+    // Access control profiles:
+    const vector<TestProfile> testProfiles = {// Profile 0 (reader authentication)
+                                              {0, readerCertificate.value(), false, 0},
+                                              // Profile 1 (no authentication)
+                                              {1, {}, false, 0}};
+
+    HardwareAuthToken authToken = {};
+
+    // Here's the actual test data:
+    const vector<TestEntryData> testEntries = {
+            {"PersonalData", "Last name", string("Turing"), vector<uint16_t>{0, 1}},
+            {"PersonalData", "Birth date", string("19120623"), vector<uint16_t>{0, 1}},
+            {"PersonalData", "First name", string("Alan"), vector<uint16_t>{0, 1}},
+            {"PersonalData", "Home address", string("Maida Vale, London, England"),
+             vector<uint16_t>{0}},
+            {"Image", "Portrait image", portraitImage, vector<uint16_t>{0, 1}},
+    };
+    const vector<uint16_t> testEntriesEntryCounts = {static_cast<uint16_t>(testEntries.size() - 1),
+                                                     1u};
+
+    string cborPretty;
+    sp<IWritableIdentityCredential> writableCredential;
+
+    hidl_vec<uint8_t> empty{0};
+
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    Result result;
+    credentialStore_->createCredential(
+            docType, testCredential,
+            [&](const Result& _result, const sp<IWritableIdentityCredential>& _writableCredential) {
+                result = _result;
+                writableCredential = _writableCredential;
+            });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+    ASSERT_NE(writableCredential, nullptr);
+
+    string challenge = "attestationChallenge";
+    vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+    vector<uint8_t> attestationCertificate;
+    writableCredential->getAttestationCertificate(
+            attestationChallenge,
+            [&](const Result& _result, const hidl_vec<uint8_t>& _attestationCertificate) {
+                result = _result;
+                attestationCertificate = _attestationCertificate;
+            });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts,
+                                             [&](const Result& _result) { result = _result; });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    vector<SecureAccessControlProfile> returnedSecureProfiles;
+    for (const auto& testProfile : testProfiles) {
+        SecureAccessControlProfile profile;
+        writableCredential->addAccessControlProfile(
+                testProfile.id, testProfile.readerCertificate,
+                testProfile.userAuthenticationRequired, testProfile.timeoutMillis,
+                0,  // secureUserId
+                [&](const Result& _result, const SecureAccessControlProfile& _profile) {
+                    result = _result;
+                    profile = _profile;
+                });
+        EXPECT_EQ("", result.message);
+        ASSERT_EQ(ResultCode::OK, result.code);
+        ASSERT_EQ(testProfile.id, profile.id);
+        ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate);
+        ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+        ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+        ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+        returnedSecureProfiles.push_back(profile);
+    }
+
+    // Uses TestEntryData* pointer as key and values are the encrypted blobs. This
+    // is a little hacky but it works well enough.
+    map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+
+    for (const auto& entry : testEntries) {
+        vector<vector<uint8_t>> chunks = support::chunkVector(entry.valueCbor, dataChunkSize_);
+
+        writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+                                          entry.valueCbor.size(),
+                                          [&](const Result& _result) { result = _result; });
+        EXPECT_EQ("", result.message);
+        ASSERT_EQ(ResultCode::OK, result.code);
+
+        vector<vector<uint8_t>> encryptedChunks;
+        for (const auto& chunk : chunks) {
+            writableCredential->addEntryValue(
+                    chunk, [&](const Result& result, hidl_vec<uint8_t> encryptedContent) {
+                        EXPECT_EQ("", result.message);
+                        ASSERT_EQ(ResultCode::OK, result.code);
+                        ASSERT_GT(encryptedContent.size(), 0u);
+                        encryptedChunks.push_back(encryptedContent);
+                    });
+        }
+        encryptedBlobs[&entry] = encryptedChunks;
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    writableCredential->finishAddingEntries(
+            [&](const Result& _result, const hidl_vec<uint8_t>& _credentialData,
+                const hidl_vec<uint8_t>& _proofOfProvisioningSignature) {
+                result = _result;
+                credentialData = _credentialData;
+                proofOfProvisioningSignature = _proofOfProvisioningSignature;
+            });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    optional<vector<uint8_t>> proofOfProvisioning =
+            support::coseSignGetPayload(proofOfProvisioningSignature);
+    ASSERT_TRUE(proofOfProvisioning);
+    cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
+    EXPECT_EQ(
+            "[\n"
+            "  'ProofOfProvisioning',\n"
+            "  'org.iso.18013-5.2019.mdl',\n"
+            "  [\n"
+            "    {\n"
+            "      'id' : 0,\n"
+            "      'readerCertificate' : <not printed>,\n"
+            "    },\n"
+            "    {\n"
+            "      'id' : 1,\n"
+            "    },\n"
+            "  ],\n"
+            "  {\n"
+            "    'PersonalData' : [\n"
+            "      {\n"
+            "        'name' : 'Last name',\n"
+            "        'value' : 'Turing',\n"
+            "        'accessControlProfiles' : [0, 1, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'Birth date',\n"
+            "        'value' : '19120623',\n"
+            "        'accessControlProfiles' : [0, 1, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'First name',\n"
+            "        'value' : 'Alan',\n"
+            "        'accessControlProfiles' : [0, 1, ],\n"
+            "      },\n"
+            "      {\n"
+            "        'name' : 'Home address',\n"
+            "        'value' : 'Maida Vale, London, England',\n"
+            "        'accessControlProfiles' : [0, ],\n"
+            "      },\n"
+            "    ],\n"
+            "    'Image' : [\n"
+            "      {\n"
+            "        'name' : 'Portrait image',\n"
+            "        'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+            "        'accessControlProfiles' : [0, 1, ],\n"
+            "      },\n"
+            "    ],\n"
+            "  },\n"
+            "  true,\n"
+            "]",
+            cborPretty);
+
+    optional<vector<uint8_t>> credentialPubKey =
+            support::certificateChainGetTopMostKey(attestationCertificate);
+    ASSERT_TRUE(credentialPubKey);
+    EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+                                                 {},  // Additional data
+                                                 credentialPubKey.value()));
+    writableCredential = nullptr;
+
+    // Now that the credential has been provisioned, read it back and check the
+    // correct data is returned.
+    sp<IIdentityCredential> credential;
+    credentialStore_->getCredential(
+            credentialData, [&](const Result& _result, const sp<IIdentityCredential>& _credential) {
+                result = _result;
+                credential = _credential;
+            });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+    ASSERT_NE(credential, nullptr);
+
+    optional<vector<uint8_t>> readerEphemeralKeyPair = support::createEcKeyPair();
+    ASSERT_TRUE(readerEphemeralKeyPair);
+    optional<vector<uint8_t>> readerEphemeralPublicKey =
+            support::ecKeyPairGetPublicKey(readerEphemeralKeyPair.value());
+    credential->setReaderEphemeralPublicKey(readerEphemeralPublicKey.value(),
+                                            [&](const Result& _result) { result = _result; });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    vector<uint8_t> ephemeralKeyPair;
+    credential->createEphemeralKeyPair(
+            [&](const Result& _result, const hidl_vec<uint8_t>& _ephemeralKeyPair) {
+                result = _result;
+                ephemeralKeyPair = _ephemeralKeyPair;
+            });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+    optional<vector<uint8_t>> ephemeralPublicKey = support::ecKeyPairGetPublicKey(ephemeralKeyPair);
+
+    // Calculate requestData field and sign it with the reader key.
+    auto [getXYSuccess, ephX, ephY] = support::ecPublicKeyGetXandY(ephemeralPublicKey.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 =
+            cppbor::Map("nameSpaces",
+                        cppbor::Map()
+                                .add("PersonalData", cppbor::Map()
+                                                             .add("Last name", false)
+                                                             .add("Birth date", false)
+                                                             .add("First name", false)
+                                                             .add("Home address", true))
+                                .add("Image", cppbor::Map().add("Portrait image", false)))
+                    .encode();
+    cborPretty = support::cborPrettyPrint(itemsRequestBytes, 32, {"EphemeralPublicKey"});
+    EXPECT_EQ(
+            "{\n"
+            "  'nameSpaces' : {\n"
+            "    'PersonalData' : {\n"
+            "      'Last name' : false,\n"
+            "      'Birth date' : false,\n"
+            "      'First name' : false,\n"
+            "      'Home address' : true,\n"
+            "    },\n"
+            "    'Image' : {\n"
+            "      'Portrait image' : false,\n"
+            "    },\n"
+            "  },\n"
+            "}",
+            cborPretty);
+    vector<uint8_t> dataToSign = cppbor::Array()
+                                         .add("ReaderAuthentication")
+                                         .add(sessionTranscript.clone())
+                                         .add(cppbor::Semantic(24, itemsRequestBytes))
+                                         .encode();
+    optional<vector<uint8_t>> readerSignature =
+            support::coseSignEcDsa(readerKey.value(), {},  // content
+                                   dataToSign,             // detached content
+                                   readerCertificate.value());
+    ASSERT_TRUE(readerSignature);
+
+    credential->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+                               sessionTranscriptBytes, readerSignature.value(),
+                               testEntriesEntryCounts,
+                               [&](const Result& _result) { result = _result; });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    for (const auto& entry : testEntries) {
+        credential->startRetrieveEntryValue(entry.nameSpace, entry.name, entry.valueCbor.size(),
+                                            entry.profileIds,
+                                            [&](const Result& _result) { result = _result; });
+        EXPECT_EQ("", result.message);
+        ASSERT_EQ(ResultCode::OK, result.code);
+
+        auto it = encryptedBlobs.find(&entry);
+        ASSERT_NE(it, encryptedBlobs.end());
+        const vector<vector<uint8_t>>& encryptedChunks = it->second;
+
+        vector<uint8_t> content;
+        for (const auto& encryptedChunk : encryptedChunks) {
+            vector<uint8_t> chunk;
+            credential->retrieveEntryValue(
+                    encryptedChunk, [&](const Result& _result, const hidl_vec<uint8_t>& _chunk) {
+                        result = _result;
+                        chunk = _chunk;
+                    });
+            EXPECT_EQ("", result.message);
+            ASSERT_EQ(ResultCode::OK, result.code);
+            content.insert(content.end(), chunk.begin(), chunk.end());
+        }
+        EXPECT_EQ(content, entry.valueCbor);
+    }
+
+    // Generate the key that will be used to sign AuthenticatedData.
+    vector<uint8_t> signingKeyBlob;
+    vector<uint8_t> signingKeyCertificate;
+    credential->generateSigningKeyPair([&](const Result& _result,
+                                           const hidl_vec<uint8_t> _signingKeyBlob,
+                                           const hidl_vec<uint8_t> _signingKeyCertificate) {
+        result = _result;
+        signingKeyBlob = _signingKeyBlob;
+        signingKeyCertificate = _signingKeyCertificate;
+    });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+
+    vector<uint8_t> mac;
+    vector<uint8_t> deviceNameSpacesBytes;
+    credential->finishRetrieval(signingKeyBlob,
+                                [&](const Result& _result, const hidl_vec<uint8_t> _mac,
+                                    const hidl_vec<uint8_t> _deviceNameSpacesBytes) {
+                                    result = _result;
+                                    mac = _mac;
+                                    deviceNameSpacesBytes = _deviceNameSpacesBytes;
+                                });
+    EXPECT_EQ("", result.message);
+    ASSERT_EQ(ResultCode::OK, result.code);
+    cborPretty = support::cborPrettyPrint(deviceNameSpacesBytes, 32, {});
+    ASSERT_EQ(
+            "{\n"
+            "  'PersonalData' : {\n"
+            "    'Last name' : 'Turing',\n"
+            "    'Birth date' : '19120623',\n"
+            "    'First name' : 'Alan',\n"
+            "    'Home address' : 'Maida Vale, London, England',\n"
+            "  },\n"
+            "  'Image' : {\n"
+            "    'Portrait image' : <bstr size=262134 "
+            "sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+            "  },\n"
+            "}",
+            cborPretty);
+    // The data that is MACed is ["DeviceAuthentication", sessionTranscriptBytes, docType,
+    // deviceNameSpacesBytes] so build up that structure
+    cppbor::Array deviceAuthentication;
+    deviceAuthentication.add("DeviceAuthentication");
+    deviceAuthentication.add(sessionTranscript.clone());
+    deviceAuthentication.add(docType);
+    deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
+    vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
+    optional<vector<uint8_t>> signingPublicKey =
+            support::certificateChainGetTopMostKey(signingKeyCertificate);
+    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());
+    ASSERT_TRUE(sharedSecret);
+    vector<uint8_t> salt = {0x00};
+    vector<uint8_t> info = {};
+    optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+    ASSERT_TRUE(derivedKey);
+    optional<vector<uint8_t>> calculatedMac =
+            support::coseMac0(derivedKey.value(), {},        // payload
+                              encodedDeviceAuthentication);  // detached content
+    ASSERT_TRUE(calculatedMac);
+    EXPECT_EQ(mac, calculatedMac);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, IdentityCredentialStoreHidlTest,
+                         testing::ValuesIn(android::hardware::getAllHalInstanceNames(
+                                 IIdentityCredentialStore::descriptor)),
+                         android::hardware::PrintInstanceNameToString);
+
+}  // namespace test
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
diff --git a/identity/support/Android.bp b/identity/support/Android.bp
new file mode 100644
index 0000000..38dc10b
--- /dev/null
+++ b/identity/support/Android.bp
@@ -0,0 +1,103 @@
+// 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.
+//
+
+cc_library {
+    name: "android.hardware.identity-support-lib",
+    vendor_available: true,
+    srcs: [
+        "src/IdentityCredentialSupport.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "android.hardware.identity@1.0",
+        "libcrypto",
+        "libbase",
+        "libhidlbase",
+        "libhardware",
+    ],
+    static_libs: [
+        "libcppbor",
+    ],
+}
+
+cc_test {
+    name: "android.hardware.identity-support-lib-test",
+    srcs: [
+        "tests/IdentityCredentialSupportTest.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.identity-support-lib",
+        "android.hardware.identity@1.0",
+        "libcrypto",
+        "libbase",
+        "libhidlbase",
+        "libhardware",
+    ],
+    static_libs: [
+        "libcppbor",
+        "libgmock",
+    ],
+    test_suites: ["general-tests"],
+}
+
+// --
+
+cc_library {
+    name: "libcppbor",
+    vendor_available: true,
+    host_supported: true,
+    srcs: [
+        "src/cppbor.cpp",
+        "src/cppbor_parse.cpp",
+    ],
+    export_include_dirs: [
+        "include/cppbor",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+cc_test {
+    name: "cppbor_test",
+    srcs: [
+        "tests/cppbor_test.cpp",
+    ],
+    shared_libs: [
+        "libcppbor",
+        "libbase",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    test_suites: ["general-tests"],
+}
+
+cc_test_host {
+    name: "cppbor_host_test",
+    srcs: [
+        "tests/cppbor_test.cpp",
+    ],
+    shared_libs: [
+        "libcppbor",
+        "libbase",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
new file mode 100644
index 0000000..485571a
--- /dev/null
+++ b/identity/support/include/android/hardware/identity/support/IdentityCredentialSupport.h
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
+#define IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
+
+#include <cstdint>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <android/hardware/identity/1.0/types.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace support {
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+// ---------------------------------------------------------------------------
+// Miscellaneous utilities.
+// ---------------------------------------------------------------------------
+
+// Dumps the data in |data| to stderr. The written data will be of the following
+// form for the call hexdump("signature", data) where |data| is of size 71:
+//
+//   signature: dumping 71 bytes
+//   0000  30 45 02 21 00 ac c6 12 60 56 a2 e9 ee 16 be 14  0E.!....`V......
+//   0010  69 7f c4 00 95 8c e8 55 1f 22 de 34 0b 08 8a 3b  i......U.".4...;
+//   0020  a0 56 54 05 07 02 20 58 77 d9 8c f9 eb 41 df fd  .VT... Xw....A..
+//   0030  c1 a3 14 e0 bf b0 a2 c5 0c b6 85 8c 4a 0d f9 2b  ............J..+
+//   0040  b7 8f d2 1d 9b 11 ac                             .......
+//
+// This should only be used for debugging.
+void hexdump(const string& name, const vector<uint8_t>& data);
+
+string encodeHex(const string& str);
+
+string encodeHex(const vector<uint8_t>& data);
+
+string encodeHex(const uint8_t* data, size_t dataLen);
+
+optional<vector<uint8_t>> decodeHex(const string& hexEncoded);
+
+// ---------------------------------------------------------------------------
+// CBOR utilities.
+// ---------------------------------------------------------------------------
+
+// Returns pretty-printed CBOR for |value|.
+//
+// Only valid CBOR should be passed to this function.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+// The |mapKeysToNotPrint| parameter specifies the name of map values
+// to not print. This is useful for unit tests.
+string cborPrettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize = 32,
+                       const vector<string>& mapKeysToNotPrint = {});
+
+// ---------------------------------------------------------------------------
+// Crypto functionality / abstraction.
+// ---------------------------------------------------------------------------
+
+constexpr size_t kAesGcmIvSize = 12;
+constexpr size_t kAesGcmTagSize = 16;
+constexpr size_t kAes128GcmKeySize = 16;
+
+// Returns |numBytes| bytes of random data.
+optional<vector<uint8_t>> getRandom(size_t numBytes);
+
+// Calculates the SHA-256 of |data|.
+vector<uint8_t> sha256(const vector<uint8_t>& data);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext. The format of |encryptedData| must
+// be as specified in the encryptAes128Gcm() function.
+optional<vector<uint8_t>> decryptAes128Gcm(const vector<uint8_t>& key,
+                                           const vector<uint8_t>& encryptedData,
+                                           const vector<uint8_t>& additionalAuthenticatedData);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag).
+optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vector<uint8_t>& nonce,
+                                           const vector<uint8_t>& data,
+                                           const vector<uint8_t>& additionalAuthenticatedData);
+
+// ---------------------------------------------------------------------------
+// EC crypto functionality / abstraction (only supports P-256).
+// ---------------------------------------------------------------------------
+
+// Creates an 256-bit EC key using the NID_X9_62_prime256v1 curve, returns the
+// PKCS#8 encoded key-pair.
+//
+optional<vector<uint8_t>> createEcKeyPair();
+
+// For an EC key |keyPair| encoded in PKCS#8 format, extracts the public key in
+// uncompressed point form.
+//
+optional<vector<uint8_t>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair);
+
+// For an EC key |keyPair| encoded in PKCS#8 format, extracts the private key as
+// an EC uncompressed key.
+//
+optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair);
+
+// 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
+// |serialDecimal|, |issuer|, |subject|, |validityNotBefore|, and
+// |validityNotAfter|.
+//
+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,
+                                             time_t validityNotAfter);
+
+// Signs |data| with |key| (which must be in the format returned by
+// ecKeyPairGetPrivateKey()). Signature is returned and will be in DER format.
+//
+optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data);
+
+// Calculates the HMAC with SHA-256 for |data| using |key|. The calculated HMAC
+// is returned and will be 32 bytes.
+//
+optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<uint8_t>& data);
+
+// Checks that |signature| (in DER format) is a valid signature of |digest|,
+// made with |publicKey| (which must be in the format returned by
+// ecKeyPairGetPublicKey()).
+//
+bool checkEcDsaSignature(const vector<uint8_t>& digest, const vector<uint8_t>& signature,
+                         const vector<uint8_t>& publicKey);
+
+// Extracts the public-key from the top-most certificate in |certificateChain|
+// (which should be a concatenated chain of DER-encoded X.509 certificates).
+//
+// The returned public key will be in the same format as returned by
+// ecKeyPairGetPublicKey().
+//
+optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& certificateChain);
+
+// Generates a X.509 certificate for |publicKey| (which must be in the format
+// returned by ecKeyPairGetPublicKey()).
+//
+// The certificate is signed by |signingKey| (which must be in the format
+// returned by ecKeyPairGetPrivateKey())
+//
+optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
+        const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
+        const string& serialDecimal, const string& issuer, const string& subject,
+        time_t validityNotBefore, time_t validityNotAfter);
+
+// Performs Elliptic-curve Diffie-Helman using |publicKey| (which must be in the
+// format returned by ecKeyPairGetPublicKey()) and |privateKey| (which must be
+// in the format returned by ecKeyPairGetPrivateKey()).
+//
+// On success, the computed shared secret is returned.
+//
+optional<vector<uint8_t>> ecdh(const vector<uint8_t>& publicKey, const vector<uint8_t>& privateKey);
+
+// Key derivation function using SHA-256, conforming to RFC 5869.
+//
+// On success, the derived key is returned.
+//
+optional<vector<uint8_t>> hkdf(const vector<uint8_t>& sharedSecret, const vector<uint8_t>& salt,
+                               const vector<uint8_t>& info, size_t size);
+
+// Returns the X and Y coordinates from |publicKey| (which must be in the format
+// returned by ecKeyPairGetPublicKey()).
+//
+// Success is indicated by the first value in the returned tuple. If successful,
+// the returned coordinates will be in uncompressed form.
+//
+tuple<bool, vector<uint8_t>, vector<uint8_t>> ecPublicKeyGetXandY(const vector<uint8_t>& publicKey);
+
+// Concatenates all certificates into |certificateChain| together into a
+// single bytestring.
+//
+// This is the reverse operation of certificateChainSplit().
+vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain);
+
+// Splits all the certificates in a single bytestring into individual
+// certificates.
+//
+// Returns nothing if |certificateChain| contains invalid data.
+//
+// This is the reverse operation of certificateChainJoin().
+optional<vector<vector<uint8_t>>> certificateChainSplit(const vector<uint8_t>& certificateChain);
+
+// Validates that the certificate chain is valid. In particular, checks that each
+// certificate in the chain is signed by the public key in the following certificate.
+//
+// Returns false if |certificateChain| failed validation or if each certificate
+// is not signed by its successor.
+//
+bool certificateChainValidate(const vector<uint8_t>& certificateChain);
+
+// Signs |data| and |detachedContent| with |key| (which must be in the format
+// returned by ecKeyPairGetPrivateKey()).
+//
+// On success, the Signature is returned and will be in COSE_Sign1 format.
+//
+// If |certificateChain| is non-empty it's included in the 'x5chain'
+// protected header element (as as described in'draft-ietf-cose-x509-04').
+//
+optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data,
+                                        const vector<uint8_t>& detachedContent,
+                                        const vector<uint8_t>& certificateChain);
+
+// Checks that |signatureCoseSign1| (in COSE_Sign1 format) is a valid signature
+// made with |public_key| (which must be in the format returned by
+// ecKeyPairGetPublicKey()) where |detachedContent| is the detached content.
+//
+bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
+                             const vector<uint8_t>& detachedContent,
+                             const vector<uint8_t>& publicKey);
+
+// Extracts the payload from a COSE_Sign1.
+optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1);
+
+// Extracts the X.509 certificate chain, if present. Returns the data as a
+// concatenated chain of DER-encoded X.509 certificates
+//
+// Returns nothing if there is no 'x5chain' element or an error occurs.
+//
+optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCoseSign1);
+
+// MACs |data| and |detachedContent| with |key| (which can be any sequence of
+// bytes).
+//
+// If successful, the MAC is returned and will be in COSE_Mac0 format.
+//
+optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint8_t>& data,
+                                   const vector<uint8_t>& detachedContent);
+
+// ---------------------------------------------------------------------------
+// Platform abstraction.
+// ---------------------------------------------------------------------------
+
+// Returns the hardware-bound AES-128 key.
+const vector<uint8_t>& getHardwareBoundKey();
+
+// ---------------------------------------------------------------------------
+// Utility functions specific to IdentityCredential.
+// ---------------------------------------------------------------------------
+
+// Returns a reference to a Result with code OK and empty message.
+const Result& resultOK();
+
+// Returns a new Result with the given code and message.
+Result result(ResultCode code, const char* format, ...) __attribute__((format(printf, 2, 3)));
+
+// Splits the given bytestring into chunks. If the given vector is smaller or equal to
+// |maxChunkSize| a vector with |content| as the only element is returned. Otherwise
+// |content| is split into N vectors each of size |maxChunkSize| except the final element
+// may be smaller than |maxChunkSize|.
+vector<vector<uint8_t>> chunkVector(const vector<uint8_t>& content, size_t maxChunkSize);
+
+// Calculates the MAC for |profile| using |storageKey|.
+optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
+        const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
+
+// Checks authenticity of the MAC in |profile| using |storageKey|.
+bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
+                                        const vector<uint8_t>& storageKey);
+
+// Returns the testing AES-128 key where all bits are set to 0.
+const vector<uint8_t>& getTestHardwareBoundKey();
+
+// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+                                          const vector<uint16_t> accessControlProfileIds);
+
+}  // namespace support
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
+
+#endif  // IDENTITY_SUPPORT_INCLUDE_IDENTITY_CREDENTIAL_UTILS_H_
diff --git a/identity/support/include/cppbor/README.md b/identity/support/include/cppbor/README.md
new file mode 100644
index 0000000..723bfcf
--- /dev/null
+++ b/identity/support/include/cppbor/README.md
@@ -0,0 +1,216 @@
+CppBor: A Modern C++ CBOR Parser and Generator
+==============================================
+
+CppBor provides a natural and easy-to-use syntax for constructing and
+parsing CBOR messages.  It does not (yet) support all features of
+CBOR, nor (yet) support validation against CDDL schemata, though both
+are planned.  CBOR features that aren't supported include:
+
+* Indefinite length values
+* Semantic tagging
+* Floating point
+
+CppBor requires C++-17.
+
+## CBOR representation
+
+CppBor represents CBOR data items as instances of the `Item` class or,
+more precisely, as instances of subclasses of `Item`, since `Item` is a
+pure interface.  The subclasses of `Item` correspond almost one-to-one
+with CBOR major types, and are named to match the CDDL names to which
+they correspond.  They are:
+
+* `Uint` corresponds to major type 0, and can hold unsigned integers
+  up through (2^64 - 1).
+* `Nint` corresponds to major type 1.  It can only hold values from -1
+  to -(2^63 - 1), since it's internal representation is an int64_t.
+  This can be fixed, but it seems unlikely that applications will need
+  the omitted range from -(2^63) to (2^64 - 1), since it's
+  inconvenient to represent them in many programming languages.
+* `Int` is an abstract base of `Uint` and `Nint` that facilitates
+  working with all signed integers representable with int64_t.
+* `Bstr` corresponds to major type 2, a byte string.
+* `Tstr` corresponds to major type 3, a text string.
+* `Array` corresponds to major type 4, an Array.  It holds a
+  variable-length array of `Item`s.
+* `Map` corresponds to major type 5, a Map.  It holds a
+  variable-length array of pairs of `Item`s.
+* `Simple` corresponds to major type 7.  It's an abstract class since
+  items require more specific type.
+* `Bool` is the only currently-implemented subclass of `Simple`.
+
+Note that major type 6, semantic tag, is not yet implemented.
+
+In practice, users of CppBor will rarely use most of these classes
+when generating CBOR encodings.  This is because CppBor provides
+straightforward conversions from the obvious normal C++ types.
+Specifically, the following conversions are provided in appropriate
+contexts:
+
+* Signed and unsigned integers convert to `Uint` or `Nint`, as
+  appropriate.
+* `std::string`, `std::string_view`, `const char*` and
+  `std::pair<char iterator, char iterator>` convert to `Tstr`.
+* `std::vector<uint8_t>`, `std::pair<uint8_t iterator, uint8_t
+  iterator>` and `std::pair<uint8_t*, size_t>` convert to `Bstr`.
+* `bool` converts to `Bool`.
+
+## CBOR generation
+
+### Complete tree generation
+
+The set of `encode` methods in `Item` provide the interface for
+producing encoded CBOR.  The basic process for "complete tree"
+generation (as opposed to "incremental" generation, which is discussed
+below) is to construct an `Item` which models the data to be encoded,
+and then call one of the `encode` methods, whichever is convenient for
+the encoding destination.  A trivial example:
+
+```
+cppbor::Uint val(0);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+    It's relatively rare that single values are encoded as above.  More often, the
+    "root" data item will be an `Array` or `Map` which contains a more complex structure.For example
+    :
+
+``` using cppbor::Map;
+using cppbor::Array;
+
+std::vector<uint8_t> vec =  // ...
+    Map val("key1", Array(Map("key_a", 99 "key_b", vec), "foo"), "key2", true);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+This creates a map with two entries, with `Tstr` keys "Outer1" and
+"Outer2", respectively.  The "Outer1" entry has as its value an
+`Array` containing a `Map` and a `Tstr`.  The "Outer2" entry has a
+`Bool` value.
+
+This example demonstrates how automatic conversion of C++ types to
+CppBor `Item` subclass instances is done.  Where the caller provides a
+C++ or C string, a `Tstr` entry is added.  Where the caller provides
+an integer literal or variable, a `Uint` or `Nint` is added, depending
+on whether the value is positive or negative.
+
+As an alternative, a more fluent-style API is provided for building up
+structures.  For example:
+
+```
+using cppbor::Map;
+using cppbor::Array;
+
+std::vector<uint8_t> vec =  // ...
+    Map val();
+val.add("key1", Array().add(Map().add("key_a", 99).add("key_b", vec)).add("foo")).add("key2", true);
+std::vector<uint8_t> encoding = val.encode();
+```
+
+    An advantage of this interface over the constructor -
+    based creation approach above is that it need not be done all at once
+        .The `add` methods return a reference to the object added to to allow calls to be chained,
+    but chaining is not necessary; calls can be made
+sequentially, as the data to add is available.
+
+#### `encode` methods
+
+There are several variations of `Item::encode`, all of which
+accomplish the same task but output the encoded data in different
+ways, and with somewhat different performance characteristics.  The
+provided options are:
+
+* `bool encode(uint8\_t** pos, const uint8\_t* end)` encodes into the
+  buffer referenced by the range [`*pos`, end).  `*pos` is moved.  If
+  the encoding runs out of buffer space before finishing, the method
+  returns false.  This is the most efficient way to encode, into an
+  already-allocated buffer.
+* `void encode(EncodeCallback encodeCallback)` calls `encodeCallback`
+  for each encoded byte.  It's the responsibility of the implementor
+  of the callback to behave safely in the event that the output buffer
+  (if applicable) is exhausted.  This is less efficient than the prior
+  method because it imposes an additional function call for each byte.
+* `template </*...*/> void encode(OutputIterator i)`
+  encodes into the provided iterator.  SFINAE ensures that the
+  template doesn't match for non-iterators.  The implementation
+  actually uses the callback-based method, plus has whatever overhead
+  the iterator adds.
+* `std::vector<uint8_t> encode()` creates a new std::vector, reserves
+  sufficient capacity to hold the encoding, and inserts the encoded
+  bytes with a std::pushback_iterator and the previous method.
+* `std::string toString()` does the same as the previous method, but
+  returns a string instead of a vector.
+
+### Incremental generation
+
+Incremental generation requires deeper understanding of CBOR, because
+the library can't do as much to ensure that the output is valid.  The
+basic tool for intcremental generation is the `encodeHeader`
+function.  There are two variations, one which writes into a buffer,
+and one which uses a callback.  Both simply write out the bytes of a
+header.  To construct the same map as in the above examples,
+incrementally, one might write:
+
+```
+using namespace cppbor;  // For example brevity
+
+std::vector encoding;
+auto iter = std::back_inserter(result);
+encodeHeader(MAP, 2 /* # of map entries */, iter);
+std::string s = "key1";
+encodeHeader(TSTR, s.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+encodeHeader(ARRAY, 2 /* # of array entries */, iter);
+Map().add("key_a", 99).add("key_b", vec).encode(iter)
+s = "foo";
+encodeHeader(TSTR, foo.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+s = "key2";
+encodeHeader(TSTR, foo.size(), iter);
+std::copy(s.begin(), s.end(), iter);
+encodeHeader(SIMPLE, TRUE, iter);
+```
+
+As the above example demonstrates, the styles can be mixed -- Note the
+creation and encoding of the inner Map using the fluent style.
+
+## Parsing
+
+CppBor also supports parsing of encoded CBOR data, with the same
+feature set as encoding.  There are two basic approaches to parsing,
+"full" and "stream"
+
+### Full parsing
+
+Full parsing means completely parsing a (possibly-compound) data
+item from a byte buffer.  The `parse` functions that do not take a
+`ParseClient` pointer do this.  They return a `ParseResult` which is a
+tuple of three values:
+
+* std::unique_ptr<Item> that points to the parsed item, or is nullptr
+  if there was a parse error.
+* const uint8_t* that points to the byte after the end of the decoded
+  item, or to the first unparseable byte in the event of an error.
+* std::string that is empty on success or contains an error message if
+  a parse error occurred.
+
+Assuming a successful parse, you can then use `Item::type()` to
+discover the type of the parsed item (e.g. MAP), and then use the
+appropriate `Item::as*()` method (e.g. `Item::asMap()`) to get a
+pointer to an interface which allows you to retrieve specific values.
+
+### Stream parsing
+
+Stream parsing is more complex, but more flexible.  To use
+StreamParsing, you must create your own subclass of `ParseClient` and
+call one of the `parse` functions that accepts it.  See the
+`ParseClient` methods docstrings for details.
+
+One unusual feature of stream parsing is that the `ParseClient`
+callback methods not only provide the parsed Item, but also pointers
+to the portion of the buffer that encode that Item.  This is useful
+if, for example, you want to find an element inside of a structure,
+and then copy the encoding of that sub-structure, without bothering to
+parse the rest.
+
+The full parser is implemented with the stream parser.
diff --git a/identity/support/include/cppbor/cppbor.h b/identity/support/include/cppbor/cppbor.h
new file mode 100644
index 0000000..a755db1
--- /dev/null
+++ b/identity/support/include/cppbor/cppbor.h
@@ -0,0 +1,827 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+namespace cppbor {
+
+enum MajorType : uint8_t {
+    UINT = 0 << 5,
+    NINT = 1 << 5,
+    BSTR = 2 << 5,
+    TSTR = 3 << 5,
+    ARRAY = 4 << 5,
+    MAP = 5 << 5,
+    SEMANTIC = 6 << 5,
+    SIMPLE = 7 << 5,
+};
+
+enum SimpleType {
+    BOOLEAN,
+    NULL_T,  // Only two supported, as yet.
+};
+
+enum SpecialAddlInfoValues : uint8_t {
+    FALSE = 20,
+    TRUE = 21,
+    NULL_V = 22,
+    ONE_BYTE_LENGTH = 24,
+    TWO_BYTE_LENGTH = 25,
+    FOUR_BYTE_LENGTH = 26,
+    EIGHT_BYTE_LENGTH = 27,
+};
+
+class Item;
+class Uint;
+class Nint;
+class Int;
+class Tstr;
+class Bstr;
+class Simple;
+class Bool;
+class Array;
+class Map;
+class Null;
+class Semantic;
+
+/**
+ * Returns the size of a CBOR header that contains the additional info value addlInfo.
+ */
+size_t headerSize(uint64_t addlInfo);
+
+/**
+ * Encodes a CBOR header with the specified type and additional info into the range [pos, end).
+ * Returns a pointer to one past the last byte written, or nullptr if there isn't sufficient space
+ * to write the header.
+ */
+uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end);
+
+using EncodeCallback = std::function<void(uint8_t)>;
+
+/**
+ * Encodes a CBOR header with the specified type and additional info, passing each byte in turn to
+ * encodeCallback.
+ */
+void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback);
+
+/**
+ * Encodes a CBOR header with the specified type and additional info, writing each byte to the
+ * provided OutputIterator.
+ */
+template <typename OutputIterator,
+          typename = std::enable_if_t<std::is_base_of_v<
+                  std::output_iterator_tag,
+                  typename std::iterator_traits<OutputIterator>::iterator_category>>>
+void encodeHeader(MajorType type, uint64_t addlInfo, OutputIterator iter) {
+    return encodeHeader(type, addlInfo, [&](uint8_t v) { *iter++ = v; });
+}
+
+/**
+ * Item represents a CBOR-encodeable data item.  Item is an abstract interface with a set of virtual
+ * methods that allow encoding of the item or conversion to the appropriate derived type.
+ */
+class Item {
+  public:
+    virtual ~Item() {}
+
+    /**
+     * Returns the CBOR type of the item.
+     */
+    virtual MajorType type() const = 0;
+
+    // These methods safely downcast an Item to the appropriate subclass.
+    virtual const Int* asInt() const { return nullptr; }
+    virtual const Uint* asUint() const { return nullptr; }
+    virtual const Nint* asNint() const { return nullptr; }
+    virtual const Tstr* asTstr() const { return nullptr; }
+    virtual const Bstr* asBstr() const { return nullptr; }
+    virtual const Simple* asSimple() const { return nullptr; }
+    virtual const Map* asMap() const { return nullptr; }
+    virtual const Array* asArray() const { return nullptr; }
+    virtual const Semantic* asSemantic() const { return nullptr; }
+
+    /**
+     * Returns true if this is a "compound" item, i.e. one that contains one or more other items.
+     */
+    virtual bool isCompound() const { return false; }
+
+    bool operator==(const Item& other) const&;
+    bool operator!=(const Item& other) const& { return !(*this == other); }
+
+    /**
+     * Returns the number of bytes required to encode this Item into CBOR.  Note that if this is a
+     * complex Item, calling this method will require walking the whole tree.
+     */
+    virtual size_t encodedSize() const = 0;
+
+    /**
+     * Encodes the Item into buffer referenced by range [*pos, end).  Returns a pointer to one past
+     * the last position written.  Returns nullptr if there isn't enough space to encode.
+     */
+    virtual uint8_t* encode(uint8_t* pos, const uint8_t* end) const = 0;
+
+    /**
+     * Encodes the Item by passing each encoded byte to encodeCallback.
+     */
+    virtual void encode(EncodeCallback encodeCallback) const = 0;
+
+    /**
+     * Clones the Item
+     */
+    virtual std::unique_ptr<Item> clone() const = 0;
+
+    /**
+     * Encodes the Item into the provided OutputIterator.
+     */
+    template <typename OutputIterator,
+              typename = typename std::iterator_traits<OutputIterator>::iterator_category>
+    void encode(OutputIterator i) const {
+        return encode([&](uint8_t v) { *i++ = v; });
+    }
+
+    /**
+     * Encodes the Item into a new std::vector<uint8_t>.
+     */
+    std::vector<uint8_t> encode() const {
+        std::vector<uint8_t> retval;
+        retval.reserve(encodedSize());
+        encode(std::back_inserter(retval));
+        return retval;
+    }
+
+    /**
+     * Encodes the Item into a new std::string.
+     */
+    std::string toString() const {
+        std::string retval;
+        retval.reserve(encodedSize());
+        encode([&](uint8_t v) { retval.push_back(v); });
+        return retval;
+    }
+
+    /**
+     * Encodes only the header of the Item.
+     */
+    inline uint8_t* encodeHeader(uint64_t addlInfo, uint8_t* pos, const uint8_t* end) const {
+        return ::cppbor::encodeHeader(type(), addlInfo, pos, end);
+    }
+
+    /**
+     * Encodes only the header of the Item.
+     */
+    inline void encodeHeader(uint64_t addlInfo, EncodeCallback encodeCallback) const {
+        ::cppbor::encodeHeader(type(), addlInfo, encodeCallback);
+    }
+};
+
+/**
+ * Int is an abstraction that allows Uint and Nint objects to be manipulated without caring about
+ * the sign.
+ */
+class Int : public Item {
+  public:
+    bool operator==(const Int& other) const& { return value() == other.value(); }
+
+    virtual int64_t value() const = 0;
+
+    const Int* asInt() const override { return this; }
+};
+
+/**
+ * Uint is a concrete Item that implements CBOR major type 0.
+ */
+class Uint : public Int {
+  public:
+    static constexpr MajorType kMajorType = UINT;
+
+    explicit Uint(uint64_t v) : mValue(v) {}
+
+    bool operator==(const Uint& other) const& { return mValue == other.mValue; }
+
+    MajorType type() const override { return kMajorType; }
+    const Uint* asUint() const override { return this; }
+
+    size_t encodedSize() const override { return headerSize(mValue); }
+
+    int64_t value() const override { return mValue; }
+    uint64_t unsignedValue() const { return mValue; }
+
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+        return encodeHeader(mValue, pos, end);
+    }
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(mValue, encodeCallback);
+    }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Uint>(mValue); }
+
+  private:
+    uint64_t mValue;
+};
+
+/**
+ * Nint is a concrete Item that implements CBOR major type 1.
+
+ * Note that it is incapable of expressing the full range of major type 1 values, becaue it can only
+ * express values that fall into the range [std::numeric_limits<int64_t>::min(), -1].  It cannot
+ * express values in the range [std::numeric_limits<int64_t>::min() - 1,
+ * -std::numeric_limits<uint64_t>::max()].
+ */
+class Nint : public Int {
+  public:
+    static constexpr MajorType kMajorType = NINT;
+
+    explicit Nint(int64_t v);
+
+    bool operator==(const Nint& other) const& { return mValue == other.mValue; }
+
+    MajorType type() const override { return kMajorType; }
+    const Nint* asNint() const override { return this; }
+    size_t encodedSize() const override { return headerSize(addlInfo()); }
+
+    int64_t value() const override { return mValue; }
+
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+        return encodeHeader(addlInfo(), pos, end);
+    }
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(addlInfo(), encodeCallback);
+    }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Nint>(mValue); }
+
+  private:
+    uint64_t addlInfo() const { return -1ll - mValue; }
+
+    int64_t mValue;
+};
+
+/**
+ * Bstr is a concrete Item that implements major type 2.
+ */
+class Bstr : public Item {
+  public:
+    static constexpr MajorType kMajorType = BSTR;
+
+    // Construct from a vector
+    explicit Bstr(std::vector<uint8_t> v) : mValue(std::move(v)) {}
+
+    // Construct from a string
+    explicit Bstr(const std::string& v)
+        : mValue(reinterpret_cast<const uint8_t*>(v.data()),
+                 reinterpret_cast<const uint8_t*>(v.data()) + v.size()) {}
+
+    // Construct from a pointer/size pair
+    explicit Bstr(const std::pair<const uint8_t*, size_t>& buf)
+        : mValue(buf.first, buf.first + buf.second) {}
+
+    // Construct from a pair of iterators
+    template <typename I1, typename I2,
+              typename = typename std::iterator_traits<I1>::iterator_category,
+              typename = typename std::iterator_traits<I2>::iterator_category>
+    explicit Bstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {}
+
+    // Construct from an iterator range.
+    template <typename I1, typename I2,
+              typename = typename std::iterator_traits<I1>::iterator_category,
+              typename = typename std::iterator_traits<I2>::iterator_category>
+    Bstr(I1 begin, I2 end) : mValue(begin, end) {}
+
+    bool operator==(const Bstr& other) const& { return mValue == other.mValue; }
+
+    MajorType type() const override { return kMajorType; }
+    const Bstr* asBstr() const override { return this; }
+    size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); }
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(mValue.size(), encodeCallback);
+        encodeValue(encodeCallback);
+    }
+
+    const std::vector<uint8_t>& value() const { return mValue; }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bstr>(mValue); }
+
+  private:
+    void encodeValue(EncodeCallback encodeCallback) const;
+
+    std::vector<uint8_t> mValue;
+};
+
+/**
+ * Bstr is a concrete Item that implements major type 3.
+ */
+class Tstr : public Item {
+  public:
+    static constexpr MajorType kMajorType = TSTR;
+
+    // Construct from a string
+    explicit Tstr(std::string v) : mValue(std::move(v)) {}
+
+    // Construct from a string_view
+    explicit Tstr(const std::string_view& v) : mValue(v) {}
+
+    // Construct from a C string
+    explicit Tstr(const char* v) : mValue(std::string(v)) {}
+
+    // Construct from a pair of iterators
+    template <typename I1, typename I2,
+              typename = typename std::iterator_traits<I1>::iterator_category,
+              typename = typename std::iterator_traits<I2>::iterator_category>
+    explicit Tstr(const std::pair<I1, I2>& pair) : mValue(pair.first, pair.second) {}
+
+    // Construct from an iterator range
+    template <typename I1, typename I2,
+              typename = typename std::iterator_traits<I1>::iterator_category,
+              typename = typename std::iterator_traits<I2>::iterator_category>
+    Tstr(I1 begin, I2 end) : mValue(begin, end) {}
+
+    bool operator==(const Tstr& other) const& { return mValue == other.mValue; }
+
+    MajorType type() const override { return kMajorType; }
+    const Tstr* asTstr() const override { return this; }
+    size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); }
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(mValue.size(), encodeCallback);
+        encodeValue(encodeCallback);
+    }
+
+    const std::string& value() const { return mValue; }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Tstr>(mValue); }
+
+  private:
+    void encodeValue(EncodeCallback encodeCallback) const;
+
+    std::string mValue;
+};
+
+/**
+ * CompoundItem is an abstract Item that provides common functionality for Items that contain other
+ * items, i.e. Arrays (CBOR type 4) and Maps (CBOR type 5).
+ */
+class CompoundItem : public Item {
+  public:
+    bool operator==(const CompoundItem& other) const&;
+
+    virtual size_t size() const { return mEntries.size(); }
+
+    bool isCompound() const override { return true; }
+
+    size_t encodedSize() const override {
+        return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()),
+                               [](size_t sum, auto& entry) { return sum + entry->encodedSize(); });
+    }
+
+    using Item::encode;  // Make base versions visible.
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override;
+    void encode(EncodeCallback encodeCallback) const override;
+
+    virtual uint64_t addlInfo() const = 0;
+
+  protected:
+    std::vector<std::unique_ptr<Item>> mEntries;
+};
+
+/*
+ * Array is a concrete Item that implements CBOR major type 4.
+ *
+ * Note that Arrays are not copyable.  This is because copying them is expensive and making them
+ * move-only ensures that they're never copied accidentally.  If you actually want to copy an Array,
+ * use the clone() method.
+ */
+class Array : public CompoundItem {
+  public:
+    static constexpr MajorType kMajorType = ARRAY;
+
+    Array() = default;
+    Array(const Array& other) = delete;
+    Array(Array&&) = default;
+    Array& operator=(const Array&) = delete;
+    Array& operator=(Array&&) = default;
+
+    /**
+     * Construct an Array from a variable number of arguments of different types.  See
+     * details::makeItem below for details on what types may be provided.  In general, this accepts
+     * all of the types you'd expect and doest the things you'd expect (integral values are addes as
+     * Uint or Nint, std::string and char* are added as Tstr, bools are added as Bool, etc.).
+     */
+    template <typename... Args, typename Enable>
+    Array(Args&&... args);
+
+    /**
+     * Append a single element to the Array, of any compatible type.
+     */
+    template <typename T>
+    Array& add(T&& v) &;
+    template <typename T>
+    Array&& add(T&& v) &&;
+
+    const std::unique_ptr<Item>& operator[](size_t index) const { return mEntries[index]; }
+    std::unique_ptr<Item>& operator[](size_t index) { return mEntries[index]; }
+
+    MajorType type() const override { return kMajorType; }
+    const Array* asArray() const override { return this; }
+
+    virtual std::unique_ptr<Item> clone() const override;
+
+    uint64_t addlInfo() const override { return size(); }
+};
+
+/*
+ * Map is a concrete Item that implements CBOR major type 5.
+ *
+ * Note that Maps are not copyable.  This is because copying them is expensive and making them
+ * move-only ensures that they're never copied accidentally.  If you actually want to copy a
+ * Map, use the clone() method.
+ */
+class Map : public CompoundItem {
+  public:
+    static constexpr MajorType kMajorType = MAP;
+
+    Map() = default;
+    Map(const Map& other) = delete;
+    Map(Map&&) = default;
+    Map& operator=(const Map& other) = delete;
+    Map& operator=(Map&&) = default;
+
+    /**
+     * Construct a Map from a variable number of arguments of different types.  An even number of
+     * arguments must be provided (this is verified statically). See details::makeItem below for
+     * details on what types may be provided.  In general, this accepts all of the types you'd
+     * expect and doest the things you'd expect (integral values are addes as Uint or Nint,
+     * std::string and char* are added as Tstr, bools are added as Bool, etc.).
+     */
+    template <typename... Args, typename Enable>
+    Map(Args&&... args);
+
+    /**
+     * Append a key/value pair to the Map, of any compatible types.
+     */
+    template <typename Key, typename Value>
+    Map& add(Key&& key, Value&& value) &;
+    template <typename Key, typename Value>
+    Map&& add(Key&& key, Value&& value) &&;
+
+    size_t size() const override {
+        assertInvariant();
+        return mEntries.size() / 2;
+    }
+
+    template <typename Key, typename Enable>
+    std::pair<std::unique_ptr<Item>&, bool> get(Key key);
+
+    std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> operator[](
+            size_t index) const {
+        assertInvariant();
+        return {mEntries[index * 2], mEntries[index * 2 + 1]};
+    }
+
+    std::pair<std::unique_ptr<Item>&, std::unique_ptr<Item>&> operator[](size_t index) {
+        assertInvariant();
+        return {mEntries[index * 2], mEntries[index * 2 + 1]};
+    }
+
+    MajorType type() const override { return kMajorType; }
+    const Map* asMap() const override { return this; }
+
+    virtual std::unique_ptr<Item> clone() const override;
+
+    uint64_t addlInfo() const override { return size(); }
+
+  private:
+    void assertInvariant() const;
+};
+
+class Semantic : public CompoundItem {
+  public:
+    static constexpr MajorType kMajorType = SEMANTIC;
+
+    template <typename T>
+    explicit Semantic(uint64_t value, T&& child);
+
+    Semantic(const Semantic& other) = delete;
+    Semantic(Semantic&&) = default;
+    Semantic& operator=(const Semantic& other) = delete;
+    Semantic& operator=(Semantic&&) = default;
+
+    size_t size() const override {
+        assertInvariant();
+        return 1;
+    }
+
+    size_t encodedSize() const override {
+        return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(mValue),
+                               [](size_t sum, auto& entry) { return sum + entry->encodedSize(); });
+    }
+
+    MajorType type() const override { return kMajorType; }
+    const Semantic* asSemantic() const override { return this; }
+
+    const std::unique_ptr<Item>& child() const {
+        assertInvariant();
+        return mEntries[0];
+    }
+
+    std::unique_ptr<Item>& child() {
+        assertInvariant();
+        return mEntries[0];
+    }
+
+    uint64_t value() const { return mValue; }
+
+    uint64_t addlInfo() const override { return value(); }
+
+    virtual std::unique_ptr<Item> clone() const override {
+        assertInvariant();
+        return std::make_unique<Semantic>(mValue, mEntries[0]->clone());
+    }
+
+  protected:
+    Semantic() = default;
+    Semantic(uint64_t value) : mValue(value) {}
+    uint64_t mValue;
+
+  private:
+    void assertInvariant() const;
+};
+
+/**
+ * Simple is abstract Item that implements CBOR major type 7.  It is intended to be subclassed to
+ * create concrete Simple types.  At present only Bool is provided.
+ */
+class Simple : public Item {
+  public:
+    static constexpr MajorType kMajorType = SIMPLE;
+
+    bool operator==(const Simple& other) const&;
+
+    virtual SimpleType simpleType() const = 0;
+    MajorType type() const override { return kMajorType; }
+
+    const Simple* asSimple() const override { return this; }
+
+    virtual const Bool* asBool() const { return nullptr; };
+    virtual const Null* asNull() const { return nullptr; };
+};
+
+/**
+ * Bool is a concrete type that implements CBOR major type 7, with additional item values for TRUE
+ * and FALSE.
+ */
+class Bool : public Simple {
+  public:
+    static constexpr SimpleType kSimpleType = BOOLEAN;
+
+    explicit Bool(bool v) : mValue(v) {}
+
+    bool operator==(const Bool& other) const& { return mValue == other.mValue; }
+
+    SimpleType simpleType() const override { return kSimpleType; }
+    const Bool* asBool() const override { return this; }
+
+    size_t encodedSize() const override { return 1; }
+
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+        return encodeHeader(mValue ? TRUE : FALSE, pos, end);
+    }
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(mValue ? TRUE : FALSE, encodeCallback);
+    }
+
+    bool value() const { return mValue; }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Bool>(mValue); }
+
+  private:
+    bool mValue;
+};
+
+/**
+ * Null is a concrete type that implements CBOR major type 7, with additional item value for NULL
+ */
+class Null : public Simple {
+  public:
+    static constexpr SimpleType kSimpleType = NULL_T;
+
+    explicit Null() {}
+
+    SimpleType simpleType() const override { return kSimpleType; }
+    const Null* asNull() const override { return this; }
+
+    size_t encodedSize() const override { return 1; }
+
+    using Item::encode;
+    uint8_t* encode(uint8_t* pos, const uint8_t* end) const override {
+        return encodeHeader(NULL_V, pos, end);
+    }
+    void encode(EncodeCallback encodeCallback) const override {
+        encodeHeader(NULL_V, encodeCallback);
+    }
+
+    virtual std::unique_ptr<Item> clone() const override { return std::make_unique<Null>(); }
+};
+
+template <typename T>
+std::unique_ptr<T> downcastItem(std::unique_ptr<Item>&& v) {
+    static_assert(std::is_base_of_v<Item, T> && !std::is_abstract_v<T>,
+                  "returned type is not an Item or is an abstract class");
+    if (v && T::kMajorType == v->type()) {
+        if constexpr (std::is_base_of_v<Simple, T>) {
+            if (T::kSimpleType != v->asSimple()->simpleType()) {
+                return nullptr;
+            }
+        }
+        return std::unique_ptr<T>(static_cast<T*>(v.release()));
+    } else {
+        return nullptr;
+    }
+}
+
+/**
+ * Details. Mostly you shouldn't have to look below, except perhaps at the docstring for makeItem.
+ */
+namespace details {
+
+template <typename T, typename V, typename Enable = void>
+struct is_iterator_pair_over : public std::false_type {};
+
+template <typename I1, typename I2, typename V>
+struct is_iterator_pair_over<
+        std::pair<I1, I2>, V,
+        typename std::enable_if_t<std::is_same_v<V, typename std::iterator_traits<I1>::value_type>>>
+    : public std::true_type {};
+
+template <typename T, typename V, typename Enable = void>
+struct is_unique_ptr_of_subclass_of_v : public std::false_type {};
+
+template <typename T, typename P>
+struct is_unique_ptr_of_subclass_of_v<T, std::unique_ptr<P>,
+                                      typename std::enable_if_t<std::is_base_of_v<T, P>>>
+    : public std::true_type {};
+
+/* check if type is one of std::string (1), std::string_view (2), null-terminated char* (3) or pair
+ *     of iterators (4)*/
+template <typename T, typename Enable = void>
+struct is_text_type_v : public std::false_type {};
+
+template <typename T>
+struct is_text_type_v<
+        T, typename std::enable_if_t<
+                   /* case 1 */  //
+                   std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string>
+                   /* case 2 */  //
+                   || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, std::string_view>
+                   /* case 3 */                                                 //
+                   || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, char*>  //
+                   || std::is_same_v<std::remove_cv_t<std::decay_t<T>>, const char*>
+                   /* case 4 */
+                   || details::is_iterator_pair_over<T, char>::value>> : public std::true_type {};
+
+/**
+ * Construct a unique_ptr<Item> from many argument types. Accepts:
+ *
+ * (a) booleans;
+ * (b) integers, all sizes and signs;
+ * (c) text strings, as defined by is_text_type_v above;
+ * (d) byte strings, as std::vector<uint8_t>(d1), pair of iterators (d2) or pair<uint8_t*, size_T>
+ *     (d3); and
+ * (e) Item subclass instances, including Array and Map.  Items may be provided by naked pointer
+ *     (e1), unique_ptr (e2), reference (e3) or value (e3).  If provided by reference or value, will
+ *     be moved if possible.  If provided by pointer, ownership is taken.
+ * (f) null pointer;
+ */
+template <typename T>
+std::unique_ptr<Item> makeItem(T v) {
+    Item* p = nullptr;
+    if constexpr (/* case a */ std::is_same_v<T, bool>) {
+        p = new Bool(v);
+    } else if constexpr (/* case b */ std::is_integral_v<T>) {  // b
+        if (v < 0) {
+            p = new Nint(v);
+        } else {
+            p = new Uint(static_cast<uint64_t>(v));
+        }
+    } else if constexpr (/* case c */  //
+                         details::is_text_type_v<T>::value) {
+        p = new Tstr(v);
+    } else if constexpr (/* case d1 */  //
+                         std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
+                                        std::vector<uint8_t>>
+                         /* case d2 */  //
+                         || details::is_iterator_pair_over<T, uint8_t>::value
+                         /* case d3 */  //
+                         || std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
+                                           std::pair<uint8_t*, size_t>>) {
+        p = new Bstr(v);
+    } else if constexpr (/* case e1 */  //
+                         std::is_pointer_v<T> &&
+                         std::is_base_of_v<Item, std::remove_pointer_t<T>>) {
+        p = v;
+    } else if constexpr (/* case e2 */  //
+                         details::is_unique_ptr_of_subclass_of_v<Item, T>::value) {
+        p = v.release();
+    } else if constexpr (/* case e3 */  //
+                         std::is_base_of_v<Item, T>) {
+        p = new T(std::move(v));
+    } else if constexpr (/* case f */ std::is_null_pointer_v<T>) {
+        p = new Null();
+    } else {
+        // It's odd that this can't be static_assert(false), since it shouldn't be evaluated if one
+        // of the above ifs matches.  But static_assert(false) always triggers.
+        static_assert(std::is_same_v<T, bool>, "makeItem called with unsupported type");
+    }
+    return std::unique_ptr<Item>(p);
+}
+
+}  // namespace details
+
+template <typename... Args,
+          /* Prevent use as copy ctor */ typename = std::enable_if_t<
+                  (sizeof...(Args)) != 1 ||
+                  !(std::is_same_v<Array, std::remove_cv_t<std::remove_reference_t<Args>>> || ...)>>
+Array::Array(Args&&... args) {
+    mEntries.reserve(sizeof...(args));
+    (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...);
+}
+
+template <typename T>
+Array& Array::add(T&& v) & {
+    mEntries.push_back(details::makeItem(std::forward<T>(v)));
+    return *this;
+}
+
+template <typename T>
+Array&& Array::add(T&& v) && {
+    mEntries.push_back(details::makeItem(std::forward<T>(v)));
+    return std::move(*this);
+}
+
+template <typename... Args,
+          /* Prevent use as copy ctor */ typename = std::enable_if_t<(sizeof...(Args)) != 1>>
+Map::Map(Args&&... args) {
+    static_assert((sizeof...(Args)) % 2 == 0, "Map must have an even number of entries");
+    mEntries.reserve(sizeof...(args));
+    (mEntries.push_back(details::makeItem(std::forward<Args>(args))), ...);
+}
+
+template <typename Key, typename Value>
+Map& Map::add(Key&& key, Value&& value) & {
+    mEntries.push_back(details::makeItem(std::forward<Key>(key)));
+    mEntries.push_back(details::makeItem(std::forward<Value>(value)));
+    return *this;
+}
+
+template <typename Key, typename Value>
+Map&& Map::add(Key&& key, Value&& value) && {
+    this->add(std::forward<Key>(key), std::forward<Value>(value));
+    return std::move(*this);
+}
+
+template <typename Key, typename = std::enable_if_t<std::is_integral_v<Key> ||
+                                                    details::is_text_type_v<Key>::value>>
+std::pair<std::unique_ptr<Item>&, bool> Map::get(Key key) {
+    assertInvariant();
+    auto keyItem = details::makeItem(key);
+    for (size_t i = 0; i < mEntries.size(); i += 2) {
+        if (*keyItem == *mEntries[i]) {
+            return {mEntries[i + 1], true};
+        }
+    }
+    return {keyItem, false};
+}
+
+template <typename T>
+Semantic::Semantic(uint64_t value, T&& child) : mValue(value) {
+    mEntries.reserve(1);
+    mEntries.push_back(details::makeItem(std::forward<T>(child)));
+}
+
+}  // namespace cppbor
diff --git a/identity/support/include/cppbor/cppbor_parse.h b/identity/support/include/cppbor/cppbor_parse.h
new file mode 100644
index 0000000..66cd5a3
--- /dev/null
+++ b/identity/support/include/cppbor/cppbor_parse.h
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "cppbor.h"
+
+namespace cppbor {
+
+using ParseResult = std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+                               std::string /* errMsg */>;
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the range [begin, end).
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+ParseResult parse(const uint8_t* begin, const uint8_t* end);
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the byte vector.
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+inline ParseResult parse(const std::vector<uint8_t>& encoding) {
+    return parse(encoding.data(), encoding.data() + encoding.size());
+}
+
+/**
+ * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size).
+ *
+ * Returns a tuple of Item pointer, buffer pointer and error message.  If parsing is successful, the
+ * Item pointer is non-null, the buffer pointer points to the first byte after the
+ * successfully-parsed item and the error message string is empty.  If parsing fails, the Item
+ * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte
+ * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is
+ * too large for the remining buffer, etc.) and the string contains an error message describing the
+ * problem encountered.
+ */
+inline ParseResult parse(const uint8_t* begin, size_t size) {
+    return parse(begin, begin + size);
+}
+
+class ParseClient;
+
+/**
+ * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the
+ * provided ParseClient when elements are found.
+ */
+void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient);
+
+/**
+ * Parse the CBOR data in the vector in streaming fashion, calling methods on the
+ * provided ParseClient when elements are found.
+ */
+inline void parse(const std::vector<uint8_t>& encoding, ParseClient* parseClient) {
+    return parse(encoding.data(), encoding.data() + encoding.size(), parseClient);
+}
+
+/**
+ * A pure interface that callers of the streaming parse functions must implement.
+ */
+class ParseClient {
+  public:
+    virtual ~ParseClient() {}
+
+    /**
+     * Called when an item is found.  The Item pointer points to the found item; use type() and
+     * the appropriate as*() method to examine the value.  hdrBegin points to the first byte of the
+     * header, valueBegin points to the first byte of the value and end points one past the end of
+     * the item.  In the case of header-only items, such as integers, and compound items (ARRAY,
+     * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to
+     * the byte past the header.
+     *
+     * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content.  For
+     * Map and Array items, the size() method will return a correct value, but the index operators
+     * are unsafe, and the object cannot be safely compared with another Array/Map.
+     *
+     * The method returns a ParseClient*.  In most cases "return this;" will be the right answer,
+     * but a different ParseClient may be returned, which the parser will begin using. If the method
+     * returns nullptr, parsing will be aborted immediately.
+     */
+    virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+                              const uint8_t* valueBegin, const uint8_t* end) = 0;
+
+    /**
+     * Called when the end of a compound item (MAP or ARRAY) is found.  The item argument will be
+     * the same one passed to the item() call -- and may be empty if item() moved its value out.
+     * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the
+     * first contained value, and one past the end of the last contained value, respectively.
+     *
+     * Note that the Item will have no content.
+     *
+     * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the
+     * parsing by returning nullptr;
+     */
+    virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+                                 const uint8_t* valueBegin, const uint8_t* end) = 0;
+
+    /**
+     * Called when parsing encounters an error.  position is set to the first unparsed byte (one
+     * past the last successfully-parsed byte) and errorMessage contains an message explaining what
+     * sort of error occurred.
+     */
+    virtual void error(const uint8_t* position, const std::string& errorMessage) = 0;
+};
+
+}  // namespace cppbor
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
new file mode 100644
index 0000000..7d93a4b
--- /dev/null
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -0,0 +1,1815 @@
+/*
+ * Copyright 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IdentityCredentialSupport"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#define _POSIX_C_SOURCE 199309L
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+#include <iomanip>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android {
+namespace hardware {
+namespace identity {
+namespace support {
+
+using ::std::pair;
+using ::std::unique_ptr;
+
+// ---------------------------------------------------------------------------
+// Miscellaneous utilities.
+// ---------------------------------------------------------------------------
+
+void hexdump(const string& name, const vector<uint8_t>& data) {
+    fprintf(stderr, "%s: dumping %zd bytes\n", name.c_str(), data.size());
+    size_t n, m, o;
+    for (n = 0; n < data.size(); n += 16) {
+        fprintf(stderr, "%04zx  ", n);
+        for (m = 0; m < 16 && n + m < data.size(); m++) {
+            fprintf(stderr, "%02x ", data[n + m]);
+        }
+        for (o = m; o < 16; o++) {
+            fprintf(stderr, "   ");
+        }
+        fprintf(stderr, " ");
+        for (m = 0; m < 16 && n + m < data.size(); m++) {
+            int c = data[n + m];
+            fprintf(stderr, "%c", isprint(c) ? c : '.');
+        }
+        fprintf(stderr, "\n");
+    }
+    fprintf(stderr, "\n");
+}
+
+string encodeHex(const uint8_t* data, size_t dataLen) {
+    static const char hexDigits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                                       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+    string ret;
+    ret.resize(dataLen * 2);
+    for (size_t n = 0; n < dataLen; n++) {
+        uint8_t byte = data[n];
+        ret[n * 2 + 0] = hexDigits[byte >> 4];
+        ret[n * 2 + 1] = hexDigits[byte & 0x0f];
+    }
+
+    return ret;
+}
+
+string encodeHex(const string& str) {
+    return encodeHex(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+
+string encodeHex(const vector<uint8_t>& data) {
+    return encodeHex(data.data(), data.size());
+}
+
+// Returns -1 on error, otherwise an integer in the range 0 through 15, both inclusive.
+int parseHexDigit(char hexDigit) {
+    if (hexDigit >= '0' && hexDigit <= '9') {
+        return int(hexDigit) - '0';
+    } else if (hexDigit >= 'a' && hexDigit <= 'f') {
+        return int(hexDigit) - 'a' + 10;
+    } else if (hexDigit >= 'A' && hexDigit <= 'F') {
+        return int(hexDigit) - 'A' + 10;
+    }
+    return -1;
+}
+
+optional<vector<uint8_t>> decodeHex(const string& hexEncoded) {
+    vector<uint8_t> out;
+    size_t hexSize = hexEncoded.size();
+    if ((hexSize & 1) != 0) {
+        LOG(ERROR) << "Size of data cannot be odd";
+        return {};
+    }
+
+    out.resize(hexSize / 2);
+    for (size_t n = 0; n < hexSize / 2; n++) {
+        int upperNibble = parseHexDigit(hexEncoded[n * 2]);
+        int lowerNibble = parseHexDigit(hexEncoded[n * 2 + 1]);
+        if (upperNibble == -1 || lowerNibble == -1) {
+            LOG(ERROR) << "Invalid hex digit at position " << n;
+            return {};
+        }
+        out[n] = (upperNibble << 4) + lowerNibble;
+    }
+
+    return out;
+}
+
+// ---------------------------------------------------------------------------
+// CBOR utilities.
+// ---------------------------------------------------------------------------
+
+static bool cborAreAllElementsNonCompound(const cppbor::CompoundItem* compoundItem) {
+    if (compoundItem->type() == cppbor::ARRAY) {
+        const cppbor::Array* array = compoundItem->asArray();
+        for (size_t n = 0; n < array->size(); n++) {
+            const cppbor::Item* entry = (*array)[n].get();
+            switch (entry->type()) {
+                case cppbor::ARRAY:
+                case cppbor::MAP:
+                    return false;
+                default:
+                    break;
+            }
+        }
+    } else {
+        const cppbor::Map* map = compoundItem->asMap();
+        for (size_t n = 0; n < map->size(); n++) {
+            auto [keyEntry, valueEntry] = (*map)[n];
+            switch (keyEntry->type()) {
+                case cppbor::ARRAY:
+                case cppbor::MAP:
+                    return false;
+                default:
+                    break;
+            }
+            switch (valueEntry->type()) {
+                case cppbor::ARRAY:
+                case cppbor::MAP:
+                    return false;
+                default:
+                    break;
+            }
+        }
+    }
+    return true;
+}
+
+static bool cborPrettyPrintInternal(const cppbor::Item* item, string& out, size_t indent,
+                                    size_t maxBStrSize, const vector<string>& mapKeysToNotPrint) {
+    char buf[80];
+
+    string indentString(indent, ' ');
+
+    switch (item->type()) {
+        case cppbor::UINT:
+            snprintf(buf, sizeof(buf), "%" PRIu64, item->asUint()->unsignedValue());
+            out.append(buf);
+            break;
+
+        case cppbor::NINT:
+            snprintf(buf, sizeof(buf), "%" PRId64, item->asNint()->value());
+            out.append(buf);
+            break;
+
+        case cppbor::BSTR: {
+            const cppbor::Bstr* bstr = item->asBstr();
+            const vector<uint8_t>& value = bstr->value();
+            if (value.size() > maxBStrSize) {
+                unsigned char digest[SHA_DIGEST_LENGTH];
+                SHA_CTX ctx;
+                SHA1_Init(&ctx);
+                SHA1_Update(&ctx, value.data(), value.size());
+                SHA1_Final(digest, &ctx);
+                char buf2[SHA_DIGEST_LENGTH * 2 + 1];
+                for (size_t n = 0; n < SHA_DIGEST_LENGTH; n++) {
+                    snprintf(buf2 + n * 2, 3, "%02x", digest[n]);
+                }
+                snprintf(buf, sizeof(buf), "<bstr size=%zd sha1=%s>", value.size(), buf2);
+                out.append(buf);
+            } else {
+                out.append("{");
+                for (size_t n = 0; n < value.size(); n++) {
+                    if (n > 0) {
+                        out.append(", ");
+                    }
+                    snprintf(buf, sizeof(buf), "0x%02x", value[n]);
+                    out.append(buf);
+                }
+                out.append("}");
+            }
+        } break;
+
+        case cppbor::TSTR:
+            out.append("'");
+            {
+                // TODO: escape "'" characters
+                out.append(item->asTstr()->value().c_str());
+            }
+            out.append("'");
+            break;
+
+        case cppbor::ARRAY: {
+            const cppbor::Array* array = item->asArray();
+            if (array->size() == 0) {
+                out.append("[]");
+            } else if (cborAreAllElementsNonCompound(array)) {
+                out.append("[");
+                for (size_t n = 0; n < array->size(); n++) {
+                    if (!cborPrettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+                                                 mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(", ");
+                }
+                out.append("]");
+            } else {
+                out.append("[\n" + indentString);
+                for (size_t n = 0; n < array->size(); n++) {
+                    out.append("  ");
+                    if (!cborPrettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize,
+                                                 mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(",\n" + indentString);
+                }
+                out.append("]");
+            }
+        } break;
+
+        case cppbor::MAP: {
+            const cppbor::Map* map = item->asMap();
+
+            if (map->size() == 0) {
+                out.append("{}");
+            } else {
+                out.append("{\n" + indentString);
+                for (size_t n = 0; n < map->size(); n++) {
+                    out.append("  ");
+
+                    auto [map_key, map_value] = (*map)[n];
+
+                    if (!cborPrettyPrintInternal(map_key.get(), out, indent + 2, maxBStrSize,
+                                                 mapKeysToNotPrint)) {
+                        return false;
+                    }
+                    out.append(" : ");
+                    if (map_key->type() == cppbor::TSTR &&
+                        std::find(mapKeysToNotPrint.begin(), mapKeysToNotPrint.end(),
+                                  map_key->asTstr()->value()) != mapKeysToNotPrint.end()) {
+                        out.append("<not printed>");
+                    } else {
+                        if (!cborPrettyPrintInternal(map_value.get(), out, indent + 2, maxBStrSize,
+                                                     mapKeysToNotPrint)) {
+                            return false;
+                        }
+                    }
+                    out.append(",\n" + indentString);
+                }
+                out.append("}");
+            }
+        } break;
+
+        case cppbor::SEMANTIC: {
+            const cppbor::Semantic* semantic = item->asSemantic();
+            snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", semantic->value());
+            out.append(buf);
+            cborPrettyPrintInternal(semantic->child().get(), out, indent, maxBStrSize,
+                                    mapKeysToNotPrint);
+        } break;
+
+        case cppbor::SIMPLE:
+            const cppbor::Bool* asBool = item->asSimple()->asBool();
+            const cppbor::Null* asNull = item->asSimple()->asNull();
+            if (asBool != nullptr) {
+                out.append(asBool->value() ? "true" : "false");
+            } else if (asNull != nullptr) {
+                out.append("null");
+            } else {
+                LOG(ERROR) << "Only boolean/null is implemented for SIMPLE";
+                return false;
+            }
+            break;
+    }
+
+    return true;
+}
+
+string cborPrettyPrint(const vector<uint8_t>& encodedCbor, size_t maxBStrSize,
+                       const vector<string>& mapKeysToNotPrint) {
+    auto [item, _, message] = cppbor::parse(encodedCbor);
+    if (item == nullptr) {
+        LOG(ERROR) << "Data to pretty print is not valid CBOR: " << message;
+        return "";
+    }
+
+    string out;
+    cborPrettyPrintInternal(item.get(), out, 0, maxBStrSize, mapKeysToNotPrint);
+    return out;
+}
+
+// ---------------------------------------------------------------------------
+// Crypto functionality / abstraction.
+// ---------------------------------------------------------------------------
+
+struct EVP_CIPHER_CTX_Deleter {
+    void operator()(EVP_CIPHER_CTX* ctx) const {
+        if (ctx != nullptr) {
+            EVP_CIPHER_CTX_free(ctx);
+        }
+    }
+};
+
+using EvpCipherCtxPtr = unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
+
+// bool getRandom(size_t numBytes, vector<uint8_t>& output) {
+optional<vector<uint8_t>> getRandom(size_t numBytes) {
+    vector<uint8_t> output;
+    output.resize(numBytes);
+    if (RAND_bytes(output.data(), numBytes) != 1) {
+        LOG(ERROR) << "RAND_bytes: failed getting " << numBytes << " random";
+        return {};
+    }
+    return output;
+}
+
+optional<vector<uint8_t>> decryptAes128Gcm(const vector<uint8_t>& key,
+                                           const vector<uint8_t>& encryptedData,
+                                           const vector<uint8_t>& additionalAuthenticatedData) {
+    int cipherTextSize = int(encryptedData.size()) - kAesGcmIvSize - kAesGcmTagSize;
+    if (cipherTextSize < 0) {
+        LOG(ERROR) << "encryptedData too small";
+        return {};
+    }
+    unsigned char* nonce = (unsigned char*)encryptedData.data();
+    unsigned char* cipherText = nonce + kAesGcmIvSize;
+    unsigned char* tag = cipherText + cipherTextSize;
+
+    vector<uint8_t> plainText;
+    plainText.resize(cipherTextSize);
+
+    auto ctx = EvpCipherCtxPtr(EVP_CIPHER_CTX_new());
+    if (ctx.get() == nullptr) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_new: failed";
+        return {};
+    }
+
+    if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
+        LOG(ERROR) << "EVP_DecryptInit_ex: failed";
+        return {};
+    }
+
+    if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIvSize, NULL) != 1) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting nonce length";
+        return {};
+    }
+
+    if (EVP_DecryptInit_ex(ctx.get(), NULL, NULL, (unsigned char*)key.data(), nonce) != 1) {
+        LOG(ERROR) << "EVP_DecryptInit_ex: failed";
+        return {};
+    }
+
+    int numWritten;
+    if (additionalAuthenticatedData.size() > 0) {
+        if (EVP_DecryptUpdate(ctx.get(), NULL, &numWritten,
+                              (unsigned char*)additionalAuthenticatedData.data(),
+                              additionalAuthenticatedData.size()) != 1) {
+            LOG(ERROR) << "EVP_DecryptUpdate: failed for additionalAuthenticatedData";
+            return {};
+        }
+        if ((size_t)numWritten != additionalAuthenticatedData.size()) {
+            LOG(ERROR) << "EVP_DecryptUpdate: Unexpected outl=" << numWritten << " (expected "
+                       << additionalAuthenticatedData.size() << ") for additionalAuthenticatedData";
+            return {};
+        }
+    }
+
+    if (EVP_DecryptUpdate(ctx.get(), (unsigned char*)plainText.data(), &numWritten, cipherText,
+                          cipherTextSize) != 1) {
+        LOG(ERROR) << "EVP_DecryptUpdate: failed";
+        return {};
+    }
+    if (numWritten != cipherTextSize) {
+        LOG(ERROR) << "EVP_DecryptUpdate: Unexpected outl=" << numWritten << " (expected "
+                   << cipherTextSize << ")";
+        return {};
+    }
+
+    if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag)) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting expected tag";
+        return {};
+    }
+
+    int ret = EVP_DecryptFinal_ex(ctx.get(), (unsigned char*)plainText.data() + numWritten,
+                                  &numWritten);
+    if (ret != 1) {
+        LOG(ERROR) << "EVP_DecryptFinal_ex: failed";
+        return {};
+    }
+    if (numWritten != 0) {
+        LOG(ERROR) << "EVP_DecryptFinal_ex: Unexpected non-zero outl=" << numWritten;
+        return {};
+    }
+
+    return plainText;
+}
+
+optional<vector<uint8_t>> encryptAes128Gcm(const vector<uint8_t>& key, const vector<uint8_t>& nonce,
+                                           const vector<uint8_t>& data,
+                                           const vector<uint8_t>& additionalAuthenticatedData) {
+    if (key.size() != kAes128GcmKeySize) {
+        LOG(ERROR) << "key is not kAes128GcmKeySize bytes";
+        return {};
+    }
+    if (nonce.size() != kAesGcmIvSize) {
+        LOG(ERROR) << "nonce is not kAesGcmIvSize bytes";
+        return {};
+    }
+
+    // The result is the nonce (kAesGcmIvSize bytes), the ciphertext, and
+    // finally the tag (kAesGcmTagSize bytes).
+    vector<uint8_t> encryptedData;
+    encryptedData.resize(data.size() + kAesGcmIvSize + kAesGcmTagSize);
+    unsigned char* noncePtr = (unsigned char*)encryptedData.data();
+    unsigned char* cipherText = noncePtr + kAesGcmIvSize;
+    unsigned char* tag = cipherText + data.size();
+    memcpy(noncePtr, nonce.data(), kAesGcmIvSize);
+
+    auto ctx = EvpCipherCtxPtr(EVP_CIPHER_CTX_new());
+    if (ctx.get() == nullptr) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_new: failed";
+        return {};
+    }
+
+    if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) {
+        LOG(ERROR) << "EVP_EncryptInit_ex: failed";
+        return {};
+    }
+
+    if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIvSize, NULL) != 1) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed setting nonce length";
+        return {};
+    }
+
+    if (EVP_EncryptInit_ex(ctx.get(), NULL, NULL, (unsigned char*)key.data(),
+                           (unsigned char*)nonce.data()) != 1) {
+        LOG(ERROR) << "EVP_EncryptInit_ex: failed";
+        return {};
+    }
+
+    int numWritten;
+    if (additionalAuthenticatedData.size() > 0) {
+        if (EVP_EncryptUpdate(ctx.get(), NULL, &numWritten,
+                              (unsigned char*)additionalAuthenticatedData.data(),
+                              additionalAuthenticatedData.size()) != 1) {
+            LOG(ERROR) << "EVP_EncryptUpdate: failed for additionalAuthenticatedData";
+            return {};
+        }
+        if ((size_t)numWritten != additionalAuthenticatedData.size()) {
+            LOG(ERROR) << "EVP_EncryptUpdate: Unexpected outl=" << numWritten << " (expected "
+                       << additionalAuthenticatedData.size() << ") for additionalAuthenticatedData";
+            return {};
+        }
+    }
+
+    if (data.size() > 0) {
+        if (EVP_EncryptUpdate(ctx.get(), cipherText, &numWritten, (unsigned char*)data.data(),
+                              data.size()) != 1) {
+            LOG(ERROR) << "EVP_EncryptUpdate: failed";
+            return {};
+        }
+        if ((size_t)numWritten != data.size()) {
+            LOG(ERROR) << "EVP_EncryptUpdate: Unexpected outl=" << numWritten << " (expected "
+                       << data.size() << ")";
+            return {};
+        }
+    }
+
+    if (EVP_EncryptFinal_ex(ctx.get(), cipherText + numWritten, &numWritten) != 1) {
+        LOG(ERROR) << "EVP_EncryptFinal_ex: failed";
+        return {};
+    }
+    if (numWritten != 0) {
+        LOG(ERROR) << "EVP_EncryptFinal_ex: Unexpected non-zero outl=" << numWritten;
+        return {};
+    }
+
+    if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, tag) != 1) {
+        LOG(ERROR) << "EVP_CIPHER_CTX_ctrl: failed getting tag";
+        return {};
+    }
+
+    return encryptedData;
+}
+
+struct EC_KEY_Deleter {
+    void operator()(EC_KEY* key) const {
+        if (key != nullptr) {
+            EC_KEY_free(key);
+        }
+    }
+};
+using EC_KEY_Ptr = unique_ptr<EC_KEY, EC_KEY_Deleter>;
+
+struct EVP_PKEY_Deleter {
+    void operator()(EVP_PKEY* key) const {
+        if (key != nullptr) {
+            EVP_PKEY_free(key);
+        }
+    }
+};
+using EVP_PKEY_Ptr = unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
+
+struct EVP_PKEY_CTX_Deleter {
+    void operator()(EVP_PKEY_CTX* ctx) const {
+        if (ctx != nullptr) {
+            EVP_PKEY_CTX_free(ctx);
+        }
+    }
+};
+using EVP_PKEY_CTX_Ptr = unique_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_Deleter>;
+
+struct EC_GROUP_Deleter {
+    void operator()(EC_GROUP* group) const {
+        if (group != nullptr) {
+            EC_GROUP_free(group);
+        }
+    }
+};
+using EC_GROUP_Ptr = unique_ptr<EC_GROUP, EC_GROUP_Deleter>;
+
+struct EC_POINT_Deleter {
+    void operator()(EC_POINT* point) const {
+        if (point != nullptr) {
+            EC_POINT_free(point);
+        }
+    }
+};
+
+using EC_POINT_Ptr = unique_ptr<EC_POINT, EC_POINT_Deleter>;
+
+struct ECDSA_SIG_Deleter {
+    void operator()(ECDSA_SIG* sig) const {
+        if (sig != nullptr) {
+            ECDSA_SIG_free(sig);
+        }
+    }
+};
+using ECDSA_SIG_Ptr = unique_ptr<ECDSA_SIG, ECDSA_SIG_Deleter>;
+
+struct X509_Deleter {
+    void operator()(X509* x509) const {
+        if (x509 != nullptr) {
+            X509_free(x509);
+        }
+    }
+};
+using X509_Ptr = unique_ptr<X509, X509_Deleter>;
+
+struct PKCS12_Deleter {
+    void operator()(PKCS12* pkcs12) const {
+        if (pkcs12 != nullptr) {
+            PKCS12_free(pkcs12);
+        }
+    }
+};
+using PKCS12_Ptr = unique_ptr<PKCS12, PKCS12_Deleter>;
+
+struct BIGNUM_Deleter {
+    void operator()(BIGNUM* bignum) const {
+        if (bignum != nullptr) {
+            BN_free(bignum);
+        }
+    }
+};
+using BIGNUM_Ptr = unique_ptr<BIGNUM, BIGNUM_Deleter>;
+
+struct ASN1_INTEGER_Deleter {
+    void operator()(ASN1_INTEGER* value) const {
+        if (value != nullptr) {
+            ASN1_INTEGER_free(value);
+        }
+    }
+};
+using ASN1_INTEGER_Ptr = unique_ptr<ASN1_INTEGER, ASN1_INTEGER_Deleter>;
+
+struct ASN1_TIME_Deleter {
+    void operator()(ASN1_TIME* value) const {
+        if (value != nullptr) {
+            ASN1_TIME_free(value);
+        }
+    }
+};
+using ASN1_TIME_Ptr = unique_ptr<ASN1_TIME, ASN1_TIME_Deleter>;
+
+struct X509_NAME_Deleter {
+    void operator()(X509_NAME* value) const {
+        if (value != nullptr) {
+            X509_NAME_free(value);
+        }
+    }
+};
+using X509_NAME_Ptr = unique_ptr<X509_NAME, X509_NAME_Deleter>;
+
+vector<uint8_t> certificateChainJoin(const vector<vector<uint8_t>>& certificateChain) {
+    vector<uint8_t> ret;
+    for (const vector<uint8_t>& certificate : certificateChain) {
+        ret.insert(ret.end(), certificate.begin(), certificate.end());
+    }
+    return ret;
+}
+
+optional<vector<vector<uint8_t>>> certificateChainSplit(const vector<uint8_t>& certificateChain) {
+    const unsigned char* pStart = (unsigned char*)certificateChain.data();
+    const unsigned char* p = pStart;
+    const unsigned char* pEnd = p + certificateChain.size();
+    vector<vector<uint8_t>> certificates;
+    while (p < pEnd) {
+        size_t begin = p - pStart;
+        auto x509 = X509_Ptr(d2i_X509(nullptr, &p, pEnd - p));
+        size_t next = p - pStart;
+        if (x509 == nullptr) {
+            LOG(ERROR) << "Error parsing X509 certificate";
+            return {};
+        }
+        vector<uint8_t> cert =
+                vector<uint8_t>(certificateChain.begin() + begin, certificateChain.begin() + next);
+        certificates.push_back(std::move(cert));
+    }
+    return certificates;
+}
+
+static bool parseX509Certificates(const vector<uint8_t>& certificateChain,
+                                  vector<X509_Ptr>& parsedCertificates) {
+    const unsigned char* p = (unsigned char*)certificateChain.data();
+    const unsigned char* pEnd = p + certificateChain.size();
+    parsedCertificates.resize(0);
+    while (p < pEnd) {
+        auto x509 = X509_Ptr(d2i_X509(nullptr, &p, pEnd - p));
+        if (x509 == nullptr) {
+            LOG(ERROR) << "Error parsing X509 certificate";
+            return false;
+        }
+        parsedCertificates.push_back(std::move(x509));
+    }
+    return true;
+}
+
+// TODO: Right now the only check we perform is to check that each certificate
+//       is signed by its successor. We should - but currently don't - also check
+//       things like valid dates etc.
+//
+//       It would be nice to use X509_verify_cert() instead of doing our own thing.
+//
+bool certificateChainValidate(const vector<uint8_t>& certificateChain) {
+    vector<X509_Ptr> certs;
+
+    if (!parseX509Certificates(certificateChain, certs)) {
+        LOG(ERROR) << "Error parsing X509 certificates";
+        return false;
+    }
+
+    if (certs.size() == 1) {
+        return true;
+    }
+
+    for (size_t n = 1; n < certs.size(); n++) {
+        const X509_Ptr& keyCert = certs[n - 1];
+        const X509_Ptr& signingCert = certs[n];
+        EVP_PKEY_Ptr signingPubkey(X509_get_pubkey(signingCert.get()));
+        if (X509_verify(keyCert.get(), signingPubkey.get()) != 1) {
+            LOG(ERROR) << "Error validating cert at index " << n - 1
+                       << " is signed by its successor";
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool checkEcDsaSignature(const vector<uint8_t>& digest, const vector<uint8_t>& signature,
+                         const vector<uint8_t>& publicKey) {
+    const unsigned char* p = (unsigned char*)signature.data();
+    auto sig = ECDSA_SIG_Ptr(d2i_ECDSA_SIG(nullptr, &p, signature.size()));
+    if (sig.get() == nullptr) {
+        LOG(ERROR) << "Error decoding DER encoded signature";
+        return false;
+    }
+
+    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+    if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+        1) {
+        LOG(ERROR) << "Error decoding publicKey";
+        return false;
+    }
+    auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+        LOG(ERROR) << "Memory allocation failed";
+        return false;
+    }
+    if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+        LOG(ERROR) << "Error setting group";
+        return false;
+    }
+    if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+        LOG(ERROR) << "Error setting point";
+        return false;
+    }
+    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+        LOG(ERROR) << "Error setting key";
+        return false;
+    }
+
+    int rc = ECDSA_do_verify(digest.data(), digest.size(), sig.get(), ecKey.get());
+    if (rc != 1) {
+        LOG(ERROR) << "Error verifying signature (rc=" << rc << ")";
+        return false;
+    }
+
+    return true;
+}
+
+vector<uint8_t> sha256(const vector<uint8_t>& data) {
+    vector<uint8_t> ret;
+    ret.resize(SHA256_DIGEST_LENGTH);
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+    SHA256_Update(&ctx, data.data(), data.size());
+    SHA256_Final((unsigned char*)ret.data(), &ctx);
+    return ret;
+}
+
+optional<vector<uint8_t>> signEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data) {
+    auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr));
+    if (bn.get() == nullptr) {
+        LOG(ERROR) << "Error creating BIGNUM";
+        return {};
+    }
+
+    auto ec_key = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+    if (EC_KEY_set_private_key(ec_key.get(), bn.get()) != 1) {
+        LOG(ERROR) << "Error setting private key from BIGNUM";
+        return {};
+    }
+
+    auto digest = sha256(data);
+    ECDSA_SIG* sig = ECDSA_do_sign(digest.data(), digest.size(), ec_key.get());
+    if (sig == nullptr) {
+        LOG(ERROR) << "Error signing digest";
+        return {};
+    }
+    size_t len = i2d_ECDSA_SIG(sig, nullptr);
+    vector<uint8_t> signature;
+    signature.resize(len);
+    unsigned char* p = (unsigned char*)signature.data();
+    i2d_ECDSA_SIG(sig, &p);
+    ECDSA_SIG_free(sig);
+    return signature;
+}
+
+optional<vector<uint8_t>> hmacSha256(const vector<uint8_t>& key, const vector<uint8_t>& data) {
+    HMAC_CTX ctx;
+    HMAC_CTX_init(&ctx);
+    if (HMAC_Init_ex(&ctx, key.data(), key.size(), EVP_sha256(), nullptr /* impl */) != 1) {
+        LOG(ERROR) << "Error initializing HMAC_CTX";
+        return {};
+    }
+    if (HMAC_Update(&ctx, data.data(), data.size()) != 1) {
+        LOG(ERROR) << "Error updating HMAC_CTX";
+        return {};
+    }
+    vector<uint8_t> hmac;
+    hmac.resize(32);
+    unsigned int size = 0;
+    if (HMAC_Final(&ctx, hmac.data(), &size) != 1) {
+        LOG(ERROR) << "Error finalizing HMAC_CTX";
+        return {};
+    }
+    if (size != 32) {
+        LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+        return {};
+    }
+    return hmac;
+}
+
+optional<vector<uint8_t>> createEcKeyPair() {
+    auto ec_key = EC_KEY_Ptr(EC_KEY_new());
+    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    if (ec_key.get() == nullptr || pkey.get() == nullptr) {
+        LOG(ERROR) << "Memory allocation failed";
+        return {};
+    }
+
+    if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
+        EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
+        LOG(ERROR) << "Error generating key";
+        return {};
+    }
+
+    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.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>> ecKeyPairGetPublicKey(const vector<uint8_t>& keyPair) {
+    const unsigned char* p = (const unsigned char*)keyPair.data();
+    auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+    if (pkey.get() == nullptr) {
+        LOG(ERROR) << "Error parsing keyPair";
+        return {};
+    }
+
+    auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+    if (ecKey.get() == nullptr) {
+        LOG(ERROR) << "Failed getting EC key";
+        return {};
+    }
+
+    auto ecGroup = EC_KEY_get0_group(ecKey.get());
+    auto ecPoint = EC_KEY_get0_public_key(ecKey.get());
+    int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0,
+                                  nullptr);
+    if (size == 0) {
+        LOG(ERROR) << "Error generating public key encoding";
+        return {};
+    }
+
+    vector<uint8_t> publicKey;
+    publicKey.resize(size);
+    EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(),
+                       publicKey.size(), nullptr);
+    return publicKey;
+}
+
+optional<vector<uint8_t>> ecKeyPairGetPrivateKey(const vector<uint8_t>& keyPair) {
+    const unsigned char* p = (const unsigned char*)keyPair.data();
+    auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+    if (pkey.get() == nullptr) {
+        LOG(ERROR) << "Error parsing keyPair";
+        return {};
+    }
+
+    auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+    if (ecKey.get() == nullptr) {
+        LOG(ERROR) << "Failed getting EC key";
+        return {};
+    }
+
+    const BIGNUM* bignum = EC_KEY_get0_private_key(ecKey.get());
+    if (bignum == nullptr) {
+        LOG(ERROR) << "Error getting bignum from private key";
+        return {};
+    }
+    vector<uint8_t> privateKey;
+    privateKey.resize(BN_num_bytes(bignum));
+    BN_bn2bin(bignum, privateKey.data());
+    return privateKey;
+}
+
+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,
+                                             time_t validityNotAfter) {
+    const unsigned char* p = (const unsigned char*)keyPair.data();
+    auto pkey = EVP_PKEY_Ptr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, keyPair.size()));
+    if (pkey.get() == nullptr) {
+        LOG(ERROR) << "Error parsing keyPair";
+        return {};
+    }
+
+    auto x509 = X509_Ptr(X509_new());
+    if (!x509.get()) {
+        LOG(ERROR) << "Error creating X509 certificate";
+        return {};
+    }
+
+    if (!X509_set_version(x509.get(), 2 /* version 3, but zero-based */)) {
+        LOG(ERROR) << "Error setting version to 3";
+        return {};
+    }
+
+    if (X509_set_pubkey(x509.get(), pkey.get()) != 1) {
+        LOG(ERROR) << "Error setting public key";
+        return {};
+    }
+
+    BIGNUM* bignumSerial = nullptr;
+    if (BN_dec2bn(&bignumSerial, serialDecimal.c_str()) == 0) {
+        LOG(ERROR) << "Error parsing serial";
+        return {};
+    }
+    auto bignumSerialPtr = BIGNUM_Ptr(bignumSerial);
+    auto asnSerial = ASN1_INTEGER_Ptr(BN_to_ASN1_INTEGER(bignumSerial, nullptr));
+    if (X509_set_serialNumber(x509.get(), asnSerial.get()) != 1) {
+        LOG(ERROR) << "Error setting serial";
+        return {};
+    }
+
+    auto x509Issuer = X509_NAME_Ptr(X509_NAME_new());
+    if (x509Issuer.get() == nullptr ||
+        X509_NAME_add_entry_by_txt(x509Issuer.get(), "CN", MBSTRING_ASC,
+                                   (const uint8_t*)issuer.c_str(), issuer.size(), -1 /* loc */,
+                                   0 /* set */) != 1 ||
+        X509_set_issuer_name(x509.get(), x509Issuer.get()) != 1) {
+        LOG(ERROR) << "Error setting issuer";
+        return {};
+    }
+
+    auto x509Subject = X509_NAME_Ptr(X509_NAME_new());
+    if (x509Subject.get() == nullptr ||
+        X509_NAME_add_entry_by_txt(x509Subject.get(), "CN", MBSTRING_ASC,
+                                   (const uint8_t*)subject.c_str(), subject.size(), -1 /* loc */,
+                                   0 /* set */) != 1 ||
+        X509_set_subject_name(x509.get(), x509Subject.get()) != 1) {
+        LOG(ERROR) << "Error setting subject";
+        return {};
+    }
+
+    auto asnNotBefore = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotBefore));
+    if (asnNotBefore.get() == nullptr || X509_set_notBefore(x509.get(), asnNotBefore.get()) != 1) {
+        LOG(ERROR) << "Error setting notBefore";
+        return {};
+    }
+
+    auto asnNotAfter = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotAfter));
+    if (asnNotAfter.get() == nullptr || X509_set_notAfter(x509.get(), asnNotAfter.get()) != 1) {
+        LOG(ERROR) << "Error setting notAfter";
+        return {};
+    }
+
+    if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) == 0) {
+        LOG(ERROR) << "Error signing X509 certificate";
+        return {};
+    }
+
+    // Ideally we wouldn't encrypt it (we're only using this function for
+    // sending a key-pair over binder to the Android app) but BoringSSL does not
+    // support this: from pkcs8_x509.c in BoringSSL: "In OpenSSL, -1 here means
+    // to use no encryption, which we do not currently support."
+    //
+    // Passing nullptr as |pass|, though, means "no password". So we'll do that.
+    // Compare with the receiving side - CredstoreIdentityCredential.java - where
+    // an empty char[] is passed as the password.
+    //
+    auto pkcs12 = PKCS12_Ptr(PKCS12_create(nullptr, name.c_str(), pkey.get(), x509.get(),
+                                           nullptr,  // ca
+                                           0,        // nid_key
+                                           0,        // nid_cert
+                                           0,        // iter,
+                                           0,        // mac_iter,
+                                           0));      // keytype
+    if (pkcs12.get() == nullptr) {
+        char buf[128];
+        long errCode = ERR_get_error();
+        ERR_error_string_n(errCode, buf, sizeof buf);
+        LOG(ERROR) << "Error creating PKCS12, code " << errCode << ": " << buf;
+        return {};
+    }
+
+    unsigned char* buffer = nullptr;
+    int length = i2d_PKCS12(pkcs12.get(), &buffer);
+    if (length < 0) {
+        LOG(ERROR) << "Error encoding PKCS12";
+        return {};
+    }
+    vector<uint8_t> pkcs12Bytes;
+    pkcs12Bytes.resize(length);
+    memcpy(pkcs12Bytes.data(), buffer, length);
+    OPENSSL_free(buffer);
+
+    return pkcs12Bytes;
+}
+
+optional<vector<uint8_t>> ecPublicKeyGenerateCertificate(
+        const vector<uint8_t>& publicKey, const vector<uint8_t>& signingKey,
+        const string& serialDecimal, const string& issuer, const string& subject,
+        time_t validityNotBefore, time_t validityNotAfter) {
+    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+    if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+        1) {
+        LOG(ERROR) << "Error decoding publicKey";
+        return {};
+    }
+    auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+        LOG(ERROR) << "Memory allocation failed";
+        return {};
+    }
+    if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+        LOG(ERROR) << "Error setting group";
+        return {};
+    }
+    if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+        LOG(ERROR) << "Error setting point";
+        return {};
+    }
+    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+        LOG(ERROR) << "Error setting key";
+        return {};
+    }
+
+    auto bn = BIGNUM_Ptr(BN_bin2bn(signingKey.data(), signingKey.size(), nullptr));
+    if (bn.get() == nullptr) {
+        LOG(ERROR) << "Error creating BIGNUM for private key";
+        return {};
+    }
+    auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+    if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) {
+        LOG(ERROR) << "Error setting private key from BIGNUM";
+        return {};
+    }
+    auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) {
+        LOG(ERROR) << "Error setting private key";
+        return {};
+    }
+
+    auto x509 = X509_Ptr(X509_new());
+    if (!x509.get()) {
+        LOG(ERROR) << "Error creating X509 certificate";
+        return {};
+    }
+
+    if (!X509_set_version(x509.get(), 2 /* version 3, but zero-based */)) {
+        LOG(ERROR) << "Error setting version to 3";
+        return {};
+    }
+
+    if (X509_set_pubkey(x509.get(), pkey.get()) != 1) {
+        LOG(ERROR) << "Error setting public key";
+        return {};
+    }
+
+    BIGNUM* bignumSerial = nullptr;
+    if (BN_dec2bn(&bignumSerial, serialDecimal.c_str()) == 0) {
+        LOG(ERROR) << "Error parsing serial";
+        return {};
+    }
+    auto bignumSerialPtr = BIGNUM_Ptr(bignumSerial);
+    auto asnSerial = ASN1_INTEGER_Ptr(BN_to_ASN1_INTEGER(bignumSerial, nullptr));
+    if (X509_set_serialNumber(x509.get(), asnSerial.get()) != 1) {
+        LOG(ERROR) << "Error setting serial";
+        return {};
+    }
+
+    auto x509Issuer = X509_NAME_Ptr(X509_NAME_new());
+    if (x509Issuer.get() == nullptr ||
+        X509_NAME_add_entry_by_txt(x509Issuer.get(), "CN", MBSTRING_ASC,
+                                   (const uint8_t*)issuer.c_str(), issuer.size(), -1 /* loc */,
+                                   0 /* set */) != 1 ||
+        X509_set_issuer_name(x509.get(), x509Issuer.get()) != 1) {
+        LOG(ERROR) << "Error setting issuer";
+        return {};
+    }
+
+    auto x509Subject = X509_NAME_Ptr(X509_NAME_new());
+    if (x509Subject.get() == nullptr ||
+        X509_NAME_add_entry_by_txt(x509Subject.get(), "CN", MBSTRING_ASC,
+                                   (const uint8_t*)subject.c_str(), subject.size(), -1 /* loc */,
+                                   0 /* set */) != 1 ||
+        X509_set_subject_name(x509.get(), x509Subject.get()) != 1) {
+        LOG(ERROR) << "Error setting subject";
+        return {};
+    }
+
+    auto asnNotBefore = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotBefore));
+    if (asnNotBefore.get() == nullptr || X509_set_notBefore(x509.get(), asnNotBefore.get()) != 1) {
+        LOG(ERROR) << "Error setting notBefore";
+        return {};
+    }
+
+    auto asnNotAfter = ASN1_TIME_Ptr(ASN1_TIME_set(nullptr, validityNotAfter));
+    if (asnNotAfter.get() == nullptr || X509_set_notAfter(x509.get(), asnNotAfter.get()) != 1) {
+        LOG(ERROR) << "Error setting notAfter";
+        return {};
+    }
+
+    if (X509_sign(x509.get(), privPkey.get(), EVP_sha256()) == 0) {
+        LOG(ERROR) << "Error signing X509 certificate";
+        return {};
+    }
+
+    unsigned char* buffer = nullptr;
+    int length = i2d_X509(x509.get(), &buffer);
+    if (length < 0) {
+        LOG(ERROR) << "Error DER encoding X509 certificate";
+        return {};
+    }
+
+    vector<uint8_t> certificate;
+    certificate.resize(length);
+    memcpy(certificate.data(), buffer, length);
+    OPENSSL_free(buffer);
+    return certificate;
+}
+
+optional<vector<uint8_t>> ecdh(const vector<uint8_t>& publicKey,
+                               const vector<uint8_t>& privateKey) {
+    auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    auto point = EC_POINT_Ptr(EC_POINT_new(group.get()));
+    if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) !=
+        1) {
+        LOG(ERROR) << "Error decoding publicKey";
+        return {};
+    }
+    auto ecKey = EC_KEY_Ptr(EC_KEY_new());
+    auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    if (ecKey.get() == nullptr || pkey.get() == nullptr) {
+        LOG(ERROR) << "Memory allocation failed";
+        return {};
+    }
+    if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) {
+        LOG(ERROR) << "Error setting group";
+        return {};
+    }
+    if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) {
+        LOG(ERROR) << "Error setting point";
+        return {};
+    }
+    if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+        LOG(ERROR) << "Error setting key";
+        return {};
+    }
+
+    auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr));
+    if (bn.get() == nullptr) {
+        LOG(ERROR) << "Error creating BIGNUM for private key";
+        return {};
+    }
+    auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+    if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) {
+        LOG(ERROR) << "Error setting private key from BIGNUM";
+        return {};
+    }
+    auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new());
+    if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) {
+        LOG(ERROR) << "Error setting private key";
+        return {};
+    }
+
+    auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(privPkey.get(), NULL));
+    if (ctx.get() == nullptr) {
+        LOG(ERROR) << "Error creating context";
+        return {};
+    }
+
+    if (EVP_PKEY_derive_init(ctx.get()) != 1) {
+        LOG(ERROR) << "Error initializing context";
+        return {};
+    }
+
+    if (EVP_PKEY_derive_set_peer(ctx.get(), pkey.get()) != 1) {
+        LOG(ERROR) << "Error setting peer";
+        return {};
+    }
+
+    /* Determine buffer length for shared secret */
+    size_t secretLen = 0;
+    if (EVP_PKEY_derive(ctx.get(), NULL, &secretLen) != 1) {
+        LOG(ERROR) << "Error determing length of shared secret";
+        return {};
+    }
+    vector<uint8_t> sharedSecret;
+    sharedSecret.resize(secretLen);
+
+    if (EVP_PKEY_derive(ctx.get(), sharedSecret.data(), &secretLen) != 1) {
+        LOG(ERROR) << "Error deriving shared secret";
+        return {};
+    }
+    return sharedSecret;
+}
+
+optional<vector<uint8_t>> hkdf(const vector<uint8_t>& sharedSecret, const vector<uint8_t>& salt,
+                               const vector<uint8_t>& info, size_t size) {
+    vector<uint8_t> derivedKey;
+    derivedKey.resize(size);
+    if (HKDF(derivedKey.data(), derivedKey.size(), EVP_sha256(), sharedSecret.data(),
+             sharedSecret.size(), salt.data(), salt.size(), info.data(), info.size()) != 1) {
+        LOG(ERROR) << "Error deriving key";
+        return {};
+    }
+    return derivedKey;
+}
+
+void removeLeadingZeroes(vector<uint8_t>& vec) {
+    while (vec.size() >= 1 && vec[0] == 0x00) {
+        vec.erase(vec.begin());
+    }
+}
+
+tuple<bool, vector<uint8_t>, vector<uint8_t>> ecPublicKeyGetXandY(
+        const vector<uint8_t>& publicKey) {
+    if (publicKey.size() != 65 || publicKey[0] != 0x04) {
+        LOG(ERROR) << "publicKey is not in the expected format";
+        return std::make_tuple(false, vector<uint8_t>(), vector<uint8_t>());
+    }
+    vector<uint8_t> x, y;
+    x.resize(32);
+    y.resize(32);
+    memcpy(x.data(), publicKey.data() + 1, 32);
+    memcpy(y.data(), publicKey.data() + 33, 32);
+
+    removeLeadingZeroes(x);
+    removeLeadingZeroes(y);
+
+    return std::make_tuple(true, x, y);
+}
+
+optional<vector<uint8_t>> certificateChainGetTopMostKey(const vector<uint8_t>& certificateChain) {
+    vector<X509_Ptr> certs;
+    if (!parseX509Certificates(certificateChain, certs)) {
+        return {};
+    }
+    if (certs.size() < 1) {
+        LOG(ERROR) << "No certificates in chain";
+        return {};
+    }
+
+    int algoId = OBJ_obj2nid(certs[0]->cert_info->key->algor->algorithm);
+    if (algoId != NID_X9_62_id_ecPublicKey) {
+        LOG(ERROR) << "Expected NID_X9_62_id_ecPublicKey, got " << OBJ_nid2ln(algoId);
+        return {};
+    }
+
+    auto pkey = EVP_PKEY_Ptr(X509_get_pubkey(certs[0].get()));
+    if (pkey.get() == nullptr) {
+        LOG(ERROR) << "No public key";
+        return {};
+    }
+
+    auto ecKey = EC_KEY_Ptr(EVP_PKEY_get1_EC_KEY(pkey.get()));
+    if (ecKey.get() == nullptr) {
+        LOG(ERROR) << "Failed getting EC key";
+        return {};
+    }
+
+    auto ecGroup = EC_KEY_get0_group(ecKey.get());
+    auto ecPoint = EC_KEY_get0_public_key(ecKey.get());
+    int size = EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0,
+                                  nullptr);
+    if (size == 0) {
+        LOG(ERROR) << "Error generating public key encoding";
+        return {};
+    }
+    vector<uint8_t> publicKey;
+    publicKey.resize(size);
+    EC_POINT_point2oct(ecGroup, ecPoint, POINT_CONVERSION_UNCOMPRESSED, publicKey.data(),
+                       publicKey.size(), nullptr);
+    return publicKey;
+}
+
+// ---------------------------------------------------------------------------
+// COSE Utility Functions
+// ---------------------------------------------------------------------------
+
+vector<uint8_t> coseBuildToBeSigned(const vector<uint8_t>& encodedProtectedHeaders,
+                                    const vector<uint8_t>& data,
+                                    const vector<uint8_t>& detachedContent) {
+    cppbor::Array sigStructure;
+    sigStructure.add("Signature1");
+    sigStructure.add(encodedProtectedHeaders);
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    vector<uint8_t> emptyExternalAad;
+    sigStructure.add(emptyExternalAad);
+
+    // Next field is the payload, independently of how it's transported (RFC
+    // 8152 section 4.4). Since our API specifies only one of |data| and
+    // |detachedContent| can be non-empty, it's simply just the non-empty one.
+    if (data.size() > 0) {
+        sigStructure.add(data);
+    } else {
+        sigStructure.add(detachedContent);
+    }
+    return sigStructure.encode();
+}
+
+vector<uint8_t> coseEncodeHeaders(const cppbor::Map& protectedHeaders) {
+    if (protectedHeaders.size() == 0) {
+        cppbor::Bstr emptyBstr(vector<uint8_t>({}));
+        return emptyBstr.encode();
+    }
+    return protectedHeaders.encode();
+}
+
+// From https://tools.ietf.org/html/rfc8152
+const int COSE_LABEL_ALG = 1;
+const int COSE_LABEL_X5CHAIN = 33;  // temporary identifier
+
+// From "COSE Algorithms" registry
+const int COSE_ALG_ECDSA_256 = -7;
+const int COSE_ALG_HMAC_256_256 = 5;
+
+bool ecdsaSignatureCoseToDer(const vector<uint8_t>& ecdsaCoseSignature,
+                             vector<uint8_t>& ecdsaDerSignature) {
+    if (ecdsaCoseSignature.size() != 64) {
+        LOG(ERROR) << "COSE signature length is " << ecdsaCoseSignature.size() << ", expected 64";
+        return false;
+    }
+
+    auto rBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data(), 32, nullptr));
+    if (rBn.get() == nullptr) {
+        LOG(ERROR) << "Error creating BIGNUM for r";
+        return false;
+    }
+
+    auto sBn = BIGNUM_Ptr(BN_bin2bn(ecdsaCoseSignature.data() + 32, 32, nullptr));
+    if (sBn.get() == nullptr) {
+        LOG(ERROR) << "Error creating BIGNUM for s";
+        return false;
+    }
+
+    ECDSA_SIG sig;
+    sig.r = rBn.get();
+    sig.s = sBn.get();
+
+    size_t len = i2d_ECDSA_SIG(&sig, nullptr);
+    ecdsaDerSignature.resize(len);
+    unsigned char* p = (unsigned char*)ecdsaDerSignature.data();
+    i2d_ECDSA_SIG(&sig, &p);
+
+    return true;
+}
+
+bool ecdsaSignatureDerToCose(const vector<uint8_t>& ecdsaDerSignature,
+                             vector<uint8_t>& ecdsaCoseSignature) {
+    ECDSA_SIG* sig;
+    const unsigned char* p = ecdsaDerSignature.data();
+    sig = d2i_ECDSA_SIG(nullptr, &p, ecdsaDerSignature.size());
+    if (sig == nullptr) {
+        LOG(ERROR) << "Error decoding DER signature";
+        return false;
+    }
+
+    ecdsaCoseSignature.clear();
+    ecdsaCoseSignature.resize(64);
+    if (BN_bn2binpad(sig->r, ecdsaCoseSignature.data(), 32) != 32) {
+        LOG(ERROR) << "Error encoding r";
+        return false;
+    }
+    if (BN_bn2binpad(sig->s, ecdsaCoseSignature.data() + 32, 32) != 32) {
+        LOG(ERROR) << "Error encoding s";
+        return false;
+    }
+    return true;
+}
+
+optional<vector<uint8_t>> coseSignEcDsa(const vector<uint8_t>& key, const vector<uint8_t>& data,
+                                        const vector<uint8_t>& detachedContent,
+                                        const vector<uint8_t>& certificateChain) {
+    cppbor::Map unprotectedHeaders;
+    cppbor::Map protectedHeaders;
+
+    if (data.size() > 0 && detachedContent.size() > 0) {
+        LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+        return {};
+    }
+
+    protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
+
+    if (certificateChain.size() != 0) {
+        optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certificateChain);
+        if (!certs) {
+            LOG(ERROR) << "Error splitting certificate chain";
+            return {};
+        }
+        if (certs.value().size() == 1) {
+            unprotectedHeaders.add(COSE_LABEL_X5CHAIN, certs.value()[0]);
+        } else {
+            cppbor::Array certArray;
+            for (const vector<uint8_t>& cert : certs.value()) {
+                certArray.add(cert);
+            }
+            unprotectedHeaders.add(COSE_LABEL_X5CHAIN, std::move(certArray));
+        }
+    }
+
+    vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);
+    vector<uint8_t> toBeSigned =
+            coseBuildToBeSigned(encodedProtectedHeaders, data, detachedContent);
+
+    optional<vector<uint8_t>> derSignature = signEcDsa(key, toBeSigned);
+    if (!derSignature) {
+        LOG(ERROR) << "Error signing toBeSigned data";
+        return {};
+    }
+    vector<uint8_t> coseSignature;
+    if (!ecdsaSignatureDerToCose(derSignature.value(), coseSignature)) {
+        LOG(ERROR) << "Error converting ECDSA signature from DER to COSE format";
+        return {};
+    }
+
+    cppbor::Array coseSign1;
+    coseSign1.add(encodedProtectedHeaders);
+    coseSign1.add(std::move(unprotectedHeaders));
+    if (data.size() == 0) {
+        cppbor::Null nullValue;
+        coseSign1.add(std::move(nullValue));
+    } else {
+        coseSign1.add(data);
+    }
+    coseSign1.add(coseSignature);
+    vector<uint8_t> signatureCoseSign1;
+    signatureCoseSign1 = coseSign1.encode();
+    return signatureCoseSign1;
+}
+
+bool coseCheckEcDsaSignature(const vector<uint8_t>& signatureCoseSign1,
+                             const vector<uint8_t>& detachedContent,
+                             const vector<uint8_t>& publicKey) {
+    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+    if (item == nullptr) {
+        LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+        return false;
+    }
+    const cppbor::Array* array = item->asArray();
+    if (array == nullptr) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+        return false;
+    }
+    if (array->size() != 4) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+        return false;
+    }
+
+    const cppbor::Bstr* encodedProtectedHeadersBstr = (*array)[0]->asBstr();
+    ;
+    if (encodedProtectedHeadersBstr == nullptr) {
+        LOG(ERROR) << "Value for encodedProtectedHeaders is not a bstr";
+        return false;
+    }
+    const vector<uint8_t> encodedProtectedHeaders = encodedProtectedHeadersBstr->value();
+
+    const cppbor::Map* unprotectedHeaders = (*array)[1]->asMap();
+    if (unprotectedHeaders == nullptr) {
+        LOG(ERROR) << "Value for unprotectedHeaders is not a map";
+        return false;
+    }
+
+    vector<uint8_t> data;
+    const cppbor::Simple* payloadAsSimple = (*array)[2]->asSimple();
+    if (payloadAsSimple != nullptr) {
+        if (payloadAsSimple->asNull() == nullptr) {
+            LOG(ERROR) << "Value for payload is not null or a bstr";
+            return false;
+        }
+    } else {
+        const cppbor::Bstr* payloadAsBstr = (*array)[2]->asBstr();
+        if (payloadAsBstr == nullptr) {
+            LOG(ERROR) << "Value for payload is not null or a bstr";
+            return false;
+        }
+        data = payloadAsBstr->value();  // TODO: avoid copy
+    }
+
+    if (data.size() > 0 && detachedContent.size() > 0) {
+        LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+        return false;
+    }
+
+    const cppbor::Bstr* signatureBstr = (*array)[3]->asBstr();
+    if (signatureBstr == nullptr) {
+        LOG(ERROR) << "Value for signature is a bstr";
+        return false;
+    }
+    const vector<uint8_t>& coseSignature = signatureBstr->value();
+
+    vector<uint8_t> derSignature;
+    if (!ecdsaSignatureCoseToDer(coseSignature, derSignature)) {
+        LOG(ERROR) << "Error converting ECDSA signature from COSE to DER format";
+        return false;
+    }
+
+    vector<uint8_t> toBeSigned =
+            coseBuildToBeSigned(encodedProtectedHeaders, data, detachedContent);
+    if (!checkEcDsaSignature(support::sha256(toBeSigned), derSignature, publicKey)) {
+        LOG(ERROR) << "Signature check failed";
+        return false;
+    }
+    return true;
+}
+
+optional<vector<uint8_t>> coseSignGetPayload(const vector<uint8_t>& signatureCoseSign1) {
+    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+    if (item == nullptr) {
+        LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+        return {};
+    }
+    const cppbor::Array* array = item->asArray();
+    if (array == nullptr) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+        return {};
+    }
+    if (array->size() != 4) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+        return {};
+    }
+
+    vector<uint8_t> data;
+    const cppbor::Simple* payloadAsSimple = (*array)[2]->asSimple();
+    if (payloadAsSimple != nullptr) {
+        if (payloadAsSimple->asNull() == nullptr) {
+            LOG(ERROR) << "Value for payload is not null or a bstr";
+            return {};
+        }
+        // payload is null, so |data| should be empty (as it is)
+    } else {
+        const cppbor::Bstr* payloadAsBstr = (*array)[2]->asBstr();
+        if (payloadAsBstr == nullptr) {
+            LOG(ERROR) << "Value for payload is not null or a bstr";
+            return {};
+        }
+        // Copy payload into |data|
+        data = payloadAsBstr->value();
+    }
+
+    return data;
+}
+
+optional<vector<uint8_t>> coseSignGetX5Chain(const vector<uint8_t>& signatureCoseSign1) {
+    auto [item, _, message] = cppbor::parse(signatureCoseSign1);
+    if (item == nullptr) {
+        LOG(ERROR) << "Passed-in COSE_Sign1 is not valid CBOR: " << message;
+        return {};
+    }
+    const cppbor::Array* array = item->asArray();
+    if (array == nullptr) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array";
+        return {};
+    }
+    if (array->size() != 4) {
+        LOG(ERROR) << "Value for COSE_Sign1 is not an array of size 4";
+        return {};
+    }
+
+    const cppbor::Map* unprotectedHeaders = (*array)[1]->asMap();
+    if (unprotectedHeaders == nullptr) {
+        LOG(ERROR) << "Value for unprotectedHeaders is not a map";
+        return {};
+    }
+
+    for (size_t n = 0; n < unprotectedHeaders->size(); n++) {
+        auto [keyItem, valueItem] = (*unprotectedHeaders)[n];
+        const cppbor::Int* number = keyItem->asInt();
+        if (number == nullptr) {
+            LOG(ERROR) << "Key item in top-level map is not a number";
+            return {};
+        }
+        int label = number->value();
+        if (label == COSE_LABEL_X5CHAIN) {
+            const cppbor::Bstr* bstr = valueItem->asBstr();
+            if (bstr != nullptr) {
+                return bstr->value();
+            }
+            const cppbor::Array* array = valueItem->asArray();
+            if (array != nullptr) {
+                vector<uint8_t> certs;
+                for (size_t m = 0; m < array->size(); m++) {
+                    const cppbor::Bstr* bstr = ((*array)[m])->asBstr();
+                    if (bstr == nullptr) {
+                        LOG(ERROR) << "Item in x5chain array is not a bstr";
+                        return {};
+                    }
+                    const vector<uint8_t>& certValue = bstr->value();
+                    certs.insert(certs.end(), certValue.begin(), certValue.end());
+                }
+                return certs;
+            }
+            LOG(ERROR) << "Value for x5chain label is not a bstr or array";
+            return {};
+        }
+    }
+    LOG(ERROR) << "Did not find x5chain label in unprotected headers";
+    return {};
+}
+
+vector<uint8_t> coseBuildToBeMACed(const vector<uint8_t>& encodedProtectedHeaders,
+                                   const vector<uint8_t>& data,
+                                   const vector<uint8_t>& detachedContent) {
+    cppbor::Array macStructure;
+    macStructure.add("MAC0");
+    macStructure.add(encodedProtectedHeaders);
+
+    // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+    // so external_aad is the empty bstr
+    vector<uint8_t> emptyExternalAad;
+    macStructure.add(emptyExternalAad);
+
+    // Next field is the payload, independently of how it's transported (RFC
+    // 8152 section 4.4). Since our API specifies only one of |data| and
+    // |detachedContent| can be non-empty, it's simply just the non-empty one.
+    if (data.size() > 0) {
+        macStructure.add(data);
+    } else {
+        macStructure.add(detachedContent);
+    }
+
+    return macStructure.encode();
+}
+
+optional<vector<uint8_t>> coseMac0(const vector<uint8_t>& key, const vector<uint8_t>& data,
+                                   const vector<uint8_t>& detachedContent) {
+    cppbor::Map unprotectedHeaders;
+    cppbor::Map protectedHeaders;
+
+    if (data.size() > 0 && detachedContent.size() > 0) {
+        LOG(ERROR) << "data and detachedContent cannot both be non-empty";
+        return {};
+    }
+
+    protectedHeaders.add(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
+
+    vector<uint8_t> encodedProtectedHeaders = coseEncodeHeaders(protectedHeaders);
+    vector<uint8_t> toBeMACed = coseBuildToBeMACed(encodedProtectedHeaders, data, detachedContent);
+
+    optional<vector<uint8_t>> mac = hmacSha256(key, toBeMACed);
+    if (!mac) {
+        LOG(ERROR) << "Error MACing toBeMACed data";
+        return {};
+    }
+
+    cppbor::Array array;
+    array.add(encodedProtectedHeaders);
+    array.add(std::move(unprotectedHeaders));
+    if (data.size() == 0) {
+        cppbor::Null nullValue;
+        array.add(std::move(nullValue));
+    } else {
+        array.add(data);
+    }
+    array.add(mac.value());
+    return array.encode();
+}
+
+// ---------------------------------------------------------------------------
+// Platform abstraction.
+// ---------------------------------------------------------------------------
+
+// This is not a very random HBK but that's OK because this is the SW
+// implementation where it can't be kept secret.
+vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+const vector<uint8_t>& getHardwareBoundKey() {
+    return hardwareBoundKey;
+}
+
+// ---------------------------------------------------------------------------
+// Utility functions specific to IdentityCredential.
+// ---------------------------------------------------------------------------
+
+Result okResult{ResultCode::OK, ""};
+
+const Result& resultOK() {
+    return okResult;
+}
+
+Result result(ResultCode code, const char* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    string str;
+    android::base::StringAppendV(&str, format, ap);
+    va_end(ap);
+    return Result{code, str};
+}
+
+vector<vector<uint8_t>> chunkVector(const vector<uint8_t>& content, size_t maxChunkSize) {
+    vector<vector<uint8_t>> ret;
+
+    size_t contentSize = content.size();
+    if (contentSize <= maxChunkSize) {
+        ret.push_back(content);
+        return ret;
+    }
+
+    size_t numChunks = (contentSize + maxChunkSize - 1) / maxChunkSize;
+
+    size_t pos = 0;
+    for (size_t n = 0; n < numChunks; n++) {
+        size_t size = contentSize - pos;
+        if (size > maxChunkSize) {
+            size = maxChunkSize;
+        }
+        auto begin = content.begin() + pos;
+        auto end = content.begin() + pos + size;
+        ret.emplace_back(vector<uint8_t>(begin, end));
+        pos += maxChunkSize;
+    }
+
+    return ret;
+}
+
+vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
+    cppbor::Map map;
+    map.add("id", profile.id);
+
+    if (profile.readerCertificate.size() > 0) {
+        map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate));
+    }
+
+    if (profile.userAuthenticationRequired) {
+        map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
+        map.add("timeoutMillis", profile.timeoutMillis);
+        map.add("secureUserId", profile.secureUserId);
+    }
+
+    return map.encode();
+}
+
+optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
+        const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
+    vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
+
+    optional<vector<uint8_t>> nonce = getRandom(12);
+    if (!nonce) {
+        return {};
+    }
+    optional<vector<uint8_t>> macO = encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
+    if (!macO) {
+        return {};
+    }
+    return macO.value();
+}
+
+bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
+                                        const vector<uint8_t>& storageKey) {
+    vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
+
+    if (profile.mac.size() < kAesGcmIvSize) {
+        return false;
+    }
+    vector<uint8_t> nonce =
+            vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + kAesGcmIvSize);
+    optional<vector<uint8_t>> mac = encryptAes128Gcm(storageKey, nonce, {}, cborData);
+    if (!mac) {
+        return false;
+    }
+    if (mac.value() != vector<uint8_t>(profile.mac)) {
+        return false;
+    }
+    return true;
+}
+
+vector<uint8_t> testHardwareBoundKey = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+const vector<uint8_t>& getTestHardwareBoundKey() {
+    return testHardwareBoundKey;
+}
+
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+                                          const vector<uint16_t> accessControlProfileIds) {
+    cppbor::Map map;
+    map.add("Namespace", nameSpace);
+    map.add("Name", name);
+
+    cppbor::Array acpIds;
+    for (auto id : accessControlProfileIds) {
+        acpIds.add(id);
+    }
+    map.add("AccessControlProfileIds", std::move(acpIds));
+    return map.encode();
+}
+
+}  // namespace support
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
diff --git a/identity/support/src/cppbor.cpp b/identity/support/src/cppbor.cpp
new file mode 100644
index 0000000..d289985
--- /dev/null
+++ b/identity/support/src/cppbor.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+#include "cppbor.h"
+#include "cppbor_parse.h"
+
+#define LOG_TAG "CppBor"
+#include <android-base/logging.h>
+
+namespace cppbor {
+
+namespace {
+
+template <typename T, typename Iterator, typename = std::enable_if<std::is_unsigned<T>::value>>
+Iterator writeBigEndian(T value, Iterator pos) {
+    for (unsigned i = 0; i < sizeof(value); ++i) {
+        *pos++ = static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1)));
+        value = static_cast<T>(value << 8);
+    }
+    return pos;
+}
+
+template <typename T, typename = std::enable_if<std::is_unsigned<T>::value>>
+void writeBigEndian(T value, std::function<void(uint8_t)>& cb) {
+    for (unsigned i = 0; i < sizeof(value); ++i) {
+        cb(static_cast<uint8_t>(value >> (8 * (sizeof(value) - 1))));
+        value = static_cast<T>(value << 8);
+    }
+}
+
+}  // namespace
+
+size_t headerSize(uint64_t addlInfo) {
+    if (addlInfo < ONE_BYTE_LENGTH) return 1;
+    if (addlInfo <= std::numeric_limits<uint8_t>::max()) return 2;
+    if (addlInfo <= std::numeric_limits<uint16_t>::max()) return 3;
+    if (addlInfo <= std::numeric_limits<uint32_t>::max()) return 5;
+    return 9;
+}
+
+uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end) {
+    size_t sz = headerSize(addlInfo);
+    if (end - pos < static_cast<ssize_t>(sz)) return nullptr;
+    switch (sz) {
+        case 1:
+            *pos++ = type | static_cast<uint8_t>(addlInfo);
+            return pos;
+        case 2:
+            *pos++ = type | ONE_BYTE_LENGTH;
+            *pos++ = static_cast<uint8_t>(addlInfo);
+            return pos;
+        case 3:
+            *pos++ = type | TWO_BYTE_LENGTH;
+            return writeBigEndian(static_cast<uint16_t>(addlInfo), pos);
+        case 5:
+            *pos++ = type | FOUR_BYTE_LENGTH;
+            return writeBigEndian(static_cast<uint32_t>(addlInfo), pos);
+        case 9:
+            *pos++ = type | EIGHT_BYTE_LENGTH;
+            return writeBigEndian(addlInfo, pos);
+        default:
+            CHECK(false);  // Impossible to get here.
+            return nullptr;
+    }
+}
+
+void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback) {
+    size_t sz = headerSize(addlInfo);
+    switch (sz) {
+        case 1:
+            encodeCallback(type | static_cast<uint8_t>(addlInfo));
+            break;
+        case 2:
+            encodeCallback(type | ONE_BYTE_LENGTH);
+            encodeCallback(static_cast<uint8_t>(addlInfo));
+            break;
+        case 3:
+            encodeCallback(type | TWO_BYTE_LENGTH);
+            writeBigEndian(static_cast<uint16_t>(addlInfo), encodeCallback);
+            break;
+        case 5:
+            encodeCallback(type | FOUR_BYTE_LENGTH);
+            writeBigEndian(static_cast<uint32_t>(addlInfo), encodeCallback);
+            break;
+        case 9:
+            encodeCallback(type | EIGHT_BYTE_LENGTH);
+            writeBigEndian(addlInfo, encodeCallback);
+            break;
+        default:
+            CHECK(false);  // Impossible to get here.
+    }
+}
+
+bool Item::operator==(const Item& other) const& {
+    if (type() != other.type()) return false;
+    switch (type()) {
+        case UINT:
+            return *asUint() == *(other.asUint());
+        case NINT:
+            return *asNint() == *(other.asNint());
+        case BSTR:
+            return *asBstr() == *(other.asBstr());
+        case TSTR:
+            return *asTstr() == *(other.asTstr());
+        case ARRAY:
+            return *asArray() == *(other.asArray());
+        case MAP:
+            return *asMap() == *(other.asMap());
+        case SIMPLE:
+            return *asSimple() == *(other.asSimple());
+        case SEMANTIC:
+            return *asSemantic() == *(other.asSemantic());
+        default:
+            CHECK(false);  // Impossible to get here.
+            return false;
+    }
+}
+
+Nint::Nint(int64_t v) : mValue(v) {
+    CHECK(v < 0) << "Only negative values allowed";
+}
+
+bool Simple::operator==(const Simple& other) const& {
+    if (simpleType() != other.simpleType()) return false;
+
+    switch (simpleType()) {
+        case BOOLEAN:
+            return *asBool() == *(other.asBool());
+        case NULL_T:
+            return true;
+        default:
+            CHECK(false);  // Impossible to get here.
+            return false;
+    }
+}
+
+uint8_t* Bstr::encode(uint8_t* pos, const uint8_t* end) const {
+    pos = encodeHeader(mValue.size(), pos, end);
+    if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr;
+    return std::copy(mValue.begin(), mValue.end(), pos);
+}
+
+void Bstr::encodeValue(EncodeCallback encodeCallback) const {
+    for (auto c : mValue) {
+        encodeCallback(c);
+    }
+}
+
+uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const {
+    pos = encodeHeader(mValue.size(), pos, end);
+    if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr;
+    return std::copy(mValue.begin(), mValue.end(), pos);
+}
+
+void Tstr::encodeValue(EncodeCallback encodeCallback) const {
+    for (auto c : mValue) {
+        encodeCallback(static_cast<uint8_t>(c));
+    }
+}
+
+bool CompoundItem::operator==(const CompoundItem& other) const& {
+    return type() == other.type()             //
+           && addlInfo() == other.addlInfo()  //
+           // Can't use vector::operator== because the contents are pointers.  std::equal lets us
+           // provide a predicate that does the dereferencing.
+           && std::equal(mEntries.begin(), mEntries.end(), other.mEntries.begin(),
+                         [](auto& a, auto& b) -> bool { return *a == *b; });
+}
+
+uint8_t* CompoundItem::encode(uint8_t* pos, const uint8_t* end) const {
+    pos = encodeHeader(addlInfo(), pos, end);
+    if (!pos) return nullptr;
+    for (auto& entry : mEntries) {
+        pos = entry->encode(pos, end);
+        if (!pos) return nullptr;
+    }
+    return pos;
+}
+
+void CompoundItem::encode(EncodeCallback encodeCallback) const {
+    encodeHeader(addlInfo(), encodeCallback);
+    for (auto& entry : mEntries) {
+        entry->encode(encodeCallback);
+    }
+}
+
+void Map::assertInvariant() const {
+    CHECK(mEntries.size() % 2 == 0);
+}
+
+std::unique_ptr<Item> Map::clone() const {
+    assertInvariant();
+    auto res = std::make_unique<Map>();
+    for (size_t i = 0; i < mEntries.size(); i += 2) {
+        res->add(mEntries[i]->clone(), mEntries[i + 1]->clone());
+    }
+    return res;
+}
+
+std::unique_ptr<Item> Array::clone() const {
+    auto res = std::make_unique<Array>();
+    for (size_t i = 0; i < mEntries.size(); i++) {
+        res->add(mEntries[i]->clone());
+    }
+    return res;
+}
+
+void Semantic::assertInvariant() const {
+    CHECK(mEntries.size() == 1);
+}
+
+}  // namespace cppbor
diff --git a/identity/support/src/cppbor_parse.cpp b/identity/support/src/cppbor_parse.cpp
new file mode 100644
index 0000000..c9ebb8a
--- /dev/null
+++ b/identity/support/src/cppbor_parse.cpp
@@ -0,0 +1,351 @@
+/*
+ * 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.
+ */
+
+#include "cppbor_parse.h"
+
+#include <sstream>
+#include <stack>
+
+#define LOG_TAG "CppBor"
+#include <android-base/logging.h>
+
+namespace cppbor {
+
+namespace {
+
+std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail,
+                                     const std::string& type) {
+    std::stringstream errStream;
+    errStream << "Need " << bytesNeeded << " byte(s) for " << type << ", have " << bytesAvail
+              << ".";
+    return errStream.str();
+}
+
+template <typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
+std::tuple<bool, uint64_t, const uint8_t*> parseLength(const uint8_t* pos, const uint8_t* end,
+                                                       ParseClient* parseClient) {
+    if (pos + sizeof(T) > end) {
+        parseClient->error(pos - 1, insufficientLengthString(sizeof(T), end - pos, "length field"));
+        return {false, 0, pos};
+    }
+
+    const uint8_t* intEnd = pos + sizeof(T);
+    T result = 0;
+    do {
+        result = static_cast<T>((result << 8) | *pos++);
+    } while (pos < intEnd);
+    return {true, result, pos};
+}
+
+std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end,
+                                                          ParseClient* parseClient);
+
+std::tuple<const uint8_t*, ParseClient*> handleUint(uint64_t value, const uint8_t* hdrBegin,
+                                                    const uint8_t* hdrEnd,
+                                                    ParseClient* parseClient) {
+    std::unique_ptr<Item> item = std::make_unique<Uint>(value);
+    return {hdrEnd,
+            parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleNint(uint64_t value, const uint8_t* hdrBegin,
+                                                    const uint8_t* hdrEnd,
+                                                    ParseClient* parseClient) {
+    if (value > std::numeric_limits<int64_t>::max()) {
+        parseClient->error(hdrBegin, "NINT values that don't fit in int64_t are not supported.");
+        return {hdrBegin, nullptr /* end parsing */};
+    }
+    std::unique_ptr<Item> item = std::make_unique<Nint>(-1 - static_cast<uint64_t>(value));
+    return {hdrEnd,
+            parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleBool(uint64_t value, const uint8_t* hdrBegin,
+                                                    const uint8_t* hdrEnd,
+                                                    ParseClient* parseClient) {
+    std::unique_ptr<Item> item = std::make_unique<Bool>(value == TRUE);
+    return {hdrEnd,
+            parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleNull(const uint8_t* hdrBegin, const uint8_t* hdrEnd,
+                                                    ParseClient* parseClient) {
+    std::unique_ptr<Item> item = std::make_unique<Null>();
+    return {hdrEnd,
+            parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)};
+}
+
+template <typename T>
+std::tuple<const uint8_t*, ParseClient*> handleString(uint64_t length, const uint8_t* hdrBegin,
+                                                      const uint8_t* valueBegin, const uint8_t* end,
+                                                      const std::string& errLabel,
+                                                      ParseClient* parseClient) {
+    if (end - valueBegin < static_cast<ssize_t>(length)) {
+        parseClient->error(hdrBegin, insufficientLengthString(length, end - valueBegin, errLabel));
+        return {hdrBegin, nullptr /* end parsing */};
+    }
+
+    std::unique_ptr<Item> item = std::make_unique<T>(valueBegin, valueBegin + length);
+    return {valueBegin + length,
+            parseClient->item(item, hdrBegin, valueBegin, valueBegin + length)};
+}
+
+class IncompleteItem {
+  public:
+    virtual ~IncompleteItem() {}
+    virtual void add(std::unique_ptr<Item> item) = 0;
+};
+
+class IncompleteArray : public Array, public IncompleteItem {
+  public:
+    IncompleteArray(size_t size) : mSize(size) {}
+
+    // We return the "complete" size, rather than the actual size.
+    size_t size() const override { return mSize; }
+
+    void add(std::unique_ptr<Item> item) override {
+        mEntries.reserve(mSize);
+        mEntries.push_back(std::move(item));
+    }
+
+  private:
+    size_t mSize;
+};
+
+class IncompleteMap : public Map, public IncompleteItem {
+  public:
+    IncompleteMap(size_t size) : mSize(size) {}
+
+    // We return the "complete" size, rather than the actual size.
+    size_t size() const override { return mSize; }
+
+    void add(std::unique_ptr<Item> item) override {
+        mEntries.reserve(mSize * 2);
+        mEntries.push_back(std::move(item));
+    }
+
+  private:
+    size_t mSize;
+};
+
+class IncompleteSemantic : public Semantic, public IncompleteItem {
+  public:
+    IncompleteSemantic(uint64_t value) : Semantic(value) {}
+
+    // We return the "complete" size, rather than the actual size.
+    size_t size() const override { return 1; }
+
+    void add(std::unique_ptr<Item> item) override {
+        mEntries.reserve(1);
+        mEntries.push_back(std::move(item));
+    }
+};
+
+std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const uint8_t* hdrBegin,
+                                                       const uint8_t* pos, const uint8_t* end,
+                                                       const std::string& typeName,
+                                                       ParseClient* parseClient) {
+    while (entryCount > 0) {
+        --entryCount;
+        if (pos == end) {
+            parseClient->error(hdrBegin, "Not enough entries for " + typeName + ".");
+            return {hdrBegin, nullptr /* end parsing */};
+        }
+        std::tie(pos, parseClient) = parseRecursively(pos, end, parseClient);
+        if (!parseClient) return {hdrBegin, nullptr};
+    }
+    return {pos, parseClient};
+}
+
+std::tuple<const uint8_t*, ParseClient*> handleCompound(
+        std::unique_ptr<Item> item, uint64_t entryCount, const uint8_t* hdrBegin,
+        const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName,
+        ParseClient* parseClient) {
+    parseClient =
+            parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */);
+    if (!parseClient) return {hdrBegin, nullptr};
+
+    const uint8_t* pos;
+    std::tie(pos, parseClient) =
+            handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, parseClient);
+    if (!parseClient) return {hdrBegin, nullptr};
+
+    return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)};
+}
+
+std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end,
+                                                          ParseClient* parseClient) {
+    const uint8_t* pos = begin;
+
+    MajorType type = static_cast<MajorType>(*pos & 0xE0);
+    uint8_t tagInt = *pos & 0x1F;
+    ++pos;
+
+    bool success = true;
+    uint64_t addlData;
+    if (tagInt < ONE_BYTE_LENGTH || tagInt > EIGHT_BYTE_LENGTH) {
+        addlData = tagInt;
+    } else {
+        switch (tagInt) {
+            case ONE_BYTE_LENGTH:
+                std::tie(success, addlData, pos) = parseLength<uint8_t>(pos, end, parseClient);
+                break;
+
+            case TWO_BYTE_LENGTH:
+                std::tie(success, addlData, pos) = parseLength<uint16_t>(pos, end, parseClient);
+                break;
+
+            case FOUR_BYTE_LENGTH:
+                std::tie(success, addlData, pos) = parseLength<uint32_t>(pos, end, parseClient);
+                break;
+
+            case EIGHT_BYTE_LENGTH:
+                std::tie(success, addlData, pos) = parseLength<uint64_t>(pos, end, parseClient);
+                break;
+
+            default:
+                CHECK(false);  //  It's impossible to get here
+                break;
+        }
+    }
+
+    if (!success) return {begin, nullptr};
+
+    switch (type) {
+        case UINT:
+            return handleUint(addlData, begin, pos, parseClient);
+
+        case NINT:
+            return handleNint(addlData, begin, pos, parseClient);
+
+        case BSTR:
+            return handleString<Bstr>(addlData, begin, pos, end, "byte string", parseClient);
+
+        case TSTR:
+            return handleString<Tstr>(addlData, begin, pos, end, "text string", parseClient);
+
+        case ARRAY:
+            return handleCompound(std::make_unique<IncompleteArray>(addlData), addlData, begin, pos,
+                                  end, "array", parseClient);
+
+        case MAP:
+            return handleCompound(std::make_unique<IncompleteMap>(addlData), addlData * 2, begin,
+                                  pos, end, "map", parseClient);
+
+        case SEMANTIC:
+            return handleCompound(std::make_unique<IncompleteSemantic>(addlData), 1, begin, pos,
+                                  end, "semantic", parseClient);
+
+        case SIMPLE:
+            switch (addlData) {
+                case TRUE:
+                case FALSE:
+                    return handleBool(addlData, begin, pos, parseClient);
+                case NULL_V:
+                    return handleNull(begin, pos, parseClient);
+            }
+    }
+    CHECK(false);  // Impossible to get here.
+    return {};
+}
+
+class FullParseClient : public ParseClient {
+  public:
+    virtual ParseClient* item(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*,
+                              const uint8_t* end) override {
+        if (mParentStack.empty() && !item->isCompound()) {
+            // This is the first and only item.
+            mTheItem = std::move(item);
+            mPosition = end;
+            return nullptr;  //  We're done.
+        }
+
+        if (item->isCompound()) {
+            // Starting a new compound data item, i.e. a new parent.  Save it on the parent stack.
+            // It's safe to save a raw pointer because the unique_ptr is guaranteed to stay in
+            // existence until the corresponding itemEnd() call.
+            assert(dynamic_cast<CompoundItem*>(item.get()));
+            mParentStack.push(static_cast<CompoundItem*>(item.get()));
+            return this;
+        } else {
+            appendToLastParent(std::move(item));
+            return this;
+        }
+    }
+
+    virtual ParseClient* itemEnd(std::unique_ptr<Item>& item, const uint8_t*, const uint8_t*,
+                                 const uint8_t* end) override {
+        CHECK(item->isCompound() && item.get() == mParentStack.top());
+        mParentStack.pop();
+
+        if (mParentStack.empty()) {
+            mTheItem = std::move(item);
+            mPosition = end;
+            return nullptr;  // We're done
+        } else {
+            appendToLastParent(std::move(item));
+            return this;
+        }
+    }
+
+    virtual void error(const uint8_t* position, const std::string& errorMessage) override {
+        mPosition = position;
+        mErrorMessage = errorMessage;
+    }
+
+    std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+               std::string /* errMsg */>
+    parseResult() {
+        std::unique_ptr<Item> p = std::move(mTheItem);
+        return {std::move(p), mPosition, std::move(mErrorMessage)};
+    }
+
+  private:
+    void appendToLastParent(std::unique_ptr<Item> item) {
+        auto parent = mParentStack.top();
+        assert(dynamic_cast<IncompleteItem*>(parent));
+        if (parent->type() == ARRAY) {
+            static_cast<IncompleteArray*>(parent)->add(std::move(item));
+        } else if (parent->type() == MAP) {
+            static_cast<IncompleteMap*>(parent)->add(std::move(item));
+        } else if (parent->type() == SEMANTIC) {
+            static_cast<IncompleteSemantic*>(parent)->add(std::move(item));
+        } else {
+            CHECK(false);  // Impossible to get here.
+        }
+    }
+
+    std::unique_ptr<Item> mTheItem;
+    std::stack<CompoundItem*> mParentStack;
+    const uint8_t* mPosition = nullptr;
+    std::string mErrorMessage;
+};
+
+}  // anonymous namespace
+
+void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) {
+    parseRecursively(begin, end, parseClient);
+}
+
+std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */,
+           std::string /* errMsg */>
+parse(const uint8_t* begin, const uint8_t* end) {
+    FullParseClient parseClient;
+    parse(begin, end, &parseClient);
+    return parseClient.parseResult();
+}
+
+}  // namespace cppbor
diff --git a/identity/support/tests/IdentityCredentialSupportTest.cpp b/identity/support/tests/IdentityCredentialSupportTest.cpp
new file mode 100644
index 0000000..c356549
--- /dev/null
+++ b/identity/support/tests/IdentityCredentialSupportTest.cpp
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ */
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+using std::optional;
+using std::string;
+using std::vector;
+
+namespace android {
+namespace hardware {
+namespace identity {
+
+TEST(IdentityCredentialSupport, encodeHex) {
+    EXPECT_EQ("", support::encodeHex(vector<uint8_t>({})));
+    EXPECT_EQ("01", support::encodeHex(vector<uint8_t>({1})));
+    EXPECT_EQ("000102030405060708090a0b0c0d0e0f10",
+              support::encodeHex(
+                      vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})));
+    EXPECT_EQ("0102ffe060", support::encodeHex(vector<uint8_t>({1, 2, 255, 224, 96})));
+}
+
+TEST(IdentityCredentialSupport, decodeHex) {
+    EXPECT_EQ(vector<uint8_t>({}), support::decodeHex(""));
+    EXPECT_EQ(vector<uint8_t>({1}), support::decodeHex("01"));
+
+    EXPECT_EQ(vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
+              support::decodeHex("000102030405060708090a0b0c0d0e0f10"));
+
+    EXPECT_FALSE(support::decodeHex("0g"));
+    EXPECT_FALSE(support::decodeHex("0"));
+    EXPECT_FALSE(support::decodeHex("012"));
+}
+
+TEST(IdentityCredentialSupport, CborPrettyPrint) {
+    EXPECT_EQ("'Some text'", support::cborPrettyPrint(cppbor::Tstr("Some text").encode()));
+
+    EXPECT_EQ("''", support::cborPrettyPrint(cppbor::Tstr("").encode()));
+
+    EXPECT_EQ("{0x01, 0x00, 0x02, 0xf0, 0xff, 0x40}",
+              support::cborPrettyPrint(
+                      cppbor::Bstr(vector<uint8_t>({1, 0, 2, 240, 255, 64})).encode()));
+
+    EXPECT_EQ("{}", support::cborPrettyPrint(cppbor::Bstr(vector<uint8_t>()).encode()));
+
+    EXPECT_EQ("true", support::cborPrettyPrint(cppbor::Bool(true).encode()));
+
+    EXPECT_EQ("false", support::cborPrettyPrint(cppbor::Bool(false).encode()));
+
+    EXPECT_EQ("42", support::cborPrettyPrint(cppbor::Uint(42).encode()));
+
+    EXPECT_EQ("9223372036854775807",  // 0x7fff ffff ffff ffff
+              support::cborPrettyPrint(cppbor::Uint(std::numeric_limits<int64_t>::max()).encode()));
+
+    EXPECT_EQ("-42", support::cborPrettyPrint(cppbor::Nint(-42).encode()));
+
+    EXPECT_EQ("-9223372036854775808",  // -0x8000 0000 0000 0000
+              support::cborPrettyPrint(cppbor::Nint(std::numeric_limits<int64_t>::min()).encode()));
+}
+
+TEST(IdentityCredentialSupport, CborPrettyPrintCompound) {
+    cppbor::Array array = cppbor::Array("foo", "bar", "baz");
+    EXPECT_EQ("['foo', 'bar', 'baz', ]", support::cborPrettyPrint(array.encode()));
+
+    cppbor::Map map = cppbor::Map().add("foo", 42).add("bar", 43).add("baz", 44);
+    EXPECT_EQ(
+            "{\n"
+            "  'foo' : 42,\n"
+            "  'bar' : 43,\n"
+            "  'baz' : 44,\n"
+            "}",
+            support::cborPrettyPrint(map.encode()));
+
+    cppbor::Array array2 = cppbor::Array(cppbor::Tstr("Some text"), cppbor::Nint(-42));
+    EXPECT_EQ("['Some text', -42, ]", support::cborPrettyPrint(array2.encode()));
+
+    cppbor::Map map2 = cppbor::Map().add(42, "foo").add(43, "bar").add(44, "baz");
+    EXPECT_EQ(
+            "{\n"
+            "  42 : 'foo',\n"
+            "  43 : 'bar',\n"
+            "  44 : 'baz',\n"
+            "}",
+            support::cborPrettyPrint(map2.encode()));
+
+    cppbor::Array deeplyNestedArrays =
+            cppbor::Array(cppbor::Array(cppbor::Array("a", "b", "c")),
+                          cppbor::Array(cppbor::Array("d", "e", cppbor::Array("f", "g"))));
+    EXPECT_EQ(
+            "[\n"
+            "  ['a', 'b', 'c', ],\n"
+            "  [\n    'd',\n"
+            "    'e',\n"
+            "    ['f', 'g', ],\n"
+            "  ],\n"
+            "]",
+            support::cborPrettyPrint(deeplyNestedArrays.encode()));
+
+    EXPECT_EQ(
+            "[\n"
+            "  {0x0a, 0x0b},\n"
+            "  'foo',\n"
+            "  42,\n"
+            "  ['foo', 'bar', 'baz', ],\n"
+            "  {\n"
+            "    'foo' : 42,\n"
+            "    'bar' : 43,\n"
+            "    'baz' : 44,\n"
+            "  },\n"
+            "  {\n"
+            "    'deep1' : ['Some text', -42, ],\n"
+            "    'deep2' : {\n"
+            "      42 : 'foo',\n"
+            "      43 : 'bar',\n"
+            "      44 : 'baz',\n"
+            "    },\n"
+            "  },\n"
+            "]",
+            support::cborPrettyPrint(cppbor::Array(cppbor::Bstr(vector<uint8_t>{10, 11}),
+                                                   cppbor::Tstr("foo"), cppbor::Uint(42),
+                                                   std::move(array), std::move(map),
+                                                   (cppbor::Map()
+                                                            .add("deep1", std::move(array2))
+                                                            .add("deep2", std::move(map2))))
+                                             .encode()));
+}
+
+TEST(IdentityCredentialSupport, Signatures) {
+    vector<uint8_t> data = {1, 2, 3};
+
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    optional<vector<uint8_t>> signature = support::signEcDsa(privKey.value(), data);
+    ASSERT_TRUE(
+            support::checkEcDsaSignature(support::sha256(data), signature.value(), pubKey.value()));
+
+    // Manipulate the signature, check that verification fails.
+    vector<uint8_t> modifiedSignature = signature.value();
+    modifiedSignature[0] ^= 0xff;
+    ASSERT_FALSE(
+            support::checkEcDsaSignature(support::sha256(data), modifiedSignature, pubKey.value()));
+
+    // Manipulate the data being checked, check that verification fails.
+    vector<uint8_t> modifiedDigest = support::sha256(data);
+    modifiedDigest[0] ^= 0xff;
+    ASSERT_FALSE(support::checkEcDsaSignature(modifiedDigest, signature.value(), pubKey.value()));
+}
+
+string replaceLine(const string& str, ssize_t lineNumber, const string& replacement) {
+    vector<string> lines;
+    std::istringstream f(str);
+    string s;
+    while (std::getline(f, s, '\n')) {
+        lines.push_back(s);
+    }
+
+    size_t numLines = lines.size();
+    if (lineNumber < 0) {
+        lineNumber = numLines - (-lineNumber);
+    }
+
+    string ret;
+    size_t n = 0;
+    for (const string& line : lines) {
+        if (n == lineNumber) {
+            ret += replacement + "\n";
+        } else {
+            ret += line + "\n";
+        }
+        n++;
+    }
+    return ret;
+}
+
+TEST(IdentityCredentialSupport, CoseSignatures) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    vector<uint8_t> data = {1, 2, 3};
+    optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa(
+            privKey.value(), data, {} /* detachedContent */, {} /* x5chain */);
+    ASSERT_TRUE(support::coseCheckEcDsaSignature(coseSign1.value(), {} /* detachedContent */,
+                                                 pubKey.value()));
+
+    optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+    ASSERT_TRUE(payload);
+    ASSERT_EQ(data, payload.value());
+
+    // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message
+    string out = support::cborPrettyPrint(coseSign1.value());
+    out = replaceLine(out, -2, "  [] // Signature Removed");
+    EXPECT_EQ(
+            "[\n"
+            "  {0xa1, 0x01, 0x26},\n"  // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256"
+            "  {},\n"
+            "  {0x01, 0x02, 0x03},\n"
+            "  [] // Signature Removed\n"
+            "]\n",
+            out);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesAdditionalData) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    vector<uint8_t> detachedContent = {1, 2, 3};
+    optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa(privKey.value(), {} /* data */,
+                                                                 detachedContent, {} /* x5chain */);
+    ASSERT_TRUE(
+            support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+    optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+    ASSERT_TRUE(payload);
+    ASSERT_EQ(0, payload.value().size());
+
+    // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message
+    string out = support::cborPrettyPrint(coseSign1.value());
+    out = replaceLine(out, -2, "  [] // Signature Removed");
+    EXPECT_EQ(
+            "[\n"
+            "  {0xa1, 0x01, 0x26},\n"  // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256"
+            "  {},\n"
+            "  null,\n"
+            "  [] // Signature Removed\n"
+            "]\n",
+            out);
+}
+
+vector<uint8_t> generateCertChain(size_t numCerts) {
+    vector<vector<uint8_t>> certs;
+
+    for (size_t n = 0; n < numCerts; n++) {
+        optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+        optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+        optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+
+        optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+                pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+        certs.push_back(cert.value());
+    }
+    return support::certificateChainJoin(certs);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithSingleCert) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    vector<uint8_t> certChain = generateCertChain(1);
+    optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain);
+    ASSERT_EQ(1, splitCerts.value().size());
+
+    vector<uint8_t> detachedContent = {1, 2, 3};
+    optional<vector<uint8_t>> coseSign1 =
+            support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain);
+    ASSERT_TRUE(
+            support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+    optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+    ASSERT_TRUE(payload);
+    ASSERT_EQ(0, payload.value().size());
+
+    optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value());
+    EXPECT_EQ(certsRecovered.value(), certChain);
+}
+
+TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithMultipleCerts) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    vector<uint8_t> certChain = generateCertChain(5);
+    optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain);
+    ASSERT_EQ(5, splitCerts.value().size());
+
+    vector<uint8_t> detachedContent = {1, 2, 3};
+    optional<vector<uint8_t>> coseSign1 =
+            support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain);
+    ASSERT_TRUE(
+            support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value()));
+
+    optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value());
+    ASSERT_TRUE(payload);
+    ASSERT_EQ(0, payload.value().size());
+
+    optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value());
+    EXPECT_EQ(certsRecovered.value(), certChain);
+}
+
+TEST(IdentityCredentialSupport, CertificateChain) {
+    optional<vector<uint8_t>> keyPair = support::createEcKeyPair();
+    ASSERT_TRUE(keyPair);
+    optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(privKey);
+    optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(pubKey);
+
+    optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate(
+            pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+
+    optional<vector<uint8_t>> extractedPubKey =
+            support::certificateChainGetTopMostKey(cert.value());
+    ASSERT_TRUE(extractedPubKey);
+    ASSERT_EQ(pubKey.value(), extractedPubKey.value());
+
+    // We expect to the chain returned by ecPublicKeyGenerateCertificate() to only have a
+    // single element
+    optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(cert.value());
+    ASSERT_EQ(1, splitCerts.value().size());
+    ASSERT_EQ(splitCerts.value()[0], cert.value());
+
+    optional<vector<uint8_t>> otherKeyPair = support::createEcKeyPair();
+    ASSERT_TRUE(otherKeyPair);
+    optional<vector<uint8_t>> otherPrivKey = support::ecKeyPairGetPrivateKey(keyPair.value());
+    ASSERT_TRUE(otherPrivKey);
+    optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value());
+    ASSERT_TRUE(otherPubKey);
+    optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate(
+            otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0);
+
+    // Now both cert and otherCert are two distinct certificates. Let's make a
+    // chain and check that certificateChainSplit() works as expected.
+    ASSERT_NE(cert.value(), otherCert.value());
+    const vector<vector<uint8_t>> certs2 = {cert.value(), otherCert.value()};
+    vector<uint8_t> certs2combined = support::certificateChainJoin(certs2);
+    ASSERT_EQ(certs2combined.size(), cert.value().size() + otherCert.value().size());
+    optional<vector<vector<uint8_t>>> splitCerts2 = support::certificateChainSplit(certs2combined);
+    ASSERT_EQ(certs2, splitCerts2.value());
+}
+
+vector<uint8_t> strToVec(const string& str) {
+    vector<uint8_t> ret;
+    size_t size = str.size();
+    ret.resize(size);
+    memcpy(ret.data(), str.data(), size);
+    return ret;
+}
+
+// Test vector from https://en.wikipedia.org/wiki/HMAC
+TEST(IdentityCredentialSupport, hmacSha256) {
+    vector<uint8_t> key = strToVec("key");
+    vector<uint8_t> data = strToVec("The quick brown fox jumps over the lazy dog");
+
+    vector<uint8_t> expected =
+            support::decodeHex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8")
+                    .value();
+
+    optional<vector<uint8_t>> hmac = support::hmacSha256(key, data);
+    ASSERT_TRUE(hmac);
+    ASSERT_EQ(expected, hmac.value());
+}
+
+// See also CoseMac0 test in UtilUnitTest.java inside cts/tests/tests/identity/
+TEST(IdentityCredentialSupport, CoseMac0) {
+    vector<uint8_t> key;
+    key.resize(32);
+    vector<uint8_t> data = {0x10, 0x11, 0x12, 0x13};
+    vector<uint8_t> detachedContent = {};
+
+    optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent);
+    ASSERT_TRUE(mac);
+
+    EXPECT_EQ(
+            "[\n"
+            "  {0xa1, 0x01, 0x05},\n"
+            "  {},\n"
+            "  {0x10, 0x11, 0x12, 0x13},\n"
+            "  {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, "
+            "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, "
+            "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n"
+            "]",
+            support::cborPrettyPrint(mac.value()));
+}
+
+TEST(IdentityCredentialSupport, CoseMac0DetachedContent) {
+    vector<uint8_t> key;
+    key.resize(32);
+    vector<uint8_t> data = {};
+    vector<uint8_t> detachedContent = {0x10, 0x11, 0x12, 0x13};
+
+    optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent);
+    ASSERT_TRUE(mac);
+
+    // Same HMAC as in CoseMac0 test, only difference is that payload is null.
+    EXPECT_EQ(
+            "[\n"
+            "  {0xa1, 0x01, 0x05},\n"
+            "  {},\n"
+            "  null,\n"
+            "  {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, "
+            "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, "
+            "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n"
+            "]",
+            support::cborPrettyPrint(mac.value()));
+}
+
+}  // namespace identity
+}  // namespace hardware
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/identity/support/tests/cppbor_test.cpp b/identity/support/tests/cppbor_test.cpp
new file mode 100644
index 0000000..3eb5598
--- /dev/null
+++ b/identity/support/tests/cppbor_test.cpp
@@ -0,0 +1,1013 @@
+/*
+ * 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.
+ */
+
+#include <iomanip>
+#include <sstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "cppbor.h"
+#include "cppbor_parse.h"
+
+using namespace cppbor;
+using namespace std;
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::ByRef;
+using ::testing::InSequence;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::Truly;
+using ::testing::Unused;
+
+string hexDump(const string& str) {
+    stringstream s;
+    for (auto c : str) {
+        s << setfill('0') << setw(2) << hex << (static_cast<unsigned>(c) & 0xff);
+    }
+    return s.str();
+}
+
+TEST(SimpleValueTest, UnsignedValueSizes) {
+    // Check that unsigned integers encode to correct lengths, and that encodedSize() is correct.
+    vector<pair<uint64_t /* value */, size_t /* expected encoded size */>> testCases{
+            {0, 1},
+            {1, 1},
+            {23, 1},
+            {24, 2},
+            {255, 2},
+            {256, 3},
+            {65535, 3},
+            {65536, 5},
+            {4294967295, 5},
+            {4294967296, 9},
+            {std::numeric_limits<uint64_t>::max(), 9},
+    };
+    for (auto& testCase : testCases) {
+        Uint val(testCase.first);
+        EXPECT_EQ(testCase.second, val.encodedSize()) << "Wrong size for value " << testCase.first;
+        EXPECT_EQ(val.encodedSize(), val.toString().size())
+                << "encodedSize and encoding disagree for value " << testCase.first;
+    }
+}
+
+TEST(SimpleValueTest, UnsignedValueEncodings) {
+    EXPECT_EQ("\x00"s, Uint(0u).toString());
+    EXPECT_EQ("\x01"s, Uint(1u).toString());
+    EXPECT_EQ("\x0a"s, Uint(10u).toString());
+    EXPECT_EQ("\x17"s, Uint(23u).toString());
+    EXPECT_EQ("\x18\x18"s, Uint(24u).toString());
+    EXPECT_EQ("\x18\x19"s, Uint(25u).toString());
+    EXPECT_EQ("\x18\x64"s, Uint(100u).toString());
+    EXPECT_EQ("\x19\x03\xe8"s, Uint(1000u).toString());
+    EXPECT_EQ("\x1a\x00\x0f\x42\x40"s, Uint(1000000u).toString());
+    EXPECT_EQ("\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00"s, Uint(1000000000000u).toString());
+    EXPECT_EQ("\x1B\x7f\xff\xff\xff\xff\xff\xff\xff"s,
+              Uint(std::numeric_limits<int64_t>::max()).toString());
+}
+
+TEST(SimpleValueTest, NegativeValueEncodings) {
+    EXPECT_EQ("\x20"s, Nint(-1).toString());
+    EXPECT_EQ("\x28"s, Nint(-9).toString());
+    EXPECT_EQ("\x29"s, Nint(-10).toString());
+    EXPECT_EQ("\x36"s, Nint(-23).toString());
+    EXPECT_EQ("\x37"s, Nint(-24).toString());
+    EXPECT_EQ("\x38\x18"s, Nint(-25).toString());
+    EXPECT_EQ("\x38\x62"s, Nint(-99).toString());
+    EXPECT_EQ("\x38\x63"s, Nint(-100).toString());
+    EXPECT_EQ("\x39\x03\xe6"s, Nint(-999).toString());
+    EXPECT_EQ("\x39\x03\xe7"s, Nint(-1000).toString());
+    EXPECT_EQ("\x3a\x00\x0f\x42\x3F"s, Nint(-1000000).toString());
+    EXPECT_EQ("\x3b\x00\x00\x00\xe8\xd4\xa5\x0f\xff"s, Nint(-1000000000000).toString());
+    EXPECT_EQ("\x3B\x7f\xff\xff\xff\xff\xff\xff\xff"s,
+              Nint(std::numeric_limits<int64_t>::min()).toString());
+}
+
+TEST(SimpleValueDeathTest, NegativeValueEncodings) {
+    EXPECT_DEATH(Nint(0), "");
+    EXPECT_DEATH(Nint(1), "");
+}
+
+TEST(SimpleValueTest, BooleanEncodings) {
+    EXPECT_EQ("\xf4"s, Bool(false).toString());
+    EXPECT_EQ("\xf5"s, Bool(true).toString());
+}
+
+TEST(SimpleValueTest, ByteStringEncodings) {
+    EXPECT_EQ("\x40", Bstr("").toString());
+    EXPECT_EQ("\x41\x61", Bstr("a").toString());
+    EXPECT_EQ("\x41\x41", Bstr("A").toString());
+    EXPECT_EQ("\x44\x49\x45\x54\x46", Bstr("IETF").toString());
+    EXPECT_EQ("\x42\x22\x5c", Bstr("\"\\").toString());
+    EXPECT_EQ("\x42\xc3\xbc", Bstr("\xc3\xbc").toString());
+    EXPECT_EQ("\x43\xe6\xb0\xb4", Bstr("\xe6\xb0\xb4").toString());
+    EXPECT_EQ("\x44\xf0\x90\x85\x91", Bstr("\xf0\x90\x85\x91").toString());
+    EXPECT_EQ("\x44\x01\x02\x03\x04", Bstr("\x01\x02\x03\x04").toString());
+    EXPECT_EQ("\x44\x40\x40\x40\x40", Bstr("@@@@").toString());
+}
+
+TEST(SimpleValueTest, TextStringEncodings) {
+    EXPECT_EQ("\x60"s, Tstr("").toString());
+    EXPECT_EQ("\x61\x61"s, Tstr("a").toString());
+    EXPECT_EQ("\x61\x41"s, Tstr("A").toString());
+    EXPECT_EQ("\x64\x49\x45\x54\x46"s, Tstr("IETF").toString());
+    EXPECT_EQ("\x62\x22\x5c"s, Tstr("\"\\").toString());
+    EXPECT_EQ("\x62\xc3\xbc"s, Tstr("\xc3\xbc").toString());
+    EXPECT_EQ("\x63\xe6\xb0\xb4"s, Tstr("\xe6\xb0\xb4").toString());
+    EXPECT_EQ("\x64\xf0\x90\x85\x91"s, Tstr("\xf0\x90\x85\x91").toString());
+    EXPECT_EQ("\x64\x01\x02\x03\x04"s, Tstr("\x01\x02\x03\x04").toString());
+}
+
+TEST(IsIteratorPairOverTest, All) {
+    EXPECT_TRUE((
+            details::is_iterator_pair_over<pair<string::iterator, string::iterator>, char>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<string::const_iterator, string::iterator>,
+                                                char>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<string::iterator, string::const_iterator>,
+                                                char>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, char*>, char>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<const char*, char*>, char>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<char*, const char*>, char>::value));
+    EXPECT_FALSE((details::is_iterator_pair_over<pair<string::iterator, string::iterator>,
+                                                 uint8_t>::value));
+    EXPECT_FALSE((details::is_iterator_pair_over<pair<char*, char*>, uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<
+                 pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<
+                 pair<vector<uint8_t>::const_iterator, vector<uint8_t>::iterator>,
+                 uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<
+                 pair<vector<uint8_t>::iterator, vector<uint8_t>::const_iterator>,
+                 uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, uint8_t*>, uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<const uint8_t*, uint8_t*>, uint8_t>::value));
+    EXPECT_TRUE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, uint8_t>::value));
+    EXPECT_FALSE((details::is_iterator_pair_over<
+                  pair<vector<uint8_t>::iterator, vector<uint8_t>::iterator>, char>::value));
+    EXPECT_FALSE((details::is_iterator_pair_over<pair<uint8_t*, const uint8_t*>, char>::value));
+}
+
+TEST(MakeEntryTest, Boolean) {
+    EXPECT_EQ("\xf4"s, details::makeItem(false)->toString());
+}
+
+TEST(MakeEntryTest, Integers) {
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint8_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint16_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint32_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<uint64_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<int8_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<int16_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<int32_t>(0))->toString());
+    EXPECT_EQ("\x00"s, details::makeItem(static_cast<int64_t>(0))->toString());
+    EXPECT_EQ("\x20"s, details::makeItem(static_cast<int8_t>(-1))->toString());
+    EXPECT_EQ("\x20"s, details::makeItem(static_cast<int16_t>(-1))->toString());
+    EXPECT_EQ("\x20"s, details::makeItem(static_cast<int32_t>(-1))->toString());
+    EXPECT_EQ("\x20"s, details::makeItem(static_cast<int64_t>(-1))->toString());
+
+    EXPECT_EQ("\x1b\xff\xff\xff\xff\xff\xff\xff\xff"s,
+              details::makeItem(static_cast<uint64_t>(std::numeric_limits<uint64_t>::max()))
+                      ->toString());
+}
+
+TEST(MakeEntryTest, StdStrings) {
+    string s1("hello");
+    const string s2("hello");
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString());  // copy of string
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s,
+              details::makeItem(s2)->toString());  // copy of const string
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s,
+              details::makeItem(std::move(s1))->toString());  // move string
+    EXPECT_EQ(0U, s1.size());                                 // Prove string was moved, not copied.
+}
+
+TEST(MakeEntryTest, StdStringViews) {
+    string_view s1("hello");
+    const string_view s2("hello");
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString());
+}
+
+TEST(MakeEntryTest, CStrings) {
+    char s1[] = "hello";
+    const char s2[] = "hello";
+    const char* s3 = "hello";
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s3)->toString());
+}
+
+TEST(MakeEntryTest, StringIteratorPairs) {
+    // Use iterators from string to prove that "real" iterators work
+    string s1 = "hello"s;
+    pair<string::iterator, string::iterator> p1 = make_pair(s1.begin(), s1.end());
+
+    const pair<string::iterator, string::iterator> p2 = p1;
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p1)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p2)->toString());
+
+    // Use char*s  as iterators
+    const char* s2 = "hello";
+    pair p3 = make_pair(s2, s2 + 5);
+    const pair p4 = p3;
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p3)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p4)->toString());
+}
+
+TEST(MakeEntryTest, ByteStrings) {
+    vector<uint8_t> v1 = {0x00, 0x01, 0x02};
+    const vector<uint8_t> v2 = {0x00, 0x01, 0x02};
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v1)->toString());  // copy of vector
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v2)->toString());  // copy of const vector
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(std::move(v1))->toString());  // move vector
+    EXPECT_EQ(0U, v1.size());  // Prove vector was moved, not copied.
+}
+
+TEST(MakeEntryTest, ByteStringIteratorPairs) {
+    using vec = vector<uint8_t>;
+    using iter = vec::iterator;
+    vec v1 = {0x00, 0x01, 0x02};
+    pair<iter, iter> p1 = make_pair(v1.begin(), v1.end());
+    const pair<iter, iter> p2 = make_pair(v1.begin(), v1.end());
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p1)->toString());
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p2)->toString());
+
+    // Use uint8_t*s as iterators
+    uint8_t v2[] = {0x00, 0x01, 0x02};
+    uint8_t* v3 = v2;
+    pair<uint8_t*, uint8_t*> p3 = make_pair(v2, v2 + 3);
+    const pair<uint8_t*, uint8_t*> p4 = make_pair(v2, v2 + 3);
+    pair<uint8_t*, uint8_t*> p5 = make_pair(v3, v3 + 3);
+    const pair<uint8_t*, uint8_t*> p6 = make_pair(v3, v3 + 3);
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p3)->toString());
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p4)->toString());
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p5)->toString());
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p6)->toString());
+}
+
+TEST(MakeEntryTest, ByteStringBuffers) {
+    uint8_t v1[] = {0x00, 0x01, 0x02};
+    EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(make_pair(v1, 3))->toString());
+}
+
+TEST(MakeEntryTest, ItemPointer) {
+    Uint* p1 = new Uint(0);
+    EXPECT_EQ("\x00"s, details::makeItem(p1)->toString());
+    EXPECT_EQ("\x60"s, details::makeItem(new Tstr(string()))->toString());
+}
+
+TEST(MakeEntryTest, ItemReference) {
+    Tstr str("hello"s);
+    Tstr& strRef = str;
+    const Tstr& strConstRef = str;
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(str)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strRef)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strConstRef)->toString());
+    EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(std::move(str))->toString());
+    EXPECT_EQ("\x60"s, details::makeItem(str)->toString());  // Prove that it moved
+
+    EXPECT_EQ("\x00"s, details::makeItem(Uint(0))->toString());
+
+    EXPECT_EQ("\x43\x00\x01\x02"s,
+              details::makeItem(Bstr(vector<uint8_t>{0x00, 0x01, 0x02}))->toString());
+
+    EXPECT_EQ("\x80"s, details::makeItem(Array())->toString());
+    EXPECT_EQ("\xa0"s, details::makeItem(Map())->toString());
+}
+
+TEST(CompoundValueTest, ArrayOfInts) {
+    EXPECT_EQ("\x80"s, Array().toString());
+    Array(Uint(0)).toString();
+
+    EXPECT_EQ("\x81\x00"s, Array(Uint(0U)).toString());
+    EXPECT_EQ("\x82\x00\x01"s, Array(Uint(0), Uint(1)).toString());
+    EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(Uint(0), Uint(1), Nint(-99)).toString());
+
+    EXPECT_EQ("\x81\x00"s, Array(0).toString());
+    EXPECT_EQ("\x82\x00\x01"s, Array(0, 1).toString());
+    EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(0, 1, -99).toString());
+}
+
+TEST(CompoundValueTest, MapOfInts) {
+    EXPECT_EQ("\xA0"s, Map().toString());
+    EXPECT_EQ("\xA1\x00\x01"s, Map(Uint(0), Uint(1)).toString());
+    // Maps with an odd number of arguments will fail to compile.  Uncomment the next lines to test.
+    // EXPECT_EQ("\xA1\x00"s, Map(Int(0)).toString());
+    // EXPECT_EQ("\xA1\x00\x01\x02"s, Map(Int(0), Int(1), Int(2)).toString());
+}
+
+TEST(CompoundValueTest, MixedArray) {
+    vector<uint8_t> vec = {3, 2, 1};
+    EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+              Array(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString());
+
+    EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+              Array(1, -1, vec, "hello").toString());
+}
+
+TEST(CompoundValueTest, MixedMap) {
+    vector<uint8_t> vec = {3, 2, 1};
+    EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+              Map(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString());
+
+    EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s,
+              Map(1, -1, vec, "hello").toString());
+}
+
+TEST(CompoundValueTest, NestedStructures) {
+    vector<uint8_t> vec = {3, 2, 1};
+
+    string expectedEncoding =
+            "\xA2\x66\x4F\x75\x74\x65\x72\x31\x82\xA2\x66\x49\x6E\x6E\x65\x72\x31\x18\x63\x66\x49"
+            "\x6E"
+            "\x6E\x65\x72\x32\x43\x03\x02\x01\x63\x66\x6F\x6F\x66\x4F\x75\x74\x65\x72\x32\x0A"s;
+
+    // Do it with explicitly-created Items
+    EXPECT_EQ(expectedEncoding,
+              Map(Tstr("Outer1"),
+                  Array(  //
+                          Map(Tstr("Inner1"), Uint(99), Tstr("Inner2"), Bstr(vec)), Tstr("foo")),
+                  Tstr("Outer2"),  //
+                  Uint(10))
+                      .toString());
+    EXPECT_EQ(3U, vec.size());
+
+    // Now just use convertible types
+    EXPECT_EQ(expectedEncoding, Map("Outer1",
+                                    Array(Map("Inner1", 99,  //
+                                              "Inner2", vec),
+                                          "foo"),
+                                    "Outer2", 10)
+                                        .toString());
+    EXPECT_EQ(3U, vec.size());
+
+    // Finally, do it with the .add() method.  This is slightly less efficient, but has the
+    // advantage you can build a structure up incrementally, or somewhat fluently if you like.
+    // First, fluently.
+    EXPECT_EQ(expectedEncoding, Map().add("Outer1", Array().add(Map()  //
+                                                                        .add("Inner1", 99)
+                                                                        .add("Inner2", vec))
+                                                            .add("foo"))
+                                        .add("Outer2", 10)
+                                        .toString());
+    EXPECT_EQ(3U, vec.size());
+
+    // Next, more incrementally
+    Array arr;
+    arr.add(Map()  //
+                    .add("Inner1", 99)
+                    .add("Inner2", vec))
+            .add("foo");
+    EXPECT_EQ(3U, vec.size());
+
+    Map m;
+    m.add("Outer1", std::move(arr));  // Moving is necessary; Map and Array cannot be copied.
+    m.add("Outer2", 10);
+    auto s = m.toString();
+    EXPECT_EQ(expectedEncoding, s);
+}
+
+TEST(EncodingMethodsTest, AllVariants) {
+    Map map;
+    map.add("key1", Array().add(Map()  //
+                                        .add("key_a", 9999999)
+                                        .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03})
+                                        .add("key_c", std::numeric_limits<uint64_t>::max())
+                                        .add("key_d", std::numeric_limits<int16_t>::min()))
+                            .add("foo"))
+            .add("key2", true);
+
+    std::vector<uint8_t> buf;
+    buf.resize(map.encodedSize());
+
+    EXPECT_EQ(buf.data() + buf.size(), map.encode(buf.data(), buf.data() + buf.size()));
+
+    EXPECT_EQ(buf, map.encode());
+
+    std::vector<uint8_t> buf2;
+    map.encode(std::back_inserter(buf2));
+    EXPECT_EQ(buf, buf2);
+
+    auto iter = buf.begin();
+    map.encode([&](uint8_t c) { EXPECT_EQ(c, *iter++); });
+}
+
+TEST(EncodingMethodsTest, UintWithTooShortBuf) {
+    Uint val(100000);
+    vector<uint8_t> buf(val.encodedSize() - 1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, TstrWithTooShortBuf) {
+    Tstr val("01234567890123456789012345"s);
+    vector<uint8_t> buf(1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+
+    buf.resize(val.encodedSize() - 1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, BstrWithTooShortBuf) {
+    Bstr val("01234567890123456789012345"s);
+    vector<uint8_t> buf(1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+
+    buf.resize(val.encodedSize() - 1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, ArrayWithTooShortBuf) {
+    Array val("a", 5, -100);
+
+    std::vector<uint8_t> buf(val.encodedSize() - 1);
+    EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EncodingMethodsTest, MapWithTooShortBuf) {
+    Map map;
+    map.add("key1", Array().add(Map()  //
+                                        .add("key_a", 99)
+                                        .add("key_b", std::vector<uint8_t>{0x01, 0x02, 0x03}))
+                            .add("foo"))
+            .add("key2", true);
+
+    std::vector<uint8_t> buf(map.encodedSize() - 1);
+    EXPECT_EQ(nullptr, map.encode(buf.data(), buf.data() + buf.size()));
+}
+
+TEST(EqualityTest, Uint) {
+    Uint val(99);
+    EXPECT_EQ(val, Uint(99));
+
+    EXPECT_NE(val, Uint(98));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("99"));
+    EXPECT_NE(val, Bool(false));
+    EXPECT_NE(val, Array(99, 1));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Nint) {
+    Nint val(-1);
+    EXPECT_EQ(val, Nint(-1));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("99"));
+    EXPECT_NE(val, Bool(false));
+    EXPECT_NE(val, Array(99));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Tstr) {
+    Tstr val("99");
+    EXPECT_EQ(val, Tstr("99"));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("98"));
+    EXPECT_NE(val, Bstr("99"));
+    EXPECT_NE(val, Bool(false));
+    EXPECT_NE(val, Array(99, 1));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Bstr) {
+    Bstr val("99");
+    EXPECT_EQ(val, Bstr("99"));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("98"));
+    EXPECT_NE(val, Bool(false));
+    EXPECT_NE(val, Array(99, 1));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Bool) {
+    Bool val(false);
+    EXPECT_EQ(val, Bool(false));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("98"));
+    EXPECT_NE(val, Bool(true));
+    EXPECT_NE(val, Array(99, 1));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Array) {
+    Array val(99, 1);
+    EXPECT_EQ(val, Array(99, 1));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("98"));
+    EXPECT_NE(val, Bool(true));
+    EXPECT_NE(val, Array(99, 2));
+    EXPECT_NE(val, Array(98, 1));
+    EXPECT_NE(val, Array(99, 1, 2));
+    EXPECT_NE(val, Map(99, 1));
+}
+
+TEST(EqualityTest, Map) {
+    Map val(99, 1);
+    EXPECT_EQ(val, Map(99, 1));
+
+    EXPECT_NE(val, Uint(99));
+    EXPECT_NE(val, Nint(-1));
+    EXPECT_NE(val, Nint(-4));
+    EXPECT_NE(val, Tstr("99"));
+    EXPECT_NE(val, Bstr("98"));
+    EXPECT_NE(val, Bool(true));
+    EXPECT_NE(val, Array(99, 1));
+    EXPECT_NE(val, Map(99, 2));
+    EXPECT_NE(val, Map(99, 1, 99, 2));
+}
+
+TEST(ConvertTest, Uint) {
+    unique_ptr<Item> item = details::makeItem(10);
+
+    EXPECT_EQ(UINT, item->type());
+    EXPECT_NE(nullptr, item->asInt());
+    EXPECT_NE(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ(10, item->asInt()->value());
+    EXPECT_EQ(10, item->asUint()->value());
+}
+
+TEST(ConvertTest, Nint) {
+    unique_ptr<Item> item = details::makeItem(-10);
+
+    EXPECT_EQ(NINT, item->type());
+    EXPECT_NE(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_NE(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ(-10, item->asInt()->value());
+    EXPECT_EQ(-10, item->asNint()->value());
+}
+
+TEST(ConvertTest, Tstr) {
+    unique_ptr<Item> item = details::makeItem("hello");
+
+    EXPECT_EQ(TSTR, item->type());
+    EXPECT_EQ(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_NE(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ("hello"s, item->asTstr()->value());
+}
+
+TEST(ConvertTest, Bstr) {
+    vector<uint8_t> vec{0x23, 0x24, 0x22};
+    unique_ptr<Item> item = details::makeItem(vec);
+
+    EXPECT_EQ(BSTR, item->type());
+    EXPECT_EQ(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_NE(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ(vec, item->asBstr()->value());
+}
+
+TEST(ConvertTest, Bool) {
+    unique_ptr<Item> item = details::makeItem(false);
+
+    EXPECT_EQ(SIMPLE, item->type());
+    EXPECT_EQ(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_NE(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ(BOOLEAN, item->asSimple()->simpleType());
+    EXPECT_NE(nullptr, item->asSimple()->asBool());
+
+    EXPECT_FALSE(item->asSimple()->asBool()->value());
+}
+
+TEST(ConvertTest, Map) {
+    unique_ptr<Item> item(new Map);
+
+    EXPECT_EQ(MAP, item->type());
+    EXPECT_EQ(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_NE(nullptr, item->asMap());
+    EXPECT_EQ(nullptr, item->asArray());
+
+    EXPECT_EQ(0U, item->asMap()->size());
+}
+
+TEST(ConvertTest, Array) {
+    unique_ptr<Item> item(new Array);
+
+    EXPECT_EQ(ARRAY, item->type());
+    EXPECT_EQ(nullptr, item->asInt());
+    EXPECT_EQ(nullptr, item->asUint());
+    EXPECT_EQ(nullptr, item->asNint());
+    EXPECT_EQ(nullptr, item->asTstr());
+    EXPECT_EQ(nullptr, item->asBstr());
+    EXPECT_EQ(nullptr, item->asSimple());
+    EXPECT_EQ(nullptr, item->asMap());
+    EXPECT_NE(nullptr, item->asArray());
+
+    EXPECT_EQ(0U, item->asArray()->size());
+}
+
+class MockParseClient : public ParseClient {
+  public:
+    MOCK_METHOD4(item, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+                                    const uint8_t* valueBegin, const uint8_t* end));
+    MOCK_METHOD4(itemEnd, ParseClient*(std::unique_ptr<Item>& item, const uint8_t* hdrBegin,
+                                       const uint8_t* valueBegin, const uint8_t* end));
+    MOCK_METHOD2(error, void(const uint8_t* position, const std::string& errorMessage));
+};
+
+MATCHER_P(IsType, value, std::string("Type ") + (negation ? "doesn't match" : "matches")) {
+    return arg->type() == value;
+}
+
+MATCHER_P(MatchesItem, value, "") {
+    return arg && *arg == value;
+}
+
+MATCHER_P(IsArrayOfSize, value, "") {
+    return arg->type() == ARRAY && arg->asArray()->size() == value;
+}
+
+MATCHER_P(IsMapOfSize, value, "") {
+    return arg->type() == MAP && arg->asMap()->size() == value;
+}
+
+TEST(StreamParseTest, Uint) {
+    MockParseClient mpc;
+
+    Uint val(100);
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Nint) {
+    MockParseClient mpc;
+
+    Nint val(-10);
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Bool) {
+    MockParseClient mpc;
+
+    Bool val(true);
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc));
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Tstr) {
+    MockParseClient mpc;
+
+    Tstr val("Hello");
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc));
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Bstr) {
+    MockParseClient mpc;
+
+    Bstr val("Hello");
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc));
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(_, _)).Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Array) {
+    MockParseClient mpc;
+
+    Array val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max());
+    ASSERT_NE(val[2]->asArray(), nullptr);
+    const Array& interior = *(val[2]->asArray());
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    {
+        InSequence s;
+        const uint8_t* pos = encBegin;
+        EXPECT_CALL(mpc, item(IsArrayOfSize(val.size()), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0])), pos, pos + 1, pos + 6))
+                .WillOnce(Return(&mpc));
+        pos += 6;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1])), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        const uint8_t* innerArrayBegin = pos;
+        EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8))
+                .WillOnce(Return(&mpc));
+        pos += 8;
+        EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin,
+                                 innerArrayBegin + 1, pos))
+                .WillOnce(Return(&mpc));
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[3])), pos, pos + 9, pos + 9))
+                .WillOnce(Return(&mpc));
+        EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(val.size()), encBegin, encBegin + 1, encEnd))
+                .WillOnce(Return(&mpc));
+    }
+
+    EXPECT_CALL(mpc, error(_, _))  //
+            .Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Map) {
+    MockParseClient mpc;
+
+    Map val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits<uint64_t>::max());
+    ASSERT_NE(val[1].first->asArray(), nullptr);
+    const Array& interior = *(val[1].first->asArray());
+    auto encoded = val.encode();
+    uint8_t* encBegin = encoded.data();
+    uint8_t* encEnd = encoded.data() + encoded.size();
+
+    {
+        InSequence s;
+        const uint8_t* pos = encBegin;
+        EXPECT_CALL(mpc, item(_, pos, pos + 1, pos + 1)).WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].first)), pos, pos + 1, pos + 6))
+                .WillOnce(Return(&mpc));
+        pos += 6;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].second)), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        const uint8_t* innerArrayBegin = pos;
+        EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1))
+                .WillOnce(Return(&mpc));
+        ++pos;
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8))
+                .WillOnce(Return(&mpc));
+        pos += 8;
+        EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin,
+                                 innerArrayBegin + 1, pos))
+                .WillOnce(Return(&mpc));
+        EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1].second)), pos, pos + 9, pos + 9))
+                .WillOnce(Return(&mpc));
+        EXPECT_CALL(mpc, itemEnd(IsMapOfSize(val.size()), encBegin, encBegin + 1, encEnd))
+                .WillOnce(Return(&mpc));
+    }
+
+    EXPECT_CALL(mpc, error(_, _))  //
+            .Times(0);
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(StreamParseTest, Semantic) {
+    MockParseClient mpc;
+
+    vector<uint8_t> encoded;
+    auto iter = back_inserter(encoded);
+    encodeHeader(SEMANTIC, 0, iter);
+    Uint(999).encode(iter);
+
+    EXPECT_CALL(mpc, item(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0);
+    EXPECT_CALL(mpc, error(encoded.data(), "Semantic tags not supported"));
+
+    parse(encoded.data(), encoded.data() + encoded.size(), &mpc);
+}
+
+TEST(FullParserTest, Uint) {
+    Uint val(10);
+
+    auto [item, pos, message] = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Nint) {
+    Nint val(-10);
+
+    auto [item, pos, message] = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(val));
+
+    vector<uint8_t> minNint = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+    std::tie(item, pos, message) = parse(minNint);
+    EXPECT_THAT(item, NotNull());
+    EXPECT_EQ(item->asNint()->value(), std::numeric_limits<int64_t>::min());
+}
+
+TEST(FullParserTest, NintOutOfRange) {
+    vector<uint8_t> outOfRangeNint = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+    auto [item, pos, message] = parse(outOfRangeNint);
+    EXPECT_THAT(item, IsNull());
+    EXPECT_EQ(pos, outOfRangeNint.data());
+    EXPECT_EQ(message, "NINT values that don't fit in int64_t are not supported.");
+}
+
+TEST(FullParserTest, Tstr) {
+    Tstr val("Hello");
+
+    auto [item, pos, message] = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Bstr) {
+    Bstr val("\x00\x01\0x02"s);
+
+    auto [item, pos, message] = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(val));
+}
+
+TEST(FullParserTest, Array) {
+    Array val("hello", -4, 3);
+
+    auto encoded = val.encode();
+    auto [item, pos, message] = parse(encoded);
+    EXPECT_THAT(item, MatchesItem(ByRef(val)));
+    EXPECT_EQ(pos, encoded.data() + encoded.size());
+    EXPECT_EQ("", message);
+
+    // We've already checked it all, but walk it just for fun.
+    ASSERT_NE(nullptr, item->asArray());
+    const Array& arr = *(item->asArray());
+    ASSERT_EQ(arr[0]->type(), TSTR);
+    EXPECT_EQ(arr[0]->asTstr()->value(), "hello");
+}
+
+TEST(FullParserTest, Map) {
+    Map val("hello", -4, 3, Bstr("hi"));
+
+    auto [item, pos, message] = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(ByRef(val)));
+}
+
+TEST(FullParserTest, Complex) {
+    vector<uint8_t> vec = {0x01, 0x02, 0x08, 0x03};
+    Map val("Outer1",
+            Array(Map("Inner1", 99,  //
+                      "Inner2", vec),
+                  "foo"),
+            "Outer2", 10);
+
+    std::unique_ptr<Item> item;
+    const uint8_t* pos;
+    std::string message;
+    std::tie(item, pos, message) = parse(val.encode());
+    EXPECT_THAT(item, MatchesItem(ByRef(val)));
+}
+
+TEST(FullParserTest, IncompleteUint) {
+    Uint val(1000);
+
+    auto encoding = val.encode();
+    auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+    EXPECT_EQ(nullptr, item.get());
+    EXPECT_EQ(encoding.data(), pos);
+    EXPECT_EQ("Need 2 byte(s) for length field, have 1.", message);
+}
+
+TEST(FullParserTest, IncompleteString) {
+    Tstr val("hello");
+
+    auto encoding = val.encode();
+    auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2);
+    EXPECT_EQ(nullptr, item.get());
+    EXPECT_EQ(encoding.data(), pos);
+    EXPECT_EQ("Need 5 byte(s) for text string, have 3.", message);
+}
+
+TEST(FullParserTest, ArrayWithInsufficientEntries) {
+    Array val(1, 2, 3, 4);
+
+    auto encoding = val.encode();
+    auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+    EXPECT_EQ(nullptr, item.get());
+    EXPECT_EQ(encoding.data(), pos);
+    EXPECT_EQ("Not enough entries for array.", message);
+}
+
+TEST(FullParserTest, ArrayWithTruncatedEntry) {
+    Array val(1, 2, 3, 400000);
+
+    auto encoding = val.encode();
+    auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1);
+    EXPECT_EQ(nullptr, item.get());
+    EXPECT_EQ(encoding.data() + encoding.size() - 5, pos);
+    EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message);
+}
+
+TEST(FullParserTest, MapWithTruncatedEntry) {
+    Map val(1, 2, 300000, 4);
+
+    auto encoding = val.encode();
+    auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2);
+    EXPECT_EQ(nullptr, item.get());
+    EXPECT_EQ(encoding.data() + 3, pos);
+    EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message);
+}
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/neuralnetworks/1.3/types.hal b/neuralnetworks/1.3/types.hal
index 74c259c..62c5833 100644
--- a/neuralnetworks/1.3/types.hal
+++ b/neuralnetworks/1.3/types.hal
@@ -4747,7 +4747,7 @@
     RESIZE_NEAREST_NEIGHBOR = @1.2::OperationType:RESIZE_NEAREST_NEIGHBOR,
 
     /**
-     * Quantized version of {@link OperationType:LSTM}.
+     * Quantized version of {@link OperationType::LSTM}.
      *
      * The input and the output use asymmetric quantized types, while the rest
      * use symmetric ones.
diff --git a/neuralnetworks/1.3/types.t b/neuralnetworks/1.3/types.t
index e06f5d6..0d20d06 100644
--- a/neuralnetworks/1.3/types.t
+++ b/neuralnetworks/1.3/types.t
@@ -57,6 +57,8 @@
 
 %insert Operation_1.2
 
+%insert Operation_1.3
+
     /**
      * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to
      * OEM operation and data types.
diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
index e3c5376..eced063 100644
--- a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
+++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp
@@ -500,7 +500,7 @@
 class DynamicOutputShapeTest : public GeneratedTest {};
 
 // Tag for the dynamic output shape tests
-class DISABLED_QuantizationCouplingTest : public GeneratedTest {};
+class QuantizationCouplingTest : public GeneratedTest {};
 
 TEST_P(GeneratedTest, Test) {
     Execute(kDevice, kTestModel, /*testKind=*/TestKind::GENERAL);
@@ -510,7 +510,7 @@
     Execute(kDevice, kTestModel, /*testKind=*/TestKind::DYNAMIC_SHAPE);
 }
 
-TEST_P(DISABLED_QuantizationCouplingTest, Test) {
+TEST_P(QuantizationCouplingTest, Test) {
     Execute(kDevice, kTestModel, /*testKind=*/TestKind::QUANTIZATION_COUPLING);
 }
 
@@ -520,7 +520,7 @@
 INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest,
                            [](const TestModel& testModel) { return !testModel.expectFailure; });
 
-INSTANTIATE_GENERATED_TEST(DISABLED_QuantizationCouplingTest, [](const TestModel& testModel) {
+INSTANTIATE_GENERATED_TEST(QuantizationCouplingTest, [](const TestModel& testModel) {
     return testModel.hasQuant8CoupledOperands() && testModel.operations.size() == 1;
 });