admin@glassfish.java.net

CODE REVIEW: PasswordAdapter.java

From: Lloyd L Chambers <Lloyd.Chambers_at_Sun.COM>
Date: Mon, 04 Jun 2007 15:35:02 -0700

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" );
      }
  }