OSDN Git Service

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