blob: 9246311fcdc70b650717360526cddd2b33b287f0 [file] [log] [blame]
Rubin Xu3bf722a2016-12-15 16:07:38 +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
Andrew Scull507d11c2017-05-03 17:19:01 +010017package com.android.server.locksettings;
Rubin Xu3bf722a2016-12-15 16:07:38 +000018
19import android.security.keystore.KeyProperties;
20import android.security.keystore.KeyProtection;
Rubin Xu0f9c2ff2019-08-14 16:25:57 +010021import android.util.Slog;
Rubin Xu3bf722a2016-12-15 16:07:38 +000022
23import java.io.ByteArrayOutputStream;
24import java.io.IOException;
25import java.security.InvalidAlgorithmParameterException;
26import java.security.InvalidKeyException;
27import java.security.KeyStore;
28import java.security.KeyStoreException;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.security.SecureRandom;
32import java.security.UnrecoverableKeyException;
33import java.security.cert.CertificateException;
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010034import java.security.spec.InvalidParameterSpecException;
Rubin Xu3bf722a2016-12-15 16:07:38 +000035import java.util.Arrays;
36
37import javax.crypto.BadPaddingException;
38import javax.crypto.Cipher;
39import javax.crypto.IllegalBlockSizeException;
40import javax.crypto.KeyGenerator;
41import javax.crypto.NoSuchPaddingException;
42import javax.crypto.SecretKey;
43import javax.crypto.spec.GCMParameterSpec;
44import javax.crypto.spec.SecretKeySpec;
45
46public class SyntheticPasswordCrypto {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +010047 private static final String TAG = "SyntheticPasswordCrypto";
Rubin Xu3bf722a2016-12-15 16:07:38 +000048 private static final int PROFILE_KEY_IV_SIZE = 12;
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010049 private static final int DEFAULT_TAG_LENGTH_BITS = 128;
Rubin Xu3bf722a2016-12-15 16:07:38 +000050 private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
51 private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
52 // Time between the user credential is verified with GK and the decryption of synthetic password
53 // under the auth-bound key. This should always happen one after the other, but give it 15
54 // seconds just to be sure.
55 private static final int USER_AUTHENTICATION_VALIDITY = 15;
56
57 private static byte[] decrypt(SecretKey key, byte[] blob)
58 throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
59 InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
60 if (blob == null) {
61 return null;
62 }
63 byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
64 byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
65 Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
66 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010067 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
Rubin Xu3bf722a2016-12-15 16:07:38 +000068 return cipher.doFinal(ciphertext);
69 }
70
71 private static byte[] encrypt(SecretKey key, byte[] blob)
72 throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010073 InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
74 InvalidParameterSpecException {
Rubin Xu3bf722a2016-12-15 16:07:38 +000075 if (blob == null) {
76 return null;
77 }
78 Cipher cipher = Cipher.getInstance(
79 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
80 + KeyProperties.ENCRYPTION_PADDING_NONE);
81 cipher.init(Cipher.ENCRYPT_MODE, key);
82 byte[] ciphertext = cipher.doFinal(blob);
83 byte[] iv = cipher.getIV();
84 if (iv.length != PROFILE_KEY_IV_SIZE) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +010085 throw new IllegalArgumentException("Invalid iv length: " + iv.length);
Rubin Xu3bf722a2016-12-15 16:07:38 +000086 }
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010087 final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
88 GCMParameterSpec.class);
89 if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +010090 throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
Mykola Kondratenko01e3a482019-01-04 15:13:33 +010091 }
Rubin Xu3bf722a2016-12-15 16:07:38 +000092 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
93 outputStream.write(iv);
94 outputStream.write(ciphertext);
95 return outputStream.toByteArray();
96 }
97
98 public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
99 byte[] keyHash = personalisedHash(personalisation, keyBytes);
100 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
101 KeyProperties.KEY_ALGORITHM_AES);
102 try {
103 return encrypt(key, message);
104 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
Mykola Kondratenko01e3a482019-01-04 15:13:33 +0100105 | IllegalBlockSizeException | BadPaddingException | IOException
106 | InvalidParameterSpecException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100107 Slog.e(TAG, "Failed to encrypt", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000108 return null;
109 }
110 }
111
112 public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
113 byte[] keyHash = personalisedHash(personalisation, keyBytes);
114 SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
115 KeyProperties.KEY_ALGORITHM_AES);
116 try {
117 return decrypt(key, ciphertext);
118 } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
119 | IllegalBlockSizeException | BadPaddingException
120 | InvalidAlgorithmParameterException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100121 Slog.e(TAG, "Failed to decrypt", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000122 return null;
123 }
124 }
125
Rubin Xu8c528652017-10-31 15:40:32 +0000126 public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000127 try {
128 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
129 keyStore.load(null);
130
131 SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
Rubin Xuf568ae72019-09-30 10:01:59 +0100132 if (decryptionKey == null) {
133 throw new IllegalStateException("SP key is missing: " + keyAlias);
134 }
Rubin Xu3bf722a2016-12-15 16:07:38 +0000135 byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
136 return decrypt(decryptionKey, intermediate);
Rubin Xu8c528652017-10-31 15:40:32 +0000137 } catch (Exception e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100138 Slog.e(TAG, "Failed to decrypt V1 blob", e);
139 throw new IllegalStateException("Failed to decrypt blob", e);
Rubin Xu8c528652017-10-31 15:40:32 +0000140 }
141 }
142
143 public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
144 try {
145 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
146 keyStore.load(null);
147
148 SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
Rubin Xuf568ae72019-09-30 10:01:59 +0100149 if (decryptionKey == null) {
150 throw new IllegalStateException("SP key is missing: " + keyAlias);
151 }
Rubin Xu8c528652017-10-31 15:40:32 +0000152 byte[] intermediate = decrypt(decryptionKey, blob);
153 return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000154 } catch (CertificateException | IOException | BadPaddingException
155 | IllegalBlockSizeException
156 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
157 | InvalidKeyException | UnrecoverableKeyException
158 | InvalidAlgorithmParameterException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100159 Slog.e(TAG, "Failed to decrypt blob", e);
160 throw new IllegalStateException("Failed to decrypt blob", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000161 }
162 }
163
164 public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
165 try {
166 KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
Mykola Kondratenko01e3a482019-01-04 15:13:33 +0100167 keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
Rubin Xu3bf722a2016-12-15 16:07:38 +0000168 SecretKey secretKey = keyGenerator.generateKey();
169 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
170 keyStore.load(null);
171 KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
172 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
Rubin Xu12b644d2017-04-21 19:21:42 +0100173 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
174 .setCriticalToDeviceEncryption(true);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000175 if (sid != 0) {
176 builder.setUserAuthenticationRequired(true)
177 .setBoundToSpecificSecureUserId(sid)
178 .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
179 }
Rubin Xu12b644d2017-04-21 19:21:42 +0100180
Rubin Xu3bf722a2016-12-15 16:07:38 +0000181 keyStore.setEntry(keyAlias,
182 new KeyStore.SecretKeyEntry(secretKey),
183 builder.build());
Rubin Xu8c528652017-10-31 15:40:32 +0000184 byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
185 return encrypt(secretKey, intermediate);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000186 } catch (CertificateException | IOException | BadPaddingException
187 | IllegalBlockSizeException
188 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
Mykola Kondratenko01e3a482019-01-04 15:13:33 +0100189 | InvalidKeyException
190 | InvalidParameterSpecException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100191 Slog.e(TAG, "Failed to create blob", e);
192 throw new IllegalStateException("Failed to encrypt blob", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000193 }
194 }
195
196 public static void destroyBlobKey(String keyAlias) {
197 KeyStore keyStore;
198 try {
199 keyStore = KeyStore.getInstance("AndroidKeyStore");
200 keyStore.load(null);
201 keyStore.deleteEntry(keyAlias);
Rubin Xuf568ae72019-09-30 10:01:59 +0100202 Slog.i(TAG, "SP key deleted: " + keyAlias);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000203 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
204 | IOException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100205 Slog.e(TAG, "Failed to destroy blob", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000206 }
207 }
208
209 protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
210 try {
211 final int PADDING_LENGTH = 128;
212 MessageDigest digest = MessageDigest.getInstance("SHA-512");
213 if (personalisation.length > PADDING_LENGTH) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100214 throw new IllegalArgumentException("Personalisation too long");
Rubin Xu3bf722a2016-12-15 16:07:38 +0000215 }
216 // Personalize the hash
217 // Pad it to the block size of the hash function
218 personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
219 digest.update(personalisation);
220 for (byte[] data : message) {
221 digest.update(data);
222 }
223 return digest.digest();
224 } catch (NoSuchAlgorithmException e) {
Rubin Xu0f9c2ff2019-08-14 16:25:57 +0100225 throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000226 }
227 }
228}