1 /*
2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 8064546
27  * @summary Throw exceptions during reading but not closing of a
28  * CipherInputStream:
29  * - Make sure authenticated algorithms continue to throwing exceptions
30  *   when the authentication tag fails verification.
31  * - Make sure other algorithms do not throw exceptions when the stream
32  *   calls close() and only throw when read() errors.
33  */
34 
35 import java.io.ByteArrayInputStream;
36 import java.io.IOException;
37 import java.lang.Exception;
38 import java.lang.RuntimeException;
39 import java.lang.Throwable;
40 import java.security.AlgorithmParameters;
41 import javax.crypto.AEADBadTagException;
42 import javax.crypto.Cipher;
43 import javax.crypto.CipherInputStream;
44 import javax.crypto.IllegalBlockSizeException;
45 import javax.crypto.spec.IvParameterSpec;
46 import javax.crypto.spec.SecretKeySpec;
47 import javax.crypto.spec.GCMParameterSpec;
48 
49 public class CipherInputStreamExceptions {
50 
51     static SecretKeySpec key = new SecretKeySpec(new byte[16], "AES");
52     static GCMParameterSpec gcmspec = new GCMParameterSpec(128, new byte[16]);
53     static IvParameterSpec iv = new IvParameterSpec(new byte[16]);
54     static boolean failure = false;
55 
56     /* Full read stream, check that getMoreData() is throwing an exception
57      * This test
58      *   1) Encrypt 100 bytes with AES/GCM/PKCS5Padding
59      *   2) Changes the last byte to invalidate the authetication tag.
60      *   3) Fully reads CipherInputStream to decrypt the message and closes
61      */
62 
gcm_AEADBadTag()63     static void gcm_AEADBadTag() throws Exception {
64         Cipher c;
65         byte[] read = new byte[200];
66 
67         System.out.println("Running gcm_AEADBadTag");
68 
69         // Encrypt 100 bytes with AES/GCM/PKCS5Padding
70         byte[] ct = encryptedText("GCM", 100);
71         // Corrupt the encrypted message
72         ct = corruptGCM(ct);
73         // Create stream for decryption
74         CipherInputStream in = getStream("GCM", ct);
75 
76         try {
77             int size = in.read(read);
78             throw new RuntimeException("Fail: CipherInputStream.read() " +
79                     "returned " + size + " and didn't throw an exception.");
80         } catch (IOException e) {
81             Throwable ec = e.getCause();
82             if (ec instanceof AEADBadTagException) {
83                 System.out.println("  Pass.");
84             } else {
85                 System.out.println("  Fail: " + ec.getMessage());
86                 throw new RuntimeException(ec);
87             }
88         } finally {
89             in.close();
90         }
91     }
92 
93     /* Short read stream,
94      * This test
95      *   1) Encrypt 100 bytes with AES/GCM/PKCS5Padding
96      *   2) Reads 100 bytes from stream to decrypt the message and closes
97      *   3) Make sure no value is returned by read()
98      *   4) Make sure no exception is thrown
99      */
100 
gcm_shortReadAEAD()101     static void gcm_shortReadAEAD() throws Exception {
102         Cipher c;
103         byte[] read = new byte[100];
104 
105         System.out.println("Running gcm_shortReadAEAD");
106 
107         byte[] pt = new byte[600];
108         pt[0] = 1;
109         // Encrypt provided 600 bytes with AES/GCM/PKCS5Padding
110         byte[] ct = encryptedText("GCM", pt);
111         // Create stream for decryption
112         CipherInputStream in = getStream("GCM", ct);
113 
114         int size = 0;
115         try {
116             size = in.read(read);
117             in.close();
118             if (read.length != 100) {
119                 throw new RuntimeException("Fail: read size = " + read.length +
120                         "should be 100.");
121             }
122             if (read[0] != 1) {
123                 throw new RuntimeException("Fail: The decrypted text does " +
124                         "not match the plaintext: '" + read[0] +"'");
125             }
126         } catch (IOException e) {
127             System.out.println("  Fail: " + e.getMessage());
128             throw new RuntimeException(e.getCause());
129         }
130         System.out.println("  Pass.");
131     }
132 
133     /*
134      * Verify doFinal() exception is suppressed when input stream is not
135      * read before it is closed.
136      * This test:
137      *   1) Encrypt 100 bytes with AES/GCM/PKCS5Padding
138      *   2) Changes the last byte to invalidate the authetication tag.
139      *   3) Opens a CipherInputStream and the closes it. Never reads from it.
140      *
141      * There should be no exception thrown.
142      */
gcm_suppressUnreadCorrupt()143     static void gcm_suppressUnreadCorrupt() throws Exception {
144         Cipher c;
145         byte[] read = new byte[200];
146 
147         System.out.println("Running supressUnreadCorrupt test");
148 
149         // Encrypt 100 bytes with AES/GCM/PKCS5Padding
150         byte[] ct = encryptedText("GCM", 100);
151         // Corrupt the encrypted message
152         ct = corruptGCM(ct);
153         // Create stream for decryption
154         CipherInputStream in = getStream("GCM", ct);
155 
156         try {
157             in.close();
158             System.out.println("  Pass.");
159         } catch (IOException e) {
160             System.out.println("  Fail: " + e.getMessage());
161             throw new RuntimeException(e.getCause());
162         }
163     }
164 
165     /*
166      * Verify noexception thrown when 1 byte is read from a GCM stream
167      * and then closed
168      * This test:
169      *   1) Encrypt 100 bytes with AES/GCM/PKCS5Padding
170      *   2) Read one byte from the stream, expect no exception thrown.
171      *   4) Close stream,expect no exception thrown.
172      */
gcm_oneReadByte()173     static void gcm_oneReadByte() throws Exception {
174 
175         System.out.println("Running gcm_oneReadByte test");
176 
177         // Encrypt 100 bytes with AES/GCM/PKCS5Padding
178         byte[] ct = encryptedText("GCM", 100);
179         // Create stream for decryption
180         CipherInputStream in = getStream("GCM", ct);
181 
182         try {
183             in.read();
184             System.out.println("  Pass.");
185         } catch (Exception e) {
186             System.out.println("  Fail: " + e.getMessage());
187             throw new RuntimeException(e.getCause());
188         }
189     }
190 
191     /*
192      * Verify exception thrown when 1 byte is read from a corrupted GCM stream
193      * and then closed
194      * This test:
195      *   1) Encrypt 100 bytes with AES/GCM/PKCS5Padding
196      *   2) Changes the last byte to invalidate the authetication tag.
197      *   3) Read one byte from the stream, expect exception thrown.
198      *   4) Close stream,expect no exception thrown.
199      */
gcm_oneReadByteCorrupt()200     static void gcm_oneReadByteCorrupt() throws Exception {
201 
202         System.out.println("Running gcm_oneReadByteCorrupt test");
203 
204         // Encrypt 100 bytes with AES/GCM/PKCS5Padding
205         byte[] ct = encryptedText("GCM", 100);
206         // Corrupt the encrypted message
207         ct = corruptGCM(ct);
208         // Create stream for decryption
209         CipherInputStream in = getStream("GCM", ct);
210 
211         try {
212             in.read();
213             System.out.println("  Fail. No exception thrown.");
214         } catch (IOException e) {
215             Throwable ec = e.getCause();
216             if (ec instanceof AEADBadTagException) {
217                 System.out.println("  Pass.");
218             } else {
219                 System.out.println("  Fail: " + ec.getMessage());
220                 throw new RuntimeException(ec);
221             }
222         }
223     }
224 
225     /* Check that close() does not throw an exception with full message in
226      * CipherInputStream's ibuffer.
227      * This test:
228      *   1) Encrypts a 97 byte message with AES/CBC/PKCS5Padding
229      *   2) Create a stream that sends 96 bytes.
230      *   3) Read stream once,
231      *   4) Close and expect no exception
232      */
233 
cbc_shortStream()234     static void cbc_shortStream() throws Exception {
235         Cipher c;
236         AlgorithmParameters params;
237         byte[] read = new byte[200];
238 
239         System.out.println("Running cbc_shortStream");
240 
241         // Encrypt 97 byte with AES/CBC/PKCS5Padding
242         byte[] ct = encryptedText("CBC", 97);
243         // Create stream with only 96 bytes of encrypted data
244         CipherInputStream in = getStream("CBC", ct, 96);
245 
246         try {
247             int size = in.read(read);
248             in.close();
249             if (size != 80) {
250                 throw new RuntimeException("Fail: CipherInputStream.read() " +
251                         "returned " + size + ". Should have been 80");
252             }
253             System.out.println("  Pass.");
254         } catch (IOException e) {
255             System.out.println("  Fail:  " + e.getMessage());
256             throw new RuntimeException(e.getCause());
257         }
258     }
259 
260     /* Check that close() does not throw an exception when the whole message is
261      * inside the internal buffer (ibuffer) in CipherInputStream and we read
262      * one byte and close the stream.
263      * This test:
264      *   1) Encrypts a 400 byte message with AES/CBC/PKCS5Padding
265      *   2) Read one byte from the stream
266      *   3) Close and expect no exception
267      */
268 
cbc_shortRead400()269     static void cbc_shortRead400() throws Exception {
270         System.out.println("Running cbc_shortRead400");
271 
272         // Encrypt 400 byte with AES/CBC/PKCS5Padding
273         byte[] ct = encryptedText("CBC", 400);
274         // Create stream with encrypted data
275         CipherInputStream in = getStream("CBC", ct);
276 
277         try {
278             in.read();
279             in.close();
280             System.out.println("  Pass.");
281         } catch (IOException e) {
282             System.out.println("  Fail:  " + e.getMessage());
283             throw new RuntimeException(e.getCause());
284         }
285     }
286 
287     /* Check that close() does not throw an exception when the  inside the
288      * internal buffer (ibuffer) in CipherInputStream does not contain the
289      * whole message.
290      * This test:
291      *   1) Encrypts a 600 byte message with AES/CBC/PKCS5Padding
292      *   2) Read one byte from the stream
293      *   3) Close and expect no exception
294      */
295 
cbc_shortRead600()296     static void cbc_shortRead600() throws Exception {
297         System.out.println("Running cbc_shortRead600");
298 
299         // Encrypt 600 byte with AES/CBC/PKCS5Padding
300         byte[] ct = encryptedText("CBC", 600);
301         // Create stream with encrypted data
302         CipherInputStream in = getStream("CBC", ct);
303 
304         try {
305             in.read();
306             in.close();
307             System.out.println("  Pass.");
308         } catch (IOException e) {
309             System.out.println("  Fail:  " + e.getMessage());
310             throw new RuntimeException(e.getCause());
311         }
312     }
313 
314     /* Check that exception is thrown when message is fully read
315      * This test:
316      *   1) Encrypts a 96 byte message with AES/CBC/PKCS5Padding
317      *   2) Create a stream that sends 95 bytes.
318      *   3) Read stream to the end
319      *   4) Expect IllegalBlockSizeException thrown
320      */
321 
cbc_readAllIllegalBlockSize()322     static void cbc_readAllIllegalBlockSize() throws Exception {
323         byte[] read = new byte[200];
324 
325         System.out.println("Running cbc_readAllIllegalBlockSize test");
326 
327         // Encrypt 96 byte with AES/CBC/PKCS5Padding
328         byte[] ct = encryptedText("CBC", 96);
329         // Create a stream with only 95 bytes of encrypted data
330         CipherInputStream in = getStream("CBC", ct, 95);
331 
332         try {
333             int s, size = 0;
334             while ((s = in.read(read)) != -1) {
335                 size += s;
336             }
337             throw new RuntimeException("Fail: No IllegalBlockSizeException. " +
338                     "CipherInputStream.read() returned " + size);
339 
340         } catch (IOException e) {
341             Throwable ec = e.getCause();
342             if (ec instanceof IllegalBlockSizeException) {
343                 System.out.println("  Pass.");
344             } else {
345                 System.out.println("  Fail: " + ec.getMessage());
346                 throw new RuntimeException(ec);
347             }
348         }
349     }
350 
351     /* Generic method to create encrypted text */
encryptedText(String mode, int length)352     static byte[] encryptedText(String mode, int length) throws Exception{
353         return encryptedText(mode, new byte[length]);
354     }
355 
356     /* Generic method to create encrypted text */
encryptedText(String mode, byte[] pt)357     static byte[] encryptedText(String mode, byte[] pt) throws Exception{
358         Cipher c;
359         if (mode.compareTo("GCM") == 0) {
360             c = Cipher.getInstance("AES/GCM/PKCS5Padding", "SunJCE");
361             c.init(Cipher.ENCRYPT_MODE, key, gcmspec);
362         } else if (mode.compareTo("CBC") == 0) {
363             c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
364             c.init(Cipher.ENCRYPT_MODE, key, iv);
365         } else {
366             return null;
367         }
368 
369         return c.doFinal(pt);
370     }
371 
372     /* Generic method to get a properly setup CipherInputStream */
getStream(String mode, byte[] ct)373     static CipherInputStream getStream(String mode, byte[] ct) throws Exception {
374         return getStream(mode, ct, ct.length);
375     }
376 
377     /* Generic method to get a properly setup CipherInputStream */
getStream(String mode, byte[] ct, int length)378     static CipherInputStream getStream(String mode, byte[] ct, int length)
379             throws Exception {
380         Cipher c;
381 
382         if (mode.compareTo("GCM") == 0) {
383             c = Cipher.getInstance("AES/GCM/PKCS5Padding", "SunJCE");
384             c.init(Cipher.DECRYPT_MODE, key, gcmspec);
385         } else if (mode.compareTo("CBC") == 0) {
386             c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
387             c.init(Cipher.DECRYPT_MODE, key, iv);
388         } else {
389             return null;
390         }
391 
392         return new CipherInputStream(new ByteArrayInputStream(ct, 0, length), c);
393 
394     }
395 
396     /* Generic method for corrupting a GCM message.  Change the last
397      * byte on of the authentication tag
398      */
corruptGCM(byte[] ct)399     static byte[] corruptGCM(byte[] ct) {
400         ct[ct.length - 1] = (byte) (ct[ct.length - 1] + 1);
401         return ct;
402     }
403 
main(String[] args)404     public static void main(String[] args) throws Exception {
405         gcm_AEADBadTag();
406         gcm_shortReadAEAD();
407         gcm_suppressUnreadCorrupt();
408         gcm_oneReadByte();
409         gcm_oneReadByteCorrupt();
410         cbc_shortStream();
411         cbc_shortRead400();
412         cbc_shortRead600();
413         cbc_readAllIllegalBlockSize();
414     }
415 }
416