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