summaryrefslogtreecommitdiff
path: root/identity/java/android
diff options
context:
space:
mode:
authorDavid Zeuthen <zeuthen@google.com>2021-01-27 07:54:40 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-01-27 07:54:40 +0000
commit5147877a83957dca1ca86330b96bbd02da87f8b1 (patch)
tree6974107ea0bc871fa6ee671eba718ba01d3e86e5 /identity/java/android
parent3827e8c95eb15578bb9166d50ae4959ebaf300e6 (diff)
parent581bf34de0bd6a42b64e3c074b840a3eb4ff5ffa (diff)
Merge "Identity Credential: API changes for Android 12" am: 40d6635701 am: 6252f82f61 am: 581bf34de0
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1464362 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: Ie64aad9ae67a5b62c43edcbf37b841dac60849e2
Diffstat (limited to 'identity/java/android')
-rw-r--r--identity/java/android/security/identity/CredstoreIdentityCredential.java86
-rw-r--r--identity/java/android/security/identity/CredstoreIdentityCredentialStore.java1
-rw-r--r--identity/java/android/security/identity/CredstoreWritableIdentityCredential.java10
-rw-r--r--identity/java/android/security/identity/IdentityCredential.java155
-rw-r--r--identity/java/android/security/identity/IdentityCredentialStore.java14
5 files changed, 260 insertions, 6 deletions
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 7c0af6def696..6398cee74cba 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -37,6 +37,7 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.time.Instant;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
@@ -237,12 +238,18 @@ class CredstoreIdentityCredential extends IdentityCredential {
}
private boolean mAllowUsingExhaustedKeys = true;
+ private boolean mAllowUsingExpiredKeys = false;
@Override
public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
}
+ @Override
+ public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+ mAllowUsingExpiredKeys = allowUsingExpiredKeys;
+ }
+
private boolean mOperationHandleSet = false;
private long mOperationHandle = 0;
@@ -256,7 +263,8 @@ class CredstoreIdentityCredential extends IdentityCredential {
public long getCredstoreOperationHandle() {
if (!mOperationHandleSet) {
try {
- mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys);
+ mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys,
+ mAllowUsingExpiredKeys);
mOperationHandleSet = true;
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
@@ -306,7 +314,8 @@ class CredstoreIdentityCredential extends IdentityCredential {
rnsParcels,
sessionTranscript != null ? sessionTranscript : new byte[0],
readerSignature != null ? readerSignature : new byte[0],
- mAllowUsingExhaustedKeys);
+ mAllowUsingExhaustedKeys,
+ mAllowUsingExpiredKeys);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
} catch (android.os.ServiceSpecificException e) {
@@ -410,6 +419,34 @@ class CredstoreIdentityCredential extends IdentityCredential {
}
@Override
+ public void storeStaticAuthenticationData(X509Certificate authenticationKey,
+ Instant expirationDate,
+ byte[] staticAuthData)
+ throws UnknownAuthenticationKeyException {
+ try {
+ AuthKeyParcel authKeyParcel = new AuthKeyParcel();
+ authKeyParcel.x509cert = authenticationKey.getEncoded();
+ long millisSinceEpoch = (expirationDate.getEpochSecond() * 1000)
+ + (expirationDate.getNano() / 1000000);
+ mBinder.storeStaticAuthenticationDataWithExpiration(authKeyParcel,
+ millisSinceEpoch, 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_NOT_SUPPORTED) {
+ throw new UnsupportedOperationException("Not supported", e);
+ } else 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();
@@ -421,4 +458,49 @@ class CredstoreIdentityCredential extends IdentityCredential {
+ e.errorCode, e);
}
}
+
+ @Override
+ public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
+ try {
+ byte[] proofOfOwnership = mBinder.proveOwnership(challenge);
+ return proofOfOwnership;
+ } catch (android.os.RemoteException e) {
+ throw new RuntimeException("Unexpected RemoteException ", e);
+ } catch (android.os.ServiceSpecificException e) {
+ if (e.errorCode == ICredentialStore.ERROR_NOT_SUPPORTED) {
+ throw new UnsupportedOperationException("Not supported", e);
+ } else {
+ throw new RuntimeException("Unexpected ServiceSpecificException with code "
+ + e.errorCode, e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull byte[] delete(@NonNull byte[] challenge) {
+ try {
+ byte[] proofOfDeletion = mBinder.deleteWithChallenge(challenge);
+ 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);
+ }
+ }
+
+ @Override
+ public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
+ try {
+ IWritableCredential binder = mBinder.update();
+ byte[] proofOfProvision =
+ CredstoreWritableIdentityCredential.personalize(binder, personalizationData);
+ return proofOfProvision;
+ } 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
index 129063361b35..d8d47424e2e8 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -162,5 +162,4 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
+ e.errorCode, e);
}
}
-
}
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
index 725e3d8e429a..d2e7984ce19f 100644
--- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -76,7 +76,14 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
@NonNull @Override
public byte[] personalize(@NonNull PersonalizationData personalizationData) {
+ return personalize(mBinder, personalizationData);
+ }
+ // Used by both personalize() and CredstoreIdentityCredential.update().
+ //
+ @NonNull
+ static byte[] personalize(IWritableCredential binder,
+ @NonNull PersonalizationData personalizationData) {
Collection<AccessControlProfile> accessControlProfiles =
personalizationData.getAccessControlProfiles();
@@ -144,7 +151,7 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
secureUserId = getRootSid();
}
try {
- byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels,
+ byte[] personalizationReceipt = binder.personalize(acpParcels, ensParcels,
secureUserId);
return personalizationReceipt;
} catch (android.os.RemoteException e) {
@@ -164,5 +171,4 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential {
return rootSid;
}
-
}
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 4eb6e420c07f..8f175bb63edb 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -23,6 +23,7 @@ import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
+import java.time.Instant;
import java.util.Collection;
import java.util.Map;
@@ -114,6 +115,25 @@ public abstract class IdentityCredential {
public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
/**
+ * Sets whether to allow using an authentication key which has been expired if no
+ * other key is available. This must be called prior to calling
+ * {@link #getEntries(byte[], Map, byte[], byte[])}.
+ *
+ * <p>By default this is set to false.
+ *
+ * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param allowUsingExpiredKeys whether to allow using an authentication key which use count
+ * has been exceeded if no other key is available.
+ */
+ public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
* operation handle.
*
@@ -289,6 +309,21 @@ public abstract class IdentityCredential {
*
* <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
* can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
+
+ * <p>If the implementation is feature version 202101 or later,
+ * each X.509 certificate contains an X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26 which
+ * contains a DER encoded OCTET STRING with the bytes of the CBOR with the following CDDL:
+ * <pre>
+ * ProofOfBinding = [
+ * "ProofOfBinding",
+ * bstr, // Contains SHA-256(ProofOfProvisioning)
+ * ]
+ * </pre>
+ * <p>This CBOR enables an issuer to determine the exact state of the credential it
+ * returns issuer-signed data for.
+ *
+ * <p> See {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for
+ * known feature versions.
*
* @return A collection of X.509 certificates for dynamic authentication keys that need issuer
* certification.
@@ -308,16 +343,136 @@ public abstract class IdentityCredential {
* the authenticity
* and integrity of the credential data fields.
* @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
+ * @deprecated Use {@link #storeStaticAuthenticationData(X509Certificate, Instant, byte[])}
+ * instead.
*/
+ @Deprecated
public abstract void storeStaticAuthenticationData(
@NonNull X509Certificate authenticationKey,
@NonNull byte[] staticAuthData)
throws UnknownAuthenticationKeyException;
/**
+ * Store authentication data associated with a dynamic authentication key.
+ *
+ * This should only be called for an authenticated key returned by
+ * {@link #getAuthKeysNeedingCertification()}.
+ *
+ * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param authenticationKey The dynamic authentication key for which certification and
+ * associated static
+ * authentication data is being provided.
+ * @param expirationDate The expiration date of the static authentication data.
+ * @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 void storeStaticAuthenticationData(
+ @NonNull X509Certificate authenticationKey,
+ @NonNull Instant expirationDate,
+ @NonNull byte[] staticAuthData)
+ throws UnknownAuthenticationKeyException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* 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();
+
+ /**
+ * Proves ownership of a credential.
+ *
+ * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+ * with payload set to {@code ProofOfDeletion} as defined below.</p>
+ *
+ * <p>The returned CBOR is the following:</p>
+ * <pre>
+ * ProofOfOwnership = [
+ * "ProofOfOwnership", ; tstr
+ * tstr, ; DocType
+ * bstr, ; Challenge
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ * </pre>
+ *
+ * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param challenge is a non-empty byte array whose contents should be unique, fresh and
+ * provided by the issuing authority. The value provided is embedded in the
+ * generated CBOR and enables the issuing authority to verify that the
+ * returned proof is fresh.
+ * @return the COSE_Sign1 data structure above
+ */
+ public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Deletes a credential.
+ *
+ * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+ * with payload set to {@code ProofOfDeletion} as defined below.</p>
+ *
+ * <pre>
+ * ProofOfDeletion = [
+ * "ProofOfDeletion", ; tstr
+ * tstr, ; DocType
+ * bstr, ; Challenge
+ * bool ; true if this is a test credential, should
+ * ; always be false.
+ * ]
+ * </pre>
+ *
+ * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param challenge is a non-empty byte array whose contents should be unique, fresh and
+ * provided by the issuing authority. The value provided is embedded in the
+ * generated CBOR and enables the issuing authority to verify that the
+ * returned proof is fresh.
+ * @return the COSE_Sign1 data structure above
+ */
+ public @NonNull byte[] delete(@NonNull byte[] challenge) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Updates the credential with new access control profiles and data items.
+ *
+ * <p>This method is similar to
+ * {@link WritableIdentityCredential#personalize(PersonalizationData)} except that it operates
+ * on an existing credential, see the documentation for that method for the format of the
+ * returned data.
+ *
+ * <p>If this call succeeds an side-effect is that all dynamic authentication keys for the
+ * credential are deleted. The application will need to use
+ * {@link #getAuthKeysNeedingCertification()} to generate replacement keys and return
+ * them for issuer certification.
+ *
+ * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+ * fails with {@link UnsupportedOperationException}. See
+ * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+ * feature versions.
+ *
+ * @param personalizationData The data to update, including access control profiles
+ * and data elements and their values, grouped into namespaces.
+ * @return A COSE_Sign1 data structure, see above.
+ */
+ public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index 3843d9279900..6ccd0e892141 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -72,6 +72,17 @@ import java.lang.annotation.RetentionPolicy;
* <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.
+ *
+ * <p>The Identity Credential API is designed to be able to evolve and change over time
+ * but still provide 100% backwards compatibility. This is complicated by the fact that
+ * there may be a version skew between the API used by the application and the version
+ * implemented in secure hardware. To solve this problem, the API provides for a way
+ * for the application to query which feature version the hardware implements (if any
+ * at all) using
+ * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} and
+ * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}.
+ * Methods which only work on certain feature versions are clearly documented as
+ * such.
*/
public abstract class IdentityCredentialStore {
IdentityCredentialStore() {}
@@ -193,7 +204,9 @@ public abstract class IdentityCredentialStore {
* @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.
+ * @deprecated Use {@link IdentityCredential#delete(byte[])} instead.
*/
+ @Deprecated
public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
/** @hide */
@@ -201,5 +214,4 @@ public abstract class IdentityCredentialStore {
@Retention(RetentionPolicy.SOURCE)
public @interface Ciphersuite {
}
-
}