# HG changeset patch # User coffeys # Date 1533302099 -3600 # Node ID 53c3b460503c69e7f5cd1312e4834ecbdd4eb1a4 # Parent 0538a5cdb474d4764fcc8fea7d5e06ea4b3676c4 8208583: Better management of internal KeyStore buffers Reviewed-by: weijun diff -r 0538a5cdb474 -r 53c3b460503c src/java.base/share/classes/com/sun/crypto/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 protectedKey. 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 + } + } } } } diff -r 0538a5cdb474 -r 53c3b460503c src/java.base/share/classes/com/sun/crypto/provider/PBEKey.java --- 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 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. */ diff -r 0538a5cdb474 -r 53c3b460503c src/java.base/share/classes/com/sun/crypto/provider/PBEKeyFactory.java --- 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); } /** diff -r 0538a5cdb474 -r 53c3b460503c src/java.base/share/classes/javax/crypto/spec/PBEKeySpec.java --- 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; } } diff -r 0538a5cdb474 -r 53c3b460503c src/java.base/share/classes/sun/security/provider/JavaKeyStore.java --- 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> 8); passwdBytes[j++] = (byte)password[i]; } - md.update(passwdBytes); - for (i=0; iThe 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> 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; } /*