Merge "Adding device ID attestation to KeyGenParameterSpec"

This commit is contained in:
Janis Danisevskis
2021-02-11 20:03:01 +00:00
committed by Gerrit Code Review
7 changed files with 194 additions and 13 deletions

View File

@@ -8435,10 +8435,12 @@ package android.security.keystore {
}
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method @Nullable public int[] getAttestationIds();
method public int getNamespace();
}
public static final class KeyGenParameterSpec.Builder {
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
}

View File

@@ -17,6 +17,7 @@
package android.security.keystore;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
@@ -25,6 +26,8 @@ import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -477,11 +480,11 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
success = true;
return keyPair;
} catch (ProviderException e) {
} catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) {
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(e);
} else {
throw e;
throw new ProviderException(e);
}
} finally {
if (!success) {
@@ -491,7 +494,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
throws ProviderException {
throws ProviderException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
KeymasterArguments args = new KeymasterArguments();
@@ -510,6 +513,60 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
Build.MODEL.getBytes(StandardCharsets.UTF_8));
}
int[] idTypes = mSpec.getAttestationIds();
if (idTypes != null) {
final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
for (int idType : idTypes) {
idTypesSet.add(idType);
}
TelephonyManager telephonyService = null;
if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
|| idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
telephonyService =
(TelephonyManager) KeyStore.getApplicationContext().getSystemService(
Context.TELEPHONY_SERVICE);
if (telephonyService == null) {
throw new DeviceIdAttestationException(
"Unable to access telephony service");
}
}
for (final Integer idType : idTypesSet) {
switch (idType) {
case AttestationUtils.ID_TYPE_SERIAL:
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
Build.getSerial().getBytes(StandardCharsets.UTF_8)
);
break;
case AttestationUtils.ID_TYPE_IMEI: {
final String imei = telephonyService.getImei(0);
if (imei == null) {
throw new DeviceIdAttestationException("Unable to retrieve IMEI");
}
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
imei.getBytes(StandardCharsets.UTF_8)
);
break;
}
case AttestationUtils.ID_TYPE_MEID: {
final String meid = telephonyService.getMeid(0);
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
meid.getBytes(StandardCharsets.UTF_8)
);
break;
}
case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
break;
}
default:
throw new IllegalArgumentException("Unknown device ID type " + idType);
}
}
}
return getAttestationChain(privateKeyAlias, keyPair, args);
}
@@ -547,7 +604,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
private KeymasterArguments constructKeyGenerationArguments() {
private KeymasterArguments constructKeyGenerationArguments()
throws IllegalArgumentException, DeviceIdAttestationException {
KeymasterArguments args = new KeymasterArguments();
args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -565,9 +623,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
mSpec.getKeyValidityForConsumptionEnd());
addAlgorithmSpecificParameters(args);
if (mSpec.isUniqueIdIncluded())
if (mSpec.isUniqueIdIncluded()) {
args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
}
return args;
}

View File

