TIMEOUT: 13:00 PM PST Tuesday June 5, 2007
https://glassfish.dev.java.net/issues/show_bug.cgi?id=3029
I had previously prepared this fix on the 9.0 code base for 8.2; it
was backported and has been in 8.2 for some time. For some reason
the code never went into 9.0. This fix rectifies that omission.
Diffs and modified file attached. I have tested it with all variants
I could think of, including changing the master password and password
aliases multiple times.
Lloyd Chambers
---------------
llcMP:/gf/build/glassfish/appserv-commons lloyd$ cvs diff -w -u src/
java/com/sun/enterprise/security/store/PasswordAdapter.java
Index: src/java/com/sun/enterprise/security/store/PasswordAdapter.java
===================================================================
RCS file: /cvs/glassfish/appserv-commons/src/java/com/sun/enterprise/
security/store/PasswordAdapter.java,v
retrieving revision 1.5
diff -w -u -r1.5 PasswordAdapter.java
--- src/java/com/sun/enterprise/security/store/
PasswordAdapter.java 5 May 2007 05:32:04 -0000 1.5
+++ src/java/com/sun/enterprise/security/store/
PasswordAdapter.java 4 Jun 2007 22:28:30 -0000
@@ -58,11 +58,12 @@
* This class implements an adapter for password manipulation a JCEKS.
* @author Shing Wai Chan
*/
-public class PasswordAdapter {
+public final class PasswordAdapter {
public static final String PASSWORD_ALIAS_KEYSTORE = "domain-
passwords";
- private KeyStore _pwdStore = null;
- private String _keyFile = null;
- private char[] _masterPassword = null;
+
+ private KeyStore _pwdStore;
+ private final File _keyFile;
+ private char[] _masterPassword;
private char[] getMasterPassword() {
return _masterPassword;
@@ -72,6 +73,50 @@
_masterPassword = smp;
}
+ private static final boolean DEBUG = true;
+
+ private void debug( final Object o )
+ {
+ if ( DEBUG )
+ {
+ System.out.println( "PasswordAdapter.debug: " + o );
+ }
+ }
+ private void
+ debugState( final String when)
+ {
+ if ( DEBUG )
+ {
+ final String INDENT = " ";
+ try
+ {
+ String s = "PasswordAdapter Status: " + when + "\n";
+ s = s +INDENT + "Master password: " + new String
( getMasterPassword() ) +"\n";
+ s = s + INDENT + "Key file: " + _keyFile + "\n";
+ s = s +INDENT + "Aliases:\n";
+ final Enumeration<String> aliases = getAliases();
+ while ( aliases.hasMoreElements() )
+ {
+ final String alias = aliases.nextElement();
+ s = s +INDENT + INDENT + alias + "=" +
+ new String( getPasswordForAlias(alias) )
+ "\n";
+ }
+ debug(s);
+ }
+ catch( Throwable t )
+ {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ private static String
+ getDefaultKeyFileName()
+ {
+ return System.getProperty
(SystemPropertyConstants.INSTANCE_ROOT_PROPERTY) +
+ File.separator + "config" + File.separator +
PASSWORD_ALIAS_KEYSTORE;
+ }
+
/**
* Construct a PasswordAdapter with given Shared Master Password,
* SMP using the default keyfile (domain-passwords.jceks)
@@ -81,14 +126,11 @@
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
- public PasswordAdapter(char[] smp)
+ public PasswordAdapter(char[] masterPassword)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
-
- String keyfileName = System.getProperty
(SystemPropertyConstants.INSTANCE_ROOT_PROPERTY) +
- File.separator + "config" + File.separator +
PASSWORD_ALIAS_KEYSTORE;
- init(keyfileName, smp);
+ this( getDefaultKeyFileName(), masterPassword );
}
/**
@@ -101,11 +143,21 @@
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
- public PasswordAdapter(String keyfileName, char[] smp)
+ public PasswordAdapter(
+ final String keyStoreFileName,
+ final char[] masterPassword)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
- init(keyfileName, smp);
+ final File keyStoreFile = new File( keyStoreFileName );
+
+ _pwdStore = loadKeyStore( keyStoreFile, masterPassword);
+
+ // assign these only once the store is good; no need to keep
copies otherwise!
+ _keyFile = keyStoreFile;
+ _masterPassword = masterPassword;
+
+ debugState( "PasswordAdapter constructor" );
}
/**
@@ -118,34 +170,30 @@
* @exception KeyStoreException
* @exception NoSuchAlgorithmException
*/
- private void init(String keyfileName, char[] smp)
+ private static KeyStore
+ loadKeyStore( final File keyStoreFile, final char[]
masterPassword )
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
- _keyFile = keyfileName;
- _pwdStore = KeyStore.getInstance("JCEKS");
- setMasterPassword(smp);
- BufferedInputStream bInput = null;
- File file = new File(keyfileName);
- if (file.exists()) {
- bInput = new BufferedInputStream(new FileInputStream
(file));
- }
+ final KeyStore keyStore = KeyStore.getInstance("JCEKS");
+
+ if ( keyStoreFile.exists() )
+ {
+ // don't buffer keystore; it's tiny anyway
+ final FileInputStream input = new FileInputStream
( keyStoreFile );
try {
- //load must be called with null to initialize an empty
keystore
- _pwdStore.load(bInput, getMasterPassword());
- if (bInput != null) {
- bInput.close();
- bInput = null;
+ keyStore.load( input, masterPassword );
}
- } finally {
- if (bInput != null) {
- try {
- bInput.close();
- } catch(Exception ex) {
- //ignore we are cleaning up
+ finally {
+ input.close();
}
}
+ else
+ {
+ keyStore.load( null, masterPassword );
}
+
+ return keyStore;
}
/**
@@ -156,16 +204,21 @@
* @exception NoSuchAlgorithmException
* @exception UnrecoverableKeyException
*/
- public String getPasswordForAlias(String alias)
+ public synchronized String getPasswordForAlias(final String alias)
throws KeyStoreException, NoSuchAlgorithmException,
- UnrecoverableKeyException {
-
- Key key = _pwdStore.getKey(alias, getMasterPassword());
- if (key != null) {
- return new String(key.getEncoded());
- } else {
- return null;
+ UnrecoverableKeyException
+ {
+ final byte[] bytes = getKeyBytesForAlias( alias,
getMasterPassword() );
+ return bytes == null ? null : new String( bytes );
}
+
+ private byte[]
+ getKeyBytesForAlias( final String alias, final char[]
masterPassword )
+ throws KeyStoreException,
+ NoSuchAlgorithmException, UnrecoverableKeyException
+ {
+ final Key key = _pwdStore.getKey( alias, masterPassword );
-----------
REVISED FILE (minus boilerplate header)
package com.sun.enterprise.security.store;
import com.sun.enterprise.util.SystemPropertyConstants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Enumeration;
/**
* This class implements an adapter for password manipulation a JCEKS.
* @author Shing Wai Chan
*/
public final class PasswordAdapter {
public static final String PASSWORD_ALIAS_KEYSTORE = "domain-
passwords";
private KeyStore _pwdStore;
private final File _keyFile;
private char[] _masterPassword;
private char[] getMasterPassword() {
return _masterPassword;
}
private void setMasterPassword(char[] smp) {
_masterPassword = smp;
}
private static final boolean DEBUG = true;
private void debug( final Object o )
{
if ( DEBUG )
{
System.out.println( "PasswordAdapter.debug: " + o );
}
}
private void
debugState( final String when)
{
if ( DEBUG )
{
final String INDENT = " ";
try
{
String s = "PasswordAdapter Status: " + when + "\n";
s = s +INDENT + "Master password: " + new String
( getMasterPassword() ) +"\n";
s = s + INDENT + "Key file: " + _keyFile + "\n";
s = s +INDENT + "Aliases:\n";
final Enumeration<String> aliases = getAliases();
while ( aliases.hasMoreElements() )
{
final String alias = aliases.nextElement();
s = s +INDENT + INDENT + alias + "=" +
new String( getPasswordForAlias(alias) )
+ "\n";
}
debug(s);
}
catch( Throwable t )
{
t.printStackTrace();
}
}
}
private static String
getDefaultKeyFileName()
{
return System.getProperty
(SystemPropertyConstants.INSTANCE_ROOT_PROPERTY) +
File.separator + "config" + File.separator +
PASSWORD_ALIAS_KEYSTORE;
}
/**
* Construct a PasswordAdapter with given Shared Master Password,
* SMP using the default keyfile (domain-passwords.jceks)
* @param smp master password
* @throws CertificateException
* @throws IOException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
public PasswordAdapter(char[] masterPassword)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
this( getDefaultKeyFileName(), masterPassword );
}
/**
* Construct a PasswordAdapter with given Shared Master Password,
* SMP.
* @param keyfileName the jceks key file name
* @param smp master password
* @throws CertificateException
* @throws IOException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
public PasswordAdapter(
final String keyStoreFileName,
final char[] masterPassword)
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
final File keyStoreFile = new File( keyStoreFileName );
_pwdStore = loadKeyStore( keyStoreFile, masterPassword);
// assign these only once the store is good; no need to keep
copies otherwise!
_keyFile = keyStoreFile;
_masterPassword = masterPassword;
debugState( "PasswordAdapter constructor" );
}
/**
* Construct a PasswordAdapter with given Shared Master Password,
* SMP.
* @param keyfileName the jceks key file name
* @param smp the master password
* @exception CertificateException
* @exception IOException
* @exception KeyStoreException
* @exception NoSuchAlgorithmException
*/
private static KeyStore
loadKeyStore( final File keyStoreFile, final char[]
masterPassword )
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
final KeyStore keyStore = KeyStore.getInstance("JCEKS");
if ( keyStoreFile.exists() )
{
// don't buffer keystore; it's tiny anyway
final FileInputStream input = new FileInputStream
( keyStoreFile );
try {
keyStore.load( input, masterPassword );
}
finally {
input.close();
}
}
else
{
keyStore.load( null, masterPassword );
}
return keyStore;
}
/**
* This methods returns password String for a given alias and SMP.
* @param alias
* @return corresponding password or null if the alias does not
exist.
* @exception KeyStoreException
* @exception NoSuchAlgorithmException
* @exception UnrecoverableKeyException
*/
public synchronized String getPasswordForAlias(final String alias)
throws KeyStoreException, NoSuchAlgorithmException,
UnrecoverableKeyException
{
final byte[] bytes = getKeyBytesForAlias( alias,
getMasterPassword() );
return bytes == null ? null : new String( bytes );
}
private byte[]
getKeyBytesForAlias( final String alias, final char[]
masterPassword )
throws KeyStoreException,
NoSuchAlgorithmException, UnrecoverableKeyException
{
final Key key = _pwdStore.getKey( alias, masterPassword );
return key == null ? null : key.getEncoded();
}
/**
* This methods returns password SecretKey for a given alias and
SMP.
* @param alias
* @return corresponding password SecretKey or
* null if the alias does not exist.
* @exception KeyStoreException
* @exception NoSuchAlgorithmException
* @exception UnrecoverableKeyException
*/
public synchronized SecretKey getPasswordSecretKeyForAlias
(String alias)
throws KeyStoreException, NoSuchAlgorithmException,
UnrecoverableKeyException {
return (SecretKey)_pwdStore.getKey(alias, getMasterPassword());
}
/**
* See if the given alias exists
* @param alias the alias name
* @return true if the alias exists in the keystore
*/
public synchronized boolean aliasExists( final String alias)
throws KeyStoreException
{
return _pwdStore.containsAlias(alias);
}
/**
* Remove an alias from the keystore
* @param alias The name of the alias to remove
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
*/
public synchronized void removeAlias( final String alias)
throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException
{
_pwdStore.deleteEntry(alias);
writeStore();
}
/**
* Return the aliases from the keystore.
* @return An enumeration containing all the aliases in the
keystore.
*/
public synchronized Enumeration<String> getAliases()
throws KeyStoreException
{
return _pwdStore.aliases();
}
private static synchronized void writeStore(
final KeyStore keyStore,
final File file,
final char[] masterPassword )
throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException
{
// this can potentially wipe out the original
// while changePassword() uses a temporary file,
// setPasswordForAlias() and removeAlias() do not.
final FileOutputStream out = new FileOutputStream(file);
try
{
keyStore.store( out, masterPassword);
}
finally
{
out.close();
}
}
/**
* Writes the keystore to disk
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
*/
private void writeStore() throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException
{
writeStore( _pwdStore, _keyFile, getMasterPassword() );
}
/**
* This methods set alias, secretKey into JCEKS keystore.
* @param alias
* @param secretKey
* @exception CertificateException
* @exception IOException
* @exception KeyStoreException
* @exception NoSuchAlgorithmException
*/
public synchronized void setPasswordForAlias(String alias, byte
[] keyBytes)
throws CertificateException, IOException, KeyStoreException,
NoSuchAlgorithmException
{
debugState( "BEFORE setPasswordForAlias" );
setPasswordForAlias( alias, keyBytes, getMasterPassword() );
writeStore();
debugState( "AFTER setPasswordForAlias" );
}
private void
setPasswordForAlias(
final String alias,
final byte[] keyBytes,
final char[] masterPassword )
throws CertificateException, IOException,
KeyStoreException, NoSuchAlgorithmException
{
final Key key = new SecretKeySpec(keyBytes, "AES");
_pwdStore.setKeyEntry( alias, key, masterPassword, null);
}
/**
Make a new KeyStore with all the keys secured with the new
master password.
*/
private KeyStore
newKeyStore( final char[] newMasterPassword)
throws KeyStoreException, IOException,
NoSuchAlgorithmException,
CertificateException, UnrecoverableKeyException
{
final char[] oldMasterPassword = getMasterPassword();
final KeyStore oldStore = _pwdStore;
final KeyStore newStore = KeyStore.getInstance("JCEKS",
_pwdStore.getProvider() );
newStore.load( null, newMasterPassword );
final Enumeration<String> aliasesEnum = oldStore.aliases();
while ( aliasesEnum.hasMoreElements() )
{
final String alias = aliasesEnum.nextElement();
if ( ! oldStore.isKeyEntry( alias ) )
{
throw new IllegalArgumentException( "Expecting keys
only" );
}
final Key key = oldStore.getKey( alias,
oldMasterPassword );
newStore.setKeyEntry( alias, key, newMasterPassword, null);
}
return newStore;
}
/**
Changes the keystore password, including the encoding of the
keys within it.
<p>
There are several error conditions that could occur:
<ul>
<li>Problem extracting existing alias keys with new
ones.</li>
<li>Problem writing the keystore, including destroying
it if an I/O problem occurs.</li>
<li></li>
</ul>
For these reasons, we make a new KeyStore and write it, then
swap it with the old
one.
* @param newpassword the new keystore password
* @throws KeyStoreException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
*/
public synchronized void changePassword(char[] newMasterPassword)
throws KeyStoreException, IOException,
NoSuchAlgorithmException,
CertificateException, UnrecoverableKeyException
{
final char[] oldMasterPassword = getMasterPassword();
debug( "Changing master password from " + new String
(oldMasterPassword) + " to " + new String(newMasterPassword) );
debugState( "BEFORE changing master password" );
final KeyStore oldStore = _pwdStore;
final KeyStore newStore = newKeyStore( newMasterPassword );
// 'newStore' is now complete; rename the old KeyStore, the
write the new one in its place
final File saveOld = new File( _keyFile.toString() +
".save" );
if ( ! _keyFile.renameTo( saveOld ) )
{
final String msg = "Can't rename " + _keyFile + " to
" + saveOld;
debug( msg );
throw new IOException( msg );
}
try
{
debug( "Writing KeyStore to " + _keyFile + " using new
master password = " + new String(newMasterPassword) );
writeStore( newStore, _keyFile, newMasterPassword );
_pwdStore = newStore;
_masterPassword = newMasterPassword;
debug( "KeyStore written successfully" );
}
catch( final Throwable t )
{
try {
saveOld.renameTo( _keyFile );
}
catch( Throwable tt )
{
/* best effort failed */
}
throw new RuntimeException( "Can't write new KeyStore",
t );
}
try
{
debug( "deleting old keystore " + saveOld );
saveOld.delete();
// debug( "done deleting old keystore " saveOld );
}
catch( Throwable t )
{
throw new RuntimeException( "Can't remove old file \""
+ _keyFile + "\"", t );
}
debugState( "AFTER changing master password" );
loadKeyStore( _keyFile, getMasterPassword() );
debugState( "AFTER forcing reload from file" );
}
}