7160837: DigestOutputStream does not turn off digest calculation when "close()" is called
authorascarpino
Thu, 30 May 2013 22:19:28 -0700
changeset 17917 9bd1b39cdbcf
parent 17916 e02ddef88f77
child 17918 37620367ceb7
7160837: DigestOutputStream does not turn off digest calculation when "close()" is called Reviewed-by: mullan, xuelei
jdk/src/share/classes/java/security/DigestOutputStream.java
jdk/src/share/classes/javax/crypto/CipherInputStream.java
jdk/src/share/classes/javax/crypto/CipherOutputStream.java
jdk/test/javax/crypto/Cipher/CipherStreamClose.java
--- a/jdk/src/share/classes/java/security/DigestOutputStream.java	Thu May 30 22:02:43 2013 -0700
+++ b/jdk/src/share/classes/java/security/DigestOutputStream.java	Thu May 30 22:19:28 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 1999, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -112,10 +112,10 @@
      * @see MessageDigest#update(byte)
      */
     public void write(int b) throws IOException {
+        out.write(b);
         if (on) {
             digest.update((byte)b);
         }
-        out.write(b);
     }
 
     /**
@@ -142,10 +142,10 @@
      * @see MessageDigest#update(byte[], int, int)
      */
     public void write(byte[] b, int off, int len) throws IOException {
+        out.write(b, off, len);
         if (on) {
             digest.update(b, off, len);
         }
-        out.write(b, off, len);
     }
 
     /**
--- a/jdk/src/share/classes/javax/crypto/CipherInputStream.java	Thu May 30 22:02:43 2013 -0700
+++ b/jdk/src/share/classes/javax/crypto/CipherInputStream.java	Thu May 30 22:19:28 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -86,6 +86,8 @@
     private int ostart = 0;
     // the offset pointing to the last "new" byte
     private int ofinish = 0;
+    // stream status
+    private boolean closed = false;
 
     /**
      * private convenience function.
@@ -293,14 +295,17 @@
      * @since JCE1.2
      */
     public void close() throws IOException {
+        if (closed) {
+            return;
+        }
+
+        closed = true;
         input.close();
         try {
             // throw away the unprocessed data
             cipher.doFinal();
         }
-        catch (BadPaddingException ex) {
-        }
-        catch (IllegalBlockSizeException ex) {
+        catch (BadPaddingException | IllegalBlockSizeException ex) {
         }
         ostart = 0;
         ofinish = 0;
--- a/jdk/src/share/classes/javax/crypto/CipherOutputStream.java	Thu May 30 22:02:43 2013 -0700
+++ b/jdk/src/share/classes/javax/crypto/CipherOutputStream.java	Thu May 30 22:19:28 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -74,6 +74,9 @@
     // the buffer holding data ready to be written out
     private byte[] obuffer;
 
+    // stream status
+    private boolean closed = false;
+
     /**
      *
      * Constructs a CipherOutputStream from an OutputStream and a
@@ -198,11 +201,14 @@
      * @since      JCE1.2
      */
     public void close() throws IOException {
+        if (closed) {
+            return;
+        }
+
+        closed = true;
         try {
             obuffer = cipher.doFinal();
-        } catch (IllegalBlockSizeException e) {
-            obuffer = null;
-        } catch (BadPaddingException e) {
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
             obuffer = null;
         }
         try {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/crypto/Cipher/CipherStreamClose.java	Thu May 30 22:19:28 2013 -0700
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2013, 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 7160837
+ * @summary Make sure Cipher IO streams doesn't call extra doFinal if close()
+ * is called multiple times.  Additionally, verify the input and output streams
+ * match with encryption and decryption with non-stream crypto.
+ */
+
+import java.io.*;
+import java.security.DigestOutputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+public class CipherStreamClose {
+    private static final String message = "This is the sample message";
+    static boolean debug = false;
+
+    /*
+     * This method does encryption by cipher.doFinal(), and not with
+     * CipherOutputStream
+     */
+    public static byte[] blockEncrypt(String message, SecretKey key)
+        throws Exception {
+
+        byte[] data;
+        Cipher encCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+        encCipher.init(Cipher.ENCRYPT_MODE, key);
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+                oos.writeObject(message);
+            }
+            data = bos.toByteArray();
+        }
+
+        if (debug) {
+            System.out.println(DatatypeConverter.printHexBinary(data));
+        }
+        return encCipher.doFinal(data);
+
+    }
+
+    /*
+     * This method does decryption by cipher.doFinal(), and not with
+     * CipherIntputStream
+     */
+    public static Object blockDecrypt(byte[] data, SecretKey key)
+        throws Exception {
+
+        Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
+        c.init(Cipher.DECRYPT_MODE, key);
+        data = c.doFinal(data);
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
+            try (ObjectInputStream ois = new ObjectInputStream(bis)) {
+                return ois.readObject();
+            }
+        }
+    }
+
+    public static byte[] streamEncrypt(String message, SecretKey key,
+        MessageDigest digest)
+        throws Exception {
+
+        byte[] data;
+        Cipher encCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+        encCipher.init(Cipher.ENCRYPT_MODE, key);
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            DigestOutputStream dos = new DigestOutputStream(bos, digest);
+            CipherOutputStream cos = new CipherOutputStream(dos, encCipher)) {
+            try (ObjectOutputStream oos = new ObjectOutputStream(cos)) {
+                oos.writeObject(message);
+            }
+            data = bos.toByteArray();
+        }
+
+        if (debug) {
+            System.out.println(DatatypeConverter.printHexBinary(data));
+        }
+        return data;
+    }
+
+    public static Object streamDecrypt(byte[] data, SecretKey key,
+        MessageDigest digest) throws Exception {
+
+        Cipher decCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+        decCipher.init(Cipher.DECRYPT_MODE, key);
+        digest.reset();
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
+            DigestInputStream dis = new DigestInputStream(bis, digest);
+            CipherInputStream cis = new CipherInputStream(dis, decCipher)) {
+
+            try (ObjectInputStream ois = new ObjectInputStream(cis)) {
+                return ois.readObject();
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        MessageDigest digest = MessageDigest.getInstance("SHA1");
+        SecretKeySpec key = new SecretKeySpec(
+            DatatypeConverter.parseHexBinary(
+            "12345678123456781234567812345678"), "AES");
+
+        // Run 'message' through streamEncrypt
+        byte[] se = streamEncrypt(message, key, digest);
+        // 'digest' already has the value from the stream, just finish the op
+        byte[] sd = digest.digest();
+        digest.reset();
+        // Run 'message' through blockEncrypt
+        byte[] be = blockEncrypt(message, key);
+        // Take digest of encrypted blockEncrypt result
+        byte[] bd = digest.digest(be);
+        // Verify both returned the same value
+        if (!Arrays.equals(sd, bd)) {
+            System.err.println("Stream: "+DatatypeConverter.printHexBinary(se)+
+                "\t Digest: "+DatatypeConverter.printHexBinary(sd));
+            System.err.println("Block : "+DatatypeConverter.printHexBinary(be)+
+                "\t Digest: "+DatatypeConverter.printHexBinary(bd));
+            throw new Exception("stream & block encryption does not match");
+        }
+
+        digest.reset();
+        // Sanity check: Decrypt separately from stream to verify operations
+        String bm = (String) blockDecrypt(be, key);
+        if (message.compareTo(bm) != 0) {
+            System.err.println("Expected: "+message+"\nBlock:    "+bm);
+            throw new Exception("Block decryption does not match expected");
+        }
+
+        // Have decryption and digest included in the object stream
+        String sm = (String) streamDecrypt(se, key, digest);
+        if (message.compareTo(sm) != 0) {
+            System.err.println("Expected: "+message+"\nStream:   "+sm);
+            throw new Exception("Stream decryption does not match expected.");
+        }
+    }
+}