diff --git a/jdk/src/share/classes/java/security/DigestOutputStream.java b/jdk/src/share/classes/java/security/DigestOutputStream.java index 1307bdff344..31b77259ea6 100644 --- a/jdk/src/share/classes/java/security/DigestOutputStream.java +++ b/jdk/src/share/classes/java/security/DigestOutputStream.java @@ -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 @@ public class DigestOutputStream extends FilterOutputStream { * @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 @@ public class DigestOutputStream extends FilterOutputStream { * @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); } /** diff --git a/jdk/src/share/classes/javax/crypto/CipherInputStream.java b/jdk/src/share/classes/javax/crypto/CipherInputStream.java index b9f3cf8d600..f062a1bc28e 100644 --- a/jdk/src/share/classes/javax/crypto/CipherInputStream.java +++ b/jdk/src/share/classes/javax/crypto/CipherInputStream.java @@ -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 @@ public class CipherInputStream extends FilterInputStream { 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 @@ public class CipherInputStream extends FilterInputStream { * @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; diff --git a/jdk/src/share/classes/javax/crypto/CipherOutputStream.java b/jdk/src/share/classes/javax/crypto/CipherOutputStream.java index 15edd4585f4..6b8d2734901 100644 --- a/jdk/src/share/classes/javax/crypto/CipherOutputStream.java +++ b/jdk/src/share/classes/javax/crypto/CipherOutputStream.java @@ -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 @@ public class CipherOutputStream extends FilterOutputStream { // 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 @@ public class CipherOutputStream extends FilterOutputStream { * @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 { diff --git a/jdk/test/javax/crypto/Cipher/CipherStreamClose.java b/jdk/test/javax/crypto/Cipher/CipherStreamClose.java new file mode 100644 index 00000000000..1e8ff16331d --- /dev/null +++ b/jdk/test/javax/crypto/Cipher/CipherStreamClose.java @@ -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."); + } + } +}