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:
committed by
Janis Danisevskis
parent
84da5781da
commit
84cd6f2257
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user