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.content.Context; 24 import android.security.keymaster.KeymasterCertificateChain; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.security.KeyPairGenerator; 29 import java.security.KeyStore; 30 import java.security.ProviderException; 31 import java.security.SecureRandom; 32 import java.security.cert.Certificate; 33 import java.security.cert.CertificateFactory; 34 import java.security.cert.X509Certificate; 35 import java.security.spec.ECGenParameterSpec; 36 import java.util.Arrays; 37 import java.util.Collection; 38 import java.util.Random; 39 40 /** 41 * Utilities for attesting the device's hardware identifiers. 42 * 43 * @hide 44 */ 45 @SystemApi 46 public abstract class AttestationUtils { AttestationUtils()47 private AttestationUtils() { 48 } 49 50 /** 51 * Specifies that the device should attest its serial number. For use with 52 * {@link #attestDeviceIds}. 53 * 54 * @see #attestDeviceIds 55 */ 56 public static final int ID_TYPE_SERIAL = 1; 57 58 /** 59 * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}. 60 * 61 * @see #attestDeviceIds 62 */ 63 public static final int ID_TYPE_IMEI = 2; 64 65 /** 66 * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}. 67 * 68 * @see #attestDeviceIds 69 */ 70 public static final int ID_TYPE_MEID = 3; 71 72 /** 73 * Specifies that the device should sign the attestation record using its device-unique 74 * attestation certificate. For use with {@link #attestDeviceIds}. 75 * 76 * @see #attestDeviceIds 77 */ 78 public static final int USE_INDIVIDUAL_ATTESTATION = 4; 79 80 /** 81 * Creates an array of X509Certificates from the provided KeymasterCertificateChain. 82 * 83 * @hide Only called by the DevicePolicyManager. 84 */ parseCertificateChain( final KeymasterCertificateChain kmChain)85 @NonNull public static X509Certificate[] parseCertificateChain( 86 final KeymasterCertificateChain kmChain) throws 87 KeyAttestationException { 88 // Extract certificate chain. 89 final Collection<byte[]> rawChain = kmChain.getCertificates(); 90 if (rawChain.size() < 2) { 91 throw new KeyAttestationException("Attestation certificate chain contained " 92 + rawChain.size() + " entries. At least two are required."); 93 } 94 final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream(); 95 try { 96 for (final byte[] cert : rawChain) { 97 concatenatedRawChain.write(cert); 98 } 99 return CertificateFactory.getInstance("X.509").generateCertificates( 100 new ByteArrayInputStream(concatenatedRawChain.toByteArray())) 101 .toArray(new X509Certificate[0]); 102 } catch (Exception e) { 103 throw new KeyAttestationException("Unable to construct certificate chain", e); 104 } 105 } 106 107 /** 108 * Performs attestation of the device's identifiers. This method returns a certificate chain 109 * whose first element contains the requested device identifiers in an extension. The device's 110 * manufacturer, model, brand, device and product are always also included in the attestation. 111 * If the device supports attestation in secure hardware, the chain will be rooted at a 112 * trustworthy CA key. Otherwise, the chain will be rooted at an untrusted certificate. See 113 * <a href="https://developer.android.com/training/articles/security-key-attestation.html"> 114 * Key Attestation</a> for the format of the certificate extension. 115 * <p> 116 * Attestation will only be successful when all of the following are true: 117 * 1) The device has been set up to support device identifier attestation at the factory. 118 * 2) The user has not permanently disabled device identifier attestation. 119 * 3) You have permission to access the device identifiers you are requesting attestation for. 120 * <p> 121 * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is 122 * unsuccessful, the device may not support it in general or the user may have permanently 123 * disabled it. 124 * 125 * @param context the context to use for retrieving device identifiers. 126 * @param idTypes the types of device identifiers to attest. 127 * @param attestationChallenge a blob to include in the certificate alongside the device 128 * identifiers. 129 * 130 * @return a certificate chain containing the requested device identifiers in the first element 131 * 132 * @exception SecurityException if you are not permitted to obtain an attestation of the 133 * device's identifiers. 134 * @exception DeviceIdAttestationException if the attestation operation fails. 135 */ 136 @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) attestDeviceIds(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge)137 @NonNull public static X509Certificate[] attestDeviceIds(Context context, 138 @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws 139 DeviceIdAttestationException { 140 if (attestationChallenge == null) { 141 throw new NullPointerException("Missing attestation challenge"); 142 } 143 if (idTypes == null) { 144 throw new NullPointerException("Missing id types"); 145 } 146 147 String keystoreAlias = generateRandomAlias(); 148 KeyGenParameterSpec.Builder builder = 149 new KeyGenParameterSpec.Builder(keystoreAlias, KeyProperties.PURPOSE_SIGN) 150 .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) 151 .setDigests(KeyProperties.DIGEST_SHA256) 152 .setAttestationChallenge(attestationChallenge); 153 154 if (idTypes != null) { 155 builder.setAttestationIds(idTypes); 156 builder.setDevicePropertiesAttestationIncluded(true); 157 } 158 159 try { 160 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( 161 KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); 162 keyPairGenerator.initialize(builder.build()); 163 keyPairGenerator.generateKeyPair(); 164 165 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 166 keyStore.load(null); 167 168 Certificate[] certs = keyStore.getCertificateChain(keystoreAlias); 169 X509Certificate[] certificateChain = 170 Arrays.copyOf(certs, certs.length, X509Certificate[].class); 171 172 keyStore.deleteEntry(keystoreAlias); 173 174 return certificateChain; 175 } catch (SecurityException e) { 176 throw e; 177 } catch (Exception e) { 178 // If a DeviceIdAttestationException was previously wrapped with some other type, 179 // let's throw the original exception instead of wrapping it yet again. 180 if (e.getCause() instanceof DeviceIdAttestationException) { 181 throw (DeviceIdAttestationException) e.getCause(); 182 } 183 // Illegal argument errors are wrapped up by a ProviderException. Catch those so that 184 // we can unwrap them into a more meaningful exception type for the caller. 185 if (e instanceof ProviderException 186 && e.getCause() instanceof IllegalArgumentException) { 187 throw (IllegalArgumentException) e.getCause(); 188 } 189 throw new DeviceIdAttestationException("Unable to perform attestation", e); 190 } 191 } 192 generateRandomAlias()193 private static String generateRandomAlias() { 194 Random random = new SecureRandom(); 195 StringBuilder builder = new StringBuilder(); 196 // Pick random uppercase letters, A-Z. 20 of them gives us ~94 bits of entropy, which 197 // should prevent any conflicts with app-selected aliases, even for very unlucky users. 198 for (int i = 0; i < 20; ++i) { 199 builder.append(random.nextInt(26) + 'A'); 200 } 201 return builder.toString(); 202 } 203 204 /** 205 * Returns true if the attestation chain provided is a valid key attestation chain. 206 * @hide 207 */ isChainValid(KeymasterCertificateChain chain)208 public static boolean isChainValid(KeymasterCertificateChain chain) { 209 return chain != null && chain.getCertificates().size() >= 2; 210 } 211 } 212