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