8208583: Better management of internal KeyStore buffers
authorcoffeys
Fri, 03 Aug 2018 14:14:59 +0100
changeset 51293 53c3b460503c
parent 51292 0538a5cdb474
child 51308 acf02a6f369e
8208583: Better management of internal KeyStore buffers Reviewed-by: weijun
src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java
src/java.base/share/classes/com/sun/crypto/provider/PBEKey.java
src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java
src/java.base/share/classes/javax/crypto/spec/PBEKeySpec.java
src/java.base/share/classes/sun/security/provider/JavaKeyStore.java
src/java.base/share/classes/sun/security/provider/KeyProtector.java
--- a/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java	Fri Aug 03 14:14:59 2018 +0100
@@ -37,12 +37,15 @@
 import java.security.AlgorithmParameters;
 import java.security.spec.InvalidParameterSpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherSpi;
 import javax.crypto.SecretKey;
 import javax.crypto.SealedObject;
 import javax.crypto.spec.*;
+import javax.security.auth.DestroyFailedException;
+
 import sun.security.x509.AlgorithmId;
 import sun.security.util.ObjectIdentifier;
 
@@ -103,15 +106,20 @@
 
         // create PBE key from password
         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
-        SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
-        pbeKeySpec.clearPassword();
-
-        // encrypt private key
+        SecretKey sKey = null;
         PBEWithMD5AndTripleDESCipher cipher;
-        cipher = new PBEWithMD5AndTripleDESCipher();
-        cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
+        try {
+            sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
+            // encrypt private key
+            cipher = new PBEWithMD5AndTripleDESCipher();
+            cipher.engineInit(Cipher.ENCRYPT_MODE, sKey, pbeSpec, null);
+        } finally {
+            pbeKeySpec.clearPassword();
+            if (sKey != null) sKey.destroy();
+        }
         byte[] plain = key.getEncoded();
         byte[] encrKey = cipher.engineDoFinal(plain, 0, plain.length);
+        Arrays.fill(plain, (byte) 0x00);
 
         // wrap encrypted private key in EncryptedPrivateKeyInfo
         // (as defined in PKCS#8)
@@ -131,8 +139,8 @@
     Key recover(EncryptedPrivateKeyInfo encrInfo)
         throws UnrecoverableKeyException, NoSuchAlgorithmException
     {
-        byte[] plain;
-
+        byte[] plain = null;
+        SecretKey sKey = null;
         try {
             String encrAlg = encrInfo.getAlgorithm().getOID().toString();
             if (!encrAlg.equals(PBE_WITH_MD5_AND_DES3_CBC_OID)
@@ -160,8 +168,7 @@
 
                 // create PBE key from password
                 PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
-                SecretKey sKey =
-                    new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
+                sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
                 pbeKeySpec.clearPassword();
 
                 // decrypt private key
@@ -178,7 +185,6 @@
                 (new PrivateKeyInfo(plain).getAlgorithm().getOID()).getName();
             KeyFactory kFac = KeyFactory.getInstance(oidName);
             return kFac.generatePrivate(new PKCS8EncodedKeySpec(plain));
-
         } catch (NoSuchAlgorithmException ex) {
             // Note: this catch needed to be here because of the
             // later catch of GeneralSecurityException
@@ -187,6 +193,15 @@
             throw new UnrecoverableKeyException(ioe.getMessage());
         } catch (GeneralSecurityException gse) {
             throw new UnrecoverableKeyException(gse.getMessage());
+        } finally {
+            if (plain != null) Arrays.fill(plain, (byte) 0x00);
+            if (sKey != null) {
+                try {
+                    sKey.destroy();
+                } catch (DestroyFailedException e) {
+                    //shouldn't happen
+                }
+            }
         }
     }
 
@@ -262,7 +277,7 @@
         // of <code>protectedKey</code>. If the two digest values are
         // different, throw an exception.
         md.update(passwdBytes);
-        java.util.Arrays.fill(passwdBytes, (byte)0x00);
+        Arrays.fill(passwdBytes, (byte)0x00);
         passwdBytes = null;
         md.update(plainKey);
         digest = md.digest();
@@ -291,17 +306,21 @@
 
         // create PBE key from password
         PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
-        SecretKey sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
+        SecretKey sKey = null;
+        Cipher cipher;
+        try {
+            sKey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES", false);
         pbeKeySpec.clearPassword();
 
         // seal key
-        Cipher cipher;
-
         PBEWithMD5AndTripleDESCipher cipherSpi;
         cipherSpi = new PBEWithMD5AndTripleDESCipher();
         cipher = new CipherForKeyProtector(cipherSpi, SunJCE.getInstance(),
                                            "PBEWithMD5AndTripleDES");
         cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeSpec);
+        } finally {
+            if (sKey != null) sKey.destroy();
+        }
         return new SealedObjectForKeyProtector(key, cipher);
     }
 
@@ -309,12 +328,13 @@
      * Unseals the sealed key.
      */
     Key unseal(SealedObject so)
