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 com.android.server.locksettings; 18 19 import android.security.AndroidKeyStoreMaintenance; 20 import android.security.keystore.KeyProperties; 21 import android.security.keystore.KeyProtection; 22 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; 23 import android.system.keystore2.Domain; 24 import android.system.keystore2.KeyDescriptor; 25 import android.text.TextUtils; 26 import android.util.Slog; 27 28 import com.android.internal.util.ArrayUtils; 29 30 import java.io.IOException; 31 import java.security.InvalidAlgorithmParameterException; 32 import java.security.InvalidKeyException; 33 import java.security.KeyStore; 34 import java.security.KeyStoreException; 35 import java.security.MessageDigest; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.SecureRandom; 38 import java.security.UnrecoverableKeyException; 39 import java.security.cert.CertificateException; 40 import java.security.spec.InvalidParameterSpecException; 41 import java.util.Arrays; 42 43 import javax.crypto.BadPaddingException; 44 import javax.crypto.Cipher; 45 import javax.crypto.IllegalBlockSizeException; 46 import javax.crypto.KeyGenerator; 47 import javax.crypto.NoSuchPaddingException; 48 import javax.crypto.SecretKey; 49 import javax.crypto.spec.GCMParameterSpec; 50 import javax.crypto.spec.SecretKeySpec; 51 52 class SyntheticPasswordCrypto { 53 private static final String TAG = "SyntheticPasswordCrypto"; 54 private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM 55 private static final int AES_GCM_IV_SIZE = 12; 56 private static final int AES_GCM_TAG_SIZE = 16; 57 private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes(); 58 // Time between the user credential is verified with GK and the decryption of synthetic password 59 // under the auth-bound key. This should always happen one after the other, but give it 15 60 // seconds just to be sure. 61 private static final int USER_AUTHENTICATION_VALIDITY = 15; 62 decrypt(SecretKey key, byte[] blob)63 private static byte[] decrypt(SecretKey key, byte[] blob) 64 throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, 65 InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { 66 if (blob == null) { 67 return null; 68 } 69 byte[] iv = Arrays.copyOfRange(blob, 0, AES_GCM_IV_SIZE); 70 byte[] ciphertext = Arrays.copyOfRange(blob, AES_GCM_IV_SIZE, blob.length); 71 Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" 72 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); 73 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AES_GCM_TAG_SIZE * 8, iv)); 74 return cipher.doFinal(ciphertext); 75 } 76 encrypt(SecretKey key, byte[] blob)77 private static byte[] encrypt(SecretKey key, byte[] blob) 78 throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, 79 InvalidKeyException, IllegalBlockSizeException, BadPaddingException, 80 InvalidParameterSpecException { 81 if (blob == null) { 82 return null; 83 } 84 Cipher cipher = Cipher.getInstance( 85 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" 86 + KeyProperties.ENCRYPTION_PADDING_NONE); 87 cipher.init(Cipher.ENCRYPT_MODE, key); 88 byte[] ciphertext = cipher.doFinal(blob); 89 byte[] iv = cipher.getIV(); 90 if (iv.length != AES_GCM_IV_SIZE) { 91 throw new IllegalArgumentException("Invalid iv length: " + iv.length + " bytes"); 92 } 93 final GCMParameterSpec spec = cipher.getParameters().getParameterSpec( 94 GCMParameterSpec.class); 95 if (spec.getTLen() != AES_GCM_TAG_SIZE * 8) { 96 throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits"); 97 } 98 return ArrayUtils.concat(iv, ciphertext); 99 } 100 encrypt(byte[] keyBytes, byte[] personalization, byte[] message)101 public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) { 102 byte[] keyHash = personalizedHash(personalization, keyBytes); 103 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE), 104 KeyProperties.KEY_ALGORITHM_AES); 105 try { 106 return encrypt(key, message); 107 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException 108 | IllegalBlockSizeException | BadPaddingException | IOException 109 | InvalidParameterSpecException e) { 110 Slog.e(TAG, "Failed to encrypt", e); 111 return null; 112 } 113 } 114 decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext)115 public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) { 116 byte[] keyHash = personalizedHash(personalization, keyBytes); 117 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE), 118 KeyProperties.KEY_ALGORITHM_AES); 119 try { 120 return decrypt(key, ciphertext); 121 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException 122 | IllegalBlockSizeException | BadPaddingException 123 | InvalidAlgorithmParameterException e) { 124 Slog.e(TAG, "Failed to decrypt", e); 125 return null; 126 } 127 } 128 129 /** 130 * Decrypts a legacy SP blob which did the Keystore and software encryption layers in the wrong 131 * order. 132 */ decryptBlobV1(String protectorKeyAlias, byte[] blob, byte[] protectorSecret)133 public static byte[] decryptBlobV1(String protectorKeyAlias, byte[] blob, 134 byte[] protectorSecret) { 135 try { 136 KeyStore keyStore = getKeyStore(); 137 SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null); 138 if (protectorKey == null) { 139 throw new IllegalStateException("SP protector key is missing: " 140 + protectorKeyAlias); 141 } 142 byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob); 143 return decrypt(protectorKey, intermediate); 144 } catch (Exception e) { 145 Slog.e(TAG, "Failed to decrypt V1 blob", e); 146 throw new IllegalStateException("Failed to decrypt blob", e); 147 } 148 } 149 androidKeystoreProviderName()150 static String androidKeystoreProviderName() { 151 return "AndroidKeyStore"; 152 } 153 keyNamespace()154 static int keyNamespace() { 155 return KeyProperties.NAMESPACE_LOCKSETTINGS; 156 } 157 getKeyStore()158 private static KeyStore getKeyStore() 159 throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { 160 KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName()); 161 keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace())); 162 return keyStore; 163 } 164 165 /** 166 * Decrypts an SP blob that was created by {@link #createBlob}. 167 */ decryptBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret)168 public static byte[] decryptBlob(String protectorKeyAlias, byte[] blob, 169 byte[] protectorSecret) { 170 try { 171 final KeyStore keyStore = getKeyStore(); 172 173 SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null); 174 if (protectorKey == null) { 175 throw new IllegalStateException("SP protector key is missing: " 176 + protectorKeyAlias); 177 } 178 byte[] intermediate = decrypt(protectorKey, blob); 179 return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate); 180 } catch (CertificateException | IOException | BadPaddingException 181 | IllegalBlockSizeException 182 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException 183 | InvalidKeyException | UnrecoverableKeyException 184 | InvalidAlgorithmParameterException e) { 185 Slog.e(TAG, "Failed to decrypt blob", e); 186 throw new IllegalStateException("Failed to decrypt blob", e); 187 } 188 } 189 190 /** 191 * Creates a new SP blob by encrypting the given data. Two encryption layers are applied: an 192 * inner layer using a hash of protectorSecret as the key, and an outer layer using the 193 * protector key, which is a Keystore key that is optionally bound to a SID. This method 194 * creates the protector key and stores it under protectorKeyAlias. 195 * 196 * The reason we use a layer of software encryption, instead of using protectorSecret as the 197 * applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't 198 * cryptographically bind the applicationId to the key. The Keystore layer has to be the outer 199 * layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable. 200 */ createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret, long sid)201 public static byte[] createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret, 202 long sid) { 203 try { 204 KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); 205 keyGenerator.init(AES_GCM_KEY_SIZE * 8, new SecureRandom()); 206 SecretKey protectorKey = keyGenerator.generateKey(); 207 final KeyStore keyStore = getKeyStore(); 208 KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) 209 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 210 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 211 .setCriticalToDeviceEncryption(true); 212 if (sid != 0) { 213 builder.setUserAuthenticationRequired(true) 214 .setBoundToSpecificSecureUserId(sid) 215 .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY); 216 } 217 final KeyProtection protNonRollbackResistant = builder.build(); 218 builder.setRollbackResistant(true); 219 final KeyProtection protRollbackResistant = builder.build(); 220 final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(protectorKey); 221 try { 222 keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant); 223 Slog.i(TAG, "Using rollback-resistant key"); 224 } catch (KeyStoreException e) { 225 Slog.w(TAG, "Rollback-resistant keys unavailable. Falling back to " 226 + "non-rollback-resistant key"); 227 keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant); 228 } 229 230 byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data); 231 return encrypt(protectorKey, intermediate); 232 } catch (CertificateException | IOException | BadPaddingException 233 | IllegalBlockSizeException 234 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException 235 | InvalidKeyException 236 | InvalidParameterSpecException e) { 237 Slog.e(TAG, "Failed to create blob", e); 238 throw new IllegalStateException("Failed to encrypt blob", e); 239 } 240 } 241 destroyProtectorKey(String keyAlias)242 public static void destroyProtectorKey(String keyAlias) { 243 KeyStore keyStore; 244 try { 245 keyStore = getKeyStore(); 246 keyStore.deleteEntry(keyAlias); 247 Slog.i(TAG, "Deleted SP protector key " + keyAlias); 248 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException 249 | IOException e) { 250 Slog.e(TAG, "Failed to delete SP protector key " + keyAlias, e); 251 } 252 } 253 personalizedHash(byte[] personalization, byte[]... message)254 protected static byte[] personalizedHash(byte[] personalization, byte[]... message) { 255 try { 256 final int PADDING_LENGTH = 128; 257 MessageDigest digest = MessageDigest.getInstance("SHA-512"); 258 if (personalization.length > PADDING_LENGTH) { 259 throw new IllegalArgumentException("Personalization too long"); 260 } 261 // Personalize the hash 262 // Pad it to the block size of the hash function 263 personalization = Arrays.copyOf(personalization, PADDING_LENGTH); 264 digest.update(personalization); 265 for (byte[] data : message) { 266 digest.update(data); 267 } 268 return digest.digest(); 269 } catch (NoSuchAlgorithmException e) { 270 throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e); 271 } 272 } 273 migrateLockSettingsKey(String alias)274 static boolean migrateLockSettingsKey(String alias) { 275 final KeyDescriptor legacyKey = new KeyDescriptor(); 276 legacyKey.domain = Domain.APP; 277 legacyKey.nspace = KeyProperties.NAMESPACE_APPLICATION; 278 legacyKey.alias = alias; 279 280 final KeyDescriptor newKey = new KeyDescriptor(); 281 newKey.domain = Domain.SELINUX; 282 newKey.nspace = SyntheticPasswordCrypto.keyNamespace(); 283 newKey.alias = alias; 284 Slog.i(TAG, "Migrating key " + alias); 285 int err = AndroidKeyStoreMaintenance.migrateKeyNamespace(legacyKey, newKey); 286 if (err == 0) { 287 return true; 288 } else if (err == AndroidKeyStoreMaintenance.KEY_NOT_FOUND) { 289 Slog.i(TAG, "Key does not exist"); 290 // Treat this as a success so we don't migrate again. 291 return true; 292 } else if (err == AndroidKeyStoreMaintenance.INVALID_ARGUMENT) { 293 Slog.i(TAG, "Key already exists"); 294 // Treat this as a success so we don't migrate again. 295 return true; 296 } else { 297 Slog.e(TAG, TextUtils.formatSimple("Failed to migrate key: %d", err)); 298 return false; 299 } 300 } 301 } 302