jdk/test/javax/crypto/Cipher/CipherInputStreamExceptions.java
changeset 29819 2de061001dcb
equal deleted inserted replaced
29818:7ca0a63012d2 29819:2de061001dcb
       
     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 }