Add Framework APIs for Identity Credential.

The Identity Credential APIs provides an interface to a secure store
for user identity documents.  These APIs are 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
these APIs.

The Identity Credential APIs rely on user authentication to protect
data elements in credentials which is implemented through
auth-tokens. This CL contains changes to CryptoObject to allow this.

Bug: 111446262
Test: CtsIdentityTestCases
Change-Id: I48f21a561b762d86c9ca8d229962782572412f47
diff --git a/Android.bp b/Android.bp
index f57acfa..26a28c6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -73,6 +73,14 @@
 }
 
 filegroup {
+    name: "framework-identity-sources",
+    srcs: [
+        "identity/java/**/*.java",
+    ],
+    path: "identity/java",
+}
+
+filegroup {
     name: "framework-keystore-sources",
     srcs: [
         "keystore/java/**/*.java",
@@ -216,6 +224,7 @@
         ":framework-drm-sources",
         ":framework-graphics-sources",
         ":framework-keystore-sources",
+        ":framework-identity-sources",
         ":framework-location-sources",
         ":framework-lowpan-sources",
         ":framework-media-sources",
@@ -238,6 +247,7 @@
         ":platform-compat-native-aidl",
 
         // AIDL sources from external directories
+        ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
         ":gatekeeper_aidl",
@@ -289,6 +299,7 @@
             "core/java",
             "drm/java",
             "graphics/java",
+            "identity/java",
             "keystore/java",
             "location/java",
             "lowpan/java",
diff --git a/api/current.txt b/api/current.txt
index 08d7034..1e80723 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16637,7 +16637,9 @@
     ctor public BiometricPrompt.CryptoObject(@NonNull java.security.Signature);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(@NonNull javax.crypto.Mac);
+    ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     method public javax.crypto.Cipher getCipher();
+    method @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method public javax.crypto.Mac getMac();
     method public java.security.Signature getSignature();
   }
@@ -17575,7 +17577,9 @@
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
     ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+    ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     method @Deprecated public javax.crypto.Cipher getCipher();
+    method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @Deprecated public javax.crypto.Mac getMac();
     method @Deprecated public java.security.Signature getSignature();
   }
@@ -41048,6 +41052,139 @@
 
 }
 
