· 7 years ago · Jun 25, 2018, 02:34 PM
1package com.wallet.crypto.trustapp.util;
2
3import android.annotation.TargetApi;
4import android.app.Activity;
5import android.app.KeyguardManager;
6import android.content.Context;
7import android.content.Intent;
8import android.security.keystore.KeyGenParameterSpec;
9import android.security.keystore.KeyProperties;
10import android.security.keystore.UserNotAuthenticatedException;
11import android.util.Log;
12
13import com.wallet.crypto.trustapp.R;
14import com.wallet.crypto.trustapp.entity.ServiceErrorException;
15
16import java.io.ByteArrayOutputStream;
17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.security.InvalidAlgorithmParameterException;
24import java.security.InvalidKeyException;
25import java.security.KeyStore;
26import java.security.KeyStoreException;
27import java.security.NoSuchAlgorithmException;
28import java.security.UnrecoverableKeyException;
29import java.security.cert.CertificateException;
30
31import javax.crypto.Cipher;
32import javax.crypto.CipherInputStream;
33import javax.crypto.CipherOutputStream;
34import javax.crypto.KeyGenerator;
35import javax.crypto.NoSuchPaddingException;
36import javax.crypto.SecretKey;
37import javax.crypto.spec.IvParameterSpec;
38
39import static com.wallet.crypto.trustapp.entity.ServiceErrorException.INVALID_KEY;
40import static com.wallet.crypto.trustapp.entity.ServiceErrorException.IV_OR_ALIAS_NO_ON_DISK;
41import static com.wallet.crypto.trustapp.entity.ServiceErrorException.KEY_IS_GONE;
42import static com.wallet.crypto.trustapp.entity.ServiceErrorException.KEY_STORE_ERROR;
43import static com.wallet.crypto.trustapp.entity.ServiceErrorException.USER_NOT_AUTHENTICATED;
44
45@TargetApi(23)
46public class KS {
47 private static final String TAG = "KS";
48
49 private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
50 private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
51 private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
52 private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
53
54 private synchronized static boolean setData(
55 Context context,
56 byte[] data,
57 String alias,
58 String aliasFile,
59 String aliasIV) throws ServiceErrorException {
60 if (data == null) {
61 throw new ServiceErrorException(
62 ServiceErrorException.INVALID_DATA, "keystore insert data is null");
63 }
64 KeyStore keyStore;
65 try {
66 keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
67 keyStore.load(null);
68 // Create the keys if necessary
69 if (!keyStore.containsAlias(alias)) {
70 KeyGenerator keyGenerator = KeyGenerator.getInstance(
71 KeyProperties.KEY_ALGORITHM_AES,
72 ANDROID_KEY_STORE);
73
74 // Set the alias of the entry in Android KeyStore where the key will appear
75 // and the constrains (purposes) in the constructor of the Builder
76 keyGenerator.init(new KeyGenParameterSpec.Builder(
77 alias,
78 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
79 .setBlockModes(BLOCK_MODE)
80 .setKeySize(256)
81 .setUserAuthenticationRequired(false)
82 .setRandomizedEncryptionRequired(true)
83 .setEncryptionPaddings(PADDING)
84 .build());
85 keyGenerator.generateKey();
86 }
87 String encryptedDataFilePath = getFilePath(context, aliasFile);
88 SecretKey secret = (SecretKey) keyStore.getKey(alias, null);
89 if (secret == null) {
90 throw new ServiceErrorException(
91 ServiceErrorException.KEY_STORE_SECRET,
92 "secret is null on setData: " + alias);
93 }
94 Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM);
95 inCipher.init(Cipher.ENCRYPT_MODE, secret);
96 byte[] iv = inCipher.getIV();
97 String path = getFilePath(context, aliasIV);
98 boolean success = writeBytesToFile(path, iv);
99 if (!success) {
100 keyStore.deleteEntry(alias);
101 throw new ServiceErrorException(
102 ServiceErrorException.FAIL_TO_SAVE_IV_FILE,
103 "Failed to save the iv file for: " + alias);
104 }
105 CipherOutputStream cipherOutputStream = null;
106 try {
107 cipherOutputStream = new CipherOutputStream(
108 new FileOutputStream(encryptedDataFilePath),
109 inCipher);
110 cipherOutputStream.write(data);
111 } catch (Exception ex) {
112 throw new ServiceErrorException(
113 ServiceErrorException.KEY_STORE_ERROR,
114 "Failed to save the file for: " + alias);
115 } finally {
116 if (cipherOutputStream != null) {
117 cipherOutputStream.close();
118 }
119 }
120 return true;
121 } catch (UserNotAuthenticatedException e) {
122 throw new ServiceErrorException(USER_NOT_AUTHENTICATED);
123 } catch (ServiceErrorException ex) {
124 Log.d(TAG, "Key store error", ex);
125 throw ex;
126 } catch (Exception ex) {
127 Log.d(TAG, "Key store error", ex);
128 throw new ServiceErrorException(KEY_STORE_ERROR);
129 }
130 }
131
132 private synchronized static byte[] getData(
133 final Context context,
134 String alias,
135 String aliasFile,
136 String aliasIV)
137 throws ServiceErrorException {
138 KeyStore keyStore;
139 String encryptedDataFilePath = getFilePath(context, aliasFile);
140 try {
141 keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
142 keyStore.load(null);
143 SecretKey secretKey = (SecretKey) keyStore.getKey(alias, null);
144 if (secretKey == null) {
145 /* no such key, the key is just simply not there */
146 boolean fileExists = new File(encryptedDataFilePath).exists();
147 if (!fileExists) {
148 return null;/* file also not there, fine then */
149 }
150 throw new ServiceErrorException(
151 KEY_IS_GONE,
152 "file is present but the key is gone: " + alias);
153 }
154
155 boolean ivExists = new File(getFilePath(context, aliasIV)).exists();
156 boolean aliasExists = new File(getFilePath(context, aliasFile)).exists();
157 if (!ivExists || !aliasExists) {
158 removeAliasAndFiles(context, alias, aliasFile, aliasIV);
159 //report it if one exists and not the other.
160 if (ivExists != aliasExists) {
161 throw new ServiceErrorException(
162 IV_OR_ALIAS_NO_ON_DISK,
163 "file is present but the key is gone: " + alias);
164 } else {
165 throw new ServiceErrorException(
166 IV_OR_ALIAS_NO_ON_DISK,
167 "!ivExists && !aliasExists: " + alias);
168 }
169 }
170
171 byte[] iv = readBytesFromFile(getFilePath(context, aliasIV));
172 if (iv == null || iv.length == 0) {
173 throw new NullPointerException("iv is missing for " + alias);
174 }
175 Cipher outCipher = Cipher.getInstance(CIPHER_ALGORITHM);
176 outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
177 CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(encryptedDataFilePath), outCipher);
178 return readBytesFromStream(cipherInputStream);
179 } catch (InvalidKeyException e) {
180 if (e instanceof UserNotAuthenticatedException) {
181// showAuthenticationScreen(context, requestCode);
182 throw new ServiceErrorException(USER_NOT_AUTHENTICATED);
183 } else {
184 throw new ServiceErrorException(INVALID_KEY);
185 }
186 } catch (IOException | CertificateException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
187 throw new ServiceErrorException(KEY_STORE_ERROR);
188 }
189 }
190
191 private synchronized static String getFilePath(Context context, String fileName) {
192 return new File(context.getFilesDir(), fileName).getAbsolutePath();
193 }
194
195 private static boolean writeBytesToFile(String path, byte[] data) {
196 FileOutputStream fos = null;
197 try {
198 File file = new File(path);
199 fos = new FileOutputStream(file);
200 // Writes bytes from the specified byte array to this file output stream
201 fos.write(data);
202 return true;
203 } catch (FileNotFoundException e) {
204 System.out.println("File not found" + e);
205 } catch (IOException ioe) {
206 System.out.println("Exception while writing file " + ioe);
207 } finally {
208 // close the streams using close method
209 try {
210 if (fos != null) {
211 fos.close();
212 }
213 } catch (IOException ioe) {
214 System.out.println("Error while closing stream: " + ioe);
215 }
216 }
217 return false;
218 }
219
220 private synchronized static void removeAliasAndFiles(Context context, String alias, String dataFileName, String ivFileName) {
221 KeyStore keyStore;
222 try {
223 keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
224 keyStore.load(null);
225 keyStore.deleteEntry(alias);
226 new File(getFilePath(context, dataFileName)).delete();
227 new File(getFilePath(context, ivFileName)).delete();
228 } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
229 e.printStackTrace();
230 }
231 }
232
233 private static byte[] readBytesFromStream(InputStream in) {
234 // this dynamically extends to take the bytes you read
235 ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
236 // this is storage overwritten on each iteration with bytes
237 int bufferSize = 1024;
238 byte[] buffer = new byte[bufferSize];
239 // we need to know how may bytes were read to write them to the byteBuffer
240 int len;
241 try {
242 while ((len = in.read(buffer)) != -1) {
243 byteBuffer.write(buffer, 0, len);
244 }
245 } catch (IOException e) {
246 e.printStackTrace();
247 } finally {
248 try {
249 byteBuffer.close();
250 } catch (IOException e) {
251 e.printStackTrace();
252 }
253 if (in != null) try {
254 in.close();
255 } catch (IOException e) {
256 e.printStackTrace();
257 }
258 }
259 // and then we can return your byte array.
260 return byteBuffer.toByteArray();
261 }
262
263 private static byte[] readBytesFromFile(String path) {
264 byte[] bytes = null;
265 FileInputStream fin;
266 try {
267 File file = new File(path);
268 fin = new FileInputStream(file);
269 bytes = readBytesFromStream(fin);
270 } catch (IOException e) {
271 e.printStackTrace();
272 }
273 return bytes;
274 }
275
276 public static void put(Context context, String address, String password) throws ServiceErrorException {
277 setData(context, password.getBytes(), address, address, address+"iv");
278 }
279
280 public static byte[] get(Context context, String address) throws ServiceErrorException {
281 return getData(context, address, address, address+"iv");
282 }
283
284 public static void showAuthenticationScreen(Context context, int requestCode) {
285 // Create the Confirm Credentials screen. You can customize the title and description. Or
286 // we will provide a generic one for you if you leave it null
287 Log.e(TAG, "showAuthenticationScreen: ");
288 if (context instanceof Activity) {
289 Activity app = (Activity) context;
290 KeyguardManager mKeyguardManager = (KeyguardManager) app.getSystemService(Context.KEYGUARD_SERVICE);
291 if (mKeyguardManager == null) {
292 return;
293 }
294 Intent intent = mKeyguardManager
295 .createConfirmDeviceCredentialIntent(
296 context.getString(R.string.unlock_screen_title_android),
297 context.getString(R.string.unlock_screen_prompt_android));
298 if (intent != null) {
299 app.startActivityForResult(intent, requestCode);
300 } else {
301 Log.e(TAG, "showAuthenticationScreen: failed to create intent for auth");
302 app.finish();
303 }
304 } else {
305 Log.e(TAG, "showAuthenticationScreen: context is not activity!");
306 }
307 }
308}