Port IdentityCredential HAL to AIDL.

This includes add a partial types-only HAL for KeyMaster for
HardwareAuthToken.

Bug: 111446262
Test: atest android.security.identity.cts
Test: VtsHalIdentityTargetTest
Test: android.hardware.identity-support-lib-test
Change-Id: I7a6254d33200bfd62269aed1957cbb2a84b16272
diff --git a/identity/aidl/Android.bp b/identity/aidl/Android.bp
new file mode 100644
index 0000000..72b19a1
--- /dev/null
+++ b/identity/aidl/Android.bp
@@ -0,0 +1,21 @@
+aidl_interface {
+    name: "android.hardware.identity",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/identity/*.aidl",
+    ],
+    imports: [
+        "android.hardware.keymaster",
+    ],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            vndk: {
+                enabled: true,
+            },
+        },
+    },
+}
diff --git a/identity/aidl/android/hardware/identity/Certificate.aidl b/identity/aidl/android/hardware/identity/Certificate.aidl
new file mode 100644
index 0000000..5bbc17c
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/Certificate.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+@VintfStability
+parcelable Certificate {
+    /**
+     * encodedCertificate contains the bytes of a DER-encoded X.509 certificate.
+     *
+     * If there is no certificate, this array is empty.
+     */
+    byte[] encodedCertificate;
+}
diff --git a/identity/aidl/android/hardware/identity/CipherSuite.aidl b/identity/aidl/android/hardware/identity/CipherSuite.aidl
new file mode 100644
index 0000000..20b02a8
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/CipherSuite.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Cipher suites that can be used for communication between holder and reader devices.
+ */
+@VintfStability
+@Backing(type="int")
+enum CipherSuite {
+    /**
+     * Specifies that the cipher suite that will be used to secure communications between the reader
+     * is:
+     *
+     * - 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.
+     *
+     * At present this is the only supported cipher suite and it is mandatory for all
+     * implementations to support it.
+     */
+    CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1,
+}
diff --git a/identity/aidl/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/android/hardware/identity/HardwareInformation.aidl
new file mode 100644
index 0000000..d67739d
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/HardwareInformation.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+@VintfStability
+parcelable HardwareInformation {
+    /**
+     * credentialStoreName is the name of the credential store implementation.
+     */
+    @utf8InCpp String credentialStoreName;
+
+    /**
+     * credentialStoreAuthorName is the name of the credential store author.
+     */
+    @utf8InCpp String credentialStoreAuthorName;
+
+    /**
+     * dataChunkSize is the size of data chunks to be used when sending and recieving data
+     * entries. All data chunks for a data item must be this size except for the last.
+     */
+    int dataChunkSize;
+
+    /**
+     * isDirectAccess specifies whether the provisioned credential is available through
+     * direct access. Credentials provisioned in credential stores with this set
+     * to true, should use reader authentication on all data elements.
+     */
+    boolean isDirectAccess;
+
+    /**
+     * supportedDocTypes if empty, then any document type is supported, otherwise
+     * only the document types returned are supported.
+     *
+     * Document types are defined in the relevant standard for the document, for example for the
+     * for Mobile Driving License as defined by ISO 18013-5 the document type is defined to
+     * be "org.iso.18013.5.1.mDL".
+     *
+     */
+    @utf8InCpp String[] supportedDocTypes;
+}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
new file mode 100644
index 0000000..10ce4c2
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -0,0 +1,318 @@
+/*
+ * 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;
+
+import android.hardware.identity.Certificate;
+import android.hardware.identity.SecureAccessControlProfile;
+import android.hardware.keymaster.HardwareAuthToken;
+
+@VintfStability
+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 a COSE_Sign1 signature described above.
+     */
+    byte[] deleteCredential();
+
+    /**
+     * 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, STATUS_FAILED
+     * will be returned.
+     *
+     * @return the unencrypted key-pair in PKCS#8 format.
+     */
+    byte[] createEphemeralKeyPair();
+
+    /**
+     * 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, STATUS_FAILED
+     * will be returned.
+     *
+     * @param publicKey contains the reader's ephemeral public key, in uncompressed form.
+     */
+    void setReaderEphemeralPublicKey(in byte[] publicKey);
+
+    /**
+     * 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, STATUS_FAILED
+     * will be returned.
+     *
+     * @return challenge, a non-zero number.
+     */
+    long createAuthChallenge();
+
+    /**
+     * 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 STATUS_SESSION_TRANSCRIPT_MISMATCH error.
+     *
+     * If the provided authToken is not valid this method fails with STATUS_INVALID_AUTH_TOKEN.
+     *
+     * Each of the provided accessControlProfiles is checked in this call. If they are not
+     * all valid, the call fails with STATUS_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
+     * STATUS_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
+     * STATUS_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 STATUS_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
+     * STATUS_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.
+     */
+    void startRetrieval(in SecureAccessControlProfile[] accessControlProfiles,
+        in HardwareAuthToken authToken,
+        in byte[] itemsRequest,
+        in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
+
+    /**
+     * 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 STATUS_NOT_IN_REQUEST_MESSAGE.
+     *
+     * If nameSpace or name is empty this call fails with STATUS_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 STATUS_USER_AUTHENTICATION_FAILED. If reader authentication is required and
+     * a suitable reader certificate chain isn't presented, the call fails with
+     * STATUS_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 STATUS_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 STATUS_INVALID_DATA.
+     */
+    void startRetrieveEntryValue(in @utf8InCpp String nameSpace, in @utf8InCpp String name,
+                                 in int entrySize, in int[] accessControlProfileIds);
+
+    /**
+     * 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 STATUS_INVALID_DATA.
+     *
+     * @param encryptedContent contains the encrypted and MACed content.
+     *
+     * @return the entry value as CBOR, or part of the entry value in the case the
+     *    content exceeds gcmChunkSize in length.
+     */
+    byte[] retrieveEntryValue(in byte[] encryptedContent);
+
+    /**
+     * 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 STATUS_INVALID_DATA.
+     *
+     * @param out 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
+     *
+     *
+     * @param out deviceNameSpaces the bytes of DeviceNameSpaces.
+     */
+    void finishRetrieval(in byte[] signingKeyBlob, out byte[] mac, out byte[] deviceNameSpaces);
+
+    /**
+     * Generate a key pair to be used for signing session data and retrieved data items.
+     *
+     * @param out signingKeyBlob contains an encrypted copy of the newly-generated private
+     *     signing key.
+     *
+     * @return an X.509 certificate for the new signing key, signed by the credential key.
+     */
+    Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
new file mode 100644
index 0000000..23cb1b7
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -0,0 +1,239 @@
+/*
+ * 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;
+
+import android.hardware.identity.IIdentityCredential;
+import android.hardware.identity.IWritableIdentityCredential;
+import android.hardware.identity.HardwareInformation;
+import android.hardware.identity.CipherSuite;
+
+/**
+ * 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).
+ *
+ * All binder calls in the HAL may return a ServiceSpecificException with statuses from the
+ * STATUS_* integers defined in this interface. Each method states which status can be returned
+ * and under which circumstances.
+ */
+@VintfStability
+interface IIdentityCredentialStore {
+    /**
+     * Success.
+     */
+    const int STATUS_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.
+     */
+    const int STATUS_FAILED = 1;
+
+    /**
+     * Unsupported cipher suite.
+     */
+    const int STATUS_CIPHER_SUITE_NOT_SUPPORTED = 2;
+
+    /**
+     * The passed data was invalid. This is a generic catch all for errors that don't belong
+     * in other categories related to parameter validation.
+     */
+    const int STATUS_INVALID_DATA = 3;
+
+    /**
+     * The authToken parameter passed to IIdentityCredential.startRetrieval() is not valid.
+     */
+    const int STATUS_INVALID_AUTH_TOKEN = 4;
+
+    /**
+     * The itemsRequest parameter passed to IIdentityCredential.startRetrieval() does not meet
+     * the requirements described in the documentation for that method.
+     */
+    const int STATUS_INVALID_ITEMS_REQUEST_MESSAGE = 5;
+
+    /**
+     * The readerSignature parameter in IIdentityCredential.startRetrieval() is invalid,
+     * doesn't contain an embedded certificate chain, or the signature failed to
+     * validate.
+     */
+    const int STATUS_READER_SIGNATURE_CHECK_FAILED = 6;
+
+    /**
+     * The sessionTranscript passed to startRetrieval() did not contain the ephmeral public
+     * key returned by createEphemeralPublicKey().
+     */
+    const int STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 7;
+
+    /**
+     * An access condition related to user authentication was not satisfied.
+     */
+    const int STATUS_USER_AUTHENTICATION_FAILED = 8;
+
+    /**
+     * An access condition related to reader authentication was not satisfied.
+     */
+    const int STATUS_READER_AUTHENTICATION_FAILED = 9;
+
+    /**
+     * The request data element has no access control profiles associated so it cannot be accessed.
+     */
+    const int STATUS_NO_ACCESS_CONTROL_PROFILES = 10;
+
+    /**
+     * The requested data element is not in the provided non-empty itemsRequest message.
+     */
+    const int STATUS_NOT_IN_REQUEST_MESSAGE = 11;
+
+    /**
+     * The passed-in sessionTranscript doesn't match the previously passed-in sessionTranscript.
+     */
+    const int STATUS_SESSION_TRANSCRIPT_MISMATCH = 12;
+
+    /**
+     * Returns information about hardware.
+     *
+     * @return a HardwareInformation with information about the hardware.
+     */
+    HardwareInformation getHardwareInformation();
+
+    /**
+     * 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 an IWritableIdentityCredential interface that provides operations to
+     *     provision a credential.
+     */
+    IWritableIdentityCredential createCredential(in @utf8InCpp String docType,
+                                                 in boolean testCredential);
+
+    /**
+     * getCredential retrieves an IIdentityCredential 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.
+     *
+     * This method fails with STATUS_INVALID_DATA if the passed in credentialData cannot be
+     * decoded or decrypted.
+     *
+     * @param cipherSuite is the cipher suite to use.
+     *
+     * @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 an IIdentityCredential HIDL interface that provides operations on the Credential.
+     */
+    IIdentityCredential getCredential(in CipherSuite cipherSuite, in byte[] credentialData);
+}
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
new file mode 100644
index 0000000..483b0c7
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -0,0 +1,252 @@
+/*
+ * 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;
+
+import android.hardware.identity.Certificate;
+import android.hardware.identity.SecureAccessControlProfile;
+
+/**
+ * IWritableIdentityCredential is used to personalize a new identity credential.  Credentials cannot
+ * be updated or modified after creation; any changes require deletion and re-creation.
+ */
+@VintfStability
+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) with the
+     * following additional requirements:
+     *
+     *  - The attestationVersion field in the attestation extension must be at least 3.
+     *
+     *  - The attestationSecurityLevel field must be set to either Software (0),
+     *    TrustedEnvironment (1), or StrongBox (2) depending on how attestation is
+     *    implemented. Only the default AOSP implementation of this HAL may use
+     *    value 0 (additionally, this implementation must not be used on production
+     *    devices).
+     *
+     *  - The keymasterVersion field in the attestation extension must be set to (10*major + minor)
+     *    where major and minor are the Identity Credential interface major and minor versions.
+     *    Specifically for this version of the interface (1.0) this value is 10.
+     *
+     *  - The keymasterSecurityLevel field in the attestation extension must be set to
+     *    either Software (0), TrustedEnvironment (1), or StrongBox (2) depending on how
+     *    the Trusted Application backing the HAL implementation is implemented. Only
+     *    the default AOSP implementation of this HAL may use value 0 (additionally, this
+     *    implementation must not be used on production devices)
+     *
+     *  - The attestationChallenge field must be set to the passed-in challenge.
+     *
+     *  - The uniqueId field must be empty.
+     *
+     *  - The softwareEnforced field in the attestation extension must include
+     *    Tag::ATTESTATION_APPLICATION_ID which must be set to the bytes of the passed-in
+     *    attestationApplicationId.
+     *
+     *  - The teeEnforced field in the attestation extension must include
+     *    Tag::IDENTITY_CREDENTIAL_KEY. This tag indicates that the 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).
+     *
+     * Additional authorizations may be needed in the softwareEnforced and teeEnforced
+     * fields - the above is not an exhaustive list.
+     *
+     * @param attestationApplicationId is the DER encoded value to be stored
+     *     in Tag::ATTESTATION_APPLICATION_ID. This schema is described in
+     *     https://developer.android.com/training/articles/security-key-attestation#certificate_schema_attestationid
+     *
+     * @param attestationChallenge a challenge set by the issuer to ensure freshness.
+     *
+     * @return the X.509 certificate chain for the credentialKey
+     */
+    Certificate[] getAttestationCertificate(in byte[] attestationApplicationId, in byte[] attestationChallenge);
+
+    /**
+     * 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.
+     */
+    void startPersonalization(in int accessControlProfileCount, in int[] entryCounts);
+
+    /**
+     * 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 STATUS_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
+     *     STATUS_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 STATUS_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
+     *     STATUS_INVALID_DATA.
+     *
+     * @return a structure with the passed-in data and MAC created with storageKey for authenticating
+     *     the data at a later point in time.
+     */
+    SecureAccessControlProfile addAccessControlProfile(in int id, in Certificate readerCertificate,
+        in boolean userAuthenticationRequired, in long timeoutMillis, in long secureUserId);
+
+    /**
+     * 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 STATUS_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 STATUS_INVALID_DATA.
+     */
+    void beginAddEntry(in int[] accessControlProfileIds, in @utf8InCpp String nameSpace,
+        in @utf8InCpp String name, in int entrySize);
+
+    /**
+     * 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 STATUS_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 the encrypted and MACed content.  For directly-available credentials the contents are
+     *     implementation-defined. For other credentials, the result contains
+     *
+     *         AES-GCM-ENC(storageKey, R, Data, AdditionalData)
+     *
+     *     where:
+     *
+     *         Data = any   ; value
+     *
+     *         AdditionalData = {
+     *             "Namespace" : tstr,
+     *             "Name" : tstr,
+     *             "AccessControlProfileIds" : [ + uint ],
+     *         }
+     */
+    byte[] addEntryValue(in byte[] content);
+
+    /**
+     * 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.
+     *
+     * @param out 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
+     *         ]
+     *
+     * @param out 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), 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 ],
+     *          }
+     */
+    void finishAddingEntries(out byte[] credentialData,
+        out byte[] proofOfProvisioningSignature);
+}
diff --git a/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl
new file mode 100644
index 0000000..01d312d
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import android.hardware.identity.Certificate;
+
+@VintfStability
+parcelable SecureAccessControlProfile {
+    /**
+     * id is a numeric identifier that must be unique within the context of a Credential and may be
+     * used to reference the profile.
+     */
+    int 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.
+     */
+    Certificate readerCertificate;
+
+    /**
+     * if true, the user is required to authenticate to allow requests.  Required authentication
+     * fressness is specified by timeout below.
+     *
+     */
+    boolean 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.
+     */
+    long 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.
+     */
+    long 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
+     *          )
+     *      }
+     */
+    byte[] mac;
+}
+
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
new file mode 100644
index 0000000..2eb0faa
--- /dev/null
+++ b/identity/aidl/default/Android.bp
@@ -0,0 +1,29 @@
+cc_binary {
+    name: "android.hardware.identity-service.example",
+    relative_install_path: "hw",
+    init_rc: ["identity-default.rc"],
+    vintf_fragments: ["identity-default.xml"],
+    vendor: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcppbor",
+        "libcrypto",
+        "liblog",
+        "libutils",
+        "android.hardware.identity-support-lib",
+        "android.hardware.identity-ndk_platform",
+        "android.hardware.keymaster-ndk_platform",
+    ],
+    srcs: [
+        "IdentityCredential.cpp",
+        "IdentityCredentialStore.cpp",
+        "WritableIdentityCredential.cpp",
+        "Util.cpp",
+        "service.cpp",
+    ],
+}
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
new file mode 100644
index 0000000..d5b3a0f
--- /dev/null
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -0,0 +1,768 @@
+/*
+ * 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 "Util.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::Timestamp;
+using ::std::optional;
+
+using namespace ::android::hardware::identity;
+
+int IdentityCredential::initialize() {
+    auto [item, _, message] = cppbor::parse(credentialData_);
+    if (item == nullptr) {
+        LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+        return IIdentityCredentialStore::STATUS_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 IIdentityCredentialStore::STATUS_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 IIdentityCredentialStore::STATUS_INVALID_DATA;
+    }
+
+    docType_ = docTypeItem->value();
+    testCredential_ = testCredentialItem->value();
+
+    vector<uint8_t> hardwareBoundKey;
+    if (testCredential_) {
+        hardwareBoundKey = support::getTestHardwareBoundKey();
+    } else {
+        hardwareBoundKey = 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 IIdentityCredentialStore::STATUS_INVALID_DATA;
+    }
+
+    auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
+    if (dckItem == nullptr) {
+        LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
+        return IIdentityCredentialStore::STATUS_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 IIdentityCredentialStore::STATUS_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 IIdentityCredentialStore::STATUS_INVALID_DATA;
+    }
+    storageKey_ = storageKeyItem->value();
+    credentialPrivKey_ = credentialPrivKeyItem->value();
+
+    return IIdentityCredentialStore::STATUS_OK;
+}
+
+ndk::ScopedAStatus IdentityCredential::deleteCredential(
+        vector<int8_t>* outProofOfDeletionSignature) {
+    cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
+    vector<uint8_t> proofOfDeletion = array.encode();
+
+    optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
+                                                                 proofOfDeletion,  // payload
+                                                                 {},               // additionalData
+                                                                 {});  // certificateChain
+    if (!signature) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+    }
+
+    *outProofOfDeletionSignature = byteStringToSigned(signature.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<int8_t>* outKeyPair) {
+    optional<vector<uint8_t>> kp = support::createEcKeyPair();
+    if (!kp) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
+    }
+
+    // Stash public key of this key-pair for later check in startRetrieval().
+    optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
+    if (!publicKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error getting public part of ephemeral key pair"));
+    }
+    ephemeralPublicKey_ = publicKey.value();
+
+    *outKeyPair = byteStringToSigned(kp.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::setReaderEphemeralPublicKey(
+        const vector<int8_t>& publicKey) {
+    readerPublicKey_ = byteStringToUnsigned(publicKey);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
+    uint64_t challenge = 0;
+    while (challenge == 0) {
+        optional<vector<uint8_t>> bytes = support::getRandom(8);
+        if (!bytes) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED,
+                    "Error getting random data for challenge"));
+        }
+
+        challenge = 0;
+        for (size_t n = 0; n < bytes.value().size(); n++) {
+            challenge |= ((bytes.value())[n] << (n * 8));
+        }
+    }
+
+    *outChallenge = challenge;
+    return ndk::ScopedAStatus::ok();
+}
+
+// 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(
+            byteStringToUnsigned(profile.readerCertificate.encodedCertificate));
+    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);
+    Timestamp ts;
+    ts.milliSeconds = time.tv_sec * 1000 + time.tv_nsec / 1000000;
+    return ts;
+}
+
+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 != int64_t(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.milliSeconds > now.milliSeconds) {
+        LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
+                   << ") is in the future (now: " << now.milliSeconds << ")";
+        return false;
+    }
+    if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
+        LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
+                   << profile.timeoutMillis << " = "
+                   << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
+                   << ") is in the past (now: " << now.milliSeconds << ")";
+        return false;
+    }
+    return true;
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieval(
+        const vector<SecureAccessControlProfile>& accessControlProfiles,
+        const HardwareAuthToken& authToken, const vector<int8_t>& itemsRequestS,
+        const vector<int8_t>& sessionTranscriptS, const vector<int8_t>& readerSignatureS,
+        const vector<int32_t>& requestCounts) {
+    auto sessionTranscript = byteStringToUnsigned(sessionTranscriptS);
+    auto itemsRequest = byteStringToUnsigned(itemsRequestS);
+    auto readerSignature = byteStringToUnsigned(readerSignatureS);
+
+    if (sessionTranscript.size() > 0) {
+        auto [item, _, message] = cppbor::parse(sessionTranscript);
+        if (item == nullptr) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "SessionTranscript contains invalid CBOR"));
+        }
+        sessionTranscriptItem_ = std::move(item);
+    }
+    if (numStartRetrievalCalls_ > 0) {
+        if (sessionTranscript_ != sessionTranscript) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
+                    "Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
+        }
+    }
+    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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                    "Unable to get reader certificate chain from COSE_Sign1"));
+        }
+
+        if (!support::certificateChainValidate(readerCertificateChain.value())) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                    "Error validating reader certificate chain"));
+        }
+
+        optional<vector<uint8_t>> readerPublicKey =
+                support::certificateChainGetTopMostKey(readerCertificateChain.value());
+        if (!readerPublicKey) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                    "Unable to get public key from reader certificate chain"));
+        }
+
+        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())) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+                    "readerSignature check failed"));
+        }
+    }
+
+    // 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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                    "SessionTranscript is not an array with two items"));
+        }
+        const cppbor::Semantic* taggedEncodedDE = (*array)[0]->asSemantic();
+        if (taggedEncodedDE == nullptr || taggedEncodedDE->value() != 24) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                    "First item in SessionTranscript array is not a "
+                    "semantic with value 24"));
+        }
+        const cppbor::Bstr* encodedDE = (taggedEncodedDE->child())->asBstr();
+        if (encodedDE == nullptr) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                    "Child of semantic in first item in SessionTranscript "
+                    "array is not a bstr"));
+        }
+        const vector<uint8_t>& bytesDE = encodedDE->value();
+
+        auto [getXYSuccess, ePubX, ePubY] = support::ecPublicKeyGetXandY(ephemeralPublicKey_);
+        if (!getXYSuccess) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND,
+                    "Error extracting X and Y from ePub"));
+        }
+        if (sessionTranscript.size() > 0 &&
+            !(memmem(bytesDE.data(), bytesDE.size(), ePubX.data(), ePubX.size()) != nullptr &&
+              memmem(bytesDE.data(), bytesDE.size(), ePubY.data(), ePubY.size()) != nullptr)) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_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)"));
+        }
+    }
+
+    // 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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+                    "Error decoding CBOR in itemsRequest"));
+        }
+
+        // 2. The CBOR structure must be a map.
+        const cppbor::Map* map = item->asMap();
+        if (map == nullptr) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+                    "itemsRequest is not a CBOR map"));
+        }
+
+        // 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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+                    "No nameSpaces map in top-most map"));
+        }
+
+        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) {
+                return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                        IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+                        "Type mismatch in nameSpaces map"));
+            }
+            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) {
+                    return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                            IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
+                            "Type mismatch in value in nameSpaces map"));
+                }
+                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 (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Error checking MAC for profile"));
+        }
+        int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
+        if (profile.userAuthenticationRequired) {
+            if (!haveAuthToken || !checkUserAuthentication(profile, authToken, authChallenge_)) {
+                accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
+            }
+        } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
+            if (!readerCertificateChain ||
+                !checkReaderAuthentication(profile, readerCertificateChain.value())) {
+                accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
+            }
+        }
+        profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
+    }
+
+    deviceNameSpacesMap_ = cppbor::Map();
+    currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
+
+    requestCountsRemaining_ = requestCounts;
+    currentNameSpace_ = "";
+
+    itemsRequest_ = itemsRequest;
+
+    numStartRetrievalCalls_ += 1;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
+        const string& nameSpace, const string& name, int32_t entrySize,
+        const vector<int32_t>& accessControlProfileIds) {
+    if (name.empty()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Name cannot be empty"));
+    }
+    if (nameSpace.empty()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Name space cannot be empty"));
+    }
+
+    if (requestCountsRemaining_.size() == 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "No more name spaces left to go through"));
+    }
+
+    if (currentNameSpace_ == "") {
+        // First call.
+        currentNameSpace_ = nameSpace;
+    }
+
+    if (nameSpace == currentNameSpace_) {
+        // Same namespace.
+        if (requestCountsRemaining_[0] == 0) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "No more entries to be retrieved in current name space"));
+        }
+        requestCountsRemaining_[0] -= 1;
+    } else {
+        // New namespace.
+        if (requestCountsRemaining_[0] != 0) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Moved to new name space but one or more entries need to be retrieved "
+                    "in current name space"));
+        }
+        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()) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+                    "Name space was not requested in startRetrieval"));
+        }
+        const auto& dataItemNames = it->second;
+        if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
+                    "Data item name in name space was not requested in startRetrieval"));
+        }
+    }
+
+    // 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.
+    //
+    int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
+    for (auto id : accessControlProfileIds) {
+        auto search = profileIdToAccessCheckResult_.find(id);
+        if (search == profileIdToAccessCheckResult_.end()) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Requested entry with unvalidated profile id"));
+        }
+        int accessControlForProfile = search->second;
+        if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
+            accessControl = IIdentityCredentialStore::STATUS_OK;
+            break;
+        }
+        accessControl = accessControlForProfile;
+    }
+    if (accessControl != IIdentityCredentialStore::STATUS_OK) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                int(accessControl), "Access control check failed"));
+    }
+
+    entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+    currentName_ = name;
+    entryRemainingBytes_ = entrySize;
+    entryValue_.resize(0);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<int8_t>& encryptedContentS,
+                                                          vector<int8_t>* outContent) {
+    auto encryptedContent = byteStringToUnsigned(encryptedContentS);
+
+    optional<vector<uint8_t>> content =
+            support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+    if (!content) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
+    }
+
+    size_t chunkSize = content.value().size();
+
+    if (chunkSize > entryRemainingBytes_) {
+        LOG(ERROR) << "Retrieved chunk of size " << chunkSize
+                   << " is bigger than remaining space of size " << entryRemainingBytes_;
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "Retrieved chunk is bigger than remaining space"));
+    }
+
+    entryRemainingBytes_ -= chunkSize;
+    if (entryRemainingBytes_ > 0) {
+        if (chunkSize != IdentityCredentialStore::kGcmChunkSize) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Retrieved non-final chunk of size which isn't kGcmChunkSize"));
+        }
+    }
+
+    entryValue_.insert(entryValue_.end(), content.value().begin(), content.value().end());
+
+    if (entryRemainingBytes_ == 0) {
+        auto [entryValueItem, _, message] = cppbor::parse(entryValue_);
+        if (entryValueItem == nullptr) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Retrieved data which is invalid CBOR"));
+        }
+        currentNameSpaceDeviceNameSpacesMap_.add(currentName_, std::move(entryValueItem));
+    }
+
+    *outContent = byteStringToSigned(content.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::finishRetrieval(const vector<int8_t>& signingKeyBlobS,
+                                                       vector<int8_t>* outMac,
+                                                       vector<int8_t>* outDeviceNameSpaces) {
+    auto signingKeyBlob = byteStringToUnsigned(signingKeyBlobS);
+
+    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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Error decrypting signingKeyBlob"));
+        }
+
+        optional<vector<uint8_t>> sharedSecret =
+                support::ecdh(readerPublicKey_, signingKey.value());
+        if (!sharedSecret) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED, "Error doing ECDH"));
+        }
+
+        vector<uint8_t> salt = {0x00};
+        vector<uint8_t> info = {};
+        optional<vector<uint8_t>> derivedKey = support::hkdf(sharedSecret.value(), salt, info, 32);
+        if (!derivedKey) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED,
+                    "Error deriving key from shared secret"));
+        }
+
+        mac = support::coseMac0(derivedKey.value(), {},        // payload
+                                encodedDeviceAuthentication);  // additionalData
+        if (!mac) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
+        }
+    }
+
+    *outMac = byteStringToSigned(mac.value_or(vector<uint8_t>({})));
+    *outDeviceNameSpaces = byteStringToSigned(encodedDeviceNameSpaces);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
+        vector<int8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
+    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) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
+    }
+
+    optional<vector<uint8_t>> signingPublicKey =
+            support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
+    if (!signingPublicKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error getting public part of signingKey"));
+    }
+
+    optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
+    if (!signingKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error getting private part of signingKey"));
+    }
+
+    optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
+            signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    if (!certificate) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
+    }
+
+    optional<vector<uint8_t>> nonce = support::getRandom(12);
+    if (!nonce) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
+    }
+    vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
+    optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
+            storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
+    if (!encryptedSigningKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
+    }
+    *outSigningKeyBlob = byteStringToSigned(encryptedSigningKey.value());
+    *outSigningKeyCertificate = Certificate();
+    outSigningKeyCertificate->encodedCertificate = byteStringToSigned(certificate.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/IdentityCredential.h
new file mode 100644
index 0000000..49ed0d4
--- /dev/null
+++ b/identity/aidl/default/IdentityCredential.h
@@ -0,0 +1,109 @@
+/*
+ * 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 <aidl/android/hardware/identity/BnIdentityCredential.h>
+#include <aidl/android/hardware/keymaster/HardwareAuthToken.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor/cppbor.h>
+
+namespace aidl::android::hardware::identity {
+
+using ::aidl::android::hardware::keymaster::HardwareAuthToken;
+using ::std::map;
+using ::std::string;
+using ::std::vector;
+
+using MapStringToVectorOfStrings = map<string, vector<string>>;
+
+class IdentityCredential : public BnIdentityCredential {
+  public:
+    IdentityCredential(const vector<uint8_t>& credentialData)
+        : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {}
+
+    // Parses and decrypts credentialData_, return a status code from
+    // IIdentityCredentialStore. Must be called right after construction.
+    int initialize();
+
+    // Methods from IIdentityCredential follow.
+    ndk::ScopedAStatus deleteCredential(vector<int8_t>* outProofOfDeletionSignature) override;
+    ndk::ScopedAStatus createEphemeralKeyPair(vector<int8_t>* outKeyPair) override;
+    ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<int8_t>& publicKey) override;
+    ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
+    ndk::ScopedAStatus startRetrieval(
+            const vector<SecureAccessControlProfile>& accessControlProfiles,
+            const HardwareAuthToken& authToken, const vector<int8_t>& itemsRequest,
+            const vector<int8_t>& sessionTranscript, const vector<int8_t>& readerSignature,
+            const vector<int32_t>& requestCounts) override;
+    ndk::ScopedAStatus startRetrieveEntryValue(
+            const string& nameSpace, const string& name, int32_t entrySize,
+            const vector<int32_t>& accessControlProfileIds) override;
+    ndk::ScopedAStatus retrieveEntryValue(const vector<int8_t>& encryptedContent,
+                                          vector<int8_t>* outContent) override;
+    ndk::ScopedAStatus finishRetrieval(const vector<int8_t>& signingKeyBlob, vector<int8_t>* outMac,
+                                       vector<int8_t>* outDeviceNameSpaces) override;
+    ndk::ScopedAStatus generateSigningKeyPair(vector<int8_t>* outSigningKeyBlob,
+                                              Certificate* outSigningKeyCertificate) 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<int32_t, int> profileIdToAccessCheckResult_;
+    vector<uint8_t> sessionTranscript_;
+    std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
+    vector<uint8_t> itemsRequest_;
+    vector<int32_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 aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIAL_H
diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/IdentityCredentialStore.cpp
new file mode 100644
index 0000000..1efb4b4
--- /dev/null
+++ b/identity/aidl/default/IdentityCredentialStore.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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 <android-base/logging.h>
+
+#include "IdentityCredential.h"
+#include "IdentityCredentialStore.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+ndk::ScopedAStatus IdentityCredentialStore::getHardwareInformation(
+        HardwareInformation* hardwareInformation) {
+    HardwareInformation hw;
+    hw.credentialStoreName = "Identity Credential Reference Implementation";
+    hw.credentialStoreAuthorName = "Google";
+    hw.dataChunkSize = kGcmChunkSize;
+    hw.isDirectAccess = false;
+    hw.supportedDocTypes = {};
+    *hardwareInformation = hw;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::createCredential(
+        const string& docType, bool testCredential,
+        shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+    shared_ptr<WritableIdentityCredential> wc =
+            ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential);
+    if (!wc->initialize()) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error initializing WritableIdentityCredential"));
+    }
+    *outWritableCredential = wc;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus IdentityCredentialStore::getCredential(
+        CipherSuite cipherSuite, const vector<int8_t>& credentialData,
+        shared_ptr<IIdentityCredential>* outCredential) {
+    // We only support CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 right now.
+    if (cipherSuite != CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED,
+                "Unsupported cipher suite"));
+    }
+
+    vector<uint8_t> data = vector<uint8_t>(credentialData.begin(), credentialData.end());
+    shared_ptr<IdentityCredential> credential = ndk::SharedRefBase::make<IdentityCredential>(data);
+    auto ret = credential->initialize();
+    if (ret != IIdentityCredentialStore::STATUS_OK) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                int(ret), "Error initializing IdentityCredential"));
+    }
+    *outCredential = credential;
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/IdentityCredentialStore.h
new file mode 100644
index 0000000..a205113
--- /dev/null
+++ b/identity/aidl/default/IdentityCredentialStore.h
@@ -0,0 +1,48 @@
+/*
+ * 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 <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
+
+namespace aidl::android::hardware::identity {
+
+using ::std::shared_ptr;
+using ::std::string;
+using ::std::vector;
+
+class IdentityCredentialStore : public BnIdentityCredentialStore {
+  public:
+    IdentityCredentialStore() {}
+
+    // The GCM chunk size used by this implementation is 64 KiB.
+    static constexpr size_t kGcmChunkSize = 64 * 1024;
+
+    // Methods from IIdentityCredentialStore follow.
+    ndk::ScopedAStatus getHardwareInformation(HardwareInformation* hardwareInformation) override;
+
+    ndk::ScopedAStatus createCredential(
+            const string& docType, bool testCredential,
+            shared_ptr<IWritableIdentityCredential>* outWritableCredential) override;
+
+    ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<int8_t>& credentialData,
+                                     shared_ptr<IIdentityCredential>* outCredential) override;
+};
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_IDENTITYCREDENTIALSTORE_H
diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp
new file mode 100644
index 0000000..a0f86be
--- /dev/null
+++ b/identity/aidl/default/Util.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 "Util"
+
+#include "Util.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <string.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace aidl::android::hardware::identity {
+
+using namespace ::android::hardware::identity;
+
+// 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;
+}
+
+vector<uint8_t> byteStringToUnsigned(const vector<int8_t>& value) {
+    return vector<uint8_t>(value.begin(), value.end());
+}
+
+vector<int8_t> byteStringToSigned(const vector<uint8_t>& value) {
+    return vector<int8_t>(value.begin(), value.end());
+}
+
+vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
+    cppbor::Map map;
+    map.add("id", profile.id);
+
+    if (profile.readerCertificate.encodedCertificate.size() > 0) {
+        map.add("readerCertificate",
+                cppbor::Bstr(byteStringToUnsigned(profile.readerCertificate.encodedCertificate)));
+    }
+
+    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 = support::getRandom(12);
+    if (!nonce) {
+        return {};
+    }
+    optional<vector<uint8_t>> macO =
+            support::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() < support::kAesGcmIvSize) {
+        return false;
+    }
+    vector<uint8_t> nonce =
+            vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize);
+    optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData);
+    if (!mac) {
+        return false;
+    }
+    if (mac.value() != byteStringToUnsigned(profile.mac)) {
+        return false;
+    }
+    return true;
+}
+
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+                                          const vector<int32_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 aidl::android::hardware::identity
diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h
new file mode 100644
index 0000000..ee41ad1
--- /dev/null
+++ b/identity/aidl/default/Util.h
@@ -0,0 +1,58 @@
+/*
+ * 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_UTIL_H
+#define ANDROID_HARDWARE_IDENTITY_UTIL_H
+
+#include <aidl/android/hardware/identity/BnIdentityCredential.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <map>
+#include <optional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <cppbor/cppbor.h>
+
+namespace aidl::android::hardware::identity {
+
+using ::std::optional;
+using ::std::string;
+using ::std::vector;
+
+// Returns the hardware-bound AES-128 key.
+const vector<uint8_t>& getHardwareBoundKey();
+
+// 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);
+
+// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
+vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
+                                          const vector<int32_t> accessControlProfileIds);
+
+vector<uint8_t> byteStringToUnsigned(const vector<int8_t>& value);
+
+vector<int8_t> byteStringToSigned(const vector<uint8_t>& value);
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_UTIL_H
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
new file mode 100644
index 0000000..ba2062d
--- /dev/null
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -0,0 +1,421 @@
+/*
+ * 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 <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+
+#include <cppbor/cppbor.h>
+#include <cppbor/cppbor_parse.h>
+
+#include "IdentityCredentialStore.h"
+#include "Util.h"
+#include "WritableIdentityCredential.h"
+
+namespace aidl::android::hardware::identity {
+
+using ::std::optional;
+using namespace ::android::hardware::identity;
+
+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;
+}
+
+// TODO: use |attestationApplicationId| and |attestationChallenge| and also
+//       ensure the returned certificate chain satisfy the requirements listed in
+//       the docs for IWritableIdentityCredential::getAttestationCertificate()
+//
+ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
+        const vector<int8_t>& /*attestationApplicationId*/,
+        const vector<int8_t>& /*attestationChallenge*/, vector<Certificate>* outCertificateChain) {
+    // 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) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error creating attestationKey"));
+    }
+
+    optional<vector<uint8_t>> attestationPubKey =
+            support::ecKeyPairGetPublicKey(attestationKeyPair.value());
+    if (!attestationPubKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error getting public part of attestationKey"));
+    }
+
+    optional<vector<uint8_t>> attestationPrivKey =
+            support::ecKeyPairGetPrivateKey(attestationKeyPair.value());
+    if (!attestationPrivKey) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error getting private part of attestationKey"));
+    }
+
+    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) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error creating certificate for credentialPubKey"));
+    }
+
+    // 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) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED,
+                "Error creating certificate for attestationPubKey"));
+    }
+
+    // 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());
+
+    optional<vector<vector<uint8_t>>> splitCertChain =
+            support::certificateChainSplit(certificateChain);
+    if (!splitCertChain) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error splitting certificate chain"));
+    }
+    *outCertificateChain = vector<Certificate>();
+    for (const vector<uint8_t>& cert : splitCertChain.value()) {
+        Certificate c = Certificate();
+        c.encodedCertificate = byteStringToSigned(cert);
+        outCertificateChain->push_back(std::move(c));
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
+        int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+    numAccessControlProfileRemaining_ = accessControlProfileCount;
+    remainingEntryCounts_ = entryCounts;
+    entryNameSpace_ = "";
+
+    signedDataAccessControlProfiles_ = cppbor::Array();
+    signedDataNamespaces_ = cppbor::Map();
+    signedDataCurrentNamespace_ = cppbor::Array();
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addAccessControlProfile(
+        int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
+        int64_t timeoutMillis, int64_t secureUserId,
+        SecureAccessControlProfile* outSecureAccessControlProfile) {
+    SecureAccessControlProfile profile;
+
+    if (numAccessControlProfileRemaining_ == 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "numAccessControlProfileRemaining_ is 0 and expected non-zero"));
+    }
+
+    // Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
+    // be zero.
+    if (!userAuthenticationRequired && timeoutMillis != 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "userAuthenticationRequired is false but timeout is non-zero"));
+    }
+
+    profile.id = id;
+    profile.readerCertificate = readerCertificate;
+    profile.userAuthenticationRequired = userAuthenticationRequired;
+    profile.timeoutMillis = timeoutMillis;
+    profile.secureUserId = secureUserId;
+    optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_);
+    if (!mac) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile"));
+    }
+    profile.mac = byteStringToSigned(mac.value());
+
+    cppbor::Map profileMap;
+    profileMap.add("id", profile.id);
+    if (profile.readerCertificate.encodedCertificate.size() > 0) {
+        profileMap.add(
+                "readerCertificate",
+                cppbor::Bstr(byteStringToUnsigned(profile.readerCertificate.encodedCertificate)));
+    }
+    if (profile.userAuthenticationRequired) {
+        profileMap.add("userAuthenticationRequired", profile.userAuthenticationRequired);
+        profileMap.add("timeoutMillis", profile.timeoutMillis);
+    }
+    signedDataAccessControlProfiles_.add(std::move(profileMap));
+
+    numAccessControlProfileRemaining_--;
+
+    *outSecureAccessControlProfile = profile;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::beginAddEntry(
+        const vector<int32_t>& accessControlProfileIds, const string& nameSpace, const string& name,
+        int32_t entrySize) {
+    if (numAccessControlProfileRemaining_ != 0) {
+        LOG(ERROR) << "numAccessControlProfileRemaining_ is " << numAccessControlProfileRemaining_
+                   << " and expected zero";
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "numAccessControlProfileRemaining_ is not zero"));
+    }
+
+    if (remainingEntryCounts_.size() == 0) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA, "No more namespaces to add to"));
+    }
+
+    // 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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "New namespace but a non-zero number of entries remain to be added"));
+        }
+        remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+
+        if (signedDataCurrentNamespace_.size() > 0) {
+            signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
+            signedDataCurrentNamespace_ = cppbor::Array();
+        }
+    } else {
+        // Same namespace...
+        if (remainingEntryCounts_[0] == 0) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Same namespace but no entries remain to be added"));
+        }
+        remainingEntryCounts_[0] -= 1;
+    }
+
+    entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+
+    entryRemainingBytes_ = entrySize;
+    entryNameSpace_ = nameSpace;
+    entryName_ = name;
+    entryAccessControlProfileIds_ = accessControlProfileIds;
+    entryBytes_.resize(0);
+    // LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::addEntryValue(const vector<int8_t>& contentS,
+                                                             vector<int8_t>* outEncryptedContent) {
+    auto content = byteStringToUnsigned(contentS);
+    size_t contentSize = content.size();
+
+    if (contentSize > IdentityCredentialStore::kGcmChunkSize) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "Passed in chunk of is bigger than kGcmChunkSize"));
+    }
+    if (contentSize > entryRemainingBytes_) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_INVALID_DATA,
+                "Passed in chunk is bigger than remaining space"));
+    }
+
+    entryBytes_.insert(entryBytes_.end(), content.begin(), content.end());
+    entryRemainingBytes_ -= contentSize;
+    if (entryRemainingBytes_ > 0) {
+        if (contentSize != IdentityCredentialStore::kGcmChunkSize) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA,
+                    "Retrieved non-final chunk which isn't kGcmChunkSize"));
+        }
+    }
+
+    optional<vector<uint8_t>> nonce = support::getRandom(12);
+    if (!nonce) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
+    }
+    optional<vector<uint8_t>> encryptedContent =
+            support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+    if (!encryptedContent) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content"));
+    }
+
+    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) {
+            return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                    IIdentityCredentialStore::STATUS_INVALID_DATA, "Data is not valid CBOR"));
+        }
+        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));
+    }
+
+    *outEncryptedContent = byteStringToSigned(encryptedContent.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+// 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;
+}
+
+ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
+        vector<int8_t>* outCredentialData, vector<int8_t>* outProofOfProvisioningSignature) {
+    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) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
+    }
+
+    vector<uint8_t> credentialKeys;
+    if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
+    }
+
+    vector<uint8_t> credentialData;
+    if (!generateCredentialData(
+                testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(),
+                docType_, testCredential_, credentialKeys, credentialData)) {
+        return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+                IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData"));
+    }
+
+    *outCredentialData = byteStringToSigned(credentialData);
+    *outProofOfProvisioningSignature = byteStringToSigned(signature.value());
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h
new file mode 100644
index 0000000..b380f89
--- /dev/null
+++ b/identity/aidl/default/WritableIdentityCredential.h
@@ -0,0 +1,90 @@
+/*
+ * 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 <aidl/android/hardware/identity/BnWritableIdentityCredential.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <cppbor.h>
+
+namespace aidl::android::hardware::identity {
+
+using ::std::string;
+using ::std::vector;
+
+class WritableIdentityCredential : public BnWritableIdentityCredential {
+  public:
+    WritableIdentityCredential(const 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 IWritableIdentityCredential follow.
+    ndk::ScopedAStatus getAttestationCertificate(const vector<int8_t>& attestationApplicationId,
+                                                 const vector<int8_t>& attestationChallenge,
+                                                 vector<Certificate>* outCertificateChain) override;
+
+    ndk::ScopedAStatus startPersonalization(int32_t accessControlProfileCount,
+                                            const vector<int32_t>& entryCounts) override;
+
+    ndk::ScopedAStatus addAccessControlProfile(
+            int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
+            int64_t timeoutMillis, int64_t secureUserId,
+            SecureAccessControlProfile* outSecureAccessControlProfile) override;
+
+    ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds,
+                                     const string& nameSpace, const string& name,
+                                     int32_t entrySize) override;
+
+    ndk::ScopedAStatus addEntryValue(const vector<int8_t>& content,
+                                     vector<int8_t>* outEncryptedContent) override;
+
+    ndk::ScopedAStatus finishAddingEntries(
+            vector<int8_t>* outCredentialData,
+            vector<int8_t>* outProofOfProvisioningSignature) 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<int32_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<int32_t> entryAccessControlProfileIds_;
+    vector<uint8_t> entryBytes_;
+};
+
+}  // namespace aidl::android::hardware::identity
+
+#endif  // ANDROID_HARDWARE_IDENTITY_WRITABLEIDENTITYCREDENTIAL_H
diff --git a/identity/aidl/default/identity-default.rc b/identity/aidl/default/identity-default.rc
new file mode 100644
index 0000000..d3b62c1
--- /dev/null
+++ b/identity/aidl/default/identity-default.rc
@@ -0,0 +1,3 @@
+service vendor.identity-default /vendor/bin/hw/android.hardware.identity-service.example
+    class hal
+    user nobody
diff --git a/identity/aidl/default/identity-default.xml b/identity/aidl/default/identity-default.xml
new file mode 100644
index 0000000..a47d354
--- /dev/null
+++ b/identity/aidl/default/identity-default.xml
@@ -0,0 +1,9 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.identity</name>
+        <interface>
+            <name>IIdentityCredentialStore</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp
new file mode 100644
index 0000000..f05c615
--- /dev/null
+++ b/identity/aidl/default/service.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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-service"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "IdentityCredentialStore.h"
+
+using aidl::android::hardware::identity::IdentityCredentialStore;
+
+int main() {
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+    std::shared_ptr<IdentityCredentialStore> store =
+            ndk::SharedRefBase::make<IdentityCredentialStore>();
+
+    const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default";
+    LOG(INFO) << "instance: " << instance;
+    binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str());
+    CHECK(status == STATUS_OK);
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
new file mode 100644
index 0000000..21ff440
--- /dev/null
+++ b/identity/aidl/vts/Android.bp
@@ -0,0 +1,21 @@
+cc_test {
+    name: "VtsHalIdentityTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["VtsHalIdentityTargetTest.cpp"],
+    shared_libs: [
+        "libbinder",
+        "libcppbor",
+        "android.hardware.identity-support-lib",
+    ],
+    static_libs: [
+        "android.hardware.identity-cpp",
+        "android.hardware.keymaster-cpp",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts-core",
+    ],
+}
diff --git a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp b/identity/aidl/vts/VtsHalIdentityTargetTest.cpp
new file mode 100644
index 0000000..5abe5a2
--- /dev/null
+++ b/identity/aidl/vts/VtsHalIdentityTargetTest.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.
+ */
+#define LOG_TAG "VtsHalIdentityTargetTest"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+
+namespace android::hardware::identity {
+
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+using ::android::hardware::keymaster::HardwareAuthToken;
+
+// ---------------------------------------------------------------------------
+// Test Data.
+// ---------------------------------------------------------------------------
+
+struct TestEntryData {
+    TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
+        : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+    TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+    }
+    TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+                  vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bstr(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        valueCbor = cppbor::Bool(value).encode();
+    }
+    TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
+        : TestEntryData(nameSpace, name, profileIds) {
+        if (value >= 0) {
+            valueCbor = cppbor::Uint(value).encode();
+        } else {
+            valueCbor = cppbor::Nint(-value).encode();
+        }
+    }
+
+    string nameSpace;
+    string name;
+    vector<uint8_t> valueCbor;
+    vector<int32_t> profileIds;
+};
+
+struct TestProfile {
+    uint16_t id;
+    vector<uint8_t> readerCertificate;
+    bool userAuthenticationRequired;
+    uint64_t timeoutMillis;
+};
+
+// ----------------------------------------------------------------
+
+class IdentityAidl : public testing::TestWithParam<std::string> {
+  public:
+    virtual void SetUp() override {
+        credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+                String16(GetParam().c_str()));
+        ASSERT_NE(credentialStore_, nullptr);
+    }
+
+    sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityAidl, hardwareInformation) {
+    HardwareInformation info;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&info).isOk());
+    ASSERT_GT(info.credentialStoreName.size(), 0);
+    ASSERT_GT(info.credentialStoreAuthorName.size(), 0);
+    ASSERT_GE(info.dataChunkSize, 256);
+}
+
+TEST_P(IdentityAidl, createAndRetrieveCredential) {
+    // First, generate a key-pair for the reader since its public key will be
+    // part of the request data.
+    optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+    ASSERT_TRUE(readerKeyPKCS8);
+    optional<vector<uint8_t>> readerPublicKey =
+            support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+    optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+    string serialDecimal = "1234";
+    string issuer = "Android Open Source Project";
+    string subject = "Android IdentityCredential VTS Test";
+    time_t validityNotBefore = time(nullptr);
+    time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+    optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
+            readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
+            validityNotBefore, validityNotAfter);
+    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<int32_t>{0, 1}},
+            {"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+            {"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}},
+            {"PersonalData", "Home address", string("Maida Vale, London, England"),
+             vector<int32_t>{0}},
+            {"Image", "Portrait image", portraitImage, vector<int32_t>{0, 1}},
+    };
+    const vector<int32_t> testEntriesEntryCounts = {static_cast<int32_t>(testEntries.size() - 1),
+                                                    1u};
+    HardwareInformation hwInfo;
+    ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+    string cborPretty;
+    sp<IWritableIdentityCredential> writableCredential;
+    string docType = "org.iso.18013-5.2019.mdl";
+    bool testCredential = true;
+    ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &writableCredential)
+                        .isOk());
+    ASSERT_NE(writableCredential, nullptr);
+
+    string challenge = "attestationChallenge";
+    // TODO: set it to something random and check it's in the cert chain
+    vector<uint8_t> attestationApplicationId = {};
+    vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+    vector<Certificate> attestationCertificates;
+    ASSERT_TRUE(writableCredential
+                        ->getAttestationCertificate(attestationApplicationId, attestationChallenge,
+                                                    &attestationCertificates)
+                        .isOk());
+    ASSERT_GE(attestationCertificates.size(), 2);
+
+    ASSERT_TRUE(
+            writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts)
+                    .isOk());
+
+    vector<SecureAccessControlProfile> returnedSecureProfiles;
+    for (const auto& testProfile : testProfiles) {
+        SecureAccessControlProfile profile;
+        Certificate cert;
+        cert.encodedCertificate = testProfile.readerCertificate;
+        ASSERT_TRUE(writableCredential
+                            ->addAccessControlProfile(testProfile.id, cert,
+                                                      testProfile.userAuthenticationRequired,
+                                                      testProfile.timeoutMillis,
+                                                      0,  // secureUserId
+                                                      &profile)
+                            .isOk());
+        ASSERT_EQ(testProfile.id, profile.id);
+        ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+        ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+        ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+        ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+        returnedSecureProfiles.push_back(profile);
+    }
+
+    // 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, hwInfo.dataChunkSize);
+
+        ASSERT_TRUE(writableCredential
+                            ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+                                            entry.valueCbor.size())
+                            .isOk());
+
+        vector<vector<uint8_t>> encryptedChunks;
+        for (const auto& chunk : chunks) {
+            vector<uint8_t> encryptedChunk;
+            ASSERT_TRUE(writableCredential->addEntryValue(chunk, &encryptedChunk).isOk());
+            encryptedChunks.push_back(encryptedChunk);
+        }
+        encryptedBlobs[&entry] = encryptedChunks;
+    }
+
+    vector<uint8_t> credentialData;
+    vector<uint8_t> proofOfProvisioningSignature;
+    ASSERT_TRUE(
+            writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature)
+                    .isOk());
+
+    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(attestationCertificates[0].encodedCertificate);
+    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;
+    ASSERT_TRUE(credentialStore_
+                        ->getCredential(
+                                CipherSuite::CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256,
+                                credentialData, &credential)
+                        .isOk());
+    ASSERT_NE(credential, nullptr);
+
+    optional<vector<uint8_t>> readerEphemeralKeyPair = support::createEcKeyPair();
+    ASSERT_TRUE(readerEphemeralKeyPair);
+    optional<vector<uint8_t>> readerEphemeralPublicKey =
+            support::ecKeyPairGetPublicKey(readerEphemeralKeyPair.value());
+    ASSERT_TRUE(credential->setReaderEphemeralPublicKey(readerEphemeralPublicKey.value()).isOk());
+
+    vector<uint8_t> ephemeralKeyPair;
+    ASSERT_TRUE(credential->createEphemeralKeyPair(&ephemeralKeyPair).isOk());
+    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);
+
+    ASSERT_TRUE(credential
+                        ->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+                                         sessionTranscriptBytes, readerSignature.value(),
+                                         testEntriesEntryCounts)
+                        .isOk());
+
+    for (const auto& entry : testEntries) {
+        ASSERT_TRUE(credential
+                            ->startRetrieveEntryValue(entry.nameSpace, entry.name,
+                                                      entry.valueCbor.size(), entry.profileIds)
+                            .isOk());
+
+        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;
+            ASSERT_TRUE(credential->retrieveEntryValue(encryptedChunk, &chunk).isOk());
+            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;
+    Certificate signingKeyCertificate;
+    ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+
+    vector<uint8_t> mac;
+    vector<uint8_t> deviceNameSpacesBytes;
+    ASSERT_TRUE(credential->finishRetrieval(signingKeyBlob, &mac, &deviceNameSpacesBytes).isOk());
+    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.encodedCertificate);
+    EXPECT_TRUE(signingPublicKey);
+
+    // Derive the key used for MACing.
+    optional<vector<uint8_t>> readerEphemeralPrivateKey =
+            support::ecKeyPairGetPrivateKey(readerEphemeralKeyPair.value());
+    optional<vector<uint8_t>> sharedSecret =
+            support::ecdh(signingPublicKey.value(), readerEphemeralPrivateKey.value());
+    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(
+        Identity, IdentityAidl,
+        testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+        android::PrintInstanceNameToString);
+// INSTANTIATE_TEST_SUITE_P(Identity, IdentityAidl,
+// testing::Values("android.hardware.identity.IIdentityCredentialStore/default"));
+
+}  // namespace android::hardware::identity
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ::android::ProcessState::self()->setThreadPoolMaxThreadCount(1);
+    ::android::ProcessState::self()->startThreadPool();
+    return RUN_ALL_TESTS();
+}