1 /*
   2  * Copyright (c) 2013, 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 7160837
  27  * @summary Make sure Cipher IO streams doesn't call extra doFinal if close()
  28  * is called multiple times.  Additionally, verify the input and output streams
  29  * match with encryption and decryption with non-stream crypto.
  30  * @compile -addmods java.xml.bind CipherStreamClose.java
  31  * @run main/othervm -addmods java.xml.bind CipherStreamClose
  32  */
  33 
  34 import java.io.*;
  35 import java.security.DigestOutputStream;
  36 import java.security.DigestInputStream;
  37 import java.security.MessageDigest;
  38 import java.util.Arrays;
  39 
  40 import javax.crypto.Cipher;
  41 import javax.crypto.CipherOutputStream;
  42 import javax.crypto.CipherInputStream;
  43 import javax.crypto.SecretKey;
  44 import javax.crypto.spec.SecretKeySpec;
  45 import javax.xml.bind.DatatypeConverter;
  46 
  47 public class CipherStreamClose {
  48     private static final String message = "This is the sample message";
  49     static boolean debug = false;
  50 
  51     /*
  52      * This method does encryption by cipher.doFinal(), and not with
  53      * CipherOutputStream
  54      */
  55     public static byte[] blockEncrypt(String message, SecretKey key)
  56         throws Exception {
  57 
  58         byte[] data;
  59         Cipher encCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
  60         encCipher.init(Cipher.ENCRYPT_MODE, key);
  61         try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
  62             try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
  63                 oos.writeObject(message);
  64             }
  65             data = bos.toByteArray();
  66         }
  67 
  68         if (debug) {
  69             System.out.println(DatatypeConverter.printHexBinary(data));
  70         }
  71         return encCipher.doFinal(data);
  72 
  73     }
  74 
  75     /*
  76      * This method does decryption by cipher.doFinal(), and not with
  77      * CipherIntputStream
  78      */
  79     public static Object blockDecrypt(byte[] data, SecretKey key)
  80         throws Exception {
  81 
  82         Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
  83         c.init(Cipher.DECRYPT_MODE, key);
  84         data = c.doFinal(data);
  85         try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
  86             try (ObjectInputStream ois = new ObjectInputStream(bis)) {
  87                 return ois.readObject();
  88             }
  89         }
  90     }
  91 
  92     public static byte[] streamEncrypt(String message, SecretKey key,
  93         MessageDigest digest)
  94         throws Exception {
  95 
  96         byte[] data;
  97         Cipher encCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
  98         encCipher.init(Cipher.ENCRYPT_MODE, key);
  99         try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
 100             DigestOutputStream dos = new DigestOutputStream(bos, digest);
 101             CipherOutputStream cos = new CipherOutputStream(dos, encCipher)) {
 102             try (ObjectOutputStream oos = new ObjectOutputStream(cos)) {
 103                 oos.writeObject(message);
 104             }
 105             data = bos.toByteArray();
 106         }
 107 
 108         if (debug) {
 109             System.out.println(DatatypeConverter.printHexBinary(data));
 110         }
 111         return data;
 112     }
 113 
 114     public static Object streamDecrypt(byte[] data, SecretKey key,
 115         MessageDigest digest) throws Exception {
 116 
 117         Cipher decCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
 118         decCipher.init(Cipher.DECRYPT_MODE, key);
 119         digest.reset();
 120         try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
 121             DigestInputStream dis = new DigestInputStream(bis, digest);
 122             CipherInputStream cis = new CipherInputStream(dis, decCipher)) {
 123 
 124             try (ObjectInputStream ois = new ObjectInputStream(cis)) {
 125                 return ois.readObject();
 126             }
 127         }
 128     }
 129 
 130     public static void main(String[] args) throws Exception {
 131         MessageDigest digest = MessageDigest.getInstance("SHA1");
 132         SecretKeySpec key = new SecretKeySpec(
 133             DatatypeConverter.parseHexBinary(
 134             "12345678123456781234567812345678"), "AES");
 135 
 136         // Run 'message' through streamEncrypt
 137         byte[] se = streamEncrypt(message, key, digest);
 138         // 'digest' already has the value from the stream, just finish the op
 139         byte[] sd = digest.digest();
 140         digest.reset();
 141         // Run 'message' through blockEncrypt
 142         byte[] be = blockEncrypt(message, key);
 143         // Take digest of encrypted blockEncrypt result
 144         byte[] bd = digest.digest(be);
 145         // Verify both returned the same value
 146         if (!Arrays.equals(sd, bd)) {
 147             System.err.println("Stream: "+DatatypeConverter.printHexBinary(se)+
 148                 "\t Digest: "+DatatypeConverter.printHexBinary(sd));
 149             System.err.println("Block : "+DatatypeConverter.printHexBinary(be)+
 150                 "\t Digest: "+DatatypeConverter.printHexBinary(bd));
 151             throw new Exception("stream & block encryption does not match");
 152         }
 153 
 154         digest.reset();
 155         // Sanity check: Decrypt separately from stream to verify operations
 156         String bm = (String) blockDecrypt(be, key);
 157         if (message.compareTo(bm) != 0) {
 158             System.err.println("Expected: "+message+"\nBlock:    "+bm);
 159             throw new Exception("Block decryption does not match expected");
 160         }
 161 
 162         // Have decryption and digest included in the object stream
 163         String sm = (String) streamDecrypt(se, key, digest);
 164         if (message.compareTo(sm) != 0) {
 165             System.err.println("Expected: "+message+"\nStream:   "+sm);
 166             throw new Exception("Stream decryption does not match expected.");
 167         }
 168     }
 169 }