-        throws NoSuchAlgorithmException, UnrecoverableKeyException
-    {
+        throws NoSuchAlgorithmException, UnrecoverableKeyException {
+        SecretKey sKey = null;
         try {
             // create PBE key from password
             PBEKeySpec pbeKeySpec = new PBEKeySpec(this.password);
-            SecretKey skey = new PBEKey(pbeKeySpec, "PBEWithMD5AndTripleDES");
+            sKey = new PBEKey(pbeKeySpec,
+                    "PBEWithMD5AndTripleDES", false);
             pbeKeySpec.clearPassword();
 
             SealedObjectForKeyProtector soForKeyProtector = null;
@@ -342,7 +362,7 @@
             Cipher cipher = new CipherForKeyProtector(cipherSpi,
                                                       SunJCE.getInstance(),
                                                       "PBEWithMD5AndTripleDES");
-            cipher.init(Cipher.DECRYPT_MODE, skey, params);
+            cipher.init(Cipher.DECRYPT_MODE, sKey, params);
             return soForKeyProtector.getKey(cipher);
         } catch (NoSuchAlgorithmException ex) {
             // Note: this catch needed to be here because of the
@@ -354,6 +374,14 @@
             throw new UnrecoverableKeyException(cnfe.getMessage());
         } catch (GeneralSecurityException gse) {
             throw new UnrecoverableKeyException(gse.getMessage());
+        } finally {
+            if (sKey != null) {
+                try {
+                    sKey.destroy();
+                } catch (DestroyFailedException e) {
+                    //shouldn't happen
+                }
+            }
         }
     }
 }
--- a/src/java.base/share/classes/com/sun/crypto/provider/PBEKey.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/com/sun/crypto/provider/PBEKey.java	Fri Aug 03 14:14:59 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -29,6 +29,7 @@
 import java.security.MessageDigest;
 import java.security.KeyRep;
 import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
 import java.util.Locale;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.PBEKeySpec;
@@ -54,7 +55,8 @@
      *
      * @param keytype the given PBE key specification
      */
-    PBEKey(PBEKeySpec keySpec, String keytype) throws InvalidKeySpecException {
+    PBEKey(PBEKeySpec keySpec, String keytype, boolean useCleaner)
+            throws InvalidKeySpecException {
         char[] passwd = keySpec.getPassword();
         if (passwd == null) {
             // Should allow an empty password.
@@ -71,13 +73,15 @@
         this.key = new byte[passwd.length];
         for (int i=0; i<passwd.length; i++)
             this.key[i] = (byte) (passwd[i] & 0x7f);
-        java.util.Arrays.fill(passwd, ' ');
+        Arrays.fill(passwd, ' ');
         type = keytype;
 
         // Use the cleaner to zero the key when no longer referenced
-        final byte[] k = this.key;
-        CleanerFactory.cleaner().register(this,
-                () -> java.util.Arrays.fill(k, (byte)0x00));
+        if (useCleaner) {
+            final byte[] k = this.key;
+            CleanerFactory.cleaner().register(this,
+                () -> Arrays.fill(k, (byte) 0x00));
+        }
     }
 
     public byte[] getEncoded() {
@@ -122,11 +126,23 @@
 
         byte[] thatEncoded = that.getEncoded();
         boolean ret = MessageDigest.isEqual(this.key, thatEncoded);
-        java.util.Arrays.fill(thatEncoded, (byte)0x00);
+        Arrays.fill(thatEncoded, (byte)0x00);
         return ret;
     }
 
     /**
+     * Clears the internal copy of the key.
+     *
+     */
+    @Override
+    public void destroy() {
+        if (key != null) {
+            Arrays.fill(key, (byte) 0x00);
+            key = null;
+        }
+    }
+
+    /**
      * readObject is called to restore the state of this key from
      * a stream.
      */
--- a/src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java	Fri Aug 03 14:14:59 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -216,7 +216,7 @@
         if (!(keySpec instanceof PBEKeySpec)) {
             throw new InvalidKeySpecException("Invalid key spec");
         }
-        return new PBEKey((PBEKeySpec)keySpec, type);
+        return new PBEKey((PBEKeySpec)keySpec, type, true);
     }
 
     /**
--- a/src/java.base/share/classes/javax/crypto/spec/PBEKeySpec.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/javax/crypto/spec/PBEKeySpec.java	Fri Aug 03 14:14:59 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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,6 +26,7 @@
 package javax.crypto.spec;
 
 import java.security.spec.KeySpec;
+import java.util.Arrays;
 
 /**
  * A user-chosen password that can be used with password-based encryption
@@ -174,9 +175,7 @@
      */
     public final void clearPassword() {
         if (password != null) {
-            for (int i = 0; i < password.length; i++) {
-                password[i] = ' ';
-            }
+            Arrays.fill(password, ' ');
             password = null;
         }
     }
--- a/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java	Fri Aug 03 14:14:59 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -133,18 +133,20 @@
             throw new UnrecoverableKeyException("Password must not be null");
         }
 
