| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 8064546 |
| * @summary Throw exceptions during reading but not closing of a |
| * CipherInputStream: |
| * - Make sure authenticated algorithms continue to throwing exceptions |
| * when the authentication tag fails verification. |
| * - Make sure other algorithms do not throw exceptions when the stream |
| * calls close() and only throw when read() errors. |
| */ |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.lang.Exception; |
| import java.lang.RuntimeException; |
| import java.lang.Throwable; |
| import java.security.AlgorithmParameters; |
| import javax.crypto.AEADBadTagException; |
| import javax.crypto.Cipher; |
| import javax.crypto.CipherInputStream; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.spec.IvParameterSpec; |
| import javax.crypto.spec.SecretKeySpec; |
| import javax.crypto.spec.GCMParameterSpec; |
| |
| public class CipherInputStreamExceptions { |
| |
| static SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); |
| static GCMParameterSpec gcmspec = new GCMParameterSpec(128, new byte[16]); |
| static IvParameterSpec iv = new IvParameterSpec(new byte[16]); |
| static boolean failure = false; |
| |
| /* Full read stream, check that getMoreData() is throwing an exception |
| * This test |
| * 1) Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| * 2) Changes the last byte to invalidate the authetication tag. |
| * 3) Fully reads CipherInputStream to decrypt the message and closes |
| */ |
| |
| static void gcm_AEADBadTag() throws Exception { |
| Cipher c; |
| byte[] read = new byte[200]; |
| |
| System.out.println("Running gcm_AEADBadTag"); |
| |
| // Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| byte[] ct = encryptedText("GCM", 100); |
| // Corrupt the encrypted message |
| ct = corruptGCM(ct); |
| // Create stream for decryption |
| CipherInputStream in = getStream("GCM", ct); |
| |
| try { |
| int size = in.read(read); |
| throw new RuntimeException("Fail: CipherInputStream.read() " + |
| "returned " + size + " and didn't throw an exception."); |
| } catch (IOException e) { |
| Throwable ec = e.getCause(); |
| if (ec instanceof AEADBadTagException) { |
| System.out.println(" Pass."); |
| } else { |
| System.out.println(" Fail: " + ec.getMessage()); |
| throw new RuntimeException(ec); |
| } |
| } finally { |
| in.close(); |
| } |
| } |
| |
| /* Short read stream, |
| * This test |
| * 1) Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| * 2) Reads 100 bytes from stream to decrypt the message and closes |
| * 3) Make sure no value is returned by read() |
| * 4) Make sure no exception is thrown |
| */ |
| |
| static void gcm_shortReadAEAD() throws Exception { |
| Cipher c; |
| byte[] read = new byte[100]; |
| |
| System.out.println("Running gcm_shortReadAEAD"); |
| |
| byte[] pt = new byte[600]; |
| pt[0] = 1; |
| // Encrypt provided 600 bytes with AES/GCM/PKCS5Padding |
| byte[] ct = encryptedText("GCM", pt); |
| // Create stream for decryption |
| CipherInputStream in = getStream("GCM", ct); |
| |
| int size = 0; |
| try { |
| size = in.read(read); |
| in.close(); |
| if (read.length != 100) { |
| throw new RuntimeException("Fail: read size = " + read.length + |
| "should be 100."); |
| } |
| if (read[0] != 1) { |
| throw new RuntimeException("Fail: The decrypted text does " + |
| "not match the plaintext: '" + read[0] +"'"); |
| } |
| } catch (IOException e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| System.out.println(" Pass."); |
| } |
| |
| /* |
| * Verify doFinal() exception is suppressed when input stream is not |
| * read before it is closed. |
| * This test: |
| * 1) Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| * 2) Changes the last byte to invalidate the authetication tag. |
| * 3) Opens a CipherInputStream and the closes it. Never reads from it. |
| * |
| * There should be no exception thrown. |
| */ |
| static void gcm_suppressUnreadCorrupt() throws Exception { |
| Cipher c; |
| byte[] read = new byte[200]; |
| |
| System.out.println("Running supressUnreadCorrupt test"); |
| |
| // Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| byte[] ct = encryptedText("GCM", 100); |
| // Corrupt the encrypted message |
| ct = corruptGCM(ct); |
| // Create stream for decryption |
| CipherInputStream in = getStream("GCM", ct); |
| |
| try { |
| in.close(); |
| System.out.println(" Pass."); |
| } catch (IOException e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| } |
| |
| /* |
| * Verify noexception thrown when 1 byte is read from a GCM stream |
| * and then closed |
| * This test: |
| * 1) Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| * 2) Read one byte from the stream, expect no exception thrown. |
| * 4) Close stream,expect no exception thrown. |
| */ |
| static void gcm_oneReadByte() throws Exception { |
| |
| System.out.println("Running gcm_oneReadByte test"); |
| |
| // Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| byte[] ct = encryptedText("GCM", 100); |
| // Create stream for decryption |
| CipherInputStream in = getStream("GCM", ct); |
| |
| try { |
| in.read(); |
| System.out.println(" Pass."); |
| } catch (Exception e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| } |
| |
| /* |
| * Verify exception thrown when 1 byte is read from a corrupted GCM stream |
| * and then closed |
| * This test: |
| * 1) Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| * 2) Changes the last byte to invalidate the authetication tag. |
| * 3) Read one byte from the stream, expect exception thrown. |
| * 4) Close stream,expect no exception thrown. |
| */ |
| static void gcm_oneReadByteCorrupt() throws Exception { |
| |
| System.out.println("Running gcm_oneReadByteCorrupt test"); |
| |
| // Encrypt 100 bytes with AES/GCM/PKCS5Padding |
| byte[] ct = encryptedText("GCM", 100); |
| // Corrupt the encrypted message |
| ct = corruptGCM(ct); |
| // Create stream for decryption |
| CipherInputStream in = getStream("GCM", ct); |
| |
| try { |
| in.read(); |
| System.out.println(" Fail. No exception thrown."); |
| } catch (IOException e) { |
| Throwable ec = e.getCause(); |
| if (ec instanceof AEADBadTagException) { |
| System.out.println(" Pass."); |
| } else { |
| System.out.println(" Fail: " + ec.getMessage()); |
| throw new RuntimeException(ec); |
| } |
| } |
| } |
| |
| /* Check that close() does not throw an exception with full message in |
| * CipherInputStream's ibuffer. |
| * This test: |
| * 1) Encrypts a 97 byte message with AES/CBC/PKCS5Padding |
| * 2) Create a stream that sends 96 bytes. |
| * 3) Read stream once, |
| * 4) Close and expect no exception |
| */ |
| |
| static void cbc_shortStream() throws Exception { |
| Cipher c; |
| AlgorithmParameters params; |
| byte[] read = new byte[200]; |
| |
| System.out.println("Running cbc_shortStream"); |
| |
| // Encrypt 97 byte with AES/CBC/PKCS5Padding |
| byte[] ct = encryptedText("CBC", 97); |
| // Create stream with only 96 bytes of encrypted data |
| CipherInputStream in = getStream("CBC", ct, 96); |
| |
| try { |
| int size = in.read(read); |
| in.close(); |
| if (size != 80) { |
| throw new RuntimeException("Fail: CipherInputStream.read() " + |
| "returned " + size + ". Should have been 80"); |
| } |
| System.out.println(" Pass."); |
| } catch (IOException e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| } |
| |
| /* Check that close() does not throw an exception when the whole message is |
| * inside the internal buffer (ibuffer) in CipherInputStream and we read |
| * one byte and close the stream. |
| * This test: |
| * 1) Encrypts a 400 byte message with AES/CBC/PKCS5Padding |
| * 2) Read one byte from the stream |
| * 3) Close and expect no exception |
| */ |
| |
| static void cbc_shortRead400() throws Exception { |
| System.out.println("Running cbc_shortRead400"); |
| |
| // Encrypt 400 byte with AES/CBC/PKCS5Padding |
| byte[] ct = encryptedText("CBC", 400); |
| // Create stream with encrypted data |
| CipherInputStream in = getStream("CBC", ct); |
| |
| try { |
| in.read(); |
| in.close(); |
| System.out.println(" Pass."); |
| } catch (IOException e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| } |
| |
| /* Check that close() does not throw an exception when the inside the |
| * internal buffer (ibuffer) in CipherInputStream does not contain the |
| * whole message. |
| * This test: |
| * 1) Encrypts a 600 byte message with AES/CBC/PKCS5Padding |
| * 2) Read one byte from the stream |
| * 3) Close and expect no exception |
| */ |
| |
| static void cbc_shortRead600() throws Exception { |
| System.out.println("Running cbc_shortRead600"); |
| |
| // Encrypt 600 byte with AES/CBC/PKCS5Padding |
| byte[] ct = encryptedText("CBC", 600); |
| // Create stream with encrypted data |
| CipherInputStream in = getStream("CBC", ct); |
| |
| try { |
| in.read(); |
| in.close(); |
| System.out.println(" Pass."); |
| } catch (IOException e) { |
| System.out.println(" Fail: " + e.getMessage()); |
| throw new RuntimeException(e.getCause()); |
| } |
| } |
| |
| /* Check that exception is thrown when message is fully read |
| * This test: |
| * 1) Encrypts a 96 byte message with AES/CBC/PKCS5Padding |
| * 2) Create a stream that sends 95 bytes. |
| * 3) Read stream to the end |
| * 4) Expect IllegalBlockSizeException thrown |
| */ |
| |
| static void cbc_readAllIllegalBlockSize() throws Exception { |
| byte[] read = new byte[200]; |
| |
| System.out.println("Running cbc_readAllIllegalBlockSize test"); |
| |
| // Encrypt 96 byte with AES/CBC/PKCS5Padding |
| byte[] ct = encryptedText("CBC", 96); |
| // Create a stream with only 95 bytes of encrypted data |
| CipherInputStream in = getStream("CBC", ct, 95); |
| |
| try { |
| int s, size = 0; |
| while ((s = in.read(read)) != -1) { |
| size += s; |
| } |
| throw new RuntimeException("Fail: No IllegalBlockSizeException. " + |
| "CipherInputStream.read() returned " + size); |
| |
| } catch (IOException e) { |
| Throwable ec = e.getCause(); |
| if (ec instanceof IllegalBlockSizeException) { |
| System.out.println(" Pass."); |
| } else { |
| System.out.println(" Fail: " + ec.getMessage()); |
| throw new RuntimeException(ec); |
| } |
| } |
| } |
| |
| /* Generic method to create encrypted text */ |
| static byte[] encryptedText(String mode, int length) throws Exception{ |
| return encryptedText(mode, new byte[length]); |
| } |
| |
| /* Generic method to create encrypted text */ |
| static byte[] encryptedText(String mode, byte[] pt) throws Exception{ |
| Cipher c; |
| if (mode.compareTo("GCM") == 0) { |
| c = Cipher.getInstance("AES/GCM/PKCS5Padding", "SunJCE"); |
| c.init(Cipher.ENCRYPT_MODE, key, gcmspec); |
| } else if (mode.compareTo("CBC") == 0) { |
| c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE"); |
| c.init(Cipher.ENCRYPT_MODE, key, iv); |
| } else { |
| return null; |
| } |
| |
| return c.doFinal(pt); |
| } |
| |
| /* Generic method to get a properly setup CipherInputStream */ |
| static CipherInputStream getStream(String mode, byte[] ct) throws Exception { |
| return getStream(mode, ct, ct.length); |
| } |
| |
| /* Generic method to get a properly setup CipherInputStream */ |
| static CipherInputStream getStream(String mode, byte[] ct, int length) |
| throws Exception { |
| Cipher c; |
| |
| if (mode.compareTo("GCM") == 0) { |
| c = Cipher.getInstance("AES/GCM/PKCS5Padding", "SunJCE"); |
| c.init(Cipher.DECRYPT_MODE, key, gcmspec); |
| } else if (mode.compareTo("CBC") == 0) { |
| c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE"); |
| c.init(Cipher.DECRYPT_MODE, key, iv); |
| } else { |
| return null; |
| } |
| |
| return new CipherInputStream(new ByteArrayInputStream(ct, 0, length), c); |
| |
| } |
| |
| /* Generic method for corrupting a GCM message. Change the last |
| * byte on of the authentication tag |
| */ |
| static byte[] corruptGCM(byte[] ct) { |
| ct[ct.length - 1] = (byte) (ct[ct.length - 1] + 1); |
| return ct; |
| } |
| |
| public static void main(String[] args) throws Exception { |
| gcm_AEADBadTag(); |
| gcm_shortReadAEAD(); |
| gcm_suppressUnreadCorrupt(); |
| gcm_oneReadByte(); |
| gcm_oneReadByteCorrupt(); |
| cbc_shortStream(); |
| cbc_shortRead400(); |
| cbc_shortRead600(); |
| cbc_readAllIllegalBlockSize(); |
| } |
| } |