+package android.security.identity {
+
+  public class AccessControlProfile {
+  }
+
+  public static final class AccessControlProfile.Builder {
+    ctor public AccessControlProfile.Builder(@NonNull android.security.identity.AccessControlProfileId);
+    method @NonNull public android.security.identity.AccessControlProfile build();
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setReaderCertificate(@NonNull java.security.cert.X509Certificate);
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationRequired(boolean);
+    method @NonNull public android.security.identity.AccessControlProfile.Builder setUserAuthenticationTimeout(long);
+  }
+
+  public class AccessControlProfileId {
+    ctor public AccessControlProfileId(int);
+    method public int getId();
+  }
+
+  public class AlreadyPersonalizedException extends android.security.identity.IdentityCredentialException {
+    ctor public AlreadyPersonalizedException(@NonNull String);
+    ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class CipherSuiteNotSupportedException extends android.security.identity.IdentityCredentialException {
+    ctor public CipherSuiteNotSupportedException(@NonNull String);
+    ctor public CipherSuiteNotSupportedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class DocTypeNotSupportedException extends android.security.identity.IdentityCredentialException {
+    ctor public DocTypeNotSupportedException(@NonNull String);
+    ctor public DocTypeNotSupportedException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class EphemeralPublicKeyNotFoundException extends android.security.identity.IdentityCredentialException {
+    ctor public EphemeralPublicKeyNotFoundException(@NonNull String);
+    ctor public EphemeralPublicKeyNotFoundException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class IdentityCredential {
+    method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
+    method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
+    method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
+    method @NonNull public abstract int[] getAuthenticationDataUsageCount();
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
+    method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
+    method public abstract void setAllowUsingExhaustedKeys(boolean);
+    method public abstract void setAvailableAuthenticationKeys(int, int);
+    method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
+    method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
+  }
+
+  public class IdentityCredentialException extends java.lang.Exception {
+    ctor public IdentityCredentialException(@NonNull String);
+    ctor public IdentityCredentialException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class IdentityCredentialStore {
+    method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
+    method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
+    method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
+    method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
+    method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context);
+    method @NonNull public abstract String[] getSupportedDocTypes();
+    field public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1; // 0x1
+  }
+
+  public class InvalidReaderSignatureException extends android.security.identity.IdentityCredentialException {
+    ctor public InvalidReaderSignatureException(@NonNull String);
+    ctor public InvalidReaderSignatureException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class InvalidRequestMessageException extends android.security.identity.IdentityCredentialException {
+    ctor public InvalidRequestMessageException(@NonNull String);
+    ctor public InvalidRequestMessageException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class MessageDecryptionException extends android.security.identity.IdentityCredentialException {
+    ctor public MessageDecryptionException(@NonNull String);
+    ctor public MessageDecryptionException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class NoAuthenticationKeyAvailableException extends android.security.identity.IdentityCredentialException {
+    ctor public NoAuthenticationKeyAvailableException(@NonNull String);
+    ctor public NoAuthenticationKeyAvailableException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class PersonalizationData {
+  }
+
+  public static final class PersonalizationData.Builder {
+    ctor public PersonalizationData.Builder();
+    method @NonNull public android.security.identity.PersonalizationData.Builder addAccessControlProfile(@NonNull android.security.identity.AccessControlProfile);
+    method @NonNull public android.security.identity.PersonalizationData build();
+    method @NonNull public android.security.identity.PersonalizationData.Builder setEntry(@NonNull String, @NonNull String, @NonNull java.util.Collection<android.security.identity.AccessControlProfileId>, @NonNull byte[]);
+  }
+
+  public abstract class ResultData {
+    method @NonNull public abstract byte[] getAuthenticatedData();
+    method @Nullable public abstract byte[] getEntry(@NonNull String, @NonNull String);
+    method @Nullable public abstract java.util.Collection<java.lang.String> getEntryNames(@NonNull String);
+    method @Nullable public abstract byte[] getMessageAuthenticationCode();
+    method @NonNull public abstract java.util.Collection<java.lang.String> getNamespaceNames();
+    method @Nullable public abstract java.util.Collection<java.lang.String> getRetrievedEntryNames(@NonNull String);
+    method @NonNull public abstract byte[] getStaticAuthenticationData();
+    method public abstract int getStatus(@NonNull String, @NonNull String);
+    field public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; // 0x3
+    field public static final int STATUS_NOT_REQUESTED = 2; // 0x2
+    field public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; // 0x6
+    field public static final int STATUS_NO_SUCH_ENTRY = 1; // 0x1
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; // 0x5
+    field public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; // 0x4
+  }
+
+  public class SessionTranscriptMismatchException extends android.security.identity.IdentityCredentialException {
+    ctor public SessionTranscriptMismatchException(@NonNull String);
+    ctor public SessionTranscriptMismatchException(@NonNull String, @NonNull Throwable);
+  }
+
+  public class UnknownAuthenticationKeyException extends android.security.identity.IdentityCredentialException {
+    ctor public UnknownAuthenticationKeyException(@NonNull String);
+    ctor public UnknownAuthenticationKeyException(@NonNull String, @NonNull Throwable);
+  }
+
+  public abstract class WritableIdentityCredential {
+    ctor public WritableIdentityCredential();
+    method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[]);
+    method @NonNull public abstract byte[] personalize(@NonNull android.security.identity.PersonalizationData);
+  }
+
+}
+
 package android.security.keystore {
 
   public class KeyExpiredException extends java.security.InvalidKeyException {
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 1142a07..2497ea9 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -32,6 +32,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.security.identity.IdentityCredential;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -401,6 +402,10 @@
             super(mac);
         }
 
+        public CryptoObject(@NonNull IdentityCredential credential) {
+            super(credential);
+        }
+
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -424,6 +429,14 @@
         public Mac getMac() {
             return super.getMac();
         }
+
+        /**
+         * Get {@link IdentityCredential} object.
+         * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         */
+        public @Nullable IdentityCredential getIdentityCredential() {
+            return super.getIdentityCredential();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 787dc66..0af18df 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.annotation.NonNull;
+import android.security.identity.IdentityCredential;
 import android.security.keystore.AndroidKeyStoreProvider;
 
 import java.security.Signature;
@@ -26,7 +27,8 @@
 
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
- * Currently the framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
+ * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac} and
+ * {@link IdentityCredential} objects.
  * @hide
  */
 public class CryptoObject {
@@ -44,6 +46,10 @@
         mCrypto = mac;
     }
 
+    public CryptoObject(@NonNull IdentityCredential credential) {
+        mCrypto = credential;
+    }
+
     /**
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
@@ -69,11 +75,23 @@
     }
 
     /**
+     * Get {@link IdentityCredential} object.
+     * @return {@link IdentityCredential} object or null if this doesn't contain one.
+     */
+    public IdentityCredential getIdentityCredential() {
+        return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
+    }
+
+    /**
      * @hide
      * @return the opId associated with this object or 0 if none
      */
     public final long getOpId() {
-        return mCrypto != null
-                ? AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto) : 0;
+        if (mCrypto == null) {
+            return 0;
+        } else if (mCrypto instanceof IdentityCredential) {
+            return ((IdentityCredential) mCrypto).getCredstoreOperationHandle();
+        }
+        return AndroidKeyStoreProvider.getKeyStoreOperationHandle(mCrypto);
     }
 };
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 315af32..16f9688 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -44,6 +44,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.security.identity.IdentityCredential;
 import android.util.Slog;
 
 import java.security.Signature;
@@ -125,6 +126,10 @@
             super(mac);
         }
 
+        public CryptoObject(@NonNull IdentityCredential credential) {
+            super(credential);
+        }
+
         /**
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
@@ -148,6 +153,14 @@
         public Mac getMac() {
             return super.getMac();
         }
+
+        /**
+         * Get {@link IdentityCredential} object.
+         * @return {@link IdentityCredential} object or null if this doesn't contain one.
+         */
+        public @Nullable IdentityCredential getIdentityCredential() {
+            return super.getIdentityCredential();
+        }
     }
 
     /**
diff --git a/identity/MODULE_LICENSE_APACHE2 b/identity/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/identity/MODULE_LICENSE_APACHE2
diff --git a/identity/NOTICE b/identity/NOTICE
new file mode 100644
index 0000000..64aaa8d
--- /dev/null
+++ b/identity/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2009, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/identity/OWNERS b/identity/OWNERS
new file mode 100644
index 0000000..533d90b
--- /dev/null
+++ b/identity/OWNERS
@@ -0,0 +1,3 @@
+swillden@google.com
+zeuthen@google.com
+
diff --git a/identity/java/android/security/identity/AccessControlProfile.java b/identity/java/android/security/identity/AccessControlProfile.java
new file mode 100644
index 0000000..10e451c
--- /dev/null
+++ b/identity/java/android/security/identity/AccessControlProfile.java
@@ -0,0 +1,131 @@
+/*
+ * 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.security.identity;
+
+import android.annotation.NonNull;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * A class used to specify access controls.
+ */
+public class AccessControlProfile {
+    private AccessControlProfileId mAccessControlProfileId = new AccessControlProfileId(0);
+    private X509Certificate mReaderCertificate = null;
+    private boolean mUserAuthenticationRequired = true;
+    private long mUserAuthenticationTimeout = 0;
+
+    private AccessControlProfile() {
+    }
+
+    AccessControlProfileId getAccessControlProfileId() {
+        return mAccessControlProfileId;
+    }
+
+    long getUserAuthenticationTimeout() {
+        return mUserAuthenticationTimeout;
+    }
+
+    boolean isUserAuthenticationRequired() {
+        return mUserAuthenticationRequired;
+    }
+
+    X509Certificate getReaderCertificate() {
+        return mReaderCertificate;
+    }
+
+    /**
+     * A builder for {@link AccessControlProfile}.
+     */
+    public static final class Builder {
+        private AccessControlProfile mProfile;
+
+        /**
+         * Each access control profile has numeric identifier that must be unique within the
+         * context of a Credential and may be used to reference the profile.
+         *
+         * <p>By default, the resulting {@link AccessControlProfile} will require user
+         * authentication with a timeout of zero, thus requiring the holder to authenticate for
+         * every presentation where data elements using this access control profile is used.</p>
+         *
+         * @param accessControlProfileId the access control profile identifier.
+         */
+        public Builder(@NonNull AccessControlProfileId accessControlProfileId) {
+            mProfile = new AccessControlProfile();
+            mProfile.mAccessControlProfileId = accessControlProfileId;
+        }
+
+        /**
+         * Set whether user authentication is required.
+         *
+         * <p>This should be used sparingly since disabling user authentication on just a single
+         * data element can easily create a
+         * <a href="https://en.wikipedia.org/wiki/Relay_attack">Relay Attack</a> if the device
+         * on which the credential is stored is compromised.</p>
+         *
+         * @param userAuthenticationRequired Set to true if user authentication is required,
+         *                                   false otherwise.
+         * @return The builder.
+         */
+        public @NonNull Builder setUserAuthenticationRequired(boolean userAuthenticationRequired) {
+            mProfile.mUserAuthenticationRequired = userAuthenticationRequired;
+            return this;
+        }
+
+        /**
+         * Sets the authentication timeout to use.
+         *
+         * <p>The authentication timeout specifies the amount of time, in milliseconds, for which a
+         * user authentication is valid, if user authentication is required (see
+         * {@link #setUserAuthenticationRequired(boolean)}).</p>
+         *
+         * <p>If the timeout is zero, then authentication is always required for each reader
+         * session.</p>
+         *
+         * @param userAuthenticationTimeoutMillis the authentication timeout, in milliseconds.
+         * @return The builder.
+         */
+        public @NonNull Builder setUserAuthenticationTimeout(long userAuthenticationTimeoutMillis) {
+            mProfile.mUserAuthenticationTimeout = userAuthenticationTimeoutMillis;
+            return this;
+        }
+
+        /**
+         * Sets the reader certificate to use when checking access control.
+         *
+         * <p>If set, this is checked against the certificate chain presented by
+         * reader. The access check is fulfilled only if one of the certificates
+         * in the chain, matches the certificate set by this method.</p>
+         *
+         * @param readerCertificate the certificate to use for the access control check.
+         * @return The builder.
+         */
+        public @NonNull Builder setReaderCertificate(@NonNull X509Certificate readerCertificate) {
+            mProfile.mReaderCertificate = readerCertificate;
+            return this;
+        }
+
+        /**
+         * Creates a new {@link AccessControlProfile} from the data supplied to the builder.
+         *
+         * @return The created {@link AccessControlProfile} object.
+         */
+        public @NonNull AccessControlProfile build() {
+            return mProfile;
+        }
+    }
+}
diff --git a/identity/java/android/security/identity/AccessControlProfileId.java b/identity/java/android/security/identity/AccessControlProfileId.java
new file mode 100644
index 0000000..3d59450
--- /dev/null
+++ b/identity/java/android/security/identity/AccessControlProfileId.java
@@ -0,0 +1,42 @@
+/*
+ * 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.security.identity;
+
+/**
+ * A class used to wrap an access control profile identifiers.
+ */
+public class AccessControlProfileId {
+    private int mId = 0;
+
+    /**
+     * Constructs a new object holding a numerical identifier.
+     *
+     * @param id the identifier.
+     */
+    public AccessControlProfileId(int id) {
+        this.mId = id;
+    }
+
+    /**
+     * Gets the numerical identifier wrapped by this object.
+     *
+     * @return the identifier.
+     */
+    public int getId() {
+        return this.mId;
+    }
+}
diff --git a/identity/java/android/security/identity/AlreadyPersonalizedException.java b/identity/java/android/security/identity/AlreadyPersonalizedException.java
new file mode 100644
index 0000000..1933882
--- /dev/null
+++ b/identity/java/android/security/identity/AlreadyPersonalizedException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to create a credential which already exists.
+ */
+public class AlreadyPersonalizedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link AlreadyPersonalizedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public AlreadyPersonalizedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link AlreadyPersonalizedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public AlreadyPersonalizedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/CipherSuiteNotSupportedException.java b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java
new file mode 100644
index 0000000..e7a6c89
--- /dev/null
+++ b/identity/java/android/security/identity/CipherSuiteNotSupportedException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to use a cipher suite which isn't supported.
+ */
+public class CipherSuiteNotSupportedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public CipherSuiteNotSupportedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link CipherSuiteNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public CipherSuiteNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
new file mode 100644
index 0000000..c520331
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -0,0 +1,424 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+class CredstoreIdentityCredential extends IdentityCredential {
+
+    private static final String TAG = "CredstoreIdentityCredential";
+    private String mCredentialName;
+    private @IdentityCredentialStore.Ciphersuite int mCipherSuite;
+    private Context mContext;
+    private ICredential mBinder;
+
+    CredstoreIdentityCredential(Context context, String credentialName,
+            @IdentityCredentialStore.Ciphersuite int cipherSuite,
+            ICredential binder) {
+        mContext = context;
+        mCredentialName = credentialName;
+        mCipherSuite = cipherSuite;
+        mBinder = binder;
+    }
+
+    private KeyPair mEphemeralKeyPair = null;
+    private SecretKey mSecretKey = null;
+    private SecretKey mReaderSecretKey = null;
+    private int mEphemeralCounter;
+    private int mReadersExpectedEphemeralCounter;
+
+    private void ensureEphemeralKeyPair() {
+        if (mEphemeralKeyPair != null) {
+            return;
+        }
+        try {
+            // This PKCS#12 blob is generated in credstore, using BoringSSL.
+            //
+            // The main reason for this convoluted approach and not just sending the decomposed
+            // key-pair is that this would require directly using (device-side) BouncyCastle which
+            // is tricky due to various API hiding efforts. So instead we have credstore generate
+            // this PKCS#12 blob. The blob is encrypted with no password (sadly, also, BoringSSL
+            // doesn't support not using encryption when building a PKCS#12 blob).
+            //
+            byte[] pkcs12 = mBinder.createEphemeralKeyPair();
+            String alias = "ephemeralKey";
+            char[] password = {};
+
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ByteArrayInputStream bais = new ByteArrayInputStream(pkcs12);
+            ks.load(bais, password);
+            PrivateKey privKey = (PrivateKey) ks.getKey(alias, password);
+
+            Certificate cert = ks.getCertificate(alias);
+            PublicKey pubKey = cert.getPublicKey();
+
+            mEphemeralKeyPair = new KeyPair(pubKey, privKey);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        } catch (KeyStoreException
+                | CertificateException
+                | UnrecoverableKeyException
+                | NoSuchAlgorithmException
+                | IOException e) {
+            throw new RuntimeException("Unexpected exception ", e);
+        }
+    }
+
+    @Override
+    public @NonNull KeyPair createEphemeralKeyPair() {
+        ensureEphemeralKeyPair();
+        return mEphemeralKeyPair;
+    }
+
+    @Override
+    public void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException {
+        try {
+            byte[] uncompressedForm =
+                    Util.publicKeyEncodeUncompressedForm(readerEphemeralPublicKey);
+            mBinder.setReaderEphemeralPublicKey(uncompressedForm);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+
+        ensureEphemeralKeyPair();
+
+        try {
+            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+            ka.init(mEphemeralKeyPair.getPrivate());
+            ka.doPhase(readerEphemeralPublicKey, true);
+            byte[] sharedSecret = ka.generateSecret();
+
+            byte[] salt = new byte[1];
+            byte[] info = new byte[0];
+
+            salt[0] = 0x01;
+            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+            mSecretKey = new SecretKeySpec(derivedKey, "AES");
+
+            salt[0] = 0x00;
+            derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+            mReaderSecretKey = new SecretKeySpec(derivedKey, "AES");
+
+            mEphemeralCounter = 0;
+            mReadersExpectedEphemeralCounter = 0;
+
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Error performing key agreement", e);
+        }
+    }
+
+    @Override
+    public @NonNull byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext) {
+        byte[] messageCiphertextAndAuthTag = null;
+        try {
+            ByteBuffer iv = ByteBuffer.allocate(12);
+            iv.putInt(0, 0x00000000);
+            iv.putInt(4, 0x00000001);
+            iv.putInt(8, mEphemeralCounter);
+            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array());
+            cipher.init(Cipher.ENCRYPT_MODE, mSecretKey, encryptionParameterSpec);
+            messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext);
+        } catch (BadPaddingException
+                | IllegalBlockSizeException
+                | NoSuchPaddingException
+                | InvalidKeyException
+                | NoSuchAlgorithmException
+                | InvalidAlgorithmParameterException e) {
+            throw new RuntimeException("Error encrypting message", e);
+        }
+        mEphemeralCounter += 1;
+        return messageCiphertextAndAuthTag;
+    }
+
+    @Override
+    public @NonNull byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
+            throws MessageDecryptionException {
+        ByteBuffer iv = ByteBuffer.allocate(12);
+        iv.putInt(0, 0x00000000);
+        iv.putInt(4, 0x00000000);
+        iv.putInt(8, mReadersExpectedEphemeralCounter);
+        byte[] plainText = null;
+        try {
+            final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            cipher.init(Cipher.DECRYPT_MODE, mReaderSecretKey,
+                    new GCMParameterSpec(128, iv.array()));
+            plainText = cipher.doFinal(messageCiphertext);
+        } catch (BadPaddingException
+                | IllegalBlockSizeException
+                | InvalidAlgorithmParameterException
+                | InvalidKeyException
+                | NoSuchAlgorithmException
+                | NoSuchPaddingException e) {
+            throw new MessageDecryptionException("Error decrypting message", e);
+        }
+        mReadersExpectedEphemeralCounter += 1;
+        return plainText;
+    }
+
+    @Override
+    public @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain() {
+        try {
+            byte[] certsBlob = mBinder.getCredentialKeyCertificateChain();
+            ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
+
+            Collection<? extends Certificate> certs = null;
+            try {
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                certs = factory.generateCertificates(bais);
+            } catch (CertificateException e) {
+                throw new RuntimeException("Error decoding certificates", e);
+            }
+
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            for (Certificate cert : certs) {
+                x509Certs.add((X509Certificate) cert);
+            }
+            return x509Certs;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    private boolean mAllowUsingExhaustedKeys = true;
+
+    @Override
+    public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
+        mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
+    }
+
+    private boolean mOperationHandleSet = false;
+    private long mOperationHandle = 0;
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    @Override
+    public long getCredstoreOperationHandle() {
+        if (!mOperationHandleSet) {
+            try {
+                mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys);
+                mOperationHandleSet = true;
+            } catch (android.os.RemoteException e) {
+                throw new RuntimeException("Unexpected RemoteException ", e);
+            } catch (android.os.ServiceSpecificException e) {
+                if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+                    // The NoAuthenticationKeyAvailableException will be thrown when
+                    // the caller proceeds to call getEntries().
+                }
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+        return mOperationHandle;
+    }
+
+    @NonNull
+    @Override
+    public ResultData getEntries(
+            @Nullable byte[] requestMessage,
+            @NonNull Map<String, Collection<String>> entriesToRequest,
+            @Nullable byte[] sessionTranscript,
+            @Nullable byte[] readerSignature)
+            throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
+            InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
+            InvalidRequestMessageException {
+
+        RequestNamespaceParcel[] rnsParcels = new RequestNamespaceParcel[entriesToRequest.size()];
+        int n = 0;
+        for (String namespaceName : entriesToRequest.keySet()) {
+            Collection<String> entryNames = entriesToRequest.get(namespaceName);
+            rnsParcels[n] = new RequestNamespaceParcel();
+            rnsParcels[n].namespaceName = namespaceName;
+            rnsParcels[n].entries = new RequestEntryParcel[entryNames.size()];
+            int m = 0;
+            for (String entryName : entryNames) {
+                rnsParcels[n].entries[m] = new RequestEntryParcel();
+                rnsParcels[n].entries[m].name = entryName;
+                m++;
+            }
+            n++;
+        }
+
+        GetEntriesResultParcel resultParcel = null;
+        try {
+            resultParcel = mBinder.getEntries(
+                requestMessage != null ? requestMessage : new byte[0],
+                rnsParcels,
+                sessionTranscript != null ? sessionTranscript : new byte[0],
+                readerSignature != null ? readerSignature : new byte[0],
+                mAllowUsingExhaustedKeys);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) {
+                throw new EphemeralPublicKeyNotFoundException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_INVALID_READER_SIGNATURE) {
+                throw new InvalidReaderSignatureException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_NO_AUTHENTICATION_KEY_AVAILABLE) {
+                throw new NoAuthenticationKeyAvailableException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_INVALID_ITEMS_REQUEST_MESSAGE) {
+                throw new InvalidRequestMessageException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_SESSION_TRANSCRIPT_MISMATCH) {
+                throw new SessionTranscriptMismatchException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+
+        byte[] mac = resultParcel.mac;
+        if (mac != null && mac.length == 0) {
+            mac = null;
+        }
+        CredstoreResultData.Builder resultDataBuilder = new CredstoreResultData.Builder(
+                resultParcel.staticAuthenticationData, resultParcel.deviceNameSpaces, mac);
+
+        for (ResultNamespaceParcel resultNamespaceParcel : resultParcel.resultNamespaces) {
+            for (ResultEntryParcel resultEntryParcel : resultNamespaceParcel.entries) {
+                if (resultEntryParcel.status == ICredential.STATUS_OK) {
+                    resultDataBuilder.addEntry(resultNamespaceParcel.namespaceName,
+                            resultEntryParcel.name, resultEntryParcel.value);
+                } else {
+                    resultDataBuilder.addErrorStatus(resultNamespaceParcel.namespaceName,
+                            resultEntryParcel.name,
+                            resultEntryParcel.status);
+                }
+            }
+        }
+        return resultDataBuilder.build();
+    }
+
+    @Override
+    public void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
+        try {
+            mBinder.setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public @NonNull Collection<X509Certificate> getAuthKeysNeedingCertification() {
+        try {
+            AuthKeyParcel[] authKeyParcels = mBinder.getAuthKeysNeedingCertification();
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            CertificateFactory factory = CertificateFactory.getInstance("X.509");
+            for (AuthKeyParcel authKeyParcel : authKeyParcels) {
+                Collection<? extends Certificate> certs = null;
+                ByteArrayInputStream bais = new ByteArrayInputStream(authKeyParcel.x509cert);
+                certs = factory.generateCertificates(bais);
+                if (certs.size() != 1) {
+                    throw new RuntimeException("Returned blob yields more than one X509 cert");
+                }
+                X509Certificate authKeyCert = (X509Certificate) certs.iterator().next();
+                x509Certs.add(authKeyCert);
+            }
+            return x509Certs;
+        } catch (CertificateException e) {
+            throw new RuntimeException("Error decoding authenticationKey", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public void storeStaticAuthenticationData(X509Certificate authenticationKey,
+            byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException {
+        try {
+            AuthKeyParcel authKeyParcel = new AuthKeyParcel();
+            authKeyParcel.x509cert = authenticationKey.getEncoded();
+            mBinder.storeStaticAuthenticationData(authKeyParcel, staticAuthData);
+        } catch (CertificateEncodingException e) {
+            throw new RuntimeException("Error encoding authenticationKey", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) {
+                throw new UnknownAuthenticationKeyException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
+    public @NonNull int[] getAuthenticationDataUsageCount() {
+        try {
+            int[] usageCount = mBinder.getAuthenticationDataUsageCount();
+            return usageCount;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
new file mode 100644
index 0000000..dcc6b95
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.ServiceManager;
+
+class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
+
+    private static final String TAG = "CredstoreIdentityCredentialStore";
+
+    private Context mContext = null;
+    private ICredentialStore mStore = null;
+
+    private CredstoreIdentityCredentialStore(@NonNull Context context, ICredentialStore store) {
+        mContext = context;
+        mStore = store;
+    }
+
+    static CredstoreIdentityCredentialStore getInstanceForType(@NonNull Context context,
+            int credentialStoreType) {
+        ICredentialStoreFactory storeFactory =
+                ICredentialStoreFactory.Stub.asInterface(
+                    ServiceManager.getService("android.security.identity"));
+
+        ICredentialStore credStore = null;
+        try {
+            credStore = storeFactory.getCredentialStore(credentialStoreType);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_GENERIC) {
+                return null;
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+        if (credStore == null) {
+            return null;
+        }
+
+        return new CredstoreIdentityCredentialStore(context, credStore);
+    }
+
+    private static CredstoreIdentityCredentialStore sInstanceDefault = null;
+    private static CredstoreIdentityCredentialStore sInstanceDirectAccess = null;
+
+    public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
+        if (sInstanceDefault == null) {
+            sInstanceDefault = getInstanceForType(context,
+                    ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DEFAULT);
+        }
+        return sInstanceDefault;
+    }
+
+    public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
+            Context context) {
+        if (sInstanceDirectAccess == null) {
+            sInstanceDirectAccess = getInstanceForType(context,
+                    ICredentialStoreFactory.CREDENTIAL_STORE_TYPE_DIRECT_ACCESS);
+        }
+        return sInstanceDirectAccess;
+    }
+
+    @Override
+    public @NonNull String[] getSupportedDocTypes() {
+        try {
+            SecurityHardwareInfoParcel info;
+            info = mStore.getSecurityHardwareInfo();
+            return info.supportedDocTypes;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override public @NonNull WritableIdentityCredential createCredential(
+            @NonNull String credentialName,
+            @NonNull String docType) throws AlreadyPersonalizedException,
+            DocTypeNotSupportedException {
+        try {
+            IWritableCredential wc;
+            wc = mStore.createCredential(credentialName, docType);
+            return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_ALREADY_PERSONALIZED) {
+                throw new AlreadyPersonalizedException(e.getMessage(), e);
+            } else if (e.errorCode == ICredentialStore.ERROR_DOCUMENT_TYPE_NOT_SUPPORTED) {
+                throw new DocTypeNotSupportedException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override public @Nullable IdentityCredential getCredentialByName(
+            @NonNull String credentialName,
+            @Ciphersuite int cipherSuite) throws CipherSuiteNotSupportedException {
+        try {
+            ICredential credstoreCredential;
+            credstoreCredential = mStore.getCredentialByName(credentialName, cipherSuite);
+            return new CredstoreIdentityCredential(mContext, credentialName, cipherSuite,
+                    credstoreCredential);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+                return null;
+            } else if (e.errorCode == ICredentialStore.ERROR_CIPHER_SUITE_NOT_SUPPORTED) {
+                throw new CipherSuiteNotSupportedException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
+    public @Nullable byte[] deleteCredentialByName(@NonNull String credentialName) {
+        ICredential credstoreCredential = null;
+        try {
+            try {
+                credstoreCredential = mStore.getCredentialByName(credentialName,
+                        CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+            } catch (android.os.ServiceSpecificException e) {
+                if (e.errorCode == ICredentialStore.ERROR_NO_SUCH_CREDENTIAL) {
+                    return null;
+                }
+            }
+            byte[] proofOfDeletion = credstoreCredential.deleteCredential();
+            return proofOfDeletion;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreResultData.java b/identity/java/android/security/identity/CredstoreResultData.java
new file mode 100644
index 0000000..ef7afca
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreResultData.java
@@ -0,0 +1,162 @@
+/*
+ * 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.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested from a {@link IdentityCredential}.
+ */
+class CredstoreResultData extends ResultData {
+
+    byte[] mStaticAuthenticationData = null;
+    byte[] mAuthenticatedData = null;
+    byte[] mMessageAuthenticationCode = null;
+
+    private Map<String, Map<String, EntryData>> mData = new LinkedHashMap<>();
+
+    private static class EntryData {
+        @Status
+        int mStatus;
+        byte[] mValue;
+
+        EntryData(byte[] value, @Status int status) {
+            this.mValue = value;
+            this.mStatus = status;
+        }
+    }
+
+    CredstoreResultData() {}
+
+    @Override
+    public @NonNull byte[] getAuthenticatedData() {
+        return mAuthenticatedData;
+    }
+
+    @Override
+    public @Nullable byte[] getMessageAuthenticationCode() {
+        return mMessageAuthenticationCode;
+    }
+
+    @Override
+    public @NonNull byte[] getStaticAuthenticationData() {
+        return mStaticAuthenticationData;
+    }
+
+    @Override
+    public @NonNull Collection<String> getNamespaceNames() {
+        return Collections.unmodifiableCollection(mData.keySet());
+    }
+
+    @Override
+    public @Nullable Collection<String> getEntryNames(@NonNull String namespaceName) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        return Collections.unmodifiableCollection(innerMap.keySet());
+    }
+
+    @Override
+    public @Nullable Collection<String> getRetrievedEntryNames(@NonNull String namespaceName) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        LinkedList<String> result = new LinkedList<String>();
+        for (Map.Entry<String, EntryData> entry : innerMap.entrySet()) {
+            if (entry.getValue().mStatus == STATUS_OK) {
+                result.add(entry.getKey());
+            }
+        }
+        return result;
+    }
+
+    private EntryData getEntryData(@NonNull String namespaceName, @NonNull String name) {
+        Map<String, EntryData> innerMap = mData.get(namespaceName);
+        if (innerMap == null) {
+            return null;
+        }
+        return innerMap.get(name);
+    }
+
+    @Override
+    @Status
+    public int getStatus(@NonNull String namespaceName, @NonNull String name) {
+        EntryData value = getEntryData(namespaceName, name);
+        if (value == null) {
+            return STATUS_NOT_REQUESTED;
+        }
+        return value.mStatus;
+    }
+
+    @Override
+    public @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name) {
+        EntryData value = getEntryData(namespaceName, name);
+        if (value == null) {
+            return null;
+        }
+        return value.mValue;
+    }
+
+    static class Builder {
+        private CredstoreResultData mResultData;
+
+        Builder(byte[] staticAuthenticationData,
+                byte[] authenticatedData,
+                byte[] messageAuthenticationCode) {
+            this.mResultData = new CredstoreResultData();
+            this.mResultData.mStaticAuthenticationData = staticAuthenticationData;
+            this.mResultData.mAuthenticatedData = authenticatedData;
+            this.mResultData.mMessageAuthenticationCode = messageAuthenticationCode;
+        }
+
+        private Map<String, EntryData> getOrCreateInnerMap(String namespaceName) {
+            Map<String, EntryData> innerMap = mResultData.mData.get(namespaceName);
+            if (innerMap == null) {
+                innerMap = new LinkedHashMap<>();
+                mResultData.mData.put(namespaceName, innerMap);
+            }
+            return innerMap;
+        }
+
+        Builder addEntry(String namespaceName, String name, byte[] value) {
+            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
+            innerMap.put(name, new EntryData(value, STATUS_OK));
+            return this;
+        }
+
+        Builder addErrorStatus(String namespaceName, String name, @Status int status) {
+            Map<String, EntryData> innerMap = getOrCreateInnerMap(namespaceName);
+            innerMap.put(name, new EntryData(null, status));
+            return this;
+        }
+
+        CredstoreResultData build() {
+            return mResultData;
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
new file mode 100644
index 0000000..335636c
--- /dev/null
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.GateKeeper;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.LinkedList;
+
+class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
+
+    private static final String TAG = "CredstoreWritableIdentityCredential";
+
+    private String mDocType;
+    private String mCredentialName;
+    private Context mContext;
+    private IWritableCredential mBinder;
+
+    CredstoreWritableIdentityCredential(Context context,
+            @NonNull String credentialName,
+            @NonNull String docType,
+            IWritableCredential binder) {
+        mContext = context;
+        mDocType = docType;
+        mCredentialName = credentialName;
+        mBinder = binder;
+    }
+
+    @NonNull @Override
+    public Collection<X509Certificate> getCredentialKeyCertificateChain(@NonNull byte[] challenge) {
+        try {
+            byte[] certsBlob = mBinder.getCredentialKeyCertificateChain(challenge);
+            ByteArrayInputStream bais = new ByteArrayInputStream(certsBlob);
+
+            Collection<? extends Certificate> certs = null;
+            try {
+                CertificateFactory factory = CertificateFactory.getInstance("X.509");
+                certs = factory.generateCertificates(bais);
+            } catch (CertificateException e) {
+                throw new RuntimeException("Error decoding certificates", e);
+            }
+
+            LinkedList<X509Certificate> x509Certs = new LinkedList<>();
+            for (Certificate cert : certs) {
+                x509Certs.add((X509Certificate) cert);
+            }
+            return x509Certs;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @NonNull @Override
+    public byte[] personalize(@NonNull PersonalizationData personalizationData) {
+
+        Collection<AccessControlProfile> accessControlProfiles =
+                personalizationData.getAccessControlProfiles();
+
+        AccessControlProfileParcel[] acpParcels =
+                new AccessControlProfileParcel[accessControlProfiles.size()];
+        boolean usingUserAuthentication = false;
+        int n = 0;
+        for (AccessControlProfile profile : accessControlProfiles) {
+            acpParcels[n] = new AccessControlProfileParcel();
+            acpParcels[n].id = profile.getAccessControlProfileId().getId();
+            X509Certificate cert = profile.getReaderCertificate();
+            if (cert != null) {
+                try {
+                    acpParcels[n].readerCertificate = cert.getEncoded();
+                } catch (CertificateException e) {
+                    throw new RuntimeException("Error encoding reader certificate", e);
+                }
+            } else {
+                acpParcels[n].readerCertificate = new byte[0];
+            }
+            acpParcels[n].userAuthenticationRequired = profile.isUserAuthenticationRequired();
+            acpParcels[n].userAuthenticationTimeoutMillis = profile.getUserAuthenticationTimeout();
+            if (profile.isUserAuthenticationRequired()) {
+                usingUserAuthentication = true;
+            }
+            n++;
+        }
+
+        Collection<String> namespaceNames = personalizationData.getNamespaceNames();
+
+        EntryNamespaceParcel[] ensParcels  = new EntryNamespaceParcel[namespaceNames.size()];
+        n = 0;
+        for (String namespaceName : namespaceNames) {
+            PersonalizationData.NamespaceData nsd =
+                    personalizationData.getNamespaceData(namespaceName);
+
+            ensParcels[n] = new EntryNamespaceParcel();
+            ensParcels[n].namespaceName = namespaceName;
+
+            Collection<String> entryNames = nsd.getEntryNames();
+            EntryParcel[] eParcels = new EntryParcel[entryNames.size()];
+            int m = 0;
+            for (String entryName : entryNames) {
+                eParcels[m] = new EntryParcel();
+                eParcels[m].name = entryName;
+                eParcels[m].value = nsd.getEntryValue(entryName);
+                Collection<AccessControlProfileId> acpIds =
+                        nsd.getAccessControlProfileIds(entryName);
+                eParcels[m].accessControlProfileIds = new int[acpIds.size()];
+                int o = 0;
+                for (AccessControlProfileId acpId : acpIds) {
+                    eParcels[m].accessControlProfileIds[o++] = acpId.getId();
+                }
+                m++;
+            }
+            ensParcels[n].entries = eParcels;
+            n++;
+        }
+
+        // Note: The value 0 is used to convey that no user-authentication is needed for this
+        // credential. This is to allow creating credentials w/o user authentication on devices
+        // where Secure lock screen is not enabled.
+        long secureUserId = 0;
+        if (usingUserAuthentication) {
+            secureUserId = getRootSid();
+        }
+        try {
+            byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels,
+                    secureUserId);
+            return personalizationReceipt;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    private static long getRootSid() {
+        long rootSid = GateKeeper.getSecureUserId();
+        if (rootSid == 0) {
+            throw new IllegalStateException("Secure lock screen must be enabled"
+                    + " to create credentials requiring user authentication");
+        }
+        return rootSid;
+    }
+
+
+}
diff --git a/identity/java/android/security/identity/DocTypeNotSupportedException.java b/identity/java/android/security/identity/DocTypeNotSupportedException.java
new file mode 100644
index 0000000..754e44a
--- /dev/null
+++ b/identity/java/android/security/identity/DocTypeNotSupportedException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to create a credential with an unsupported document type.
+ */
+public class DocTypeNotSupportedException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link DocTypeNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     */
+    public DocTypeNotSupportedException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link DocTypeNotSupportedException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public DocTypeNotSupportedException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java
new file mode 100644
index 0000000..265f271
--- /dev/null
+++ b/identity/java/android/security/identity/EphemeralPublicKeyNotFoundException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if the ephemeral public key was not found in the session transcript
+ * passed to {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
+ */
+public class EphemeralPublicKeyNotFoundException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
+     *
+     * @param message the detail message.
+     */
+    public EphemeralPublicKeyNotFoundException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link EphemeralPublicKeyNotFoundException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public EphemeralPublicKeyNotFoundException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
new file mode 100644
index 0000000..bd43919
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -0,0 +1,309 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Class used to read data from a previously provisioned credential.
+ *
+ * Use {@link IdentityCredentialStore#getCredentialByName(String, int)} to get a
+ * {@link IdentityCredential} instance.
+ */
+public abstract class IdentityCredential {
+    /**
+     * @hide
+     */
+    protected IdentityCredential() {}
+
+    /**
+     * Create an ephemeral key pair to use to establish a secure channel with a reader.
+     *
+     * <p>Most applications will use only the public key, and only to send it to the reader,
+     * allowing the private key to be used internally for {@link #encryptMessageToReader(byte[])}
+     * and {@link #decryptMessageFromReader(byte[])}. The private key is also provided for
+     * applications that wish to use a cipher suite that is not supported by
+     * {@link IdentityCredentialStore}.
+     *
+     * @return ephemeral key pair to use to establish a secure channel with a reader.
+     */
+    public @NonNull abstract KeyPair createEphemeralKeyPair();
+
+    /**
+     * Set the ephemeral public key provided by the reader. This must be called before
+     * {@link #encryptMessageToReader} or {@link #decryptMessageFromReader} can be called.
+     *
+     * @param readerEphemeralPublicKey The ephemeral public key provided by the reader to
+     *                                 establish a secure session.
+     * @throws InvalidKeyException if the given key is invalid.
+     */
+    public abstract void setReaderEphemeralPublicKey(@NonNull PublicKey readerEphemeralPublicKey)
+            throws InvalidKeyException;
+
+    /**
+     * Encrypt a message for transmission to the reader.
+     *
+     * @param messagePlaintext unencrypted message to encrypt.
+     * @return encrypted message.
+     */
+    public @NonNull abstract byte[] encryptMessageToReader(@NonNull byte[] messagePlaintext);
+
+    /**
+     * Decrypt a message received from the reader.
+     *
+     * @param messageCiphertext encrypted message to decrypt.
+     * @return decrypted message.
+     * @throws MessageDecryptionException if the ciphertext couldn't be decrypted.
+     */
+    public @NonNull abstract byte[] decryptMessageFromReader(@NonNull byte[] messageCiphertext)
+            throws MessageDecryptionException;
+
+    /**
+     * Gets the X.509 certificate chain for the CredentialKey which identifies this
+     * credential to the issuing authority. This is the same certificate chain that
+     * was returned by {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])}
+     * when the credential was first created and its Android Keystore extension will
+     * contain the <code>challenge</code> data set at that time. See the documentation
+     * for that method for important information about this certificate chain.
+     *
+     * @return the certificate chain for this credential's CredentialKey.
+     */
+    public @NonNull abstract Collection<X509Certificate> getCredentialKeyCertificateChain();
+
+    /**
+     * Sets whether to allow using an authentication key which use count has been exceeded if no
+     * other key is available. This must be called prior to calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
+     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
+     * object.
+     *
+     * By default this is set to true.
+     *
+     * @param allowUsingExhaustedKeys whether to allow using an authentication key which use count
+     *                                has been exceeded if no other key is available.
+     */
+    public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
+
+    /**
+     * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
+     * operation handle.
+     *
+     * @hide
+     */
+    public abstract long getCredstoreOperationHandle();
+
+    /**
+     * Retrieve data entries and associated data from this {@code IdentityCredential}.
+     *
+     * <p>If an access control check fails for one of the requested entries or if the entry
+     * doesn't exist, the entry is simply not returned. The application can detect this
+     * by using the {@link ResultData#getStatus(String, String)} method on each of the requested
+     * entries.
+     *
+     * <p>It is the responsibility of the calling application to know if authentication is needed
+     * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user
+     * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
+     * references this object. If needed, this must be done before calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])}.
+     *
+     * <p>If this method returns successfully (i.e. without throwing an exception), it must not be
+     * called again on this instance.
+     *
+     * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
+     * from the verifier. The content can be defined in the way appropriate for the credential, byt
+     * there are three requirements that must be met to work with this API:
+     * <ul>
+     * <li>The content must be a CBOR-encoded structure.</li>
+     * <li>The CBOR structure must be a map.</li>
+     * <li>The map must contain a tstr key "nameSpaces" whose value contains a map, as described in
+     *     the example below.</li>
+     * </ul>
+     *
+     * <p>Here's an example of CBOR which conforms to this requirement:
+     * <pre>
+     *   ItemsRequest = {
+     *     ? "docType" : DocType,
+     *     "nameSpaces" : NameSpaces,
+     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
+     *   }
+     *
+     *   NameSpaces = {
+     *     + NameSpace => DataElements    ; Requested data elements for each NameSpace
+     *   }
+     *
+     *   NameSpace = tstr
+     *
+     *   DataElements = {
+     *     + DataElement => IntentToRetain
+     *   }
+     *
+     *   DataElement = tstr
+     *   IntentToRetain = bool
+     * </pre>
+     *
+     * <p>If the {@code sessionTranscript} parameter is not {@code null}, it must contain CBOR
+     * data conforming to the following CDDL schema:
+     *
+     * <pre>
+     *   SessionTranscript = [
+     *     DeviceEngagementBytes,
+     *     EReaderKeyBytes
+     *   ]
+     *
+     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     * </pre>
+     *
+     * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part
+     * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear
+     * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present
+     * in uncompressed form.
+     *
+     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1
+     * structure as defined in RFC 8152. For the payload nil shall be used and the
+     * detached payload is the ReaderAuthentication CBOR described below.
+     * <pre>
+     *     ReaderAuthentication = [
+     *       "ReaderAuthentication",
+     *       SessionTranscript,
+     *       ItemsRequestBytes
+     *     ]
+     *
+     *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)   ; Bytes of ItemsRequest
+     * </pre>
+     *
+     * <p>The public key corresponding to the key used to made signature, can be
+     * found in the {@code 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).
+     *
+     * <p>Data elements protected by reader authentication is returned if, and only if, they are
+     * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
+     * certificate in {@code readerCertificateChain}, and the data element is configured
+     * with an {@link AccessControlProfile} with a {@link X509Certificate} in
+     * {@code readerCertificateChain}.
+     *
+     * <p>Note that only items referenced in {@code entriesToRequest} are returned - the
+     * {@code requestMessage} parameter is only used to for enforcing reader authentication.
+     *
+     * @param requestMessage         If not {@code null}, must contain CBOR data conforming to
+     *                               the schema mentioned above.
+     * @param entriesToRequest       The entries to request, organized as a map of namespace
+     *                               names with each value being a collection of data elements
+     *                               in the given namespace.
+     * @param readerSignature        COSE_Sign1 structure as described above or {@code null}
+     *                               if reader authentication is not being used.
+     * @return A {@link ResultData} object containing entry data organized by namespace and a
+     *         cryptographically authenticated representation of the same data.
+     * @throws SessionTranscriptMismatchException     Thrown when trying use multiple different
+     *                                                session transcripts in the same presentation
+     *                                                session.
+     * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
+     *                                                provisioned, the method
+     *                                             {@link #setAvailableAuthenticationKeys(int, int)}
+     *                                                was called with {@code keyCount} set to 0,
+     *                                                the method
+     *                                                {@link #setAllowUsingExhaustedKeys(boolean)}
+     *                                                was called with {@code false} and all
+     *                                                available authentication keys have been
+     *                                                exhausted.
+     * @throws InvalidReaderSignatureException        if the reader signature is invalid, or it
+     *                                                doesn't contain a certificate chain, or if
+     *                                                the signature failed to validate.
+     * @throws InvalidRequestMessageException         if the requestMessage is malformed.
+     * @throws EphemeralPublicKeyNotFoundException    if the ephemeral public key was not found in
+     *                                                the session transcript.
+     */
+    public abstract @NonNull ResultData getEntries(
+            @Nullable byte[] requestMessage,
+            @NonNull Map<String, Collection<String>> entriesToRequest,
+            @Nullable byte[] sessionTranscript,
+            @Nullable byte[] readerSignature)
+            throws SessionTranscriptMismatchException, NoAuthenticationKeyAvailableException,
+            InvalidReaderSignatureException, EphemeralPublicKeyNotFoundException,
+            InvalidRequestMessageException;
+
+    /**
+     * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
+     * and the number of times each should be used.
+     *
+     * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
+     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s
+     * for which this method has not been called behave as though it had been called wit
+     * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
+     *
+     * @param keyCount      The number of active, certified dynamic authentication keys the
+     *                      {@code IdentityCredential} will try to keep available. This value
+     *                      must be non-negative.
+     * @param maxUsesPerKey The maximum number of times each of the keys will be used before it's
+     *                      eligible for replacement. This value must be greater than zero.
+     */
+    public abstract void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
+
+    /**
+     * Gets a collection of dynamic authentication keys that need certification.
+     *
+     * <p>When there aren't enough certified dynamic authentication keys, either because the key
+     * count has been increased or because one or more keys have reached their usage count, this
+     * method will generate replacement keys and certificates and return them for issuer
+     * certification. The issuer certificates and associated static authentication data must then
+     * be provided back to the {@code IdentityCredential} using
+     * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.
+     *
+     * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
+     * can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
+     *
+     * @return A collection of X.509 certificates for dynamic authentication keys that need issuer
+     * certification.
+     */
+    public @NonNull abstract Collection<X509Certificate> getAuthKeysNeedingCertification();
+
+    /**
+     * Store authentication data associated with a dynamic authentication key.
+     *
+     * This should only be called for an authenticated key returned by
+     * {@link #getAuthKeysNeedingCertification()}.
+     *
+     * @param authenticationKey The dynamic authentication key for which certification and
+     *                          associated static
+     *                          authentication data is being provided.
+     * @param staticAuthData    Static authentication data provided by the issuer that validates
+     *                          the authenticity
+     *                          and integrity of the credential data fields.
+     * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
+     */
+    public abstract void storeStaticAuthenticationData(
+            @NonNull X509Certificate authenticationKey,
+            @NonNull byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException;
+
+    /**
+     * Get the number of times the dynamic authentication keys have been used.
+     *
+     * @return int array of dynamic authentication key usage counts.
+     */
+    public @NonNull abstract int[] getAuthenticationDataUsageCount();
+}
diff --git a/identity/java/android/security/identity/IdentityCredentialException.java b/identity/java/android/security/identity/IdentityCredentialException.java
new file mode 100644
index 0000000..c811380
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredentialException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for all Identity Credential exceptions.
+ */
+public class IdentityCredentialException extends Exception {
+    /**
+     * Constructs a new {@link IdentityCredentialException} exception.
+     *
+     * @param message the detail message.
+     */
+    public IdentityCredentialException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link IdentityCredentialException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public IdentityCredentialException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
new file mode 100644
index 0000000..a1dfc77
--- /dev/null
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface to a secure store for user identity documents.
+ *
+ * <p>This interface 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. 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.
+ *
+ * <p>Multiple credentials can be created.  Each credential comprises:</p>
+ * <ul>
+ * <li>A document type, which is a string.</li>
+ *
+ * <li>A set of namespaces, which serve to disambiguate value names. It is recommended
+ * that namespaces be structured as reverse domain names so that IANA effectively serves
+ * as the namespace registrar.</li>
+ *
+ * <li>For each namespace, a set of name/value pairs, each with an associated set of
+ * access control profile IDs.  Names are strings and values are typed and can be any
+ * value supported by <a href="http://cbor.io/">CBOR</a>.</li>
+ *
+ * <li>A set of access control profiles, each with a profile ID and a specification
+ * of the conditions which satisfy the profile's requirements.</li>
+ *
+ * <li>An asymmetric key pair which is used to authenticate the credential to the Issuing
+ * Authority, called the <em>CredentialKey</em>.</li>
+ *
+ * <li>A set of zero or more named reader authentication public keys, which are used to
+ * authenticate an authorized reader to the credential.</li>
+ *
+ * <li>A set of named signing keys, which are used to sign collections of values and session
+ * transcripts.</li>
+ * </ul>
+ *
+ * <p>Implementing support for user identity documents in secure storage requires dedicated
+ * hardware-backed support and may not always be available.
+ *
+ * <p>Two different credential stores exist - the <em>default</em> store and the
+ * <em>direct access</em> store. Most often credentials will be accessed through the default
+ * store 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.
+ *
+ * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader
+ * authentication to protect data elements. The reason for this is user authentication or user
+ * approval of data release is not possible when the device is off.
+ */
+public abstract class IdentityCredentialStore {
+    IdentityCredentialStore() {}
+
+    /**
+     * Specifies that the cipher suite that will be used to secure communications between the reader
+     * is:
+     *
+     * <ul>
+     * <li>ECDHE with HKDF-SHA-256 for key agreement.</li>
+     * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
+     * for every message).</li>
+     * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
+     * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential}
+     * for details on reader and prover signing keys.</li>
+     * </ul>
+     *
+     * <p>
+     * At present this is the only supported cipher suite.
+     */
+    public static final int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1;
+
+    /**
+     * Gets the default {@link IdentityCredentialStore}.
+     *
+     * @param context the application context.
+     * @return the {@link IdentityCredentialStore} or {@code null} if the device doesn't
+     *     have hardware-backed support for secure storage of user identity documents.
+     */
+    public static @Nullable IdentityCredentialStore getInstance(@NonNull Context context) {
+        return CredstoreIdentityCredentialStore.getInstance(context);
+    }
+
+    /**
+     * Gets the {@link IdentityCredentialStore} for direct access.
+     *
+     * <p>Direct access requires specialized NFC hardware and may not be supported on all
+     * devices even if default store is available. Credentials provisioned to the direct
+     * access store should <strong>always</strong> use reader authentication to protect
+     * data elements.
+     *
+     * @param context the application context.
+     * @return the {@link IdentityCredentialStore} or {@code null} if direct access is not
+     *     supported on this device.
+     */
+    public static @Nullable IdentityCredentialStore getDirectAccessInstance(@NonNull
+            Context context) {
+        return CredstoreIdentityCredentialStore.getDirectAccessInstance(context);
+    }
+
+    /**
+     * Gets a list of supported document types.
+     *
+     * <p>Only the direct-access store may restrict the kind of document types that can be used for
+     * credentials. The default store always supports any document type.
+     *
+     * @return The supported document types or the empty array if any document type is supported.
+     */
+    public abstract @NonNull String[] getSupportedDocTypes();
+
+    /**
+     * Creates a new credential.
+     *
+     * @param credentialName The name used to identify the credential.
+     * @param docType        The document type for the credential.
+     * @return A @{link WritableIdentityCredential} that can be used to create a new credential.
+     * @throws AlreadyPersonalizedException if a credential with the given name already exists.
+     * @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
+     */
+    public abstract @NonNull WritableIdentityCredential createCredential(
+            @NonNull String credentialName, @NonNull String docType)
+            throws AlreadyPersonalizedException, DocTypeNotSupportedException;
+
+    /**
+     * Retrieve a named credential.
+     *
+     * @param credentialName the name of the credential to retrieve.
+     * @param cipherSuite    the cipher suite to use for communicating with the verifier.
+     * @return The named credential, or null if not found.
+     */
+    public abstract @Nullable IdentityCredential getCredentialByName(@NonNull String credentialName,
+            @Ciphersuite int cipherSuite)
+            throws CipherSuiteNotSupportedException;
+
+    /**
+     * Delete a named credential.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+     * with payload set to {@code ProofOfDeletion} as defined below:
+     *
+     * <pre>
+     *     ProofOfDeletion = [
+     *          "ProofOfDeletion",            ; tstr
+     *          tstr,                         ; DocType
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *      ]
+     * </pre>
+     *
+     * @param credentialName the name of the credential to delete.
+     * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above
+     *     if the credential was found and deleted.
+     */
+    public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
+
+    /** @hide */
+    @IntDef(value = {CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Ciphersuite {
+    }
+
+}
diff --git a/identity/java/android/security/identity/InvalidReaderSignatureException.java b/identity/java/android/security/identity/InvalidReaderSignatureException.java
new file mode 100644
index 0000000..3f70270
--- /dev/null
+++ b/identity/java/android/security/identity/InvalidReaderSignatureException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if the reader signature is invalid, or it doesn't contain a certificate chain, or if the
+ * signature failed to validate.
+ */
+public class InvalidReaderSignatureException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link InvalidReaderSignatureException} exception.
+     *
+     * @param message the detail message.
+     */
+    public InvalidReaderSignatureException(@NonNull String message) {
+        super(message);
+    }
+
+
+    /**
+     * Constructs a new {@link InvalidReaderSignatureException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public InvalidReaderSignatureException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/InvalidRequestMessageException.java b/identity/java/android/security/identity/InvalidRequestMessageException.java
new file mode 100644
index 0000000..b0c073c
--- /dev/null
+++ b/identity/java/android/security/identity/InvalidRequestMessageException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if message with the request doesn't satisfy the requirements documented in
+ * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])}.
+ */
+public class InvalidRequestMessageException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link InvalidRequestMessageException} exception.
+     *
+     * @param message the detail message.
+     */
+    public InvalidRequestMessageException(@NonNull String message) {
+        super(message);
+    }
+
+
+    /**
+     * Constructs a new {@link InvalidRequestMessageException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public InvalidRequestMessageException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/MessageDecryptionException.java b/identity/java/android/security/identity/MessageDecryptionException.java
new file mode 100644
index 0000000..7a6169e
--- /dev/null
+++ b/identity/java/android/security/identity/MessageDecryptionException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown when failing to decrypt a message from the reader device.
+ */
+public class MessageDecryptionException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link MessageDecryptionException} exception.
+     *
+     * @param message the detail message.
+     */
+    public MessageDecryptionException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link MessageDecryptionException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public MessageDecryptionException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java
new file mode 100644
index 0000000..7f40403
--- /dev/null
+++ b/identity/java/android/security/identity/NoAuthenticationKeyAvailableException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if no dynamic authentication keys are available.
+ */
+public class NoAuthenticationKeyAvailableException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
+     *
+     * @param message the detail message.
+     */
+    public NoAuthenticationKeyAvailableException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link NoAuthenticationKeyAvailableException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public NoAuthenticationKeyAvailableException(@NonNull String message,
+            @NonNull Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/identity/java/android/security/identity/PersonalizationData.java b/identity/java/android/security/identity/PersonalizationData.java
new file mode 100644
index 0000000..44370a1
--- /dev/null
+++ b/identity/java/android/security/identity/PersonalizationData.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+/**
+ * An object that holds personalization data.
+ *
+ * This data includes access control profiles and a set of data entries and values, grouped by
+ * namespace.
+ *
+ * This is used to provision data into a {@link WritableIdentityCredential}.
+ *
+ * @see WritableIdentityCredential#personalize
+ */
+public class PersonalizationData {
+
+    private PersonalizationData() {
+    }
+
+    private LinkedList<AccessControlProfile> mProfiles = new LinkedList<>();
+
+    private LinkedHashMap<String, NamespaceData> mNamespaces = new LinkedHashMap<>();
+
+    Collection<AccessControlProfile> getAccessControlProfiles() {
+        return Collections.unmodifiableCollection(mProfiles);
+    }
+
+    Collection<String> getNamespaceNames() {
+        return Collections.unmodifiableCollection(mNamespaces.keySet());
+    }
+
+    NamespaceData getNamespaceData(String namespace) {
+        return mNamespaces.get(namespace);
+    }
+
+    static class NamespaceData {
+
+        private String mNamespace;
+        private LinkedHashMap<String, EntryData> mEntries = new LinkedHashMap<>();
+
+        private NamespaceData(String namespace) {
+            this.mNamespace = namespace;
+        }
+
+        String getNamespaceName() {
+            return mNamespace;
+        }
+
+        Collection<String> getEntryNames() {
+            return Collections.unmodifiableCollection(mEntries.keySet());
+        }
+
+        Collection<AccessControlProfileId> getAccessControlProfileIds(String name) {
+            EntryData value = mEntries.get(name);
+            if (value != null) {
+                return value.mAccessControlProfileIds;
+            }
+            return null;
+        }
+
+        byte[] getEntryValue(String name) {
+            EntryData value = mEntries.get(name);
+            if (value != null) {
+                return value.mValue;
+            }
+            return null;
+        }
+    }
+
+    private static class EntryData {
+        byte[] mValue;
+        Collection<AccessControlProfileId> mAccessControlProfileIds;
+
+        EntryData(byte[] value, Collection<AccessControlProfileId> accessControlProfileIds) {
+            this.mValue = value;
+            this.mAccessControlProfileIds = accessControlProfileIds;
+        }
+    }
+
+    /**
+     * A builder for {@link PersonalizationData}.
+     */
+    public static final class Builder {
+        private PersonalizationData mData;
+
+        /**
+         * Creates a new builder for a given namespace.
+         */
+        public Builder() {
+            this.mData = new PersonalizationData();
+        }
+
+        /**
+         * Adds a new entry to the builder.
+         *
+         * @param namespace               The namespace to use, e.g. {@code org.iso.18013-5.2019}.
+         * @param name                    The name of the entry, e.g. {@code height}.
+         * @param accessControlProfileIds A set of access control profiles to use.
+         * @param value                   The value to add, in CBOR encoding.
+         * @return The builder.
+         */
+        public @NonNull Builder setEntry(@NonNull String namespace, @NonNull String name,
+                @NonNull Collection<AccessControlProfileId> accessControlProfileIds,
+                @NonNull byte[] value) {
+            NamespaceData namespaceData = mData.mNamespaces.get(namespace);
+            if (namespaceData == null) {
+                namespaceData = new NamespaceData(namespace);
+                mData.mNamespaces.put(namespace, namespaceData);
+            }
+            // TODO: validate/verify that value is proper CBOR.
+            namespaceData.mEntries.put(name, new EntryData(value, accessControlProfileIds));
+            return this;
+        }
+
+        /**
+         * Adds a new access control profile to the builder.
+         *
+         * @param profile The access control profile.
+         * @return The builder.
+         */
+        public @NonNull Builder addAccessControlProfile(@NonNull AccessControlProfile profile) {
+            mData.mProfiles.add(profile);
+            return this;
+        }
+
+        /**
+         * Creates a new {@link PersonalizationData} with all the entries added to the builder.
+         *
+         * @return A new {@link PersonalizationData} instance.
+         */
+        public @NonNull PersonalizationData build() {
+            return mData;
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
new file mode 100644
index 0000000..0982c8a
--- /dev/null
+++ b/identity/java/android/security/identity/ResultData.java
@@ -0,0 +1,224 @@
+/*
+ * 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.security.identity;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.util.Collection;
+
+/**
+ * An object that contains the result of retrieving data from a credential. This is used to return
+ * data requested from a {@link IdentityCredential}.
+ */
+public abstract class ResultData {
+
+    /** Value was successfully retrieved. */
+    public static final int STATUS_OK = 0;
+
+    /** Requested entry does not exist. */
+    public static final int STATUS_NO_SUCH_ENTRY = 1;
+
+    /** Requested entry was not requested. */
+    public static final int STATUS_NOT_REQUESTED = 2;
+
+    /** Requested entry wasn't in the request message. */
+    public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
+
+    /** The requested entry was not retrieved because user authentication wasn't performed. */
+    public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
+
+    /** The requested entry was not retrieved because reader authentication wasn't performed. */
+    public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
+
+    /**
+     * The requested entry was not retrieved because it was configured without any access
+     * control profile.
+     */
+    public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
+
+    /**
+     * @hide
+     */
+    protected ResultData() {}
+
+    /**
+     * Returns a CBOR structure containing the retrieved data.
+     *
+     * <p>This structure - along with the session transcript - may be cryptographically
+     * authenticated to prove to the reader that the data is from a trusted credential and
+     * {@link #getMessageAuthenticationCode()} can be used to get a MAC.
+     *
+     * <p>The CBOR structure which is cryptographically authenticated is the
+     * {@code DeviceAuthentication} structure according to the following
+     * <a href="https://tools.ietf.org/html/draft-ietf-cbor-cddl-06">CDDL</a> schema:
+     *
+     * <pre>
+     *   DeviceAuthentication = [
+     *     "DeviceAuthentication",
+     *     SessionTranscript,
+     *     DocType,
+     *     DeviceNameSpacesBytes
+     *   ]
+     *
+     *   DocType = tstr
+     *
+     *   SessionTranscript = [
+     *     DeviceEngagementBytes,
+     *     EReaderKeyBytes
+     *   ]
+     *
+     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
+     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *
+     *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+     * </pre>
+     *
+     * where
+     *
+     * <pre>
+     *   DeviceNameSpaces = {
+     *     * NameSpace => DeviceSignedItems
+     *   }
+     *
+     *   DeviceSignedItems = {
+     *     + DataItemName => DataItemValue
+     *   }
+     *
+     *   NameSpace = tstr
+     *   DataItemName = tstr
+     *   DataItemValue = any
+     * </pre>
+     *
+     * <p>The returned data is the binary encoding of the {@code DeviceNameSpaces} structure
+     * as defined above.
+     *
+     * @return The bytes of the {@code DeviceNameSpaces} CBOR structure.
+     */
+    public abstract @NonNull byte[] getAuthenticatedData();
+
+    /**
+     * Returns a message authentication code over the data returned by
+     * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted
+     * credential.
+     *
+     * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
+     * produced by using the key agreement and key derivation function from the ciphersuite
+     * with the authentication private key and the reader ephemeral public key to compute a
+     * shared message authentication code (MAC) key, then using the MAC function from the
+     * ciphersuite to compute a MAC of the authenticated data.
+     *
+     * <p>If the {@code sessionTranscript} parameter passed to
+     * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null}
+     * or the reader ephmeral public key was never set using
+     * {@link IdentityCredential#setReaderEphemeralPublicKey(PublicKey)}, no message
+     * authencation code will be produced and this method will return {@code null}.
+     *
+     * @return A COSE_Mac0 structure with the message authentication code as described above
+     *         or {@code null} if the conditions specified above are not met.
+     */
+    public abstract @Nullable byte[] getMessageAuthenticationCode();
+
+    /**
+     * Returns the static authentication data associated with the dynamic authentication
+     * key used to sign or MAC the data returned by {@link #getAuthenticatedData()}.
+     *
+     * @return The static authentication data associated with dynamic authentication key used to
+     * MAC the data.
+     */
+    public abstract @NonNull byte[] getStaticAuthenticationData();
+
+    /**
+     * Gets the names of namespaces with retrieved entries.
+     *
+     * @return collection of name of namespaces containing retrieved entries. May be empty if no
+     *     data was retrieved.
+     */
+    public abstract @NonNull Collection<String> getNamespaceNames();
+
+    /**
+     * Get the names of all entries.
+     *
+     * This includes the name of entries that wasn't successfully retrieved.
+     *
+     * @param namespaceName the namespace name to get entries for.
+     * @return A collection of names or {@code null} if there are no entries for the given
+     *     namespace.
+     */
+    public abstract @Nullable Collection<String> getEntryNames(@NonNull String namespaceName);
+
+    /**
+     * Get the names of all entries that was successfully retrieved.
+     *
+     * This only return entries for which {@link #getStatus(String, String)} will return
+     * {@link #STATUS_OK}.
+     *
+     * @param namespaceName the namespace name to get entries for.
+     * @return A collection of names or {@code null} if there are no entries for the given
+     *     namespace.
+     */
+    public abstract @Nullable Collection<String> getRetrievedEntryNames(
+            @NonNull String namespaceName);
+
+    /**
+     * Gets the status of an entry.
+     *
+     * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
+     * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
+     * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
+     * present in the request message,
+     * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
+     * wasn't retrieved because the necessary user authentication wasn't performed,
+     * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
+     * didn't match the set of certificates the entry was provisioned with, or
+     * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
+     * access control profiles.
+     *
+     * @param namespaceName the namespace name of the entry.
+     * @param name the name of the entry to get the value for.
+     * @return the status indicating whether the value was retrieved and if not, why.
+     */
+    @Status
+    public abstract int getStatus(@NonNull String namespaceName, @NonNull String name);
+
+    /**
+     * Gets the raw CBOR data for the value of an entry.
+     *
+     * This should only be called on an entry for which the {@link #getStatus(String, String)}
+     * method returns {@link #STATUS_OK}.
+     *
+     * @param namespaceName the namespace name of the entry.
+     * @param name the name of the entry to get the value for.
+     * @return the raw CBOR data or {@code null} if no entry with the given name exists.
+     */
+    public abstract @Nullable byte[] getEntry(@NonNull String namespaceName, @NonNull String name);
+
+    /**
+     * The type of the entry status.
+     * @hide
+     */
+    @Retention(SOURCE)
+    @IntDef({STATUS_OK, STATUS_NO_SUCH_ENTRY, STATUS_NOT_REQUESTED, STATUS_NOT_IN_REQUEST_MESSAGE,
+                        STATUS_USER_AUTHENTICATION_FAILED, STATUS_READER_AUTHENTICATION_FAILED,
+                        STATUS_NO_ACCESS_CONTROL_PROFILES})
+    public @interface Status {
+    }
+}
diff --git a/identity/java/android/security/identity/SessionTranscriptMismatchException.java b/identity/java/android/security/identity/SessionTranscriptMismatchException.java
new file mode 100644
index 0000000..8c24060
--- /dev/null
+++ b/identity/java/android/security/identity/SessionTranscriptMismatchException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown when trying use multiple different session transcripts in the same presentation session.
+ */
+public class SessionTranscriptMismatchException extends IdentityCredentialException {
+
+    /**
+     * Constructs a new {@link SessionTranscriptMismatchException} exception.
+     *
+     * @param message the detail message.
+     */
+    public SessionTranscriptMismatchException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link SessionTranscriptMismatchException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public SessionTranscriptMismatchException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/UnknownAuthenticationKeyException.java b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java
new file mode 100644
index 0000000..f454b2c
--- /dev/null
+++ b/identity/java/android/security/identity/UnknownAuthenticationKeyException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown if trying to certify an unknown dynamic authentication key.
+ */
+public class UnknownAuthenticationKeyException extends IdentityCredentialException {
+    /**
+     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
+     *
+     * @param message the detail message.
+     */
+    public UnknownAuthenticationKeyException(@NonNull String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new {@link UnknownAuthenticationKeyException} exception.
+     *
+     * @param message the detail message.
+     * @param cause   the cause.
+     */
+    public UnknownAuthenticationKeyException(@NonNull String message, @NonNull Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/identity/java/android/security/identity/Util.java b/identity/java/android/security/identity/Util.java
new file mode 100644
index 0000000..6eefeb8
--- /dev/null
+++ b/identity/java/android/security/identity/Util.java
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+import java.util.Collection;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+class Util {
+    private static final String TAG = "Util";
+
+    static int[] integerCollectionToArray(Collection<Integer> collection) {
+        int[] result = new int[collection.size()];
+        int n = 0;
+        for (int item : collection) {
+            result[n++] = item;
+        }
+        return result;
+    }
+
+    static byte[] stripLeadingZeroes(byte[] value) {
+        int n = 0;
+        while (n < value.length && value[n] == 0) {
+            n++;
+        }
+        int newLen = value.length - n;
+        byte[] ret = new byte[newLen];
+        int m = 0;
+        while (n < value.length) {
+            ret[m++] = value[n++];
+        }
+        return ret;
+    }
+
+    static byte[] publicKeyEncodeUncompressedForm(PublicKey publicKey) {
+        ECPoint w = ((ECPublicKey) publicKey).getW();
+        // X and Y are always positive so for interop we remove any leading zeroes
+        // inserted by the BigInteger encoder.
+        byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
+        byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            baos.write(0x04);
+            baos.write(x);
+            baos.write(y);
+            return baos.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException("Unexpected IOException", e);
+        }
+    }
+
+    /**
+     * Computes an HKDF.
+     *
+     * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
+     * /crypto/tink/subtle/Hkdf.java
+     * which is also Copyright (c) Google and also licensed under the Apache 2 license.
+     *
+     * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
+     *                     "HMACSHA256".
+     * @param ikm          the input keying material.
+     * @param salt         optional salt. A possibly non-secret random value. If no salt is
+     *                     provided (i.e. if
+     *                     salt has length 0) then an array of 0s of the same size as the hash
+     *                     digest is used as salt.
+     * @param info         optional context and application specific information.
+     * @param size         The length of the generated pseudorandom string in bytes. The maximal
+     *                     size is
+     *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
+     * @return size pseudorandom bytes.
+     */
+    static byte[] computeHkdf(
+            String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
+        Mac mac = null;
+        try {
+            mac = Mac.getInstance(macAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+        }
+        if (size > 255 * mac.getMacLength()) {
+            throw new RuntimeException("size too large");
+        }
+        try {
+            if (salt == null || salt.length == 0) {
+                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+                // then HKDF uses a salt that is an array of zeros of the same length as the hash
+                // digest.
+                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+            } else {
+                mac.init(new SecretKeySpec(salt, macAlgorithm));
+            }
+            byte[] prk = mac.doFinal(ikm);
+            byte[] result = new byte[size];
+            int ctr = 1;
+            int pos = 0;
+            mac.init(new SecretKeySpec(prk, macAlgorithm));
+            byte[] digest = new byte[0];
+            while (true) {
+                mac.update(digest);
+                mac.update(info);
+                mac.update((byte) ctr);
+                digest = mac.doFinal();
+                if (pos + digest.length < size) {
+                    System.arraycopy(digest, 0, result, pos, digest.length);
+                    pos += digest.length;
+                    ctr++;
+                } else {
+                    System.arraycopy(digest, 0, result, pos, size - pos);
+                    break;
+                }
+            }
+            return result;
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException("Error MACing", e);
+        }
+    }
+
+}
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
new file mode 100644
index 0000000..5f575b9
--- /dev/null
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.annotation.NonNull;
+
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+/**
+ * Class used to personalize a new identity credential.
+ *
+ * <p>Credentials cannot be updated or modified after creation; any changes require deletion and
+ * re-creation.
+ *
+ * Use {@link IdentityCredentialStore#createCredential(String, String)} to create a new credential.
+ */
+public abstract class WritableIdentityCredential {
+    /**
+     * Generates and returns an X.509 certificate chain for the CredentialKey which identifies this
+     * credential to the issuing authority. The certificate contains an
+     * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
+     * attestation extension which describes the key and the security hardware in which it lives.
+     *
+     * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates
+     * it 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).
+     *
+     * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
+     * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is
+     * present, the passed in challenge is present, the device has verified boot enabled, that each
+     * certificate in the chain is signed by its successor, that none of the certificates have been
+     * revoked and so on.
+     *
+     * <p>It is not strictly necessary to use this method to provision a credential if the issuing
+     * authority doesn't care about the nature of the security hardware. If called, however, this
+     * method must be called before {@link #personalize(PersonalizationData)}.
+     *
+     * @param challenge is a byte array whose contents should be unique, fresh and provided by
+     *                  the issuing authority. The value provided is embedded in the attestation
+     *                  extension and enables the issuing authority to verify that the attestation
+     *                  certificate is fresh.
+     * @return the X.509 certificate for this credential's CredentialKey.
+     */
+    public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain(
+            @NonNull byte[] challenge);
+
+    /**
+     * Stores all of the data in the credential, with the specified access control profiles.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey with payload
+     * set to {@code ProofOfProvisioning} as defined below.
+     *
+     * <pre>
+     *     ProofOfProvisioning = [
+     *          "ProofOfProvisioning",        ; tstr
+     *          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 =&gt; [ + Entry ]
+     *      },
+     *
+     *      Namespace = tstr
+     *
+     *      Entry = {
+     *          "name" : tstr,
+     *          "value" : any,
+     *          "accessControlProfiles" : [ * uint ],
+     *      }
+     * </pre>
+     *
+     * <p>This data structure provides a guarantee to the issuer about the data which may be
+     * returned in the CBOR returned by
+     * {@link ResultData#getAuthenticatedData()} during a credential
+     * presentation.
+     *
+     * @param personalizationData   The data to provision, including access control profiles
+     *                              and data elements and their values, grouped into namespaces.
+     * @return A COSE_Sign1 data structure, see above.
+     */
+    public abstract @NonNull byte[] personalize(
+            @NonNull PersonalizationData personalizationData);
+}