Home | History | Annotate | Download | only in crypto
      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.Bundle;
      8 import android.test.InstrumentationTestCase;
      9 
     10 import java.io.IOException;
     11 import java.security.GeneralSecurityException;
     12 import java.util.Arrays;
     13 
     14 import javax.crypto.Cipher;
     15 
     16 /**
     17  * Functional tests for the {@link CipherFactory}. Confirms that saving and restoring data works, as
     18  * well as that {@link Cipher} instances properly encrypt and decrypt data.
     19  *
     20  * Tests that confirm that the class is thread-safe would require putting potentially flaky hooks
     21  * throughout the class to simulate artificial blockages.
     22  */
     23 public class CipherFactoryTest extends InstrumentationTestCase {
     24     private static final byte[] INPUT_DATA = {1, 16, 84};
     25 
     26     /** Generates non-random byte[] for testing. */
     27     private static class DeterministicParameterGenerator extends ByteArrayGenerator {
     28         @Override
     29         public byte[] getBytes(int numBytes) throws IOException, GeneralSecurityException {
     30             return getBytes(numBytes, (byte) 0);
     31         }
     32 
     33         /**
     34          * Generates a linearly-increasing byte[] sequence that wraps around after 0xFF.
     35          * @param numBytes Length of the byte[] to create.
     36          * @param startByte Byte to start at.
     37          * @return The completed byte[].
     38          */
     39         public byte[] getBytes(int numBytes, byte startByte) {
     40             byte[] bytes = new byte[numBytes];
     41             for (int i = 0; i < numBytes; ++i) {
     42                 bytes[i] = (byte) (startByte + i);
     43             }
     44             return bytes;
     45         }
     46     }
     47     private DeterministicParameterGenerator mNumberProvider;
     48 
     49     /**
     50      * Overrides the {@link ByteArrayGenerator} used by the {@link CipherFactory} to ensure
     51      * deterministic results.
     52      */
     53     @Override
     54     protected void setUp() throws Exception {
     55         super.setUp();
     56         mNumberProvider = new DeterministicParameterGenerator();
     57         CipherFactory.getInstance().setRandomNumberProviderForTests(mNumberProvider);
     58     }
     59 
     60     /**
     61      * {@link Cipher} instances initialized using the same parameters work in exactly the same way.
     62      */
     63     public void testCipherUse() throws Exception {
     64         // Check encryption.
     65         Cipher aEncrypt = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
     66         Cipher bEncrypt = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
     67         byte[] output = sameOutputDifferentCiphers(INPUT_DATA, aEncrypt, bEncrypt);
     68 
     69         // Check decryption.
     70         Cipher aDecrypt = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
     71         Cipher bDecrypt = CipherFactory.getInstance().getCipher(Cipher.DECRYPT_MODE);
     72         byte[] decrypted = sameOutputDifferentCiphers(output, aDecrypt, bDecrypt);
     73         assertTrue(Arrays.equals(decrypted, INPUT_DATA));
     74     }
     75 
     76     /**
     77      * Restoring a {@link Bundle} containing the same parameters already in use by the
     78      * {@link CipherFactory} should keep the same keys.
     79      */
     80     public void testSameBundleRestoration() throws Exception {
     81         // Create two bundles with the same saved state.
     82         Bundle aBundle = new Bundle();
     83         Bundle bBundle = new Bundle();
     84 
     85         byte[] sameIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100);
     86         aBundle.putByteArray(CipherFactory.BUNDLE_IV, sameIv);
     87         bBundle.putByteArray(CipherFactory.BUNDLE_IV, sameIv);
     88 
     89         byte[] sameKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 200);
     90         aBundle.putByteArray(CipherFactory.BUNDLE_KEY, sameKey);
     91         bBundle.putByteArray(CipherFactory.BUNDLE_KEY, sameKey);
     92 
     93         // Restore using the first bundle, then the second. Both should succeed.
     94         assertTrue(CipherFactory.getInstance().restoreFromBundle(aBundle));
     95         Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
     96         assertTrue(CipherFactory.getInstance().restoreFromBundle(bBundle));
     97         Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
     98 
     99         // Make sure the CipherFactory instances are using the same key.
    100         sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher);
    101     }
    102 
    103     /**
    104      * Restoring a {@link Bundle} containing a different set of parameters from those already in use
    105      * by the {@link CipherFactory} should fail. Any Ciphers created after the failed restoration
    106      * attempt should use the already-existing keys.
    107      */
    108     public void testDifferentBundleRestoration() throws Exception {
    109         // Restore one set of parameters.
    110         Bundle aBundle = new Bundle();
    111         byte[] aIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50);
    112         byte[] aKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100);
    113         aBundle.putByteArray(CipherFactory.BUNDLE_IV, aIv);
    114         aBundle.putByteArray(CipherFactory.BUNDLE_KEY, aKey);
    115         assertTrue(CipherFactory.getInstance().restoreFromBundle(aBundle));
    116         Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
    117 
    118         // Restore using a different set of parameters.
    119         Bundle bBundle = new Bundle();
    120         byte[] bIv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 150);
    121         byte[] bKey = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 200);
    122         bBundle.putByteArray(CipherFactory.BUNDLE_IV, bIv);
    123         bBundle.putByteArray(CipherFactory.BUNDLE_KEY, bKey);
    124         assertFalse(CipherFactory.getInstance().restoreFromBundle(bBundle));
    125         Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
    126 
    127         // Make sure they're using the same (original) key by encrypting the same data.
    128         sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher);
    129     }
    130 
    131     /**
    132      * Restoration from a {@link Bundle} missing data should fail.
    133      */
    134     public void testIncompleteBundleRestoration() throws Exception {
    135         // Make sure we handle the null case.
    136         assertFalse(CipherFactory.getInstance().restoreFromBundle(null));
    137 
    138         // Try restoring without the key.
    139         Bundle aBundle = new Bundle();
    140         byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50);
    141         aBundle.putByteArray(CipherFactory.BUNDLE_IV, iv);
    142         assertFalse(CipherFactory.getInstance().restoreFromBundle(aBundle));
    143 
    144         // Try restoring without the initialization vector.
    145         Bundle bBundle = new Bundle();
    146         byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100);
    147         bBundle.putByteArray(CipherFactory.BUNDLE_KEY, key);
    148         assertFalse(CipherFactory.getInstance().restoreFromBundle(bBundle));
    149     }
    150 
    151     /**
    152      * Parameters should only be saved when they're needed by the {@link CipherFactory}. Restoring
    153      * parameters from a {@link Bundle} before this point should result in {@link Cipher}s using the
    154      * restored parameters instead of any generated ones.
    155      */
    156     public void testRestorationSucceedsBeforeCipherCreated() throws Exception {
    157         byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50);
    158         byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100);
    159         Bundle bundle = new Bundle();
    160         bundle.putByteArray(CipherFactory.BUNDLE_IV, iv);
    161         bundle.putByteArray(CipherFactory.BUNDLE_KEY, key);
    162 
    163         // The keys should be initialized only after restoration.
    164         assertNull(CipherFactory.getInstance().getCipherData(false));
    165         assertTrue(CipherFactory.getInstance().restoreFromBundle(bundle));
    166         assertNotNull(CipherFactory.getInstance().getCipherData(false));
    167     }
    168 
    169     /**
    170      * If the {@link CipherFactory} has already generated parameters, restorations of different data
    171      * should fail. All {@link Cipher}s should use the generated parameters.
    172      */
    173     public void testRestorationDiscardsAfterOtherCipherAlreadyCreated() throws Exception {
    174         byte[] iv = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 50);
    175         byte[] key = mNumberProvider.getBytes(CipherFactory.NUM_BYTES, (byte) 100);
    176         Bundle bundle = new Bundle();
    177         bundle.putByteArray(CipherFactory.BUNDLE_IV, iv);
    178         bundle.putByteArray(CipherFactory.BUNDLE_KEY, key);
    179 
    180         // The keys should be initialized after creating the cipher, so the keys shouldn't match.
    181         Cipher aCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
    182         assertFalse(CipherFactory.getInstance().restoreFromBundle(bundle));
    183         Cipher bCipher = CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
    184 
    185         // B's cipher should use the keys generated for A.
    186         sameOutputDifferentCiphers(INPUT_DATA, aCipher, bCipher);
    187     }
    188 
    189     /**
    190      * Data saved out to the {@link Bundle} should match what is held by the {@link CipherFactory}.
    191      */
    192     public void testSavingToBundle() throws Exception {
    193         // Nothing should get saved out before Cipher data exists.
    194         Bundle initialBundle = new Bundle();
    195         CipherFactory.getInstance().saveToBundle(initialBundle);
    196         assertFalse(initialBundle.containsKey(CipherFactory.BUNDLE_IV));
    197         assertFalse(initialBundle.containsKey(CipherFactory.BUNDLE_KEY));
    198 
    199         // Check that Cipher data gets saved if it exists.
    200         CipherFactory.getInstance().getCipher(Cipher.ENCRYPT_MODE);
    201         Bundle afterBundle = new Bundle();
    202         CipherFactory.getInstance().saveToBundle(afterBundle);
    203         assertTrue(afterBundle.containsKey(CipherFactory.BUNDLE_IV));
    204         assertTrue(afterBundle.containsKey(CipherFactory.BUNDLE_KEY));
    205 
    206         // Confirm the saved keys match by restoring it.
    207         assertTrue(CipherFactory.getInstance().restoreFromBundle(afterBundle));
    208     }
    209 
    210     /**
    211      * Confirm that the two {@link Cipher}s are functionally equivalent.
    212      * @return The input after it has been operated on (e.g. decrypted or encrypted).
    213      */
    214     private byte[] sameOutputDifferentCiphers(byte[] input, Cipher aCipher, Cipher bCipher)
    215             throws Exception {
    216         assertNotNull(aCipher);
    217         assertNotNull(bCipher);
    218         assertNotSame(aCipher, bCipher);
    219 
    220         byte[] aOutput = aCipher.doFinal(input);
    221         byte[] bOutput = bCipher.doFinal(input);
    222 
    223         assertNotNull(aOutput);
    224         assertNotNull(bOutput);
    225         assertTrue(Arrays.equals(aOutput, bOutput));
    226 
    227         return aOutput;
    228     }
    229 }
    230