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;
18 
19 import android.security.keystore.KeyProperties;
20 import android.security.keystore.KeyProtection;
21 
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.security.InvalidAlgorithmParameterException;
25 import java.security.InvalidKeyException;
26 import java.security.KeyStore;
27 import java.security.KeyStoreException;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.security.SecureRandom;
31 import java.security.UnrecoverableKeyException;
32 import java.security.cert.CertificateException;
33 import java.security.spec.InvalidParameterSpecException;
34 import java.util.Arrays;
35 
36 import javax.crypto.BadPaddingException;
37 import javax.crypto.Cipher;
38 import javax.crypto.IllegalBlockSizeException;
39 import javax.crypto.KeyGenerator;
40 import javax.crypto.NoSuchPaddingException;
41 import javax.crypto.SecretKey;
42 import javax.crypto.spec.GCMParameterSpec;
43 import javax.crypto.spec.SecretKeySpec;
44 
45 public class SyntheticPasswordCrypto {
46     private static final int PROFILE_KEY_IV_SIZE = 12;
47     private static final int DEFAULT_TAG_LENGTH_BITS = 128;
48     private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
49     private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
50     // Time between the user credential is verified with GK and the decryption of synthetic password
51     // under the auth-bound key. This should always happen one after the other, but give it 15
52     // seconds just to be sure.
53     private static final int USER_AUTHENTICATION_VALIDITY = 15;
54 
decrypt(SecretKey key, byte[] blob)55     private static byte[] decrypt(SecretKey key, byte[] blob)
56             throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
57             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
58         if (blob == null) {
59             return null;
60         }
61         byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
62         byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
63         Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
64                 + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
65         cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
66         return cipher.doFinal(ciphertext);
67     }
68 
encrypt(SecretKey key, byte[] blob)69     private static byte[] encrypt(SecretKey key, byte[] blob)
70             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
71             InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
72             InvalidParameterSpecException {
73         if (blob == null) {
74             return null;
75         }
76         Cipher cipher = Cipher.getInstance(
77                 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
78                         + KeyProperties.ENCRYPTION_PADDING_NONE);
79         cipher.init(Cipher.ENCRYPT_MODE, key);
80         byte[] ciphertext = cipher.doFinal(blob);
81         byte[] iv = cipher.getIV();
82         if (iv.length != PROFILE_KEY_IV_SIZE) {
83             throw new RuntimeException("Invalid iv length: " + iv.length);
84         }
85         final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
86                 GCMParameterSpec.class);
87         if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
88             throw new RuntimeException("Invalid tag length: " + spec.getTLen());
89         }
90         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
91         outputStream.write(iv);
92         outputStream.write(ciphertext);
93         return outputStream.toByteArray();
94     }
95 
encrypt(byte[] keyBytes, byte[] personalisation, byte[] message)96     public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
97         byte[] keyHash = personalisedHash(personalisation, keyBytes);
98         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
99                 KeyProperties.KEY_ALGORITHM_AES);
100         try {
101             return encrypt(key, message);
102         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
103                 | IllegalBlockSizeException | BadPaddingException | IOException
104                 | InvalidParameterSpecException e) {
105             e.printStackTrace();
106             return null;
107         }
108     }
109 
decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext)110     public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
111         byte[] keyHash = personalisedHash(personalisation, keyBytes);
112         SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
113                 KeyProperties.KEY_ALGORITHM_AES);
114         try {
115             return decrypt(key, ciphertext);
116         } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
117                 | IllegalBlockSizeException | BadPaddingException
118                 | InvalidAlgorithmParameterException e) {
119             e.printStackTrace();
120             return null;
121         }
122     }
123 
decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId)124     public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
125         try {
126             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
127             keyStore.load(null);
128 
129             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
130             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
131             return decrypt(decryptionKey, intermediate);
132         } catch (Exception e) {
133             e.printStackTrace();
134             throw new RuntimeException("Failed to decrypt blob", e);
135         }
136     }
137 
decryptBlob(String keyAlias, byte[] blob, byte[] applicationId)138     public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
139         try {
140             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
141             keyStore.load(null);
142 
143             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
144             byte[] intermediate = decrypt(decryptionKey, blob);
145             return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
146         } catch (CertificateException | IOException | BadPaddingException
147                 | IllegalBlockSizeException
148                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
149                 | InvalidKeyException | UnrecoverableKeyException
150                 | InvalidAlgorithmParameterException e) {
151             e.printStackTrace();
152             throw new RuntimeException("Failed to decrypt blob", e);
153         }
154     }
155 
createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid)156     public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
157         try {
158             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
159             keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
160             SecretKey secretKey = keyGenerator.generateKey();
161             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
162             keyStore.load(null);
163             KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
164                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
165                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
166                     .setCriticalToDeviceEncryption(true);
167             if (sid != 0) {
168                 builder.setUserAuthenticationRequired(true)
169                         .setBoundToSpecificSecureUserId(sid)
170                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
171             }
172 
173             keyStore.setEntry(keyAlias,
174                     new KeyStore.SecretKeyEntry(secretKey),
175                     builder.build());
176             byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
177             return encrypt(secretKey, intermediate);
178         } catch (CertificateException | IOException | BadPaddingException
179                 | IllegalBlockSizeException
180                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
181                 | InvalidKeyException
182                 | InvalidParameterSpecException e) {
183             e.printStackTrace();
184             throw new RuntimeException("Failed to encrypt blob", e);
185         }
186     }
187 
destroyBlobKey(String keyAlias)188     public static void destroyBlobKey(String keyAlias) {
189         KeyStore keyStore;
190         try {
191             keyStore = KeyStore.getInstance("AndroidKeyStore");
192             keyStore.load(null);
193             keyStore.deleteEntry(keyAlias);
194         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
195                 | IOException e) {
196             e.printStackTrace();
197         }
198     }
199 
personalisedHash(byte[] personalisation, byte[]... message)200     protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
201         try {
202             final int PADDING_LENGTH = 128;
203             MessageDigest digest = MessageDigest.getInstance("SHA-512");
204             if (personalisation.length > PADDING_LENGTH) {
205                 throw new RuntimeException("Personalisation too long");
206             }
207             // Personalize the hash
208             // Pad it to the block size of the hash function
209             personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
210             digest.update(personalisation);
211             for (byte[] data : message) {
212                 digest.update(data);
213             }
214             return digest.digest();
215         } catch (NoSuchAlgorithmException e) {
216             throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
217         }
218     }
219 }
220