-        KeyProtector keyProtector = new KeyProtector(password);
+        byte[] passwordBytes = convertToBytes(password);
+        KeyProtector keyProtector = new KeyProtector(passwordBytes);
         byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey;
         EncryptedPrivateKeyInfo encrInfo;
-        byte[] plain;
         try {
             encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
+            return keyProtector.recover(encrInfo);
         } catch (IOException ioe) {
             throw new UnrecoverableKeyException("Private key not stored as "
                                                 + "PKCS #8 "
                                                 + "EncryptedPrivateKeyInfo");
+        } finally {
+            Arrays.fill(passwordBytes, (byte) 0x00);
         }
-        return keyProtector.recover(encrInfo);
     }
 
     /**
@@ -253,7 +255,8 @@
                                   Certificate[] chain)
         throws KeyStoreException
     {
-        KeyProtector keyProtector = null;
+        KeyProtector keyProtector;
+        byte[] passwordBytes = null;
 
         if (!(key instanceof java.security.PrivateKey)) {
             throw new KeyStoreException("Cannot store non-PrivateKeys");
@@ -264,7 +267,8 @@
                 entry.date = new Date();
 
                 // Protect the encoding of the key
-                keyProtector = new KeyProtector(password);
+                passwordBytes = convertToBytes(password);
+                keyProtector = new KeyProtector(passwordBytes);
                 entry.protectedPrivKey = keyProtector.protect(key);
 
                 // clone the chain
@@ -280,7 +284,8 @@
         } catch (NoSuchAlgorithmException nsae) {
             throw new KeyStoreException("Key protection algorithm not found");
         } finally {
-            keyProtector = null;
+            if (passwordBytes != null)
+                Arrays.fill(passwordBytes, (byte) 0x00);
         }
     }
 
@@ -793,19 +798,27 @@
     private MessageDigest getPreKeyedHash(char[] password)
         throws NoSuchAlgorithmException, UnsupportedEncodingException
     {
-        int i, j;
 
         MessageDigest md = MessageDigest.getInstance("SHA");
+        byte[] passwdBytes = convertToBytes(password);
+        md.update(passwdBytes);
+        Arrays.fill(passwdBytes, (byte) 0x00);
+        md.update("Mighty Aphrodite".getBytes("UTF8"));
+        return md;
+    }
+
+    /**
+     * Helper method to convert char[] to byte[]
+     */
+
+    private byte[] convertToBytes(char[] password) {
+        int i, j;
         byte[] passwdBytes = new byte[password.length * 2];
         for (i=0, j=0; i<password.length; i++) {
             passwdBytes[j++] = (byte)(password[i] >> 8);
             passwdBytes[j++] = (byte)password[i];
         }
-        md.update(passwdBytes);
-        for (i=0; i<passwdBytes.length; i++)
-            passwdBytes[i] = 0;
-        md.update("Mighty Aphrodite".getBytes("UTF8"));
-        return md;
+        return passwdBytes;
     }
 
     /**
--- a/src/java.base/share/classes/sun/security/provider/KeyProtector.java	Fri Aug 03 11:06:10 2018 +0200
+++ b/src/java.base/share/classes/sun/security/provider/KeyProtector.java	Fri Aug 03 14:14:59 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -35,7 +35,6 @@
 import java.security.UnrecoverableKeyException;
 import java.util.*;
 
-import jdk.internal.ref.CleanerFactory;
 import sun.security.pkcs.PKCS8Key;
 import sun.security.pkcs.EncryptedPrivateKeyInfo;
 import sun.security.x509.AlgorithmId;
@@ -120,32 +119,15 @@
     /**
      * Creates an instance of this class, and initializes it with the given
      * password.
-     *
-     * <p>The password is expected to be in printable ASCII.
-     * Normal rules for good password selection apply: at least
-     * seven characters, mixed case, with punctuation encouraged.
-     * Phrases or words which are easily guessed, for example by
-     * being found in dictionaries, are bad.
      */
-    public KeyProtector(char[] password)
+    public KeyProtector(byte[] passwordBytes)
         throws NoSuchAlgorithmException
     {
-        int i, j;
-
-        if (password == null) {
+        if (passwordBytes == null) {
            throw new IllegalArgumentException("password can't be null");
         }
         md = MessageDigest.getInstance(DIGEST_ALG);
-        // Convert password to byte array, so that it can be digested
-        passwdBytes = new byte[password.length * 2];
-        for (i=0, j=0; i<password.length; i++) {
-            passwdBytes[j++] = (byte)(password[i] >> 8);
-            passwdBytes[j++] = (byte)password[i];
-        }
-        // Use the cleaner to zero the password when no longer referenced
-        final byte[] k = this.passwdBytes;
-        CleanerFactory.cleaner().register(this,
-                () -> java.util.Arrays.fill(k, (byte)0x00));
+        this.passwdBytes = passwordBytes;
     }
 
     /*