diff options
author | dfalcantara@chromium.org <dfalcantara@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-04 21:06:13 +0000 |
---|---|---|
committer | dfalcantara@chromium.org <dfalcantara@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-04 21:06:13 +0000 |
commit | 4560459de08cf7b123c6d711af3def9a3ce39856 (patch) | |
tree | 2489663fd75c842a53883347a26fac203b6fcccf | |
parent | 4e91b6c0ee7ef5e9e16fe5c9f4d937372cec101a (diff) | |
download | chromium_src-4560459de08cf7b123c6d711af3def9a3ce39856.zip chromium_src-4560459de08cf7b123c6d711af3def9a3ce39856.tar.gz chromium_src-4560459de08cf7b123c6d711af3def9a3ce39856.tar.bz2 |
Merge 261299 "Upstream Incognito key management"
> Upstream Incognito key management
>
> * Yanks out the Incognito key management from ChromeTab in the downstream repo.
> * Cleans up the code so that intermediary results are not stored if the final
> key couldn't be generated with them.
> * Makes the class more thread-safe to allow multiple Activities to try restoring
> their copies of the Incognito key simultaneously:
> ** If a key doesn't exist yet, the bundle key is saved.
> ** If a key already exists and the bundle key matches, we return success.
> ** Otherwise, the key is out of date -- another Activity generated a
> new Incognito key.
>
> Goes with https://chrome-internal-review.googlesource.com/#/c/158482/
>
> Tested this out manually on the Samsung Note 3 for the following scenarios (I have absolutely no idea how to test this with our current infrastructure):
>
> SCENARIO 1
> 1) Opened two simultaneous Main Activities side-by-side with different incognito tabs -- they use the same Incognito key.
> 2) Opened a WebappActivity as its own window.
> 3) Opened Recents and swiped away the WebappActivity, killing Chrome's process.
> 4) Re-opened one of the Main Activities from Recents, bringing both Activities back side-by-side with their incognito tabs intact.
>
> SCENARIO 2
> 1) Opened two simultaneously Main Activities side-by-side with different incognito tabs -- they use the same Incognito key.
> 2) Opened Recents and swiped away one of the Main Activities, killing Chrome's process.
> 3) Re-opened the remaining Main Activity from Recents, bringing it back with its incognito tabs intact.
> 4) Opened a new Main instance by starting Chrome from scratch. Gets started without any incognito tabs because there's no state and the incognito key was thus lost.
>
> SCENARIO 3
> 1) Opened two simultaneously Main Activities side-by-side with different incognito tabs -- they use the same Incognito key.
> 2) Opened Recents and swiped away one of the Main Activities, killing Chrome's process.
> 3) Opened a new Main instance by starting Chrome from scratch. Gets started without any incognito tabs because there's no state and the incognito key was thus lost.
> 4) Re-opened the other Main Activity from Recents, bringing it back with its incognito tabs intact because no new Incognito key was generated yet.
>
> SCENARIO 4
> 1) Opened two simultaneously Main Activities side-by-side with different incognito tabs -- they use the same Incognito key.
> 2) Opened Recents and swiped away one of the Main Activities, killing Chrome's process.
> 3) Opened a new Main instance by starting Chrome from scratch. Gets started without any incognito tabs because there's no state and the incognito key was thus lost.
> 4) Opened a new Incognito tab in this new Main instance, forcing a new Incognito key to be generated.
> 5) Re-opened the other Main Activity from Recents, losing its incognito tabs because the incognito key is now different.
>
> Added functional unit tests for this class, but an instrumentation test for Chrome on Android proper seems out of bounds.
>
> BUG=346737
> TEST=CipherFactoryTest
>
> Review URL: https://codereview.chromium.org/214273004
TBR=dfalcantara@chromium.org
Review URL: https://codereview.chromium.org/226723002
git-svn-id: svn://svn.chromium.org/chrome/branches/1916/src@261869 0039d316-1c4b-4281-b951-d872f2087c98
5 files changed, 540 insertions, 0 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/crypto/ByteArrayGenerator.java b/content/public/android/java/src/org/chromium/content/browser/crypto/ByteArrayGenerator.java new file mode 100644 index 0000000..a7879c7 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/crypto/ByteArrayGenerator.java @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.crypto; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * Generates byte arrays for use in crypto algorithms. Defaults to pulling random data + * from /dev/urandom, but can be overwritten for other generation methods. + */ +public class ByteArrayGenerator { + /** + * Polls random data to generate the array. + * @param numBytes Length of the array to generate. + * @return byte[] containing randomly generated data. + */ + public byte[] getBytes(int numBytes) throws IOException, GeneralSecurityException { + FileInputStream fis = null; + try { + fis = new FileInputStream("/dev/urandom"); + byte[] bytes = new byte[numBytes]; + if (bytes.length != fis.read(bytes)) { + throw new GeneralSecurityException("Not enough random data available"); + } + return bytes; + } finally { + if (fis != null) { + fis.close(); + } + } + } +} diff --git a/content/public/android/java/src/org/chromium/content/browser/crypto/CipherFactory.java b/content/public/android/java/src/org/chromium/content/browser/crypto/CipherFactory.java new file mode 100644 index 0000000..b6ff368 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/crypto/CipherFactory.java @@ -0,0 +1,269 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.crypto; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +import javax.annotation.concurrent.ThreadSafe; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Generates {@link Cipher} instances for encrypting session data that is temporarily stored. + * + * When an Activity is sent to the background, Android gives it the opportunity to save state to + * restore a user's session when the Activity is restarted. In addition to saving state to disk, + * Android has a mechanism for saving instance state through {@link Bundle}s, which help + * differentiate between users pausing and ending a session: + * - If the Activity is killed in the background (e.g. to free up resources for other Activities), + * Android gives a {@link Bundle} to the Activity when the user restarts the Activity. The + * {@link Bundle} is expected to be small and fast to generate, and is managed by Android. + * - If the Activity was explicitly killed (e.g. the user swiped away the task from Recent Tasks), + * Android does not restore the {@link Bundle} when the user restarts the Activity. + * + * To securely save temporary session data to disk: + * - Encrypt data with a {@link Cipher} from {@link CipherFactory#getCipher(int)} before storing it. + * - Store {@link Cipher} parameters in the Bundle via {@link CipherFactory#saveToBundle(Bundle)}. + * + * Explicitly ending the session destroys the {@link Bundle}, making the previous session's data + * unreadable. + */ +@ThreadSafe +public class CipherFactory { + private static final String TAG = "CipherFactory"; + static final int NUM_BYTES = 16; + + static final String BUNDLE_IV = "org.chromium.content.browser.crypto.CipherFactory.IV"; + static final String BUNDLE_KEY = "org.chromium.content.browser.crypto.CipherFactory.KEY"; + + /** Holds intermediate data for the computation. */ + private static class CipherData { + public final Key key; + public final byte[] iv; + + public CipherData(Key key, byte[] iv) { + this.key = key; + this.iv = iv; + } + } + + /** Singleton holder for the class. */ + private static class LazyHolder { + private static CipherFactory sInstance = new CipherFactory(); + } + + /** + * Synchronization primitive to prevent thrashing the cipher parameters between threads + * attempting to restore previous parameters and generate new ones. + */ + private final Object mDataLock = new Object(); + + /** Used to generate data needed for the Cipher on a background thread. */ + private FutureTask<CipherData> mDataGenerator; + + /** Holds data for cipher generation. */ + private CipherData mData; + + /** Generates random data for the Ciphers. May be swapped out for tests. */ + private ByteArrayGenerator mRandomNumberProvider; + + /** @return The Singleton instance. Creates it if it doesn't exist. */ + public static CipherFactory getInstance() { + return LazyHolder.sInstance; + } + + /** + * Creates a secure Cipher for encrypting data. + * This function blocks until data needed to generate a Cipher has been created by the + * background thread. + * @param opmode One of Cipher.{ENCRYPT,DECRYPT}_MODE. + * @return A Cipher, or null if it is not possible to instantiate one. + */ + public Cipher getCipher(int opmode) { + CipherData data = getCipherData(true); + + if (data != null) { + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(opmode, data.key, new IvParameterSpec(data.iv)); + return cipher; + } catch (GeneralSecurityException e) { + // Can't do anything here. + } + } + + Log.e(TAG, "Error in creating cipher instance."); + return null; + } + + /** + * Returns data required for generating the Cipher. + * @param generateIfNeeded Generates data on the background thread, blocking until it is done. + * @return Data to use for the Cipher, null if it couldn't be generated. + */ + CipherData getCipherData(boolean generateIfNeeded) { + if (mData == null && generateIfNeeded) { + // Ideally, this task should have been started way before this. + triggerKeyGeneration(); + + // Grab the data from the task. + CipherData data; + try { + data = mDataGenerator.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + + // Only the first thread is allowed to save the data. + synchronized (mDataLock) { + if (mData == null) mData = data; + } + } + return mData; + } + + /** + * Creates a Callable that generates the data required to create a Cipher. This is done on a + * background thread to prevent blocking on the I/O required for + * {@link ByteArrayGenerator#getBytes(int)}. + * @return Callable that generates the Cipher data. + */ + private Callable<CipherData> createGeneratorCallable() { + return new Callable<CipherData>() { + @Override + public CipherData call() { + // Poll random data to generate initialization parameters for the Cipher. + byte[] seed, iv; + try { + seed = mRandomNumberProvider.getBytes(NUM_BYTES); + iv = mRandomNumberProvider.getBytes(NUM_BYTES); + } catch (Exception e) { + Log.e(TAG, "Couldn't get generator data."); + return null; + } + + try { + // Old versions of SecureRandom do not seed themselves as securely as possible. + // This workaround should suffice until the fixed version is deployed to all + // users. The seed comes from RandomNumberProvider.getBytes(), which reads + // from /dev/urandom, which is as good as the platform can get. + // + // TODO(palmer): Consider getting rid of this once the updated platform has + // shipped to everyone. Alternately, leave this in as a defense against other + // bugs in SecureRandom. + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(seed); + + KeyGenerator generator = KeyGenerator.getInstance("AES"); + generator.init(128, random); + return new CipherData(generator.generateKey(), iv); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Couldn't get generator instances."); + return null; + } + } + }; + } + + /** + * Generates the encryption key and IV on a background thread (if necessary). + * Should be explicitly called when the Activity determines that it will need a Cipher rather + * than immediately calling {@link CipherFactory#getCipher(int)}. + */ + public void triggerKeyGeneration() { + if (mData != null) return; + + synchronized (mDataLock) { + if (mDataGenerator == null) { + mDataGenerator = new FutureTask<CipherData>(createGeneratorCallable()); + AsyncTask.THREAD_POOL_EXECUTOR.execute(mDataGenerator); + } + } + } + + /** + * Saves the encryption data in a bundle. Expected to be called when an Activity saves its state + * before being sent to the background. + * + * The IV *could* go into the first block of the payload. However, since the staleness of the + * data is determined by whether or not it's able to be decrypted, the IV should not be read + * from it. + * + * @param outState The data bundle to store data into. + */ + public void saveToBundle(Bundle outState) { + CipherData data = getCipherData(false); + if (data == null) return; + + byte[] wrappedKey = data.key.getEncoded(); + if (wrappedKey != null && data.iv != null) { + outState.putByteArray(BUNDLE_KEY, wrappedKey); + outState.putByteArray(BUNDLE_IV, data.iv); + } + } + + /** + * Restores the encryption key from the given Bundle. Expected to be called when an Activity is + * being restored after being killed in the background. If the Activity was explicitly killed by + * the user, Android gives no Bundle (and therefore no key). + * + * @param savedInstanceState Bundle containing the Activity's previous state. Null if the user + * explicitly killed the Activity. + * @return True if the data was restored successfully from the Bundle, or if + * the CipherData in use matches the Bundle contents. + * + */ + public boolean restoreFromBundle(Bundle savedInstanceState) { + if (savedInstanceState == null) return false; + + byte[] wrappedKey = savedInstanceState.getByteArray(BUNDLE_KEY); + byte[] iv = savedInstanceState.getByteArray(BUNDLE_IV); + if (wrappedKey == null || iv == null) return false; + + try { + Key bundledKey = new SecretKeySpec(wrappedKey, "AES"); + synchronized (mDataLock) { + if (mData == null) { + mData = new CipherData(bundledKey, iv); + return true; + } else if (mData.key.equals(bundledKey) && Arrays.equals(mData.iv, iv)) { + return true; + } else { + Log.e(TAG, "Attempted to restore different cipher data."); + } + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error in restoring the key from the bundle."); + } + + return false; + } + + /** + * Overrides the random number generated that is normally used by the class. + * @param mockProvider Should be used to provide non-random data. + */ + void setRandomNumberProviderForTests(ByteArrayGenerator mockProvider) { + mRandomNumberProvider = mockProvider; + } + + private CipherFactory() { + mRandomNumberProvider = new ByteArrayGenerator(); + } +}
\ No newline at end of file diff --git a/content/public/android/java/src/org/chromium/content/browser/crypto/OWNERS b/content/public/android/java/src/org/chromium/content/browser/crypto/OWNERS new file mode 100644 index 0000000..fce4c0f --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/crypto/OWNERS @@ -0,0 +1,3 @@ +dfalcantara@chromium.org +palmer@chromium.org + diff --git a/content/public/android/javatests/src/org/chromium/content/browser/crypto/CipherFactoryTest.java b/content/public/android/javatests/src/org/chromium/content/browser/crypto/CipherFactoryTest.java new file mode 100644 index 0000000..7f7cfc7 --- /dev/null +++ b/content/public/android/javatests/src/org/chromium/content/browser/crypto/CipherFactoryTest.java @@ -0,0 +1,229 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.crypto; + +import android.os.Bundle; +import android.test.InstrumentationTestCase; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import javax.crypto.Cipher; + +/** + * Functional tests for the {@link CipherFactory}. Confirms that saving and restoring data works, as + * well as that {@link Cipher} instances properly encrypt and decrypt data. + * + * Tests that confirm that the class is thread-safe would require putting potentially flaky hooks + * throughout the class to simulate artificial blockages. + */ +public class CipherFactoryTest extends InstrumentationTestCase { + private static final byte[] INPUT_DATA = {1, 16, 84}; + + /** Generates non-random byte[] for testing. */ + private static class DeterministicParameterGenerator extends ByteArrayGenerator { + @Override + public byte[] getBytes(int numBytes) throws IOException, GeneralSecurityException { + return getBytes(numBytes, (byte) 0); + } + + /** + * Generates a linearly-increasing byte[] sequence that wraps around after 0xFF. + * @param numBytes Length of the byte[] to create. + * @param startByte Byte to start at. + * @return The completed byte[]. + */ + public byte[] getBytes(int numBytes, byte startByte) { + byte[] bytes = new byte[numBytes]; + for (int i = 0; i < numBytes; ++i) { + bytes[i] = (byte) (startByte + i); + } + return bytes; + } + } + private DeterministicParameterGenerator mNumberProvider; + + /** + * Overrides the {@link ByteArrayGenerator} used by the {@link CipherFactory} to ensure + * deterministic results. + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mNumberProvider = new DeterministicParameterGenerator(); + CipherFactory.getInstance().setRandomNumberProviderForTests(mNumberProvider); + } + + /** + * {@link Cipher} instances initialized using the same parameters work in exactly the same way. + */ + public void testCipherUse() throws Exception { + // Check encryption. + Cipher aEncrypt = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + Cipher bEncrypt = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + byte[] output = sameOutputDifferentCiphers(INPUT_DATA, aEncrypt, bEncrypt); + + // Check decryption. + Cipher aDecrypt = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE); + Cipher bDecrypt = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE); + byte[] decrypted = sameOutputDifferentCiphers(output, aDecrypt, bDecrypt); + assertTrue(Arrays.equals(decrypted, INPUT_DATA)); + } + + /** + * Restoring a {@link Bundle} containing the same parameters already in use by the + * {@link CipherFactory} should keep the same keys. + */ + public void testSameBundleRestoration() throws Exception { + // Create two bundles with the same saved state. + Bundle aBundle = new Bundle(); + Bundle bBundle = new Bundle(); + + byte[] sameIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100); + aBundle.putByteArray(CipherFactory.BUNDLE_IV, sameIv); + bBundle.putByteArray(CipherFactory.BUNDLE_IV, sameIv); + + byte[] sameKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 200); + aBundle.putByteArray(CipherFactory.BUNDLE_KEY, sameKey); + bBundle.putByteArray(CipherFactory.BUNDLE_KEY, sameKey); + + // Restore using the first bundle, then the second. Both should succeed. + assertTrue(CipherFactory.getInstance().restoreFromBundle(aBundle)); + Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + assertTrue(CipherFactory.getInstance().restoreFromBundle(bBundle)); + Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + + // Make sure the CipherFactory instances are using the same key. + sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher); + } + + /** + * Restoring a {@link Bundle} containing a different set of parameters from those already in use + * by the {@link CipherFactory} should fail. Any Ciphers created after the failed restoration + * attempt should use the already-existing keys. + */ + public void testDifferentBundleRestoration() throws Exception { + // Restore one set of parameters. + Bundle aBundle = new Bundle(); + byte[] aIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50); + byte[] aKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100); + aBundle.putByteArray(CipherFactory.BUNDLE_IV, aIv); + aBundle.putByteArray(CipherFactory.BUNDLE_KEY, aKey); + assertTrue(CipherFactory.getInstance().restoreFromBundle(aBundle)); + Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + + // Restore using a different set of parameters. + Bundle bBundle = new Bundle(); + byte[] bIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 150); + byte[] bKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 200); + bBundle.putByteArray(CipherFactory.BUNDLE_IV, bIv); + bBundle.putByteArray(CipherFactory.BUNDLE_KEY, bKey); + assertFalse(CipherFactory.getInstance().restoreFromBundle(bBundle)); + Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + + // Make sure they're using the same (original) key by encrypting the same data. + sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher); + } + + /** + * Restoration from a {@link Bundle} missing data should fail. + */ + public void testIncompleteBundleRestoration() throws Exception { + // Make sure we handle the null case. + assertFalse(CipherFactory.getInstance().restoreFromBundle(null)); + + // Try restoring without the key. + Bundle aBundle = new Bundle(); + byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50); + aBundle.putByteArray(CipherFactory.BUNDLE_IV, iv); + assertFalse(CipherFactory.getInstance().restoreFromBundle(aBundle)); + + // Try restoring without the initialization vector. + Bundle bBundle = new Bundle(); + byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100); + bBundle.putByteArray(CipherFactory.BUNDLE_KEY, key); + assertFalse(CipherFactory.getInstance().restoreFromBundle(bBundle)); + } + + /** + * Parameters should only be saved when they're needed by the {@link CipherFactory}. Restoring + * parameters from a {@link Bundle} before this point should result in {@link Cipher}s using the + * restored parameters instead of any generated ones. + */ + public void testRestorationSucceedsBeforeCipherCreated() throws Exception { + byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50); + byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100); + Bundle bundle = new Bundle(); + bundle.putByteArray(CipherFactory.BUNDLE_IV, iv); + bundle.putByteArray(CipherFactory.BUNDLE_KEY, key); + + // The keys should be initialized only after restoration. + assertNull(CipherFactory.getInstance().getCipherData(false)); + assertTrue(CipherFactory.getInstance().restoreFromBundle(bundle)); + assertNotNull(CipherFactory.getInstance().getCipherData(false)); + } + + /** + * If the {@link CipherFactory} has already generated parameters, restorations of different data + * should fail. All {@link Cipher}s should use the generated parameters. + */ + public void testRestorationDiscardsAfterOtherCipherAlreadyCreated() throws Exception { + byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50); + byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100); + Bundle bundle = new Bundle(); + bundle.putByteArray(CipherFactory.BUNDLE_IV, iv); + bundle.putByteArray(CipherFactory.BUNDLE_KEY, key); + + // The keys should be initialized after creating the cipher, so the keys shouldn't match. + Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + assertFalse(CipherFactory.getInstance().restoreFromBundle(bundle)); + Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + + // B's cipher should use the keys generated for A. + sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher); + } + + /** + * Data saved out to the {@link Bundle} should match what is held by the {@link CipherFactory}. + */ + public void testSavingToBundle() throws Exception { + // Nothing should get saved out before Cipher data exists. + Bundle initialBundle = new Bundle(); + CipherFactory.getInstance().saveToBundle(initialBundle); + assertFalse(initialBundle.containsKey(CipherFactory.BUNDLE_IV)); + assertFalse(initialBundle.containsKey(CipherFactory.BUNDLE_KEY)); + + // Check that Cipher data gets saved if it exists. + CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE); + Bundle afterBundle = new Bundle(); + CipherFactory.getInstance().saveToBundle(afterBundle); + assertTrue(afterBundle.containsKey(CipherFactory.BUNDLE_IV)); + assertTrue(afterBundle.containsKey(CipherFactory.BUNDLE_KEY)); + + // Confirm the saved keys match by restoring it. + assertTrue(CipherFactory.getInstance().restoreFromBundle(afterBundle)); + } + + /** + * Confirm that the two {@link Cipher}s are functionally equivalent. + * @return The input after it has been operated on (e.g. decrypted or encrypted). + */ + private byte[] sameOutputDifferentCiphers(byte[] input, Cipher aCipher, Cipher bCipher) + throws Exception { + assertNotNull(aCipher); + assertNotNull(bCipher); + assertNotSame(aCipher, bCipher); + + byte[] aOutput = aCipher.doFinal(input); + byte[] bOutput = bCipher.doFinal(input); + + assertNotNull(aOutput); + assertNotNull(bOutput); + assertTrue(Arrays.equals(aOutput, bOutput)); + + return aOutput; + } +} diff --git a/content/public/android/javatests/src/org/chromium/content/browser/crypto/OWNERS b/content/public/android/javatests/src/org/chromium/content/browser/crypto/OWNERS new file mode 100644 index 0000000..fce4c0f --- /dev/null +++ b/content/public/android/javatests/src/org/chromium/content/browser/crypto/OWNERS @@ -0,0 +1,3 @@ +dfalcantara@chromium.org +palmer@chromium.org + |