diff --git a/src/share/classes/java/security/PrivateKey.java b/src/share/classes/java/security/PrivateKey.java index d836ea3683394aa3b770957b53058a47792dfc26..40f112aa2fdbd7835aa023011fbd429b22d3f6bd 100644 --- a/src/share/classes/java/security/PrivateKey.java +++ b/src/share/classes/java/security/PrivateKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2001, 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 @@ -26,13 +26,22 @@ package java.security; /** - *

A private key. This interface contains no methods or constants. - * It merely serves to group (and provide type safety for) all private key - * interfaces. - * + * A private key. + * The purpose of this interface is to group (and provide type safety + * for) all private key interfaces. + *

* Note: The specialized private key interfaces extend this interface. - * See, for example, the DSAPrivateKey interface in - * java.security.interfaces. + * See, for example, the {@code DSAPrivateKey} interface in + * {@link java.security.interfaces}. + *

+ * Implementations should override the default {@code destroy} and + * {@code isDestroyed} methods from the + * {@link javax.security.auth.Destroyable} interface to enable + * sensitive key information to be destroyed, cleared, or in the case + * where such information is immutable, unreferenced. + * Finally, since {@code PrivateKey} is {@code Serializable}, implementations + * should also override {@link java.io.ObjectOutputStream.writeObject} + * to prevent keys that have been destroyed from being serialized. * * @see Key * @see PublicKey @@ -46,7 +55,8 @@ package java.security; * @author Josh Bloch */ -public interface PrivateKey extends Key { +public interface PrivateKey extends Key, javax.security.auth.Destroyable { + // Declare serialVersionUID to be compatible with JDK1.1 /** * The class fingerprint that is set to indicate serialization diff --git a/src/share/classes/javax/crypto/SecretKey.java b/src/share/classes/javax/crypto/SecretKey.java index 05d0e11319cf753377f3290b8971810daa0b7dc4..c32eb7108b6503b74254aba8a51aee45eab6e00e 100644 --- a/src/share/classes/javax/crypto/SecretKey.java +++ b/src/share/classes/javax/crypto/SecretKey.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 @@ -27,20 +27,27 @@ package javax.crypto; /** * A secret (symmetric) key. - * - *

This interface contains no methods or constants. - * Its only purpose is to group (and provide type safety for) secret keys. - * - *

Provider implementations of this interface must overwrite the - * equals and hashCode methods inherited from - * java.lang.Object, so that secret keys are compared based on + * The purpose of this interface is to group (and provide type safety + * for) all secret key interfaces. + *

+ * Provider implementations of this interface must overwrite the + * {@code equals} and {@code hashCode} methods inherited from + * {@link java.lang.Object}, so that secret keys are compared based on * their underlying key material and not based on reference. + * Implementations should override the default {@code destroy} and + * {@code isDestroyed} methods from the + * {@link javax.security.auth.Destroyable} interface to enable + * sensitive key information to be destroyed, cleared, or in the case + * where such information is immutable, unreferenced. + * Finally, since {@code SecretKey} is {@code Serializable}, implementations + * should also override {@link java.io.ObjectOutputStream.writeObject} + * to prevent keys that have been destroyed from being serialized. * - *

Keys that implement this interface return the string RAW - * as their encoding format (see getFormat), and return the - * raw key bytes as the result of a getEncoded method call. (The - * getFormat and getEncoded methods are inherited - * from the java.security.Key parent interface.) + *

Keys that implement this interface return the string {@code RAW} + * as their encoding format (see {@code getFormat}), and return the + * raw key bytes as the result of a {@code getEncoded} method call. (The + * {@code getFormat} and {@code getEncoded} methods are inherited + * from the {@link java.security.Key} parent interface.) * * @author Jan Luehe * @@ -49,7 +56,9 @@ package javax.crypto; * @since 1.4 */ -public interface SecretKey extends java.security.Key { +public interface SecretKey extends + java.security.Key, javax.security.auth.Destroyable { + /** * The class fingerprint that is set to indicate serialization * compatibility since J2SE 1.4. diff --git a/src/share/classes/javax/security/auth/Destroyable.java b/src/share/classes/javax/security/auth/Destroyable.java index 32f81cf2f424d34c1fdb1059d0d090e59bc9f7f7..5afb6aa810bdf0a0144bbf5d300e6cae9027d342 100644 --- a/src/share/classes/javax/security/auth/Destroyable.java +++ b/src/share/classes/javax/security/auth/Destroyable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -42,21 +42,27 @@ public interface Destroyable { * IllegalStateException being thrown. * *

+ * The default implementation throws {@code DestroyFailedException}. * * @exception DestroyFailedException if the destroy operation fails.

* * @exception SecurityException if the caller does not have permission * to destroy this Object. */ - void destroy() throws DestroyFailedException; + public default void destroy() throws DestroyFailedException { + throw new DestroyFailedException(); + } /** * Determine if this Object has been destroyed. * *

+ * The default implementation returns false. * * @return true if this Object has been destroyed, * false otherwise. */ - boolean isDestroyed(); + public default boolean isDestroyed() { + return false; + } } diff --git a/test/javax/security/auth/Destroyable/KeyDestructionTest.java b/test/javax/security/auth/Destroyable/KeyDestructionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..71570807e4aab2c97640aaa820cdf28b4d88d6ed --- /dev/null +++ b/test/javax/security/auth/Destroyable/KeyDestructionTest.java @@ -0,0 +1,253 @@ +/* + * 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 6263419 + * @summary No way to clean the memory for a java.security.Key + */ + +import java.security.*; +import java.util.*; +import javax.crypto.*; +import javax.security.auth.Destroyable; +import javax.security.auth.DestroyFailedException; + +public class KeyDestructionTest { + public static void main(String[] args) throws Exception { + KeyPair keypair = generateKeyPair("RSA", 1024); + + // Check keys that support and have implemented key destruction + testKeyDestruction(new MyDestroyableSecretKey()); + testKeyDestruction(new MyDestroyablePrivateKey()); + + // Check keys that support but have not implemented key destruction + testNoKeyDestruction(generateSecretKey("AES", 128)); + testNoKeyDestruction(keypair.getPrivate()); + + // Check keys that do not support key destruction + try { + testKeyDestruction(keypair.getPublic()); + } catch (UnsupportedOperationException uoe) { + // not an error + System.out.println(keypair.getPublic().getClass().getName() + + " keys do not support key destruction"); + } + + System.out.println("PASSED."); + } + + // Check the behaviour of a key that implements key destruction + private static void testKeyDestruction(Key key) throws Exception { + String klass = key.getClass().getName(); + boolean hasUsable = key instanceof Usable; + + try { + key.getAlgorithm(); + key.getFormat(); + if (allZero(key.getEncoded())) { + throw new Exception("error: key destroyed prematurely"); + } + } catch (IllegalStateException ise) { + throw new Exception("error: unexpected ISE", ise); + } + + if (hasUsable) { + ((Usable) key).useKey(); + } + + destroyKey(key); + + try { + if (hasUsable) { + ((Usable) key).useKey(); + } + } catch (IllegalStateException ise) { + // not an error + } + + try { + key.getAlgorithm(); + key.getFormat(); + if (!allZero(key.getEncoded())) { + throw new Exception("error: key destroyed incorrectly"); + } + } catch (IllegalStateException ise) { + // not an error + } + + System.out.println("A " + klass + + " key has been successfully destroyed"); + } + + // Check the behaviour of a key that does not implement key destruction + private static void testNoKeyDestruction(Destroyable key) + throws Exception { + String klass = key.getClass().getName(); + + if (key.isDestroyed()) { + throw new Exception("error: a " + klass + + " key has been unexpectedly destroyed"); + } + try { + key.destroy(); + } catch (DestroyFailedException dfe) { + // not an error + + if (key.isDestroyed()) { + throw new Exception("error: a " + klass + + " key has been unexpectedly destroyed"); + } + System.out.println(klass + " keys are not destroyable"); + return; + } + throw new Exception("error: key may been unexpectedly destroyed"); + } + + private static KeyPair generateKeyPair(String algorithm, int size) + throws NoSuchAlgorithmException { + KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm); + generator.initialize(size); + return generator.genKeyPair(); + } + + private static SecretKey generateSecretKey(String algorithm, int size) + throws NoSuchAlgorithmException { + KeyGenerator generator = KeyGenerator.getInstance(algorithm); + generator.init(size); + return generator.generateKey(); + } + + private static void destroyKey(Key key) throws Exception { + String klass = key.getClass().getName(); + + if (!(key instanceof Destroyable)) { + throw new UnsupportedOperationException(); + } + + Destroyable dKey = (Destroyable) key; + if (dKey.isDestroyed()) { + throw new Exception("error: a " + klass + + " key has already been destroyed"); + } + dKey.destroy(); + if (!dKey.isDestroyed()) { + throw new Exception("error: a " + klass + + " key has NOT been destroyed"); + } + } + + private static boolean allZero(byte[] bytes) { + int count = 0; + for (byte b : bytes) { + if (b == 0x00) { + count++; + } + } + return (bytes.length == count); + } +} + +interface Usable { + public void useKey(); +} + +class MyDestroyableSecretKey implements SecretKey, Usable { + private byte[] encoded = new byte[]{0x0F, 0x1F, 0x2F, 0x3F}; // non-zero + private boolean isDestroyed = false; + + @Override + public void useKey() { + if (isDestroyed) { + throw new IllegalStateException(); + } + } + + @Override + public String getAlgorithm() { + return "MyDestroyableSecretKey algorithm"; + } + + @Override + public String getFormat() { + return "MyDestroyableSecretKey format"; + } + + @Override + public byte[] getEncoded() { + return this.encoded; + } + + @Override + public void destroy() throws DestroyFailedException { + if (!this.isDestroyed) { + Arrays.fill(encoded, (byte) 0); + this.isDestroyed = true; + } + } + + @Override + public boolean isDestroyed() { + return this.isDestroyed; + } +} + +class MyDestroyablePrivateKey implements PrivateKey, Usable { + private byte[] encoded = new byte[]{0x4F, 0x5F, 0x6F, 0x7F}; // non-zero + private boolean isDestroyed = false; + + @Override + public void useKey() { + if (isDestroyed) { + throw new IllegalStateException(); + } + } + + @Override + public String getAlgorithm() { + return "MyDestroyablePrivateKey algorithm"; + } + + @Override + public String getFormat() { + return "MyDestroyablePrivateKey format"; + } + + @Override + public byte[] getEncoded() { + return this.encoded; + } + + @Override + public void destroy() throws DestroyFailedException { + if (!this.isDestroyed) { + Arrays.fill(encoded, (byte) 0); + this.isDestroyed = true; + } + } + + @Override + public boolean isDestroyed() { + return this.isDestroyed; + } +}