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 import android.util.Slog;
22 
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.security.InvalidAlgorithmParameterException;
26 import java.security.InvalidKeyException;
27 import java.security.KeyStore;
28 import java.security.KeyStoreException;
29 import java.security.MessageDigest;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.SecureRandom;
32 import java.security.UnrecoverableKeyException;
33 import java.security.cert.CertificateException;
34 import java.security.spec.InvalidParameterSpecException;
35 import java.util.Arrays;
36 
37 import javax.crypto.BadPaddingException;
38 import javax.crypto.Cipher;
39 import javax.crypto.IllegalBlockSizeException;
40 import javax.crypto.KeyGenerator;
41 import javax.crypto.NoSuchPaddingException;
42 import javax.crypto.SecretKey;
43 import javax.crypto.spec.GCMParameterSpec;
44 import javax.crypto.spec.SecretKeySpec;
45 
46 public class SyntheticPasswordCrypto {
47     private static final String TAG = "SyntheticPasswordCrypto";
48     private static final int PROFILE_KEY_IV_SIZE = 12;
49     private static final int DEFAULT_TAG_LENGTH_BITS = 128;
50     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 
decrypt(SecretKey key, byte[] blob)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);
67         cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
68         return cipher.doFinal(ciphertext);
69     }
70 
encrypt(SecretKey key, byte[] blob)71     private static byte[] encrypt(SecretKey key, byte[] blob)
72             throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
73             InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
74             InvalidParameterSpecException {
75         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) {
85             throw new IllegalArgumentException("Invalid iv length: " + iv.length);
86         }
87         final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
88                 GCMParameterSpec.class);
89         if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
90             throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
91         }
92         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
93         outputStream.write(iv);
94         outputStream.write(ciphertext);
95         return outputStream.toByteArray();
96     }
97 
encrypt(byte[] keyBytes, byte[] personalisation, byte[] message)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
105                 | IllegalBlockSizeException | BadPaddingException | IOException
106                 | InvalidParameterSpecException e) {
107             Slog.e(TAG, "Failed to encrypt", e);
108             return null;
109         }
110     }
111 
decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext)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) {
121             Slog.e(TAG, "Failed to decrypt", e);
122             return null;
123         }
124     }
125 
decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId)126     public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
127         try {
128             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
129             keyStore.load(null);
130 
131             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
132             if (decryptionKey == null) {
133                 throw new IllegalStateException("SP key is missing: " + keyAlias);
134             }
135             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
136             return decrypt(decryptionKey, intermediate);
137         } catch (Exception e) {
138             Slog.e(TAG, "Failed to decrypt V1 blob", e);
139             throw new IllegalStateException("Failed to decrypt blob", e);
140         }
141     }
142 
decryptBlob(String keyAlias, byte[] blob, byte[] applicationId)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);
149             if (decryptionKey == null) {
150                 throw new IllegalStateException("SP key is missing: " + keyAlias);
151             }
152             byte[] intermediate = decrypt(decryptionKey, blob);
153             return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
154         } catch (CertificateException | IOException | BadPaddingException
155                 | IllegalBlockSizeException
156                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
157                 | InvalidKeyException | UnrecoverableKeyException
158                 | InvalidAlgorithmParameterException e) {
159             Slog.e(TAG, "Failed to decrypt blob", e);
160             throw new IllegalStateException("Failed to decrypt blob", e);
161         }
162     }
163 
createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid)164     public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
165         try {
166             KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
167             keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
168             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)
173                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
174                     .setCriticalToDeviceEncryption(true);
175             if (sid != 0) {
176                 builder.setUserAuthenticationRequired(true)
177                         .setBoundToSpecificSecureUserId(sid)
178                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
179             }
180 
181             keyStore.setEntry(keyAlias,
182                     new KeyStore.SecretKeyEntry(secretKey),
183                     builder.build());
184             byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
185             return encrypt(secretKey, intermediate);
186         } catch (CertificateException | IOException | BadPaddingException
187                 | IllegalBlockSizeException
188                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
189                 | InvalidKeyException
190                 | InvalidParameterSpecException e) {
191             Slog.e(TAG, "Failed to create blob", e);
192             throw new IllegalStateException("Failed to encrypt blob", e);
193         }
194     }
195 
destroyBlobKey(String keyAlias)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);
202             Slog.i(TAG, "SP key deleted: " + keyAlias);
203         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
204                 | IOException e) {
205             Slog.e(TAG, "Failed to destroy blob", e);
206         }
207     }
208 
personalisedHash(byte[] personalisation, byte[]... message)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) {
214                 throw new IllegalArgumentException("Personalisation too long");
215             }
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) {
225             throw new IllegalStateException("NoSuchAlgorithmException for SHA-512", e);
226         }
227     }
228 }
229