@@ -34,6 +34,14 @@ public abstract class ArrayUtils {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
/**
* Clones an array if it is not null and has a length greater than 0. Otherwise, returns the
* array.
*/
public static int[] cloneIfNotEmpty(int[] array) {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
public static byte[] cloneIfNotEmpty(byte[] array) {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}

View File

@@ -267,6 +267,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
private final boolean mUserPresenceRequired;
private final byte[] mAttestationChallenge;
private final boolean mDevicePropertiesAttestationIncluded;
private final int[] mAttestationIds;
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mInvalidatedByBiometricEnrollment;
@@ -308,6 +309,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
boolean userPresenceRequired,
byte[] attestationChallenge,
boolean devicePropertiesAttestationIncluded,
int[] attestationIds,
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
@@ -361,6 +363,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mUserAuthenticationType = userAuthenticationType;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded;
mAttestationIds = attestationIds;
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
@@ -719,6 +722,25 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
return mDevicePropertiesAttestationIncluded;
}
/**
* @hide
* Allows the caller to specify device IDs to be attested to in the certificate for the
* generated key pair. These values are the enums specified in
* {@link android.security.keystore.AttestationUtils}
*
* @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
* @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
* @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
* @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
*
* @return integer array representing the requested device IDs to attest.
*/
@SystemApi
@Nullable
public int[] getAttestationIds() {
return Utils.cloneIfNotNull(mAttestationIds);
}
/**
* @hide This is a system-only API
*
@@ -834,6 +856,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
private boolean mUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mDevicePropertiesAttestationIncluded = false;
private int[] mAttestationIds = null;
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
@@ -902,6 +925,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mDevicePropertiesAttestationIncluded =
sourceSpec.isDevicePropertiesAttestationIncluded();
mAttestationIds = sourceSpec.getAttestationIds();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
@@ -1472,6 +1496,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
return this;
}
/**
* @hide
* Sets which IDs to attest in the attestation certificate for the key. The acceptable
* values in this integer array are the enums specified in
* {@link android.security.keystore.AttestationUtils}
*
* @param attestationIds the array of ID types to attest to in the certificate.
*
* @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
* @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
* @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
* @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
*/
@SystemApi
@NonNull
public Builder setAttestationIds(@NonNull int[] attestationIds) {
mAttestationIds = attestationIds;
return this;
}
/**
* @hide Only system apps can use this method.
*
@@ -1638,6 +1682,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mUserPresenceRequired,
mAttestationChallenge,
mDevicePropertiesAttestationIncluded,
mAttestationIds,
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
mInvalidatedByBiometricEnrollment,

View File

@@ -101,6 +101,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
out.writeBoolean(mSpec.isUserPresenceRequired());
out.writeByteArray(mSpec.getAttestationChallenge());
out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded());
out.writeIntArray(mSpec.getAttestationIds());
out.writeBoolean(mSpec.isUniqueIdIncluded());
out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
@@ -160,6 +161,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
final boolean userPresenceRequired = in.readBoolean();
final byte[] attestationChallenge = in.createByteArray();
final boolean devicePropertiesAttestationIncluded = in.readBoolean();
final int[] attestationIds = in.createIntArray();
final boolean uniqueIdIncluded = in.readBoolean();
final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
final boolean invalidatedByBiometricEnrollment = in.readBoolean();
@@ -195,6 +197,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
userPresenceRequired,
attestationChallenge,
devicePropertiesAttestationIncluded,
attestationIds,
uniqueIdIncluded,
userAuthenticationValidWhileOnBody,
invalidatedByBiometricEnrollment,

View File

@@ -33,4 +33,8 @@ abstract class Utils {
static byte[] cloneIfNotNull(byte[] value) {
return (value != null) ? value.clone() : null;
}
static int[] cloneIfNotNull(int[] value) {
return (value != null) ? value.clone() : null;
}
}

View File

@@ -18,16 +18,20 @@ package android.security.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.AttestationUtils;
import android.security.keystore.DeviceIdAttestationException;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
@@ -38,6 +42,8 @@ import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Log;
import libcore.util.EmptyArray;
@@ -478,7 +484,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
throw p;
}
} catch (UnrecoverableKeyException e) {
} catch (UnrecoverableKeyException | IllegalArgumentException
| DeviceIdAttestationException e) {
throw new ProviderException(
"Failed to construct key object from newly generated key pair.", e);
} finally {
@@ -496,7 +503,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException {
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
@@ -526,15 +533,69 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
Build.MODEL.getBytes(StandardCharsets.UTF_8)
));
}
} else {
if (mSpec.isDevicePropertiesAttestationIncluded()) {
throw new ProviderException("An attestation challenge must be provided when "
+ "requesting device properties attestation.");
int[] idTypes = mSpec.getAttestationIds();
if (idTypes == null) {
return;
}
final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
for (int idType : idTypes) {
idTypesSet.add(idType);
}
TelephonyManager telephonyService = null;
if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
|| idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
telephonyService =
(TelephonyManager) KeyStore.getApplicationContext().getSystemService(
Context.TELEPHONY_SERVICE);
if (telephonyService == null) {
throw new DeviceIdAttestationException("Unable to access telephony service");
}
}
for (final Integer idType : idTypesSet) {
switch (idType) {
case AttestationUtils.ID_TYPE_SERIAL:
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
Build.getSerial().getBytes(StandardCharsets.UTF_8)
));
break;
case AttestationUtils.ID_TYPE_IMEI: {
final String imei = telephonyService.getImei(0);
if (imei == null) {
throw new DeviceIdAttestationException("Unable to retrieve IMEI");
}
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
imei.getBytes(StandardCharsets.UTF_8)
));
break;
}
case AttestationUtils.ID_TYPE_MEID: {
final String meid = telephonyService.getMeid(0);
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
meid.getBytes(StandardCharsets.UTF_8)
));
break;
}
case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
params.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
break;
}
default:
throw new IllegalArgumentException("Unknown device ID type " + idType);
}
}
}
}
private Collection<KeyParameter> constructKeyGenerationArguments() {
private Collection<KeyParameter> constructKeyGenerationArguments()
throws DeviceIdAttestationException, IllegalArgumentException {
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
params.add(KeyStore2ParameterUtils.makeEnum(