diff options
author | Prashant Patil <patilprashant@google.com> | 2022-04-04 16:40:32 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-04-04 16:40:32 +0000 |
commit | 0650b8071d04f0f34112a6d78c2a06795e361245 (patch) | |
tree | 171da275a686c68bd5a5347e43ffd8d12156b42f /keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java | |
parent | 79f88d6cbdbce9979c2a5f9ef8614a421000087f (diff) | |
parent | cc03cf106ba6f2f0e586e4bc1f1ad3e8352c3d0e (diff) | |
download | wycheproof-0650b8071d04f0f34112a6d78c2a06795e361245.tar.gz |
KeyStore: Wycheproof github files am: 0978999584 am: 3883de5832 am: d341490638 am: cc03cf106b
Original change: https://android-review.googlesource.com/c/platform/external/wycheproof/+/2051321
Change-Id: I0da6eb664523b59b7d957f0b25915ac9b419d927
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java')
-rw-r--r-- | keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java new file mode 100644 index 0000000..a072989 --- /dev/null +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java @@ -0,0 +1,242 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.security.wycheproof; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.util.Set; +import java.util.TreeSet; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * This test uses test vectors in JSON format to test symmetric ciphers. + * + * <p>Ciphers tested in this class are unauthenticated ciphers (i.e. don't have additional data) and + * are randomized using an initialization vector as long as the JSON test vectors are represented + * with the type "IndCpaTest". + */ +@RunWith(JUnit4.class) +public class JsonCipherTest { + + /** Convenience method to get a byte array from a JsonObject. */ + protected static byte[] getBytes(JsonObject object, String name) throws Exception { + return JsonUtil.asByteArray(object.get(name)); + } + + protected static boolean arrayEquals(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + byte res = 0; + for (int i = 0; i < a.length; i++) { + res |= (byte) (a[i] ^ b[i]); + } + return res == 0; + } + + /** + * Initialize a Cipher instance. + * + * @param cipher an instance of a symmetric cipher that will be initialized. + * @param algorithm the name of the algorithm used (e.g. 'AES') + * @param opmode either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE + * @param key raw key bytes + * @param iv the initialisation vector + */ + protected static void initCipher( + Cipher cipher, String algorithm, int opmode, byte[] key, byte[] iv) throws Exception { + SecretKeySpec keySpec = null; + if (algorithm.startsWith("AES/")) { + keySpec = new SecretKeySpec(key, "AES"); + } else { + fail("Unsupported algorithm:" + algorithm); + } + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(opmode, keySpec, ivSpec); + } + + + /** Example format for test vectors + * { + * "algorithm" : "AES-CBC-PKCS5", + * "generatorVersion" : "0.2.1", + * "numberOfTests" : 183, + * "header" : [ + * ], + * "testGroups" : [ + * { + * "ivSize" : 128, + * "keySize" : 128, + * "type" : "IndCpaTest", + * "tests" : [ + * { + * "tcId" : 1, + * "comment" : "empty message", + * "key" : "e34f15c7bd819930fe9d66e0c166e61c", + * "iv" : "da9520f7d3520277035173299388bee2", + * "msg" : "", + * "ct" : "b10ab60153276941361000414aed0a9d", + * "result" : "valid" + * }, + * ... + **/ + // This is a false positive, since errorprone cannot track values passed into a method. + @SuppressWarnings("InsecureCryptoUsage") + public void testCipher(String filename, String algorithm) throws Exception { + // Testing with old test vectors may a reason for a test failure. + // Version number have the format major.minor[status]. + // Versions before 1.0 are experimental and use formats that are expected to change. + // Versions after 1.0 change the major number if the format changes and change + // the minor number if only the test vectors (but not the format) changes. + // Versions meant for distribution have no status. + final String expectedVersion = "0.4"; + JsonObject test = JsonUtil.getTestVectors(filename); + Set<String> exceptions = new TreeSet<String>(); + String generatorVersion = test.get("generatorVersion").getAsString(); + if (!generatorVersion.equals(expectedVersion)) { + System.out.println( + algorithm + + ": expecting test vectors with version " + + expectedVersion + + " found vectors with version " + + generatorVersion); + } + int numTests = test.get("numberOfTests").getAsInt(); + int cntTests = 0; + int errors = 0; + Cipher cipher; + try { + cipher = Cipher.getInstance(algorithm); + } catch (NoSuchAlgorithmException ex) { + System.out.println("Algorithm is not supported. Skipping test for " + algorithm); + return; + } + for (JsonElement g : test.getAsJsonArray("testGroups")) { + JsonObject group = g.getAsJsonObject(); + for (JsonElement t : group.getAsJsonArray("tests")) { + cntTests++; + JsonObject testcase = t.getAsJsonObject(); + int tcid = testcase.get("tcId").getAsInt(); + String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString(); + byte[] key = getBytes(testcase, "key"); + byte[] iv = getBytes(testcase, "iv"); + byte[] msg = getBytes(testcase, "msg"); + byte[] ciphertext = getBytes(testcase, "ct"); + // Result is one of "valid", "invalid", "acceptable". + // "valid" are test vectors with matching plaintext, ciphertext and tag. + // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag. + // "acceptable" are test vectors with weak parameters or legacy formats. + String result = testcase.get("result").getAsString(); + + // Test encryption + try { + initCipher(cipher, algorithm, Cipher.ENCRYPT_MODE, key, iv); + } catch (GeneralSecurityException ex) { + // Some libraries restrict key size, iv size and tag size. + // Because of the initialization of the cipher might fail. + System.out.println(ex.toString()); + continue; + } + try { + byte[] encrypted = cipher.doFinal(msg); + boolean eq = arrayEquals(ciphertext, encrypted); + if (result.equals("invalid")) { + if (eq) { + // Some test vectors use invalid parameters that should be rejected. + System.out.println("Encrypted " + tc); + errors++; + } + } else { + if (!eq) { + System.out.println( + "Incorrect ciphertext for " + + tc + + " ciphertext:" + + TestUtil.bytesToHex(encrypted)); + errors++; + } + } + } catch (GeneralSecurityException ex) { + if (result.equals("valid")) { + System.out.println("Failed to encrypt " + tc); + errors++; + } + } + + // Test decryption + // The algorithms tested in this class are typically malleable. Hence, it is in possible + // that modifying ciphertext randomly results in some other valid ciphertext. + // However, all the test vectors in Wycheproof are constructed such that they have + // invalid padding. If this changes then the test below is too strict. + try { + initCipher(cipher, algorithm, Cipher.DECRYPT_MODE, key, iv); + } catch (GeneralSecurityException ex) { + System.out.println("Parameters accepted for encryption but not decryption " + tc); + errors++; + continue; + } + try { + byte[] decrypted = cipher.doFinal(ciphertext); + boolean eq = arrayEquals(decrypted, msg); + if (result.equals("invalid")) { + System.out.println("Decrypted invalid ciphertext " + tc + " eq:" + eq); + errors++; + } else { + if (!eq) { + System.out.println( + "Incorrect decryption " + tc + " decrypted:" + TestUtil.bytesToHex(decrypted)); + } + } + } catch (GeneralSecurityException ex) { + exceptions.add(ex.getMessage()); + if (result.equals("valid")) { + System.out.println("Failed to decrypt " + tc); + errors++; + } + } + } + } + assertEquals(0, errors); + assertEquals(numTests, cntTests); + // Generally it is preferable if trying to decrypt ciphertexts with incorrect paddings + // does not leak information about invalid paddings through exceptions. + // Such information could simplify padding attacks. Ideally, providers should not include + // any distinguishing features in the exception. Hence, we expect just one exception here. + // + // Seeing distinguishable exception, doesn't necessarily mean that protocols using + // AES/CBC/PKCS5Padding with the tested provider are vulnerable to attacks. Rather it means + // that the provider might simplify attacks if the protocol is using AES/CBC/PKCS5Padding + // incorrectly. + System.out.println("Number of distinct exceptions:" + exceptions.size()); + for (String ex : exceptions) { + System.out.println(ex); + } + assertEquals(1, exceptions.size()); + } + + @Test + public void testAesCbcPkcs5() throws Exception { + testCipher("aes_cbc_pkcs5_test.json", "AES/CBC/PKCS5Padding"); + } +} |