diff options
author | Janis Danisevskis <jdanis@google.com> | 2021-02-11 20:03:01 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-02-11 20:03:01 +0000 |
commit | 3faed136d53ef713b162ce1ad3b4fdf519ac6f79 (patch) | |
tree | 2bdfa5e414a7fd06842445baa813127d9bab97b9 /keystore | |
parent | 2d29d2d82b0d72e43dce81146835765e20955c80 (diff) | |
parent | 84cd6f225766a2562f606aaa83e7c1357be3e7c7 (diff) |
Merge "Adding device ID attestation to KeyGenParameterSpec"
Diffstat (limited to 'keystore')
6 files changed, 192 insertions, 13 deletions
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 334b1110d651..988838b46334 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -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; } diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index c8c1de4a5e83..f22b6041800f 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -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; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index c2a7b2ee6323..e92eaca2b6e9 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -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; @@ -720,6 +723,25 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @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 * * Returns {@code true} if the attestation certificate will contain a unique ID field. @@ -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(); @@ -1473,6 +1497,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @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. * * Sets whether to include a temporary unique ID field in the attestation certificate. @@ -1638,6 +1682,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserPresenceRequired, mAttestationChallenge, mDevicePropertiesAttestationIncluded, + mAttestationIds, mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 8163472abdfb..1f2f853b67a8 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -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, diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java index 5722c7b53ef4..e58b1ccb5370 100644 --- a/keystore/java/android/security/keystore/Utils.java +++ b/keystore/java/android/security/keystore/Utils.java @@ -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; + } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 70e30d2de5a1..4d27c3454a84 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -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( |