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.recoverablekeystore; 18 19 import android.annotation.Nullable; 20 import android.util.Pair; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.nio.ByteBuffer; 25 import java.nio.ByteOrder; 26 import java.nio.charset.StandardCharsets; 27 import java.security.InvalidKeyException; 28 import java.security.KeyFactory; 29 import java.security.MessageDigest; 30 import java.security.NoSuchAlgorithmException; 31 import java.security.PublicKey; 32 import java.security.SecureRandom; 33 import java.security.spec.InvalidKeySpecException; 34 import java.security.spec.X509EncodedKeySpec; 35 import java.util.HashMap; 36 import java.util.Map; 37 38 import javax.crypto.AEADBadTagException; 39 import javax.crypto.KeyGenerator; 40 import javax.crypto.SecretKey; 41 42 /** 43 * Utility functions for the flow where the RecoveryController syncs keys with remote storage. 44 * 45 * @hide 46 */ 47 public class KeySyncUtils { 48 49 private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC"; 50 private static final String RECOVERY_KEY_ALGORITHM = "AES"; 51 private static final int RECOVERY_KEY_SIZE_BITS = 256; 52 53 private static final byte[] THM_ENCRYPTED_RECOVERY_KEY_HEADER = 54 "V1 THM_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); 55 private static final byte[] LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER = 56 "V1 locally_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); 57 private static final byte[] ENCRYPTED_APPLICATION_KEY_HEADER = 58 "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8); 59 private static final byte[] RECOVERY_CLAIM_HEADER = 60 "V1 KF_claim".getBytes(StandardCharsets.UTF_8); 61 private static final byte[] RECOVERY_RESPONSE_HEADER = 62 "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); 63 64 private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); 65 66 private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; 67 68 /** 69 * Encrypts the recovery key using both the lock screen hash and the remote storage's public 70 * key. 71 * 72 * @param publicKey The public key of the remote storage. 73 * @param lockScreenHash The user's lock screen hash. 74 * @param vaultParams Additional parameters to send to the remote storage. 75 * @param recoveryKey The recovery key. 76 * @return The encrypted bytes. 77 * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. 78 * @throws InvalidKeyException if the public key or the lock screen could not be used to encrypt 79 * the data. 80 * 81 * @hide 82 */ thmEncryptRecoveryKey( PublicKey publicKey, byte[] lockScreenHash, byte[] vaultParams, SecretKey recoveryKey )83 public static byte[] thmEncryptRecoveryKey( 84 PublicKey publicKey, 85 byte[] lockScreenHash, 86 byte[] vaultParams, 87 SecretKey recoveryKey 88 ) throws NoSuchAlgorithmException, InvalidKeyException { 89 byte[] encryptedRecoveryKey = locallyEncryptRecoveryKey(lockScreenHash, recoveryKey); 90 byte[] thmKfHash = calculateThmKfHash(lockScreenHash); 91 byte[] header = concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams); 92 return SecureBox.encrypt( 93 /*theirPublicKey=*/ publicKey, 94 /*sharedSecret=*/ thmKfHash, 95 /*header=*/ header, 96 /*payload=*/ encryptedRecoveryKey); 97 } 98 99 /** 100 * Calculates the THM_KF hash of the lock screen hash. 101 * 102 * @param lockScreenHash The lock screen hash. 103 * @return The hash. 104 * @throws NoSuchAlgorithmException if SHA-256 is unavailable (should never happen). 105 * 106 * @hide 107 */ calculateThmKfHash(byte[] lockScreenHash)108 public static byte[] calculateThmKfHash(byte[] lockScreenHash) 109 throws NoSuchAlgorithmException { 110 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 111 messageDigest.update(THM_KF_HASH_PREFIX); 112 messageDigest.update(lockScreenHash); 113 return messageDigest.digest(); 114 } 115 116 /** 117 * Encrypts the recovery key using the lock screen hash. 118 * 119 * @param lockScreenHash The raw lock screen hash. 120 * @param recoveryKey The recovery key. 121 * @return The encrypted bytes. 122 * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. 123 * @throws InvalidKeyException if the hash cannot be used to encrypt for some reason. 124 */ 125 @VisibleForTesting locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)126 static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey) 127 throws NoSuchAlgorithmException, InvalidKeyException { 128 return SecureBox.encrypt( 129 /*theirPublicKey=*/ null, 130 /*sharedSecret=*/ lockScreenHash, 131 /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER, 132 /*payload=*/ recoveryKey.getEncoded()); 133 } 134 135 /** 136 * Returns a new random 256-bit AES recovery key. 137 * 138 * @hide 139 */ generateRecoveryKey()140 public static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { 141 KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); 142 keyGenerator.init(RECOVERY_KEY_SIZE_BITS, new SecureRandom()); 143 return keyGenerator.generateKey(); 144 } 145 146 /** 147 * Encrypts all of the given keys with the recovery key, using SecureBox. 148 * 149 * @param recoveryKey The recovery key. 150 * @param keys The keys, indexed by their aliases. 151 * @return The encrypted key material, indexed by aliases. 152 * @throws NoSuchAlgorithmException if any of the SecureBox algorithms are unavailable. 153 * @throws InvalidKeyException if the recovery key is not appropriate for encrypting the keys. 154 * 155 * @hide 156 */ encryptKeysWithRecoveryKey( SecretKey recoveryKey, Map<String, Pair<SecretKey, byte[]>> keys)157 public static Map<String, byte[]> encryptKeysWithRecoveryKey( 158 SecretKey recoveryKey, Map<String, Pair<SecretKey, byte[]>> keys) 159 throws NoSuchAlgorithmException, InvalidKeyException { 160 HashMap<String, byte[]> encryptedKeys = new HashMap<>(); 161 for (String alias : keys.keySet()) { 162 SecretKey key = keys.get(alias).first; 163 byte[] metadata = keys.get(alias).second; 164 byte[] header; 165 if (metadata == null) { 166 header = ENCRYPTED_APPLICATION_KEY_HEADER; 167 } else { 168 // The provided metadata, if non-empty, will be bound to the authenticated 169 // encryption process of the key material. As a result, the ciphertext cannot be 170 // decrypted if a wrong metadata is provided during the recovery/decryption process. 171 // Note that Android P devices do not have the API to provide the optional metadata, 172 // so all the keys with non-empty metadata stored on Android Q+ devices cannot be 173 // recovered on Android P devices. 174 header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata); 175 } 176 byte[] encryptedKey = SecureBox.encrypt( 177 /*theirPublicKey=*/ null, 178 /*sharedSecret=*/ recoveryKey.getEncoded(), 179 /*header=*/ header, 180 /*payload=*/ key.getEncoded()); 181 encryptedKeys.put(alias, encryptedKey); 182 } 183 return encryptedKeys; 184 } 185 186 /** 187 * Returns a random 16-byte key claimant. 188 * 189 * @hide 190 */ generateKeyClaimant()191 public static byte[] generateKeyClaimant() { 192 SecureRandom secureRandom = new SecureRandom(); 193 byte[] key = new byte[KEY_CLAIMANT_LENGTH_BYTES]; 194 secureRandom.nextBytes(key); 195 return key; 196 } 197 198 /** 199 * Encrypts a claim to recover a remote recovery key. 200 * 201 * @param publicKey The public key of the remote server. 202 * @param vaultParams Associated vault parameters. 203 * @param challenge The challenge issued by the server. 204 * @param thmKfHash The THM hash of the lock screen. 205 * @param keyClaimant The random key claimant. 206 * @return The encrypted recovery claim, to be sent to the remote server. 207 * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. 208 * @throws InvalidKeyException if the {@code publicKey} could not be used to encrypt. 209 * 210 * @hide 211 */ encryptRecoveryClaim( PublicKey publicKey, byte[] vaultParams, byte[] challenge, byte[] thmKfHash, byte[] keyClaimant)212 public static byte[] encryptRecoveryClaim( 213 PublicKey publicKey, 214 byte[] vaultParams, 215 byte[] challenge, 216 byte[] thmKfHash, 217 byte[] keyClaimant) throws NoSuchAlgorithmException, InvalidKeyException { 218 return SecureBox.encrypt( 219 publicKey, 220 /*sharedSecret=*/ null, 221 /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), 222 /*payload=*/ concat(thmKfHash, keyClaimant)); 223 } 224 225 /** 226 * Decrypts response from recovery claim, returning the locally encrypted key. 227 * 228 * @param keyClaimant The key claimant, used by the remote service to encrypt the response. 229 * @param vaultParams Vault params associated with the claim. 230 * @param encryptedResponse The encrypted response. 231 * @return The locally encrypted recovery key. 232 * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. 233 * @throws InvalidKeyException if the {@code keyClaimant} could not be used to decrypt. 234 * @throws AEADBadTagException if the message has been tampered with or was encrypted with a 235 * different key. 236 */ decryptRecoveryClaimResponse( byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse)237 public static byte[] decryptRecoveryClaimResponse( 238 byte[] keyClaimant, byte[] vaultParams, byte[] encryptedResponse) 239 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 240 return SecureBox.decrypt( 241 /*ourPrivateKey=*/ null, 242 /*sharedSecret=*/ keyClaimant, 243 /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams), 244 /*encryptedPayload=*/ encryptedResponse); 245 } 246 247 /** 248 * Decrypts a recovery key, after having retrieved it from a remote server. 249 * 250 * @param lskfHash The lock screen hash associated with the key. 251 * @param encryptedRecoveryKey The encrypted key. 252 * @return The raw key material. 253 * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. 254 * @throws AEADBadTagException if the message has been tampered with or was encrypted with a 255 * different key. 256 */ decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey)257 public static byte[] decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey) 258 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 259 return SecureBox.decrypt( 260 /*ourPrivateKey=*/ null, 261 /*sharedSecret=*/ lskfHash, 262 /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER, 263 /*encryptedPayload=*/ encryptedRecoveryKey); 264 } 265 266 /** 267 * Decrypts an application key, using the recovery key. 268 * 269 * @param recoveryKey The recovery key - used to wrap all application keys. 270 * @param encryptedApplicationKey The application key to unwrap. 271 * @return The raw key material of the application key. 272 * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. 273 * @throws AEADBadTagException if the message has been tampered with or was encrypted with a 274 * different key. 275 */ decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey, @Nullable byte[] applicationKeyMetadata)276 public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey, 277 @Nullable byte[] applicationKeyMetadata) 278 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 279 byte[] header; 280 if (applicationKeyMetadata == null) { 281 header = ENCRYPTED_APPLICATION_KEY_HEADER; 282 } else { 283 header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata); 284 } 285 return SecureBox.decrypt( 286 /*ourPrivateKey=*/ null, 287 /*sharedSecret=*/ recoveryKey, 288 /*header=*/ header, 289 /*encryptedPayload=*/ encryptedApplicationKey); 290 } 291 292 /** 293 * Deserializes a X509 public key. 294 * 295 * @param key The bytes of the key. 296 * @return The key. 297 * @throws InvalidKeySpecException if the bytes of the key are not a valid key. 298 */ deserializePublicKey(byte[] key)299 public static PublicKey deserializePublicKey(byte[] key) throws InvalidKeySpecException { 300 KeyFactory keyFactory; 301 try { 302 keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM); 303 } catch (NoSuchAlgorithmException e) { 304 // Should not happen 305 throw new RuntimeException(e); 306 } 307 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key); 308 return keyFactory.generatePublic(publicKeySpec); 309 } 310 311 /** 312 * Packs vault params into a binary format. 313 * 314 * @param thmPublicKey Public key of the trusted hardware module. 315 * @param counterId ID referring to the specific counter in the hardware module. 316 * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key. 317 * @param vaultHandle Handle of the Vault. 318 * @return The binary vault params, ready for sync. 319 */ packVaultParams( PublicKey thmPublicKey, long counterId, int maxAttempts, byte[] vaultHandle)320 public static byte[] packVaultParams( 321 PublicKey thmPublicKey, long counterId, int maxAttempts, byte[] vaultHandle) { 322 int vaultParamsLength 323 = 65 // public key 324 + 8 // counterId 325 + 4 // maxAttempts 326 + vaultHandle.length; 327 return ByteBuffer.allocate(vaultParamsLength) 328 .order(ByteOrder.LITTLE_ENDIAN) 329 .put(SecureBox.encodePublicKey(thmPublicKey)) 330 .putLong(counterId) 331 .putInt(maxAttempts) 332 .put(vaultHandle) 333 .array(); 334 } 335 336 /** 337 * Returns the concatenation of all the given {@code arrays}. 338 */ 339 @VisibleForTesting concat(byte[]... arrays)340 static byte[] concat(byte[]... arrays) { 341 int length = 0; 342 for (byte[] array : arrays) { 343 length += array.length; 344 } 345 346 byte[] concatenated = new byte[length]; 347 int pos = 0; 348 for (byte[] array : arrays) { 349 System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length); 350 pos += array.length; 351 } 352 353 return concatenated; 354 } 355 356 // Statics only KeySyncUtils()357 private KeySyncUtils() {} 358 } 359