diff options
-rw-r--r-- | luni/src/test/java/libcore/javax/crypto/CipherTest.java | 607 | ||||
-rw-r--r-- | support/src/test/java/libcore/java/security/StandardNames.java | 40 |
2 files changed, 591 insertions, 56 deletions
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java index d31825a..76a4176 100644 --- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java +++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java @@ -17,37 +17,60 @@ package libcore.javax.crypto; import com.android.org.bouncycastle.asn1.x509.KeyUsage; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; -import java.security.InvalidParameterException; import java.security.Key; import java.security.KeyFactory; import java.security.PrivateKey; -import java.security.Provider.Service; import java.security.Provider; import java.security.PublicKey; +import java.security.SecureRandom; import java.security.Security; import java.security.cert.Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.AlgorithmParameterSpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; + import junit.framework.TestCase; +import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; public final class CipherTest extends TestCase { private static boolean isUnsupported(String algorithm) { + if (algorithm.equals("RC2")) { + return true; + } if (algorithm.equals("PBEWITHMD5ANDRC2")) { return true; } - if (algorithm.equals("PBEWITHSHA1ANDRC2")) { + if (algorithm.startsWith("PBEWITHSHA1ANDRC2")) { return true; } if (algorithm.equals("PBEWITHSHAAND40BITRC2-CBC")) { @@ -76,10 +99,13 @@ public final class CipherTest extends TestCase { return Cipher.DECRYPT_MODE; } - private static String getBaseAlgoritm(String algorithm) { + private static String getBaseAlgorithm(String algorithm) { if (algorithm.equals("AESWRAP")) { return "AES"; } + if (algorithm.startsWith("AES/")) { + return "AES"; + } if (algorithm.equals("PBEWITHMD5AND128BITAES-CBC-OPENSSL")) { return "AES"; } @@ -114,18 +140,24 @@ public final class CipherTest extends TestCase { return "DES"; } if (algorithm.equals("DESEDEWRAP")) { - return "DESede"; + return "DESEDE"; } if (algorithm.equals("PBEWITHSHAAND2-KEYTRIPLEDES-CBC")) { - return "DESede"; + return "DESEDE"; } if (algorithm.equals("PBEWITHSHAAND3-KEYTRIPLEDES-CBC")) { - return "DESede"; + return "DESEDE"; + } + if (algorithm.equals("PBEWITHMD5ANDTRIPLEDES")) { + return "DESEDE"; } - if (algorithm.equals("RSA/ECB/NoPadding")) { + if (algorithm.equals("PBEWITHSHA1ANDDESEDE")) { + return "DESEDE"; + } + if (algorithm.equals("RSA/ECB/NOPADDING")) { return "RSA"; } - if (algorithm.equals("RSA/ECB/PKCS1Padding")) { + if (algorithm.equals("RSA/ECB/PKCS1PADDING")) { return "RSA"; } if (algorithm.equals("PBEWITHSHAAND40BITRC4")) { @@ -138,27 +170,33 @@ public final class CipherTest extends TestCase { } private static boolean isAsymmetric(String algorithm) { - return getBaseAlgoritm(algorithm).equals("RSA"); + return getBaseAlgorithm(algorithm).equals("RSA"); } private static boolean isWrap(String algorithm) { return algorithm.endsWith("WRAP"); } + private static boolean isPBE(String algorithm) { + return algorithm.startsWith("PBE"); + } + private static Map<String, Key> ENCRYPT_KEYS = new HashMap<String, Key>(); private synchronized static Key getEncryptKey(String algorithm) throws Exception { Key key = ENCRYPT_KEYS.get(algorithm); if (key != null) { return key; } - algorithm = getBaseAlgoritm(algorithm); - if (algorithm.equals("RSA")) { + if (algorithm.startsWith("RSA")) { KeyFactory kf = KeyFactory.getInstance("RSA"); RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(RSA_2048_modulus, RSA_2048_privateExponent); key = kf.generatePrivate(keySpec); + } else if (isPBE(algorithm)) { + SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm); + key = skf.generateSecret(new PBEKeySpec("secret".toCharArray())); } else { - KeyGenerator kg = KeyGenerator.getInstance(algorithm); + KeyGenerator kg = KeyGenerator.getInstance(getBaseAlgorithm(algorithm)); key = kg.generateKey(); } ENCRYPT_KEYS.put(algorithm, key); @@ -171,8 +209,7 @@ public final class CipherTest extends TestCase { if (key != null) { return key; } - algorithm = getBaseAlgoritm(algorithm); - if (algorithm.equals("RSA")) { + if (algorithm.startsWith("RSA")) { KeyFactory kf = KeyFactory.getInstance("RSA"); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(RSA_2048_modulus, RSA_2048_publicExponent); @@ -198,9 +235,15 @@ public final class CipherTest extends TestCase { EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND192BITAES-CBC-BC", 16); EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND256BITAES-CBC-BC", 16); - EXPECTED_BLOCK_SIZE.put("AESWRAP", 0); + if (StandardNames.IS_RI) { + EXPECTED_BLOCK_SIZE.put("AESWRAP", 16); + } else { + EXPECTED_BLOCK_SIZE.put("AESWRAP", 0); + } EXPECTED_BLOCK_SIZE.put("ARC4", 0); + EXPECTED_BLOCK_SIZE.put("ARCFOUR", 0); + EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND40BITRC4", 0); EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND128BITRC4", 0); @@ -208,24 +251,51 @@ public final class CipherTest extends TestCase { EXPECTED_BLOCK_SIZE.put("DES", 8); EXPECTED_BLOCK_SIZE.put("PBEWITHMD5ANDDES", 8); + EXPECTED_BLOCK_SIZE.put("PBEWITHMD5ANDTRIPLEDES", 8); EXPECTED_BLOCK_SIZE.put("PBEWITHSHA1ANDDES", 8); + EXPECTED_BLOCK_SIZE.put("PBEWITHSHA1ANDDESEDE", 8); EXPECTED_BLOCK_SIZE.put("DESEDE", 8); EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8); EXPECTED_BLOCK_SIZE.put("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8); - EXPECTED_BLOCK_SIZE.put("DESEDEWRAP", 0); + if (StandardNames.IS_RI) { + EXPECTED_BLOCK_SIZE.put("DESEDEWRAP", 8); + } else { + EXPECTED_BLOCK_SIZE.put("DESEDEWRAP", 0); + } - EXPECTED_BLOCK_SIZE.put("RSA", 255); - EXPECTED_BLOCK_SIZE.put("RSA/ECB/NoPadding", 0); - EXPECTED_BLOCK_SIZE.put("RSA/ECB/PKCS1Padding", 0); + EXPECTED_BLOCK_SIZE.put("RSA", 0); } - private static int getExpectedBlockSize(String algorithm) { + + private static int getExpectedBlockSize(String algorithm, Key key) { + final int firstSlash = algorithm.indexOf('/'); + if (firstSlash != -1) { + algorithm = algorithm.substring(0, firstSlash); + } + + if (!StandardNames.IS_RI && "RSA".equals(getBaseAlgorithm(algorithm))) { + // Must be one less than the modulus size to make sure it fits. + return getRSAModulusSize(key) - 1; + } + Integer expected = EXPECTED_BLOCK_SIZE.get(algorithm); assertNotNull(algorithm, expected); return expected; } + private static int getRSAModulusSize(Key key) { + if (key instanceof RSAPrivateKey) { + RSAPrivateKey rsaKey = (RSAPrivateKey) key; + return rsaKey.getModulus().bitLength() / 8; + } else if (key instanceof RSAPublicKey) { + RSAPublicKey rsaKey = (RSAPublicKey) key; + return rsaKey.getModulus().bitLength() / 8; + } else { + throw new RuntimeException("Unknown RSA key type: " + key.getClass().getName()); + } + } + private static Map<String, Integer> EXPECTED_OUTPUT_SIZE = new HashMap<String, Integer>(); static { EXPECTED_OUTPUT_SIZE.put("AES", 16); @@ -239,9 +309,15 @@ public final class CipherTest extends TestCase { EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND192BITAES-CBC-BC", 16); EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND256BITAES-CBC-BC", 16); - EXPECTED_OUTPUT_SIZE.put("AESWRAP", -1); + if (StandardNames.IS_RI) { + EXPECTED_OUTPUT_SIZE.put("AESWRAP", 8); + } else { + EXPECTED_OUTPUT_SIZE.put("AESWRAP", -1); + } EXPECTED_OUTPUT_SIZE.put("ARC4", 0); + EXPECTED_OUTPUT_SIZE.put("ARCFOUR", 0); + EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND40BITRC4", 0); EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND128BITRC4", 0); @@ -249,19 +325,31 @@ public final class CipherTest extends TestCase { EXPECTED_OUTPUT_SIZE.put("DES", 8); EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5ANDDES", 8); + EXPECTED_OUTPUT_SIZE.put("PBEWITHMD5ANDTRIPLEDES", 8); EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA1ANDDES", 8); + EXPECTED_OUTPUT_SIZE.put("PBEWITHSHA1ANDDESEDE", 8); EXPECTED_OUTPUT_SIZE.put("DESEDE", 8); EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8); EXPECTED_OUTPUT_SIZE.put("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8); - EXPECTED_OUTPUT_SIZE.put("DESEDEWRAP", -1); - - EXPECTED_OUTPUT_SIZE.put("RSA", 256); - EXPECTED_OUTPUT_SIZE.put("RSA/ECB/NoPadding", 256); - EXPECTED_OUTPUT_SIZE.put("RSA/ECB/PKCS1Padding", 256); + if (StandardNames.IS_RI) { + EXPECTED_OUTPUT_SIZE.put("DESEDEWRAP", 16); + } else { + EXPECTED_OUTPUT_SIZE.put("DESEDEWRAP", -1); + } } - private static int getExpectedOutputSize(String algorithm) { + private static int getExpectedOutputSize(String algorithm, Key key) { + final int firstSlash = algorithm.indexOf('/'); + if (firstSlash != -1) { + algorithm = algorithm.substring(0, firstSlash); + } + + // Output size for RSA depends on the key size being used. + if ("RSA".equals(getBaseAlgorithm(algorithm))) { + return getRSAModulusSize(key); + } + Integer expected = EXPECTED_OUTPUT_SIZE.get(algorithm); assertNotNull(algorithm, expected); return expected; @@ -356,13 +444,19 @@ public final class CipherTest extends TestCase { }; private static byte[] getExpectedPlainText(String algorithm) { - if (algorithm.equals("RSA/ECB/NoPadding")) { + if (algorithm.equals("RSA/ECB/NOPADDING")) { return PKCS1_BLOCK_TYPE_00_PADDED_PLAIN_TEXT; } return ORIGINAL_PLAIN_TEXT; } public void test_getInstance() throws Exception { + final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(errBuffer); + + Set<String> seenBaseCipherNames = new HashSet<String>(); + Set<String> seenCiphersWithModeAndPadding = new HashSet<String>(); + Provider[] providers = Security.getProviders(); for (Provider provider : providers) { Set<Provider.Service> services = provider.getServices(); @@ -371,34 +465,84 @@ public final class CipherTest extends TestCase { if (!type.equals("Cipher")) { continue; } + String algorithm = service.getAlgorithm(); + + /* + * Any specific modes and paddings aren't tested directly here, + * but we need to make sure we see the bare algorithm from some + * provider. We will test each mode specifically when we get the + * base cipher. + */ + final int firstSlash = algorithm.indexOf('/'); + if (firstSlash == -1) { + seenBaseCipherNames.add(algorithm); + } else { + final String baseCipherName = algorithm.substring(0, firstSlash); + if (!seenBaseCipherNames.contains(baseCipherName)) { + seenCiphersWithModeAndPadding.add(baseCipherName); + } + continue; + } + try { - // Cipher.getInstance(String) - Cipher c1 = Cipher.getInstance(algorithm); - assertEquals(algorithm, c1.getAlgorithm()); - test_Cipher(c1); - - // Cipher.getInstance(String, Provider) - Cipher c2 = Cipher.getInstance(algorithm, provider); - assertEquals(algorithm, c2.getAlgorithm()); - assertEquals(provider, c2.getProvider()); - test_Cipher(c2); - - // KeyGenerator.getInstance(String, String) - Cipher c3 = Cipher.getInstance(algorithm, provider.getName()); - assertEquals(algorithm, c3.getAlgorithm()); - assertEquals(provider, c3.getProvider()); - test_Cipher(c3); + test_Cipher_Algorithm(provider, algorithm); } catch (Throwable e) { - throw new Exception("Problem testing Cipher." + algorithm, e); + out.append("Error encountered checking " + algorithm + "\n"); + e.printStackTrace(out); + } + + Set<String> modes = StandardNames.getModesForCipher(algorithm); + if (modes != null) { + for (String mode : modes) { + Set<String> paddings = StandardNames.getPaddingsForCipher(algorithm); + if (paddings != null) { + for (String padding : paddings) { + final String algorithmName = algorithm + "/" + mode + "/" + padding; + try { + test_Cipher_Algorithm(provider, algorithmName); + } catch (Throwable e) { + out.append("Error encountered checking " + algorithmName + "\n"); + e.printStackTrace(out); + } + } + } + } } } } + + seenCiphersWithModeAndPadding.removeAll(seenBaseCipherNames); + assertEquals("Ciphers seen with mode and padding but not base cipher", + Collections.EMPTY_SET, seenCiphersWithModeAndPadding); + + out.flush(); + if (errBuffer.size() > 0) { + throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n"); + } + } + + private void test_Cipher_Algorithm(Provider provider, String algorithm) throws Exception { + // Cipher.getInstance(String) + Cipher c1 = Cipher.getInstance(algorithm); + assertEquals(algorithm, c1.getAlgorithm()); + test_Cipher(c1); + + // Cipher.getInstance(String, Provider) + Cipher c2 = Cipher.getInstance(algorithm, provider); + assertEquals(algorithm, c2.getAlgorithm()); + assertEquals(provider, c2.getProvider()); + test_Cipher(c2); + + // KeyGenerator.getInstance(String, String) + Cipher c3 = Cipher.getInstance(algorithm, provider.getName()); + assertEquals(algorithm, c3.getAlgorithm()); + assertEquals(provider, c3.getProvider()); + test_Cipher(c3); } private void test_Cipher(Cipher c) throws Exception { - // TODO: test all supported modes and padding for a given algorithm - String algorithm = c.getAlgorithm(); + String algorithm = c.getAlgorithm().toUpperCase(Locale.US); if (isUnsupported(algorithm)) { return; } @@ -410,11 +554,21 @@ public final class CipherTest extends TestCase { // TODO: test keys from different factories (e.g. OpenSSLRSAPrivateKey vs JCERSAPrivateKey) Key encryptKey = getEncryptKey(algorithm); - c.init(getEncryptMode(algorithm), encryptKey); + final AlgorithmParameterSpec spec; + if (isPBE(algorithm)) { + final byte[] salt = new byte[8]; + new SecureRandom().nextBytes(salt); + spec = new PBEParameterSpec(salt, 1024); + } else { + spec = null; + } + c.init(getEncryptMode(algorithm), encryptKey, spec); - assertEquals(getExpectedBlockSize(algorithm), c.getBlockSize()); + assertEquals("getBlockSize()", getExpectedBlockSize(algorithm, encryptKey), + c.getBlockSize()); - assertEquals(getExpectedOutputSize(algorithm), c.getOutputSize(0)); + assertEquals("getOutputSize(0)", getExpectedOutputSize(algorithm, encryptKey), + c.getOutputSize(0)); // TODO: test Cipher.getIV() @@ -434,7 +588,7 @@ public final class CipherTest extends TestCase { encryptKey, decryptedKey); } else { byte[] cipherText = c.doFinal(ORIGINAL_PLAIN_TEXT); - c.init(getDecryptMode(algorithm), getDecryptKey(algorithm)); + c.init(getDecryptMode(algorithm), getDecryptKey(algorithm), spec); byte[] decryptedPlainText = c.doFinal(cipherText); assertEquals(Arrays.toString(getExpectedPlainText(algorithm)), Arrays.toString(decryptedPlainText)); @@ -479,12 +633,13 @@ public final class CipherTest extends TestCase { Cipher decryptCipher = Cipher.getInstance("RSA/ECB/NoPadding"); decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey); byte[] plainText = decryptCipher.doFinal(cipherText); - assertPadding(expectedBlockType, ORIGINAL_PLAIN_TEXT, plainText); + assertPadding(encryptKey, expectedBlockType, ORIGINAL_PLAIN_TEXT, plainText); } - private void assertPadding(byte expectedBlockType, byte[] expectedData, byte[] actualDataWithPadding) { + private void assertPadding(Key key, byte expectedBlockType, byte[] expectedData, + byte[] actualDataWithPadding) { assertNotNull(actualDataWithPadding); - assertEquals(getExpectedOutputSize("RSA"), actualDataWithPadding.length); + assertEquals(getExpectedOutputSize("RSA", key), actualDataWithPadding.length); assertEquals(0, actualDataWithPadding[0]); byte actualBlockType = actualDataWithPadding[1]; assertEquals(expectedBlockType, actualBlockType); @@ -1353,4 +1508,344 @@ public final class CipherTest extends TestCase { Cipher c = Cipher.getInstance("RSA/ECB/NoPadding"); assertNull("Parameters should be null", c.getParameters()); } + + /* + * Test vector generation: + * openssl rand -hex 16 + * echo '3d4f8970b1f27537f40a39298a41555f' | sed 's/\(..\)/(byte) 0x\1, /g' + */ + private static final byte[] AES_128_KEY = new byte[] { + (byte) 0x3d, (byte) 0x4f, (byte) 0x89, (byte) 0x70, (byte) 0xb1, (byte) 0xf2, + (byte) 0x75, (byte) 0x37, (byte) 0xf4, (byte) 0x0a, (byte) 0x39, (byte) 0x29, + (byte) 0x8a, (byte) 0x41, (byte) 0x55, (byte) 0x5f, + }; + + /* + * Test key generation: + * openssl rand -hex 24 + * echo '5a7a3d7e40b64ed996f7afa15f97fd595e27db6af428e342' | sed 's/\(..\)/(byte) 0x\1, /g' + */ + private static final byte[] AES_192_KEY = new byte[] { + (byte) 0x5a, (byte) 0x7a, (byte) 0x3d, (byte) 0x7e, (byte) 0x40, (byte) 0xb6, + (byte) 0x4e, (byte) 0xd9, (byte) 0x96, (byte) 0xf7, (byte) 0xaf, (byte) 0xa1, + (byte) 0x5f, (byte) 0x97, (byte) 0xfd, (byte) 0x59, (byte) 0x5e, (byte) 0x27, + (byte) 0xdb, (byte) 0x6a, (byte) 0xf4, (byte) 0x28, (byte) 0xe3, (byte) 0x42, + }; + + /* + * Test key generation: + * openssl rand -hex 32 + * echo 'ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935' | sed 's/\(..\)/(byte) 0x\1, /g' + */ + private static final byte[] AES_256_KEY = new byte[] { + (byte) 0xec, (byte) 0x53, (byte) 0xc6, (byte) 0xd5, (byte) 0x1d, (byte) 0x2c, + (byte) 0x49, (byte) 0x73, (byte) 0x58, (byte) 0x5f, (byte) 0xb0, (byte) 0xb8, + (byte) 0xe5, (byte) 0x1c, (byte) 0xd2, (byte) 0xe3, (byte) 0x99, (byte) 0x15, + (byte) 0xff, (byte) 0x07, (byte) 0xa1, (byte) 0x83, (byte) 0x78, (byte) 0x72, + (byte) 0x71, (byte) 0x5d, (byte) 0x61, (byte) 0x21, (byte) 0xbf, (byte) 0x86, + (byte) 0x19, (byte) 0x35, + }; + + private static final byte[][] AES_KEYS = new byte[][] { + AES_128_KEY, AES_192_KEY, AES_256_KEY, + }; + + private static final String[] AES_MODES = new String[] { + "AES/ECB", + "AES/CBC", + "AES/CFB", + "AES/CTR", + "AES/OFB", + }; + + /* + * Test vector creation: + * echo -n 'Hello, world!' | recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext = new byte[] { + (byte) 0x48, (byte) 0x65, (byte) 0x6C, (byte) 0x6C, (byte) 0x6F, (byte) 0x2C, + (byte) 0x20, (byte) 0x77, (byte) 0x6F, (byte) 0x72, (byte) 0x6C, (byte) 0x64, + (byte) 0x21, + }; + + /* + * Test vector creation: + * openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -in blah|openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -nopad -d|recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded = new byte[] { + (byte) 0x48, (byte) 0x65, (byte) 0x6C, (byte) 0x6C, (byte) 0x6F, (byte) 0x2C, + (byte) 0x20, (byte) 0x77, (byte) 0x6F, (byte) 0x72, (byte) 0x6C, (byte) 0x64, + (byte) 0x21, (byte) 0x03, (byte) 0x03, (byte) 0x03 + }; + + /* + * Test vector generation: + * openssl enc -aes-128-ecb -K 3d4f8970b1f27537f40a39298a41555f -in blah|recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted = new byte[] { + (byte) 0x65, (byte) 0x3E, (byte) 0x86, (byte) 0xFB, (byte) 0x05, (byte) 0x5A, + (byte) 0x52, (byte) 0xEA, (byte) 0xDD, (byte) 0x08, (byte) 0xE7, (byte) 0x48, + (byte) 0x33, (byte) 0x01, (byte) 0xFC, (byte) 0x5A, + }; + + /* + * Test key generation: + * openssl rand -hex 16 + * echo 'ceaa31952dfd3d0f5af4b2042ba06094' | sed 's/\(..\)/(byte) 0x\1, /g' + */ + private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_IV = new byte[] { + (byte) 0xce, (byte) 0xaa, (byte) 0x31, (byte) 0x95, (byte) 0x2d, (byte) 0xfd, + (byte) 0x3d, (byte) 0x0f, (byte) 0x5a, (byte) 0xf4, (byte) 0xb2, (byte) 0x04, + (byte) 0x2b, (byte) 0xa0, (byte) 0x60, (byte) 0x94, + }; + + /* + * Test vector generation: + * echo -n 'I only regret that I have but one test to write.' | recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext = new byte[] { + (byte) 0x49, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x6C, (byte) 0x79, + (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x65, + (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x61, (byte) 0x74, + (byte) 0x20, (byte) 0x49, (byte) 0x20, (byte) 0x68, (byte) 0x61, (byte) 0x76, + (byte) 0x65, (byte) 0x20, (byte) 0x62, (byte) 0x75, (byte) 0x74, (byte) 0x20, + (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x20, (byte) 0x74, (byte) 0x65, + (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20, + (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x2E + }; + + /* + * Test vector generation: + * echo -n 'I only regret that I have but one test to write.' | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 -d -nopad | recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded = new byte[] { + (byte) 0x49, (byte) 0x20, (byte) 0x6F, (byte) 0x6E, (byte) 0x6C, (byte) 0x79, + (byte) 0x20, (byte) 0x72, (byte) 0x65, (byte) 0x67, (byte) 0x72, (byte) 0x65, + (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x68, (byte) 0x61, (byte) 0x74, + (byte) 0x20, (byte) 0x49, (byte) 0x20, (byte) 0x68, (byte) 0x61, (byte) 0x76, + (byte) 0x65, (byte) 0x20, (byte) 0x62, (byte) 0x75, (byte) 0x74, (byte) 0x20, + (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x20, (byte) 0x74, (byte) 0x65, + (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x74, (byte) 0x6F, (byte) 0x20, + (byte) 0x77, (byte) 0x72, (byte) 0x69, (byte) 0x74, (byte) 0x65, (byte) 0x2E, + (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, + (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10, + (byte) 0x10, (byte) 0x10, (byte) 0x10, (byte) 0x10 + }; + + /* + * Test vector generation: + * echo -n 'I only regret that I have but one test to write.' | openssl enc -aes-256-cbc -K ec53c6d51d2c4973585fb0b8e51cd2e39915ff07a1837872715d6121bf861935 -iv ceaa31952dfd3d0f5af4b2042ba06094 | recode ../x1 | sed 's/0x/(byte) 0x/g' + */ + private static final byte[] AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext = new byte[] { + (byte) 0x90, (byte) 0x65, (byte) 0xDD, (byte) 0xAF, (byte) 0x7A, (byte) 0xCE, + (byte) 0xAE, (byte) 0xBF, (byte) 0xE8, (byte) 0xF6, (byte) 0x9E, (byte) 0xDB, + (byte) 0xEA, (byte) 0x65, (byte) 0x28, (byte) 0xC4, (byte) 0x9A, (byte) 0x28, + (byte) 0xEA, (byte) 0xA3, (byte) 0x95, (byte) 0x2E, (byte) 0xFF, (byte) 0xF1, + (byte) 0xA0, (byte) 0xCA, (byte) 0xC2, (byte) 0xA4, (byte) 0x65, (byte) 0xCD, + (byte) 0xBF, (byte) 0xCE, (byte) 0x9E, (byte) 0xF1, (byte) 0x57, (byte) 0xF6, + (byte) 0x32, (byte) 0x2E, (byte) 0x8F, (byte) 0x93, (byte) 0x2E, (byte) 0xAE, + (byte) 0x41, (byte) 0x33, (byte) 0x54, (byte) 0xD0, (byte) 0xEF, (byte) 0x8C, + (byte) 0x52, (byte) 0x14, (byte) 0xAC, (byte) 0x2D, (byte) 0xD5, (byte) 0xA4, + (byte) 0xF9, (byte) 0x20, (byte) 0x77, (byte) 0x25, (byte) 0x91, (byte) 0x3F, + (byte) 0xD1, (byte) 0xB9, (byte) 0x00, (byte) 0x3E + }; + + private static class CipherTestParam { + public final String mode; + + public final byte[] key; + + public final byte[] iv; + + public final byte[] plaintext; + + public final byte[] ciphertext; + + public final byte[] plaintextPadded; + + public CipherTestParam(String mode, byte[] key, byte[] iv, byte[] plaintext, + byte[] plaintextPadded, byte[] ciphertext) { + this.mode = mode; + this.key = key; + this.iv = iv; + this.plaintext = plaintext; + this.plaintextPadded = plaintextPadded; + this.ciphertext = ciphertext; + } + } + + private static List<CipherTestParam> CIPHER_TEST_PARAMS = new ArrayList<CipherTestParam>(); + static { + CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB", AES_128_KEY, + null, + AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext, + AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, + AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted)); + CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC", AES_256_KEY, + AES_256_CBC_PKCS5Padding_TestVector_1_IV, + AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext, + AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded, + AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext)); + } + + public void testCipher_Success() throws Exception { + final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(errBuffer); + for (CipherTestParam p : CIPHER_TEST_PARAMS) { + try { + checkCipher(p); + } catch (Exception e) { + out.append("Error encountered checking " + p.mode + ", keySize=" + + (p.key.length * 8) + "\n"); + e.printStackTrace(out); + } + } + out.flush(); + if (errBuffer.size() > 0) { + throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n"); + } + } + + private void checkCipher(CipherTestParam p) throws Exception { + SecretKey key = new SecretKeySpec(p.key, "AES"); + Cipher c = Cipher.getInstance(p.mode + "/PKCS5Padding"); + AlgorithmParameterSpec spec = null; + if (p.iv != null) { + spec = new IvParameterSpec(p.iv); + } + c.init(Cipher.ENCRYPT_MODE, key, spec); + + final byte[] actualCiphertext = c.doFinal(p.plaintext); + assertTrue(Arrays.equals(p.ciphertext, actualCiphertext)); + + c.init(Cipher.DECRYPT_MODE, key, spec); + + final byte[] actualPlaintext = c.doFinal(p.ciphertext); + assertTrue(Arrays.equals(p.plaintext, actualPlaintext)); + + Cipher cNoPad = Cipher.getInstance(p.mode + "/NoPadding"); + cNoPad.init(Cipher.DECRYPT_MODE, key, spec); + + final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext); + assertTrue(Arrays.equals(p.plaintextPadded, actualPlaintextPadded)); + } + + public void testCipher_ShortBlock_Failure() throws Exception { + final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(errBuffer); + for (CipherTestParam p : CIPHER_TEST_PARAMS) { + try { + checkCipher_ShortBlock_Failure(p); + } catch (Exception e) { + out.append("Error encountered checking " + p.mode + ", keySize=" + + (p.key.length * 8) + "\n"); + e.printStackTrace(out); + } + } + out.flush(); + if (errBuffer.size() > 0) { + throw new Exception("Errors encountered:\n\n" + errBuffer.toString() + "\n\n"); + } + } + + private void checkCipher_ShortBlock_Failure(CipherTestParam p) throws Exception { + SecretKey key = new SecretKeySpec(p.key, "AES"); + Cipher c = Cipher.getInstance(p.mode + "/NoPadding"); + if (c.getBlockSize() == 0) { + return; + } + + c.init(Cipher.ENCRYPT_MODE, key); + try { + c.doFinal(new byte[] { 0x01, 0x02, 0x03 }); + fail("Should throw IllegalBlockSizeException on wrong-sized block"); + } catch (IllegalBlockSizeException expected) { + } + } + + public void testAES_ECB_PKCS5Padding_ShortBuffer_Failure() throws Exception { + SecretKey key = new SecretKeySpec(AES_128_KEY, "AES"); + Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding"); + c.init(Cipher.ENCRYPT_MODE, key); + + final byte[] fragmentOutput = c.update(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext); + if (fragmentOutput != null) { + assertEquals(0, fragmentOutput.length); + } + + // Provide null buffer. + { + try { + c.doFinal(null, 0); + fail("Should throw NullPointerException on null output buffer"); + } catch (NullPointerException expected) { + } catch (IllegalArgumentException expected) { + } + } + + // Provide short buffer. + { + final byte[] output = new byte[c.getBlockSize() - 1]; + try { + c.doFinal(output, 0); + fail("Should throw ShortBufferException on short output buffer"); + } catch (ShortBufferException expected) { + } + } + + // Start 1 byte into output buffer. + { + final byte[] output = new byte[c.getBlockSize()]; + try { + c.doFinal(output, 1); + fail("Should throw ShortBufferException on short output buffer"); + } catch (ShortBufferException expected) { + } + } + + // Should keep data for real output buffer + { + final byte[] output = new byte[c.getBlockSize()]; + assertEquals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted.length, c.doFinal(output, 0)); + assertTrue(Arrays.equals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted, output)); + } + } + + public void testAES_ECB_NoPadding_IncrementalUpdate_Success() throws Exception { + SecretKey key = new SecretKeySpec(AES_128_KEY, "AES"); + Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, key); + + for (int i = 0; i < AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length - 1; i++) { + final byte[] outputFragment = c.update(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, i, 1); + if (outputFragment != null) { + assertEquals(0, outputFragment.length); + } + } + + final byte[] output = c.doFinal(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, + AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length - 1, 1); + assertNotNull(output); + assertEquals(AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded.length, output.length); + + assertTrue(Arrays.equals(AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted, output)); + } + + private static final byte[] AES_IV_ZEROES = new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + public void testAES_ECB_NoPadding_IvParameters_Failure() throws Exception { + SecretKey key = new SecretKeySpec(AES_128_KEY, "AES"); + Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); + + AlgorithmParameterSpec spec = new IvParameterSpec(AES_IV_ZEROES); + try { + c.init(Cipher.ENCRYPT_MODE, key, spec); + fail("Should not accept an IV in ECB mode"); + } catch (InvalidAlgorithmParameterException expected) { + } + } } diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java index dc57086..4211a10 100644 --- a/support/src/test/java/libcore/java/security/StandardNames.java +++ b/support/src/test/java/libcore/java/security/StandardNames.java @@ -85,6 +85,13 @@ public final class StandardNames extends Assert { */ public static final Map<String,Set<String>> PROVIDER_ALGORITHMS = new HashMap<String,Set<String>>(); + + public static final Map<String,Set<String>> CIPHER_MODES + = new HashMap<String,Set<String>>(); + + public static final Map<String,Set<String>> CIPHER_PADDINGS + = new HashMap<String,Set<String>>(); + private static void provide(String type, String algorithm) { Set<String> algorithms = PROVIDER_ALGORITHMS.get(type); if (algorithms == null) { @@ -102,6 +109,22 @@ public final class StandardNames extends Assert { assertNotNull(PROVIDER_ALGORITHMS.remove(type)); } } + private static void provideCipherModes(String algorithm, String newModes[]) { + Set<String> modes = CIPHER_MODES.get(algorithm); + if (modes == null) { + modes = new HashSet<String>(); + CIPHER_MODES.put(algorithm, modes); + } + modes.addAll(Arrays.asList(newModes)); + } + private static void provideCipherPaddings(String algorithm, String newPaddings[]) { + Set<String> paddings = CIPHER_MODES.get(algorithm); + if (paddings == null) { + paddings = new HashSet<String>(); + CIPHER_MODES.put(algorithm, paddings); + } + paddings.addAll(Arrays.asList(newPaddings)); + } static { provide("AlgorithmParameterGenerator", "DSA"); provide("AlgorithmParameterGenerator", "DiffieHellman"); @@ -122,7 +145,10 @@ public final class StandardNames extends Assert { provide("CertStore", "Collection"); provide("CertStore", "LDAP"); provide("CertificateFactory", "X.509"); + // TODO: provideCipherModes and provideCipherPaddings for other Ciphers provide("Cipher", "AES"); + provideCipherModes("AES", new String[] { "CBC", "CFB", "CTR", "CTS", "ECB", "OFB" }); + provideCipherPaddings("AES", new String[] { "NoPadding", "PKCS5Padding" }); provide("Cipher", "AESWrap"); provide("Cipher", "ARCFOUR"); provide("Cipher", "Blowfish"); @@ -848,4 +874,18 @@ public final class StandardNames extends Assert { assertValidCipherSuites(CIPHER_SUITES, cipherSuites); assertEquals(CIPHER_SUITES_DEFAULT, Arrays.asList(cipherSuites)); } + + /** + * Get all supported mode names for the given cipher. + */ + public static Set<String> getModesForCipher(String cipher) { + return CIPHER_MODES.get(cipher); + } + + /** + * Get all supported padding names for the given cipher. + */ + public static Set<String> getPaddingsForCipher(String cipher) { + return CIPHER_PADDINGS.get(cipher); + } } |