Merge "Identity Credential: API changes for Android 12" am: 40d6635701

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1464362

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Iefd4c9ff03bcb208c0817f144aabec19b887708f
This commit is contained in:
David Zeuthen
2021-01-26 15:40:03 +00:00
committed by Automerger Merge Worker
7 changed files with 298 additions and 8 deletions

View File

@@ -12128,6 +12128,8 @@ package android.content.pm {
field public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
field public static final String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE = "android.hardware.identity_credential";
field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access";
field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
@@ -36082,15 +36084,20 @@ package android.security.identity {
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 byte[] delete(@NonNull byte[]);
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 @NonNull public byte[] proveOwnership(@NonNull byte[]);
method public abstract void setAllowUsingExhaustedKeys(boolean);
method public void setAllowUsingExpiredKeys(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;
method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData);
}
public class IdentityCredentialException extends java.lang.Exception {
@@ -36100,7 +36107,7 @@ package android.security.identity {
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 @Deprecated @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);

View File

@@ -2123,6 +2123,35 @@ public abstract class PackageManager {
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CTS = "android.software.cts";
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
* {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
* at the given feature version.
*
* <p>Known feature versions include:
* <ul>
* <li><code>202009</code>: corresponds to the features included in the Identity Credential
* API shipped in Android 11.
* <li><code>202101</code>: corresponds to the features included in the Identity Credential
* API shipped in Android 12.
* </ul>
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE =
"android.hardware.identity_credential";
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
* {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
* with direct access at the given feature version.
* See {@link #FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known feature versions.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS =
"android.hardware.identity_credential_direct_access";
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports one or more methods of

View File

@@ -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) {
@@ -409,6 +418,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 {
@@ -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);
}
}
}

View File

@@ -162,5 +162,4 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
+ e.errorCode, e);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
@@ -113,6 +114,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();
}
}

View File

@@ -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 {
}
}