Adding device ID attestation to KeyGenParameterSpec

Now that attestation and generation of keys occurs in the same step, the
device ID attestation parameters need to be passed into the
KeyPairGeneratorSpi. This change shifts functionality that was
previously in AttestationUtils into KeyGenParameterSpec and the
keystore2 KeyPairGeneratorSpi. The API changes should be gated to
Platform APIs and hidden from less privileged components.

Test: atest cts/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
Bug: 177369988
Change-Id: Iafbc1661583bdf61da644b2c0838b9024018ee82
This commit is contained in:
Max Bires
2021-01-20 19:51:00 -08:00
committed by Janis Danisevskis
parent 84da5781da
commit 84cd6f2257
7 changed files with 194 additions and 13 deletions

View File

@@ -8427,10 +8427,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(