blob: 24d575e27bdd86d350912a2b8970417f73b5bbac [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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 android.annotation.Nullable;
20import android.util.Pair;
21
22import com.android.internal.annotations.VisibleForTesting;
23
24import java.nio.ByteBuffer;
25import java.nio.ByteOrder;
26import java.nio.charset.StandardCharsets;
27import java.security.InvalidKeyException;
28import java.security.KeyFactory;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.security.PublicKey;
32import java.security.SecureRandom;
33import java.security.spec.InvalidKeySpecException;
34import java.security.spec.X509EncodedKeySpec;
35import java.util.HashMap;
36import java.util.Map;
37
38import javax.crypto.AEADBadTagException;
39import javax.crypto.KeyGenerator;
40import javax.crypto.SecretKey;
41
42/**
43 * Utility functions for the flow where the RecoveryController syncs keys with remote storage.
44 *
45 * @hide
46 */
47public 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 */
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 */
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
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
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
357 private KeySyncUtils() {}
358}