Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / crypto / CipherFactory.java
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser.crypto;
6
7 import android.os.AsyncTask;
8 import android.os.Bundle;
9 import android.util.Log;
10
11 import java.io.IOException;
12 import java.security.GeneralSecurityException;
13 import java.security.Key;
14 import java.security.SecureRandom;
15 import java.util.Arrays;
16 import java.util.concurrent.Callable;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.FutureTask;
19
20 import javax.annotation.concurrent.ThreadSafe;
21 import javax.crypto.Cipher;
22 import javax.crypto.KeyGenerator;
23 import javax.crypto.spec.IvParameterSpec;
24 import javax.crypto.spec.SecretKeySpec;
25
26 /**
27  * Generates {@link Cipher} instances for encrypting session data that is temporarily stored.
28  *
29  * When an Activity is sent to the background, Android gives it the opportunity to save state to
30  * restore a user's session when the Activity is restarted. In addition to saving state to disk,
31  * Android has a mechanism for saving instance state through {@link Bundle}s, which help
32  * differentiate between users pausing and ending a session:
33  * - If the Activity is killed in the background (e.g. to free up resources for other Activities),
34  *   Android gives a {@link Bundle} to the Activity when the user restarts the Activity. The
35  *   {@link Bundle} is expected to be small and fast to generate, and is managed by Android.
36  * - If the Activity was explicitly killed (e.g. the user swiped away the task from Recent Tasks),
37  *   Android does not restore the {@link Bundle} when the user restarts the Activity.
38  *
39  * To securely save temporary session data to disk:
40  * - Encrypt data with a {@link Cipher} from {@link CipherFactory#getCipher(int)} before storing it.
41  * - Store {@link Cipher} parameters in the Bundle via {@link CipherFactory#saveToBundle(Bundle)}.
42  *
43  * Explicitly ending the session destroys the {@link Bundle}, making the previous session's data
44  * unreadable.
45  */
46 @ThreadSafe
47 public class CipherFactory {
48     private static final String TAG = "CipherFactory";
49     static final int NUM_BYTES = 16;
50
51     static final String BUNDLE_IV = "org.chromium.content.browser.crypto.CipherFactory.IV";
52     static final String BUNDLE_KEY = "org.chromium.content.browser.crypto.CipherFactory.KEY";
53
54     /** Holds intermediate data for the computation. */
55     private static class CipherData {
56         public final Key key;
57         public final byte[] iv;
58
59         public CipherData(Key key, byte[] iv) {
60             this.key = key;
61             this.iv = iv;
62         }
63     }
64
65     /** Singleton holder for the class. */
66     private static class LazyHolder {
67         private static CipherFactory sInstance = new CipherFactory();
68     }
69
70     /**
71      * Synchronization primitive to prevent thrashing the cipher parameters between threads
72      * attempting to restore previous parameters and generate new ones.
73      */
74     private final Object mDataLock = new Object();
75
76     /** Used to generate data needed for the Cipher on a background thread. */
77     private FutureTask<CipherData> mDataGenerator;
78
79     /** Holds data for cipher generation. */
80     private CipherData mData;
81
82     /** Generates random data for the Ciphers. May be swapped out for tests. */
83     private ByteArrayGenerator mRandomNumberProvider;
84
85     /** @return The Singleton instance. Creates it if it doesn't exist. */
86     public static CipherFactory getInstance() {
87         return LazyHolder.sInstance;
88     }
89
90     /**
91      * Creates a secure Cipher for encrypting data.
92      * This function blocks until data needed to generate a Cipher has been created by the
93      * background thread.
94      * @param opmode One of Cipher.{ENCRYPT,DECRYPT}_MODE.
95      * @return A Cipher, or null if it is not possible to instantiate one.
96      */
97     public Cipher getCipher(int opmode) {
98         CipherData data = getCipherData(true);
99
100         if (data != null) {
101             try {
102                 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
103                 cipher.init(opmode, data.key, new IvParameterSpec(data.iv));
104                 return cipher;
105             } catch (GeneralSecurityException e) {
106                 // Can't do anything here.
107             }
108         }
109
110         Log.e(TAG, "Error in creating cipher instance.");
111         return null;
112     }
113
114     /**
115      * Returns data required for generating the Cipher.
116      * @param generateIfNeeded Generates data on the background thread, blocking until it is done.
117      * @return Data to use for the Cipher, null if it couldn't be generated.
118      */
119     CipherData getCipherData(boolean generateIfNeeded) {
120         if (mData == null && generateIfNeeded) {
121             // Ideally, this task should have been started way before this.
122             triggerKeyGeneration();
123
124             // Grab the data from the task.
125             CipherData data;
126             try {
127                 data = mDataGenerator.get();
128             } catch (InterruptedException e) {
129                 throw new RuntimeException(e);
130             } catch (ExecutionException e) {
131                 throw new RuntimeException(e);
132             }
133
134             // Only the first thread is allowed to save the data.
135             synchronized (mDataLock) {
136                 if (mData == null) mData = data;
137             }
138         }
139         return mData;
140     }
141
142     /**
143      * Creates a Callable that generates the data required to create a Cipher. This is done on a
144      * background thread to prevent blocking on the I/O required for
145      * {@link ByteArrayGenerator#getBytes(int)}.
146      * @return Callable that generates the Cipher data.
147      */
148     private Callable<CipherData> createGeneratorCallable() {
149         return new Callable<CipherData>() {
150             @Override
151             public CipherData call() {
152                 // Poll random data to generate initialization parameters for the Cipher.
153                 byte[] seed, iv;
154                 try {
155                     seed = mRandomNumberProvider.getBytes(NUM_BYTES);
156                     iv = mRandomNumberProvider.getBytes(NUM_BYTES);
157                 } catch (IOException e) {
158                     Log.e(TAG, "Couldn't get generator data.");
159                     return null;
160                 } catch (GeneralSecurityException e) {
161                     Log.e(TAG, "Couldn't get generator data.");
162                     return null;
163                 }
164
165                 try {
166                     // Old versions of SecureRandom do not seed themselves as securely as possible.
167                     // This workaround should suffice until the fixed version is deployed to all
168                     // users. The seed comes from RandomNumberProvider.getBytes(), which reads
169                     // from /dev/urandom, which is as good as the platform can get.
170                     //
171                     // TODO(palmer): Consider getting rid of this once the updated platform has
172                     // shipped to everyone. Alternately, leave this in as a defense against other
173                     // bugs in SecureRandom.
174                     SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
175                     random.setSeed(seed);
176
177                     KeyGenerator generator = KeyGenerator.getInstance("AES");
178                     generator.init(128, random);
179                     return new CipherData(generator.generateKey(), iv);
180                 } catch (GeneralSecurityException e) {
181                     Log.e(TAG, "Couldn't get generator instances.");
182                     return null;
183                 }
184             }
185         };
186     }
187
188     /**
189      * Generates the encryption key and IV on a background thread (if necessary).
190      * Should be explicitly called when the Activity determines that it will need a Cipher rather
191      * than immediately calling {@link CipherFactory#getCipher(int)}.
192      */
193     public void triggerKeyGeneration() {
194         if (mData != null) return;
195
196         synchronized (mDataLock) {
197             if (mDataGenerator == null) {
198                 mDataGenerator = new FutureTask<CipherData>(createGeneratorCallable());
199                 AsyncTask.THREAD_POOL_EXECUTOR.execute(mDataGenerator);
200             }
201         }
202     }
203
204     /**
205      * Saves the encryption data in a bundle. Expected to be called when an Activity saves its state
206      * before being sent to the background.
207      *
208      * The IV *could* go into the first block of the payload. However, since the staleness of the
209      * data is determined by whether or not it's able to be decrypted, the IV should not be read
210      * from it.
211      *
212      * @param outState The data bundle to store data into.
213      */
214     public void saveToBundle(Bundle outState) {
215         CipherData data = getCipherData(false);
216         if (data == null) return;
217
218         byte[] wrappedKey = data.key.getEncoded();
219         if (wrappedKey != null && data.iv != null) {
220             outState.putByteArray(BUNDLE_KEY, wrappedKey);
221             outState.putByteArray(BUNDLE_IV, data.iv);
222         }
223     }
224
225     /**
226      * Restores the encryption key from the given Bundle. Expected to be called when an Activity is
227      * being restored after being killed in the background. If the Activity was explicitly killed by
228      * the user, Android gives no Bundle (and therefore no key).
229      *
230      * @param savedInstanceState Bundle containing the Activity's previous state. Null if the user
231      *                           explicitly killed the Activity.
232      * @return                   True if the data was restored successfully from the Bundle, or if
233      *                           the CipherData in use matches the Bundle contents.
234      *
235      */
236     public boolean restoreFromBundle(Bundle savedInstanceState) {
237         if (savedInstanceState == null) return false;
238
239         byte[] wrappedKey = savedInstanceState.getByteArray(BUNDLE_KEY);
240         byte[] iv = savedInstanceState.getByteArray(BUNDLE_IV);
241         if (wrappedKey == null || iv == null) return false;
242
243         try {
244             Key bundledKey = new SecretKeySpec(wrappedKey, "AES");
245             synchronized (mDataLock) {
246                 if (mData == null) {
247                     mData = new CipherData(bundledKey, iv);
248                     return true;
249                 } else if (mData.key.equals(bundledKey) && Arrays.equals(mData.iv, iv)) {
250                     return true;
251                 } else {
252                     Log.e(TAG, "Attempted to restore different cipher data.");
253                 }
254             }
255         } catch (IllegalArgumentException e) {
256             Log.e(TAG, "Error in restoring the key from the bundle.");
257         }
258
259         return false;
260     }
261
262     /**
263      * Overrides the random number generated that is normally used by the class.
264      * @param mockProvider Should be used to provide non-random data.
265      */
266     void setRandomNumberProviderForTests(ByteArrayGenerator mockProvider) {
267         mRandomNumberProvider = mockProvider;
268     }
269
270     private CipherFactory() {
271         mRandomNumberProvider = new ByteArrayGenerator();
272     }
273 }