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 
  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 
 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      */
 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      */
 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      */
 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 
 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 
 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 
 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 
 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 */
 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 */
 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 */
 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 */
 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      */
 399     static byte[] corruptGCM(byte[] ct) {
 400         ct[ct.length - 1] = (byte) (ct[ct.length - 1] + 1);
 401         return ct;
 402     }
 403 
 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 }