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