1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.keystore; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SystemApi; 23 import android.annotation.TestApi; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.os.Build; 27 import android.security.KeyStore; 28 import android.security.keymaster.KeymasterArguments; 29 import android.security.keymaster.KeymasterCertificateChain; 30 import android.security.keymaster.KeymasterDefs; 31 import android.telephony.TelephonyManager; 32 import android.text.TextUtils; 33 import android.util.ArraySet; 34 35 import java.io.ByteArrayInputStream; 36 import java.io.ByteArrayOutputStream; 37 import java.nio.charset.StandardCharsets; 38 import java.security.cert.CertificateFactory; 39 import java.security.cert.X509Certificate; 40 import java.util.Collection; 41 import java.util.Set; 42 43 /** 44 * Utilities for attesting the device's hardware identifiers. 45 * 46 * @hide 47 */ 48 @SystemApi 49 @TestApi 50 public abstract class AttestationUtils { AttestationUtils()51 private AttestationUtils() { 52 } 53 54 /** 55 * Specifies that the device should attest its serial number. For use with 56 * {@link #attestDeviceIds}. 57 * 58 * @see #attestDeviceIds 59 */ 60 public static final int ID_TYPE_SERIAL = 1; 61 62 /** 63 * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}. 64 * 65 * @see #attestDeviceIds 66 */ 67 public static final int ID_TYPE_IMEI = 2; 68 69 /** 70 * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}. 71 * 72 * @see #attestDeviceIds 73 */ 74 public static final int ID_TYPE_MEID = 3; 75 76 /** 77 * Specifies that the device should sign the attestation record using its device-unique 78 * attestation certificate. For use with {@link #attestDeviceIds}. 79 * 80 * @see #attestDeviceIds 81 */ 82 public static final int USE_INDIVIDUAL_ATTESTATION = 4; 83 84 /** 85 * Creates an array of X509Certificates from the provided KeymasterCertificateChain. 86 * 87 * @hide Only called by the DevicePolicyManager. 88 */ parseCertificateChain( final KeymasterCertificateChain kmChain)89 @NonNull public static X509Certificate[] parseCertificateChain( 90 final KeymasterCertificateChain kmChain) throws 91 KeyAttestationException { 92 // Extract certificate chain. 93 final Collection<byte[]> rawChain = kmChain.getCertificates(); 94 if (rawChain.size() < 2) { 95 throw new KeyAttestationException("Attestation certificate chain contained " 96 + rawChain.size() + " entries. At least two are required."); 97 } 98 final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream(); 99 try { 100 for (final byte[] cert : rawChain) { 101 concatenatedRawChain.write(cert); 102 } 103 return CertificateFactory.getInstance("X.509").generateCertificates( 104 new ByteArrayInputStream(concatenatedRawChain.toByteArray())) 105 .toArray(new X509Certificate[0]); 106 } catch (Exception e) { 107 throw new KeyAttestationException("Unable to construct certificate chain", e); 108 } 109 } 110 prepareAttestationArgumentsForDeviceId( Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge)111 @NonNull private static KeymasterArguments prepareAttestationArgumentsForDeviceId( 112 Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws 113 DeviceIdAttestationException { 114 // Verify that device ID attestation types are provided. 115 if (idTypes == null) { 116 throw new NullPointerException("Missing id types"); 117 } 118 119 return prepareAttestationArguments(context, idTypes, attestationChallenge); 120 } 121 122 /** 123 * Prepares Keymaster Arguments with attestation data. 124 * @hide should only be used by KeyChain. 125 */ prepareAttestationArguments(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge)126 @NonNull public static KeymasterArguments prepareAttestationArguments(Context context, 127 @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws 128 DeviceIdAttestationException { 129 return prepareAttestationArguments(context, idTypes,attestationChallenge, Build.BRAND); 130 } 131 132 /** 133 * Prepares Keymaster Arguments with attestation data for misprovisioned Pixel 2 device. 134 * See http://go/keyAttestationFailure and http://b/69471841 for more info. 135 * @hide should only be used by KeyChain. 136 */ prepareAttestationArgumentsIfMisprovisioned( Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge)137 @NonNull public static KeymasterArguments prepareAttestationArgumentsIfMisprovisioned( 138 Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws 139 DeviceIdAttestationException { 140 Resources resources = context.getResources(); 141 String misprovisionedBrand = resources.getString( 142 com.android.internal.R.string.config_misprovisionedBrandValue); 143 if (!TextUtils.isEmpty(misprovisionedBrand) && !isPotentiallyMisprovisionedDevice(context)){ 144 return null; 145 } 146 return prepareAttestationArguments( 147 context, idTypes, attestationChallenge, misprovisionedBrand); 148 } 149 isPotentiallyMisprovisionedDevice(Context context)150 @NonNull private static boolean isPotentiallyMisprovisionedDevice(Context context) { 151 Resources resources = context.getResources(); 152 String misprovisionedModel = resources.getString( 153 com.android.internal.R.string.config_misprovisionedDeviceModel); 154 return (Build.MODEL.equals(misprovisionedModel)); 155 } 156 prepareAttestationArguments(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge, String brand)157 @NonNull private static KeymasterArguments prepareAttestationArguments(Context context, 158 @NonNull int[] idTypes, @NonNull byte[] attestationChallenge, String brand) throws 159 DeviceIdAttestationException { 160 // Check method arguments, retrieve requested device IDs and prepare attestation arguments. 161 if (attestationChallenge == null) { 162 throw new NullPointerException("Missing attestation challenge"); 163 } 164 final KeymasterArguments attestArgs = new KeymasterArguments(); 165 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge); 166 // Return early if the caller did not request any device identifiers to be included in the 167 // attestation record. 168 if (idTypes == null) { 169 return attestArgs; 170 } 171 final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); 172 for (int idType : idTypes) { 173 idTypesSet.add(idType); 174 } 175 TelephonyManager telephonyService = null; 176 if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) { 177 telephonyService = (TelephonyManager) context.getSystemService( 178 Context.TELEPHONY_SERVICE); 179 if (telephonyService == null) { 180 throw new DeviceIdAttestationException("Unable to access telephony service"); 181 } 182 } 183 for (final Integer idType : idTypesSet) { 184 switch (idType) { 185 case ID_TYPE_SERIAL: 186 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, 187 Build.getSerial().getBytes(StandardCharsets.UTF_8)); 188 break; 189 case ID_TYPE_IMEI: { 190 final String imei = telephonyService.getImei(0); 191 if (imei == null) { 192 throw new DeviceIdAttestationException("Unable to retrieve IMEI"); 193 } 194 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, 195 imei.getBytes(StandardCharsets.UTF_8)); 196 break; 197 } 198 case ID_TYPE_MEID: { 199 final String meid = telephonyService.getMeid(0); 200 if (meid == null) { 201 throw new DeviceIdAttestationException("Unable to retrieve MEID"); 202 } 203 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, 204 meid.getBytes(StandardCharsets.UTF_8)); 205 break; 206 } 207 case USE_INDIVIDUAL_ATTESTATION: { 208 attestArgs.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION); 209 break; 210 } 211 default: 212 throw new IllegalArgumentException("Unknown device ID type " + idType); 213 } 214 } 215 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, 216 brand.getBytes(StandardCharsets.UTF_8)); 217 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, 218 Build.DEVICE.getBytes(StandardCharsets.UTF_8)); 219 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, 220 Build.PRODUCT.getBytes(StandardCharsets.UTF_8)); 221 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, 222 Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)); 223 attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, 224 Build.MODEL.getBytes(StandardCharsets.UTF_8)); 225 return attestArgs; 226 } 227 228 /** 229 * Performs attestation of the device's identifiers. This method returns a certificate chain 230 * whose first element contains the requested device identifiers in an extension. The device's 231 * manufacturer, model, brand, device and product are always also included in the attestation. 232 * If the device supports attestation in secure hardware, the chain will be rooted at a 233 * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See 234 * <a href="https://developer.android.com/training/articles/security-key-attestation.html"> 235 * Key Attestation</a> for the format of the certificate extension. 236 * <p> 237 * Attestation will only be successful when all of the following are true: 238 * 1) The device has been set up to support device identifier attestation at the factory. 239 * 2) The user has not permanently disabled device identifier attestation. 240 * 3) You have permission to access the device identifiers you are requesting attestation for. 241 * <p> 242 * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is 243 * unsuccessful, the device may not support it in general or the user may have permanently 244 * disabled it. 245 * 246 * @param context the context to use for retrieving device identifiers. 247 * @param idTypes the types of device identifiers to attest. 248 * @param attestationChallenge a blob to include in the certificate alongside the device 249 * identifiers. 250 * 251 * @return a certificate chain containing the requested device identifiers in the first element 252 * 253 * @exception SecurityException if you are not permitted to obtain an attestation of the 254 * device's identifiers. 255 * @exception DeviceIdAttestationException if the attestation operation fails. 256 */ 257 @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) attestDeviceIds(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge)258 @NonNull public static X509Certificate[] attestDeviceIds(Context context, 259 @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws 260 DeviceIdAttestationException { 261 final KeymasterArguments attestArgs = prepareAttestationArgumentsForDeviceId( 262 context, idTypes, attestationChallenge); 263 264 // Perform attestation. 265 final KeymasterCertificateChain outChain = new KeymasterCertificateChain(); 266 final int errorCode = KeyStore.getInstance().attestDeviceIds(attestArgs, outChain); 267 if (errorCode != KeyStore.NO_ERROR) { 268 throw new DeviceIdAttestationException("Unable to perform attestation", 269 KeyStore.getKeyStoreException(errorCode)); 270 } 271 272 try { 273 return parseCertificateChain(outChain); 274 } catch (KeyAttestationException e) { 275 throw new DeviceIdAttestationException(e.getMessage(), e); 276 } 277 } 278 279 /** 280 * Returns true if the attestation chain provided is a valid key attestation chain. 281 * @hide 282 */ isChainValid(KeymasterCertificateChain chain)283 public static boolean isChainValid(KeymasterCertificateChain chain) { 284 return chain != null && chain.getCertificates().size() >= 2; 285 } 286 } 287