aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java')
-rw-r--r--java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java279
1 files changed, 279 insertions, 0 deletions
diff --git a/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java b/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java
new file mode 100644
index 0000000..da7e4fb
--- /dev/null
+++ b/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java
@@ -0,0 +1,279 @@
+/**
+ * @license
+ * Copyright 2016 Google Inc. All rights reserved.
+ * 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 java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import junit.framework.TestCase;
+
+/** CipherInputStream tests */
+public class CipherInputStreamTest extends TestCase {
+ static final SecureRandom rand = new SecureRandom();
+
+ static byte[] randomBytes(int size) {
+ byte[] bytes = new byte[size];
+ rand.nextBytes(bytes);
+ return bytes;
+ }
+
+ static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) {
+ return new SecretKeySpec(randomBytes(keySizeInBytes), "AES");
+ }
+
+ static AlgorithmParameterSpec randomParameters(
+ String algorithm, int ivSizeInBytes, int tagSizeInBytes) {
+ if ("AES/GCM/NoPadding".equals(algorithm) || "AES/EAX/NoPadding".equals(algorithm)) {
+ return new GCMParameterSpec(8 * tagSizeInBytes, randomBytes(ivSizeInBytes));
+ }
+ return null;
+ }
+
+ /** Test vectors */
+ public static class TestVector {
+ public String algorithm;
+ public SecretKeySpec key;
+ public AlgorithmParameterSpec params;
+ public byte[] pt;
+ public byte[] aad;
+ public byte[] ct;
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public TestVector(
+ String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)
+ throws Exception {
+ this.algorithm = algorithm;
+ this.key = randomKey(algorithm, keySize);
+ this.params = randomParameters(algorithm, ivSize, tagSize);
+ this.pt = randomBytes(ptSize);
+ this.aad = randomBytes(aadSize);
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params);
+ cipher.updateAAD(aad);
+ this.ct = cipher.doFinal(pt);
+ }
+ }
+
+ Iterable<TestVector> getTestVectors(
+ String algorithm,
+ int[] keySizes,
+ int[] ivSizes,
+ int[] tagSizes,
+ int[] ptSizes,
+ int[] aadSizes)
+ throws Exception {
+ ArrayList<TestVector> result = new ArrayList<TestVector>();
+ for (int keySize : keySizes) {
+ for (int ivSize : ivSizes) {
+ for (int tagSize : tagSizes) {
+ for (int ptSize : ptSizes) {
+ for (int aadSize : aadSizes) {
+ result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize));
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testEncrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ InputStream is = new ByteArrayInputStream(t.pt);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ byte[] result = new byte[t.ct.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ assertEquals(-1, cis.read());
+ assertEquals(TestUtil.bytesToHex(t.ct), TestUtil.bytesToHex(result));
+ cis.close();
+ }
+ }
+
+ /** JDK-8016249: CipherInputStream in decrypt mode fails on close with AEAD ciphers */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testDecrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ InputStream is = new ByteArrayInputStream(t.ct);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ byte[] result = new byte[t.pt.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ assertEquals(-1, cis.read());
+ cis.close();
+ assertEquals(TestUtil.bytesToHex(t.pt), TestUtil.bytesToHex(result));
+ }
+ }
+
+ /**
+ * JDK-8016171 : CipherInputStream masks ciphertext tampering with AEAD ciphers in decrypt mode
+ * Further description of the bug is here:
+ * https://blog.heckel.xyz/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/
+ * BouncyCastle claims that this bug is fixed in version 1.51. However, the test below still fails
+ * with BouncyCastle v 1.52. A possible explanation is that BouncyCastle has its own
+ * implemenatation of CipherInputStream (org.bouncycastle.crypto.io.CipherInputStream).
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testCorruptDecrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
+ ct[ct.length - 1] ^= (byte) 1;
+ InputStream is = new ByteArrayInputStream(ct);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ try {
+ byte[] result = new byte[t.pt.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ cis.close();
+ if (result.length > 0) {
+ fail(
+ "this should fail; decrypted:"
+ + TestUtil.bytesToHex(result)
+ + " pt: "
+ + TestUtil.bytesToHex(t.pt));
+ }
+ } catch (IOException ex) {
+ // expected
+ }
+ }
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testCorruptDecryptEmpty(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
+ ct[ct.length - 1] ^= (byte) 1;
+ InputStream is = new ByteArrayInputStream(ct);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ try {
+ byte[] result = new byte[t.pt.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ cis.close();
+ fail("this should fail");
+ } catch (IOException ex) {
+ // expected
+ }
+ }
+ }
+
+ public void testAesGcm() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0, 8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ }
+
+ public void testCorruptAesGcm() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testCorruptDecrypt(v);
+ }
+
+ /**
+ * Unfortunately Oracle thinks that returning an empty array is valid behaviour for corrupt
+ * ciphertexts. Because of this we test empty plaintext separately to distinguish behaviour
+ * considered acceptable by Oracle from other behaviour.
+ */
+ public void testEmptyPlaintext() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testCorruptDecryptEmpty(v);
+ }
+
+ /** Tests CipherOutputStream with AES-EAX if this algorithm is supported by the provider. */
+ public void testAesEax() throws Exception {
+ final String algorithm = "AES/EAX/NoPadding";
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12, 16};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0, 8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ try {
+ Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Skipping testAesEax");
+ return;
+ }
+ Iterable<TestVector> v =
+ getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ testCorruptDecrypt(v);
+ }
+}