CipherFactory.java revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 5package org.chromium.content.browser.crypto; 6 7import android.os.AsyncTask; 8import android.os.Bundle; 9import android.util.Log; 10 11import java.io.IOException; 12import java.security.GeneralSecurityException; 13import java.security.Key; 14import java.security.SecureRandom; 15import java.util.Arrays; 16import java.util.concurrent.Callable; 17import java.util.concurrent.ExecutionException; 18import java.util.concurrent.FutureTask; 19 20import javax.annotation.concurrent.ThreadSafe; 21import javax.crypto.Cipher; 22import javax.crypto.KeyGenerator; 23import javax.crypto.spec.IvParameterSpec; 24import 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 47public 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}