db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kahat...@apache.org
Subject svn commit: r1221666 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/impl/jdbc/authentication/ testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ testing/org/apache/derbyTesting/functionTests/te...
Date Wed, 21 Dec 2011 10:33:01 GMT
Author: kahatlen
Date: Wed Dec 21 10:33:01 2011
New Revision: 1221666

URL: http://svn.apache.org/viewvc?rev=1221666&view=rev
Log:
DERBY-5539: Harden password hashing in the builtin authentication service

Add random salt before hashing the credentials, and apply the hash
function multiple times.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java Wed Dec
21 10:33:01 2011
@@ -828,6 +828,31 @@ public interface Property { 
     public static final String AUTHENTICATION_BUILTIN_ALGORITHM_FALLBACK =
             "SHA-1";
 
+    /**
+     * Property that specifies the number of bytes with random salt to use
+     * when hashing credentials using the configurable hash authentication
+     * scheme.
+     */
+    public static final String AUTHENTICATION_BUILTIN_SALT_LENGTH =
+            "derby.authentication.builtin.saltLength";
+
+    /**
+     * The default value for derby.authentication.builtin.saltLength.
+     */
+    public static final int AUTHENTICATION_BUILTIN_SALT_LENGTH_DEFAULT = 16;
+
+    /**
+     * Property that specifies the number of times to apply the hash
+     * function in the configurable hash authentication scheme.
+     */
+    public static final String AUTHENTICATION_BUILTIN_ITERATIONS =
+            "derby.authentication.builtin.iterations";
+
+    /**
+     * Default value for derby.authentication.builtin.iterations.
+     */
+    public static final int AUTHENTICATION_BUILTIN_ITERATIONS_DEFAULT = 1000;
+
 	/*
 	** Log
 	*/

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
Wed Dec 21 10:33:01 2011
@@ -55,6 +55,7 @@ import java.security.NoSuchAlgorithmExce
 
 import java.io.Serializable;
 import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
 import java.util.Dictionary;
 import java.util.Properties;
 import org.apache.derby.iapi.reference.SQLState;
@@ -144,6 +145,16 @@ public abstract class AuthenticationServ
     public static final String ID_PATTERN_CONFIGURABLE_HASH_SCHEME = "3b61";
 
     /**
+     * Pattern that is prefixed to the stored password in the configurable
+     * hash authentication scheme if key stretching has been applied. This
+     * scheme extends the configurable hash scheme by adding a random salt and
+     * applying the hash function multiple times when generating the hashed
+     * token.
+     */
+    public static final String
+            ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME = "3b62";
+
+    /**
         Userid with Strong password substitute DRDA security mechanism
     */
     protected static final int SECMEC_USRSSBPWD = 8;
@@ -520,9 +531,14 @@ public abstract class AuthenticationServ
 
     /**
      * <p>
-     * Encrypt a password using the specified hash algorithm and with the
-     * user name as extra salt. The algorithm must be supported by one of
-     * the registered security providers in the JVM.
+     * Hash credentials using the specified hash algorithm, possibly performing
+     * key stretching by adding random salt and applying the hash function
+     * multiple times.
+     * </p>
+     *
+     * <p>
+     * The algorithm must be supported by one of the registered security
+     * providers in the JVM.
      * </p>
      *
      * <p>
@@ -532,18 +548,31 @@ public abstract class AuthenticationServ
      * @param user the user whose password to encrypt
      * @param password the plain text password
      * @param algorithm the hash algorithm to use
+     * @param salt random salt to add to the credentials (possibly {@code null})
+     * @param iterations the number of times to apply the hash function
      * @return a digest of the user name and password formatted as a string,
      *         or {@code null} if {@code password} is {@code null}
      * @throws StandardException if the specified algorithm is not supported
      */
     String encryptPasswordConfigurableScheme(
-            String user, String password, String algorithm)
+            String user, String password, String algorithm,
+            byte[] salt, int iterations)
             throws StandardException
     {
         if (password == null) {
             return null;
         }
 
+        byte[] userBytes;
+        byte[] passwordBytes;
+        try {
+            userBytes = user.getBytes(ENCODING);
+            passwordBytes = password.getBytes(ENCODING);
+        } catch (UnsupportedEncodingException uee) {
+            // UTF-8 should always be available, so this should never happen.
+            throw StandardException.plainWrapException(uee);
+        }
+
         MessageDigest md;
         try {
             md = MessageDigest.getInstance(algorithm);
@@ -552,21 +581,36 @@ public abstract class AuthenticationServ
                     SQLState.DIGEST_NO_SUCH_ALGORITHM, nsae, algorithm);
         }
 
-        md.reset();
-
-        try {
-            md.update(user.getBytes(ENCODING));
-            md.update(password.getBytes(ENCODING));
-        } catch (UnsupportedEncodingException uee) {
-            // UTF-8 should always be available, so this should never happen.
-            throw StandardException.plainWrapException(uee);
+        byte[] digest = null;
+        for (int i = 0; i < iterations; i++) {
+            md.reset();
+            if (digest != null) {
+                md.update(digest);
+            }
+            md.update(userBytes);
+            md.update(passwordBytes);
+            if (salt != null) {
+                md.update(salt);
+            }
+            digest = md.digest();
         }
 
-        byte[] digest = md.digest();
-
-        return ID_PATTERN_CONFIGURABLE_HASH_SCHEME +
+        if ((salt == null || salt.length == 0) && iterations == 1) {
+            // No salt was used, and only a single iteration, which is
+            // identical to the default hashing scheme in 10.6-10.8. Generate
+            // a token on a format compatible with those old versions.
+            return ID_PATTERN_CONFIGURABLE_HASH_SCHEME +
                 StringUtil.toHexString(digest, 0, digest.length) +
                 SEPARATOR_CHAR + algorithm;
+        } else {
+            // Salt and/or multiple iterations was used, so we need to add
+            // those parameters to the token in order to verify the credentials
+            // later.
+            return ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME +
+                StringUtil.toHexString(digest, 0, digest.length) +
+                SEPARATOR_CHAR + StringUtil.toHexString(salt, 0, salt.length) +
+                SEPARATOR_CHAR + iterations + SEPARATOR_CHAR + algorithm;
+        }
     }
 
     /**
@@ -596,14 +640,19 @@ public abstract class AuthenticationServ
                                                 String password,
                                                 Dictionary props)
             throws StandardException {
+        DataDictionary dd = getDataDictionary();
 
         // Support for configurable hash algorithm was added in Derby 10.6, so
         // we don't want to store a hash using the new scheme if the database
         // is running in soft upgrade and may be used with an older version
         // later.
         boolean supportConfigurableHash =
-                getDataDictionary().checkVersion(
-                        DataDictionary.DD_VERSION_DERBY_10_6, null);
+                dd.checkVersion(DataDictionary.DD_VERSION_DERBY_10_6, null);
+
+        // Support for key stretching was added in Derby 10.9, so don't use it
+        // if the database may still be used with an older version.
+        boolean supportKeyStretching =
+                dd.checkVersion(DataDictionary.DD_VERSION_DERBY_10_9, null);
 
         if (supportConfigurableHash) {
             String algorithm = (String)
@@ -612,8 +661,20 @@ public abstract class AuthenticationServ
                         Property.AUTHENTICATION_BUILTIN_ALGORITHM);
 
             if (algorithm != null && algorithm.length() > 0) {
+                byte[] salt = null;
+                int iterations = 1;
+
+                if (supportKeyStretching) {
+                    salt = generateRandomSalt(props);
+                    iterations = getIntProperty(
+                            props,
+                            Property.AUTHENTICATION_BUILTIN_ITERATIONS,
+                            Property.AUTHENTICATION_BUILTIN_ITERATIONS_DEFAULT,
+                            1, Integer.MAX_VALUE);
+                }
+
                 return encryptPasswordConfigurableScheme(
-                        user, password, algorithm);
+                        user, password, algorithm, salt, iterations);
             }
         }
 
@@ -621,6 +682,60 @@ public abstract class AuthenticationServ
     }
 
     /**
+     * Get the value of an integer property.
+     *
+     * @param props database properties
+     * @param key the key of the property
+     * @param defaultValue which value to return if the property is not set,
+     *   or if the property value is not in the valid range
+     * @param minValue lowest property value to accept
+     * @param maxValue highest property value to accept
+     * @return the value of the property
+     */
+    private int getIntProperty(
+            Dictionary props, String key,
+            int defaultValue, int minValue, int maxValue) {
+
+        String sVal = (String) PropertyUtil.getPropertyFromSet(props, key);
+
+        if (sVal != null) {
+            try {
+                int i = Integer.parseInt(sVal);
+                if (i >= minValue && i <= maxValue) {
+                    return i;
+                }
+            } catch (NumberFormatException nfe) {
+                // By convention, Derby ignores property values that cannot be
+                // parsed. Use the default value instead.
+            }
+        }
+
+        return defaultValue;
+    }
+
+    /**
+     * Generate an array of random bytes to use as salt when hashing
+     * credentials.
+     *
+     * @param props database properties that possibly specify the desired
+     *   length of the salt
+     * @return random bytes
+     */
+    private byte[] generateRandomSalt(Dictionary props) {
+        int saltLength = getIntProperty(
+                props,
+                Property.AUTHENTICATION_BUILTIN_SALT_LENGTH,
+                Property.AUTHENTICATION_BUILTIN_SALT_LENGTH_DEFAULT,
+                0, Integer.MAX_VALUE);
+
+        SecureRandom random = new SecureRandom();
+        byte[] salt = new byte[saltLength];
+        random.nextBytes(salt);
+
+        return salt;
+    }
+
+    /**
      * Find the data dictionary for the current connection.
      *
      * @return the {@code DataDictionary} for the current connection

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java
Wed Dec 21 10:33:01 2011
@@ -276,7 +276,21 @@ public final class BasicAuthenticationSe
                         ID_PATTERN_CONFIGURABLE_HASH_SCHEME)) {
             String algorithm = storedPassword.substring(
                     storedPassword.indexOf(SEPARATOR_CHAR) + 1);
-            return encryptPasswordConfigurableScheme(user, password, algorithm);
+            return encryptPasswordConfigurableScheme(
+                    user, password, algorithm, null, 1);
+        } else if (storedPassword.startsWith(
+                        ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME)) {
+            int saltPos = storedPassword.indexOf(SEPARATOR_CHAR) + 1;
+            int iterPos = storedPassword.indexOf(SEPARATOR_CHAR, saltPos) + 1;
+            int algoPos = storedPassword.indexOf(SEPARATOR_CHAR, iterPos) + 1;
+
+            byte[] salt = StringUtil.fromHexString(
+                    storedPassword, saltPos, iterPos - saltPos - 1);
+            int iterations = Integer.parseInt(
+                    storedPassword.substring(iterPos, algoPos - 1));
+            String algorithm = storedPassword.substring(algoPos);
+            return encryptPasswordConfigurableScheme(
+                    user, password, algorithm, salt, iterations);
         } else {
             if (SanityManager.DEBUG) {
                 SanityManager.THROWASSERT(

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java
Wed Dec 21 10:33:01 2011
@@ -64,6 +64,12 @@ public class AuthenticationTest extends 
     private static final String BUILTIN_ALGO_PROP =
             "derby.authentication.builtin.algorithm";
 
+    private static final String BUILTIN_SALT_LENGTH_PROP =
+            "derby.authentication.builtin.saltLength";
+
+    private static final String BUILTIN_ITERATIONS_PROP =
+            "derby.authentication.builtin.iterations";
+
     private static final String USER_PREFIX = "derby.user.";
 
     private static final String NO_SUCH_ALGO = "XBCXW";
@@ -1139,32 +1145,64 @@ public class AuthenticationTest extends 
                 continue;
             }
 
-            setDatabaseProperty(BUILTIN_ALGO_PROP, algo);
+            // Test the algorithm with and without key stretching (added in
+            // DERBY-5539)
+            testVariousBuiltinAlgorithms(algo, true);
+            testVariousBuiltinAlgorithms(algo, false);
+        }
+    }
+
+    /**
+     * Worker method for {@link #testVariousBuiltinAlgorithms()}.
+     *
+     * @param algo the name of the hash algorithm to test
+     * @param keyStretching whether or not to use the authentication scheme that
+     *   performs key stretching
+     */
+    private void testVariousBuiltinAlgorithms(String algo, boolean keyStretching)
+            throws SQLException {
+        setDatabaseProperty(BUILTIN_ALGO_PROP, algo);
+
+        if (keyStretching) {
+            // Unset the properties specifying salt length and iterations, so
+            // we get the default scheme (with key stretching)
+            setDatabaseProperty(BUILTIN_SALT_LENGTH_PROP, null);
+            setDatabaseProperty(BUILTIN_ITERATIONS_PROP, null);
+        } else {
+            // Disable salt and use a single iteration
+            setDatabaseProperty(BUILTIN_SALT_LENGTH_PROP, "0");
+            setDatabaseProperty(BUILTIN_ITERATIONS_PROP, "1");
+        }
+
+        for (int i = 0; i < USERS.length; i++) {
+            String user = USERS[i];
+            String password = user + PASSWORD_SUFFIX;
+            String userProp = USER_PREFIX + user;
+
+            // Set the password for the user
+            setDatabaseProperty(userProp, password);
 
-            for (int j = 0; j < USERS.length; j++) {
-                String user = USERS[j];
-                String password = user + PASSWORD_SUFFIX;
-                String userProp = USER_PREFIX + user;
-
-                // Set the password for the user
-                setDatabaseProperty(userProp, password);
-
-                // Get the stored password token and verify that it
-                // hashed the way we expect it to be
-                String token = getDatabaseProperty(userProp);
-                if (algo == null) {
-                    assertTrue("Expected old authentication schema: " + token,
-                               token.startsWith("3b60"));
+            // Get the stored password token and verify that it
+            // hashed the way we expect it to be
+            String token = getDatabaseProperty(userProp);
+            if (algo == null) {
+                assertTrue("Expected old authentication scheme: " + token,
+                           token.startsWith("3b60"));
+            } else {
+                if (keyStretching) {
+                    assertTrue("Expected configurable hash scheme with "+
+                               "key stretching: " + token,
+                               token.startsWith("3b62"));
                 } else {
-                    assertTrue("Expected configurable hash schema: " + token,
+                    assertTrue("Expected configurable hash scheme: " + token,
                                token.startsWith("3b61"));
-                    assertTrue("Expected algorithm " + algo + ":" + token,
-                               token.endsWith(":" + algo));
                 }
-
-                // Verify that we can still connect as that user
-                openDefaultConnection(user, password).close();
+                assertTrue("Expected algorithm " + algo + ":" + token,
+                           token.endsWith(":" + algo));
             }
+
+            // Verify that we can still connect as that user
+            openDefaultConnection(user, password).close();
         }
     }
 

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java
Wed Dec 21 10:33:01 2011
@@ -22,24 +22,16 @@ package org.apache.derbyTesting.function
 
 import org.apache.derbyTesting.junit.SupportFilesSetup;
 
-import org.apache.derbyTesting.junit.JDBCDataSource;
 import java.lang.reflect.Method;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.sql.Connection;
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 
-import javax.sql.DataSource;
-
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
 import org.apache.derby.catalog.types.RoutineAliasInfo;
 import org.apache.derby.catalog.TypeDescriptor;
-import org.apache.derbyTesting.junit.JDBC;
-
 
 /**
  * Upgrade test cases for 10.6.
@@ -71,7 +63,7 @@ public class Changes10_6 extends Upgrade
     private static final   String CREATE_TYPE_DDL = "create type fooType external name 'mypackage.foo'
language java\n";
     private static final   String DROP_TYPE_DDL = "drop type fooType restrict\n";
 
-    private static final String HASH_ALGORITHM_PROPERTY =
+    static final String HASH_ALGORITHM_PROPERTY =
             "derby.authentication.builtin.algorithm";
 
     public Changes10_6(String name) {
@@ -335,145 +327,4 @@ public class Changes10_6 extends Upgrade
         assertNull(getDatabaseProperty(HASH_ALGORITHM_PROPERTY));
     }
 
-    /**
-     * Make sure builtin authentication only uses the new configurable hash
-     * scheme in hard-upgraded databases. See DERBY-4483.
-     */
-    public void testBuiltinAuthenticationWithConfigurableHash()
-            throws SQLException {
-
-        // This test needs to enable authentication, which is not supported
-        // in the default database for the upgrade tests, so roll our own.
-        DataSource ds = JDBCDataSource.getDataSourceLogical("BUILTIN_10_6");
-
-        // Add create=true or upgrade=true, as appropriate, since we don't
-        // get this for free when we don't use the default database.
-        if (getPhase() == PH_CREATE) {
-            JDBCDataSource.setBeanProperty(ds, "createDatabase", "create");
-        } else if (getPhase() == PH_HARD_UPGRADE) {
-            JDBCDataSource.setBeanProperty(
-                    ds, "connectionAttributes", "upgrade=true");
-        }
-
-        // Connect as database owner, possibly creating or upgrading the
-        // database.
-        Connection c = ds.getConnection("dbo", "the boss");
-
-        // Let's first verify that all the users can connect after the changes
-        // in the previous phase. Would for instance fail in post soft upgrade
-        // if soft upgrade saved passwords using the new scheme.
-        verifyCanConnect(ds);
-
-        CallableStatement setProp = c.prepareCall(
-                "call syscs_util.syscs_set_database_property(?, ?)");
-
-        if (getPhase() == PH_CREATE) {
-            // The database is being created. Make sure that builtin
-            // authentication is enabled.
-
-            setProp.setString(1, "derby.connection.requireAuthentication");
-            setProp.setString(2, "true");
-            setProp.execute();
-
-            setProp.setString(1, "derby.authentication.provider");
-            setProp.setString(2, "BUILTIN");
-            setProp.execute();
-        }
-
-        // Set (or reset) passwords for all users.
-        setPasswords(setProp);
-        setProp.close();
-
-        // We should still be able to connect.
-        verifyCanConnect(ds);
-
-        // Check that the passwords are stored using the expected scheme (new
-        // configurable hash scheme in hard upgrade, old scheme otherwise).
-        verifyPasswords(c, getPhase() == PH_HARD_UPGRADE);
-
-        c.close();
-
-        // The framework doesn't know how to shutdown a database using
-        // authentication, so do it manually as database owner here.
-        JDBCDataSource.setBeanProperty(ds, "user", "dbo");
-        JDBCDataSource.setBeanProperty(ds, "password", "the boss");
-        JDBCDataSource.shutdownDatabase(ds);
-    }
-
-    /**
-     * Information about users for the test of builtin authentication with
-     * configurable hash algorithm. Two-dimensional array of strings where
-     * each row contains (1) a user name, (2) a password, (3) the name of a
-     * digest algorithm with which the password should be hashed, (4) the
-     * hashed password when the old scheme is used, and (5) the hashed
-     * password when the new scheme is used.
-     */
-    private static final String[][] USERS = {
-        { "dbo", "the boss", null,
-                  "3b6071d99b1d48ab732e75a8de701b6c77632db65898",
-                  "3b6071d99b1d48ab732e75a8de701b6c77632db65898"
-        },
-        { "pat", "postman", "MD5",
-                  "3b609129e181a7f7527697235c8aead65c461a0257f3",
-                  "3b61aaca567ed43d1ba2e6402cbf1a723407:MD5"
-        },
-        { "sam", "fireman", "SHA-1",
-                  "3b609e5173cfa03620061518adc92f2a58c7b15cf04f",
-                  "3b6197160362c0122fcd7a63a9da58fd0781140901fb:SHA-1"
-        },
-    };
-
-    /**
-     * Set the passwords for all users specified in {@code USERS}.
-     *
-     * @param cs a callable statement that sets database properties
-     */
-    private void setPasswords(CallableStatement cs) throws SQLException {
-        for (int i = 0; i < USERS.length; i++) {
-            // Use the specified algorithm, if possible. (Will be ignored if
-            // the data dictionary doesn't support the new scheme.)
-            cs.setString(1, HASH_ALGORITHM_PROPERTY);
-            cs.setString(2, USERS[i][2]);
-            cs.execute();
-            // Set the password.
-            cs.setString(1, "derby.user." + USERS[i][0]);
-            cs.setString(2, USERS[i][1]);
-            cs.execute();
-        }
-    }
-
-    /**
-     * Verify that all passwords for the users in {@code USERS} are stored
-     * as expected. Raise an assert failure on mismatch.
-     *
-     * @param c a connection to the database
-     * @param newScheme if {@code true}, the passwords are expected to have
-     * been hashed with the new scheme; otherwise, the passwords are expected
-     * to have been hashed with the old scheme
-     */
-    private void verifyPasswords(Connection c, boolean newScheme)
-            throws SQLException {
-        PreparedStatement ps = c.prepareStatement(
-                "values syscs_util.syscs_get_database_property(?)");
-        for (int i = 0; i < USERS.length; i++) {
-            String expectedToken = USERS[i][newScheme ? 4 : 3];
-            ps.setString(1, "derby.user." + USERS[i][0]);
-            JDBC.assertSingleValueResultSet(ps.executeQuery(), expectedToken);
-        }
-        ps.close();
-    }
-
-    /**
-     * Verify that all users specified in {@code USERS} can connect to the
-     * database.
-     *
-     * @param ds a data source for connecting to the database
-     * @throws SQLException if one of the users cannot connect to the database
-     */
-    private void verifyCanConnect(DataSource ds) throws SQLException {
-        for (int i = 0; i < USERS.length; i++) {
-            Connection c = ds.getConnection(USERS[i][0], USERS[i][1]);
-            c.close();
-        }
-    }
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
Wed Dec 21 10:33:01 2011
@@ -20,19 +20,21 @@ limitations under the License.
 */
 package org.apache.derbyTesting.functionTests.tests.upgradeTests;
 
-import org.apache.derbyTesting.junit.SupportFilesSetup;
-
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.SQLWarning;
 import java.sql.Statement;
-import java.sql.ResultSet;
-import java.util.HashSet;
-import java.util.Set;
+
+import javax.sql.DataSource;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
 import org.apache.derbyTesting.junit.JDBC;
+import org.apache.derbyTesting.junit.JDBCDataSource;
+import org.apache.derbyTesting.junit.SupportFilesSetup;
 
 
 /**
@@ -237,4 +239,166 @@ public class Changes10_9 extends Upgrade
         
     }
     
+    /**
+     * Make sure builtin authentication doesn't use a hash scheme that's not
+     * supported by the old version until the database has been hard upgraded.
+     * See DERBY-4483 and DERBY-5539.
+     */
+    public void testBuiltinAuthenticationWithConfigurableHash()
+            throws SQLException {
+
+        // This test needs to enable authentication, which is not supported
+        // in the default database for the upgrade tests, so roll our own.
+        DataSource ds = JDBCDataSource.getDataSourceLogical("BUILTIN_10_9");
+
+        // Add create=true or upgrade=true, as appropriate, since we don't
+        // get this for free when we don't use the default database.
+        if (getPhase() == PH_CREATE) {
+            JDBCDataSource.setBeanProperty(ds, "createDatabase", "create");
+        } else if (getPhase() == PH_HARD_UPGRADE) {
+            JDBCDataSource.setBeanProperty(
+                    ds, "connectionAttributes", "upgrade=true");
+        }
+
+        // Connect as database owner, possibly creating or upgrading the
+        // database.
+        Connection c = ds.getConnection("dbo", "the boss");
+
+        // Let's first verify that all the users can connect after the changes
+        // in the previous phase. Would fail for instance in post soft upgrade
+        // if soft upgrade saved passwords using the new scheme.
+        verifyCanConnect(ds);
+
+        CallableStatement setProp = c.prepareCall(
+                "call syscs_util.syscs_set_database_property(?, ?)");
+
+        if (getPhase() == PH_CREATE) {
+            // The database is being created. Make sure that builtin
+            // authentication is enabled.
+            setProp.setString(1, "derby.connection.requireAuthentication");
+            setProp.setString(2, "true");
+            setProp.execute();
+
+            setProp.setString(1, "derby.authentication.provider");
+            setProp.setString(2, "BUILTIN");
+            setProp.execute();
+
+            // Set the length of the random salt to 0 to ensure that the
+            // hashed token doesn't vary between test runs.
+            setProp.setString(1, "derby.authentication.builtin.saltLength");
+            setProp.setInt(2, 0);
+            setProp.execute();
+        }
+
+        // Set (or reset) passwords for all users.
+        setPasswords(setProp);
+        setProp.close();
+
+        // We should still be able to connect.
+        verifyCanConnect(ds);
+
+        // Check that the passwords are stored using the expected scheme (new
+        // configurable hash scheme in hard upgrade, old scheme otherwise).
+        verifyPasswords(c);
+
+        c.close();
+
+        // The framework doesn't know how to shutdown a database using
+        // authentication, so do it manually as database owner here.
+        JDBCDataSource.setBeanProperty(ds, "user", "dbo");
+        JDBCDataSource.setBeanProperty(ds, "password", "the boss");
+        JDBCDataSource.shutdownDatabase(ds);
+    }
+
+    /**
+     * Information about users for the test of builtin authentication with
+     * configurable hash algorithm. Two-dimensional array of strings where
+     * each row contains (1) a user name, (2) a password, (3) the name of a
+     * digest algorithm with which the password should be hashed, (4) the
+     * hashed password when the old scheme is used, (5) the hashed password
+     * when the new, configurable hash scheme is used in databases that
+     * don't support the key-stretching extension (DERBY-5539), and (6) the
+     * hashed password when configurable hash with key stretching is used.
+     */
+    private static final String[][] USERS = {
+        { "dbo", "the boss", null,
+                  "3b6071d99b1d48ab732e75a8de701b6c77632db65898",
+                  "3b6071d99b1d48ab732e75a8de701b6c77632db65898",
+                  "3b6071d99b1d48ab732e75a8de701b6c77632db65898",
+        },
+        { "pat", "postman", "MD5",
+                  "3b609129e181a7f7527697235c8aead65c461a0257f3",
+                  "3b61aaca567ed43d1ba2e6402cbf1a723407:MD5",
+                  "3b624f4b0d7f3d2330c1db98a2000c62b5cd::1000:MD5",
+        },
+        { "sam", "fireman", "SHA-1",
+                  "3b609e5173cfa03620061518adc92f2a58c7b15cf04f",
+                  "3b6197160362c0122fcd7a63a9da58fd0781140901fb:SHA-1",
+                  "3b62a2d88ffac5332219116ab53e29dd3b9e1222e990::1000:SHA-1",
+        },
+    };
+
+    /**
+     * Set the passwords for all users specified in {@code USERS}.
+     *
+     * @param cs a callable statement that sets database properties
+     */
+    private void setPasswords(CallableStatement cs) throws SQLException {
+        for (int i = 0; i < USERS.length; i++) {
+            // Use the specified algorithm, if possible. (Will be ignored if
+            // the data dictionary doesn't support the new scheme.)
+            cs.setString(1, Changes10_6.HASH_ALGORITHM_PROPERTY);
+            cs.setString(2, USERS[i][2]);
+            cs.execute();
+            // Set the password.
+            cs.setString(1, "derby.user." + USERS[i][0]);
+            cs.setString(2, USERS[i][1]);
+            cs.execute();
+        }
+    }
+
+    /**
+     * Verify that all passwords for the users in {@code USERS} are stored
+     * as expected. Raise an assert failure on mismatch.
+     *
+     * @param c a connection to the database
+     */
+    private void verifyPasswords(Connection c)
+            throws SQLException {
+        int pwIdx;
+        if (getPhase() == PH_HARD_UPGRADE) {
+            // Expect configurable hash scheme with key stretching in fully
+            // upgraded databases.
+            pwIdx = 5;
+        } else if (oldAtLeast(10, 6)) {
+            // Databases whose dictionary is at least version 10.6 support
+            // configurable hash without key stretching.
+            pwIdx = 4;
+        } else {
+            // Older databases only support the old scheme based on SHA-1.
+            pwIdx = 3;
+        }
+        PreparedStatement ps = c.prepareStatement(
+                "values syscs_util.syscs_get_database_property(?)");
+        for (int i = 0; i < USERS.length; i++) {
+            String expectedToken = USERS[i][pwIdx];
+            ps.setString(1, "derby.user." + USERS[i][0]);
+            JDBC.assertSingleValueResultSet(ps.executeQuery(), expectedToken);
+        }
+        ps.close();
+    }
+
+    /**
+     * Verify that all users specified in {@code USERS} can connect to the
+     * database.
+     *
+     * @param ds a data source for connecting to the database
+     * @throws SQLException if one of the users cannot connect to the database
+     */
+    private void verifyCanConnect(DataSource ds) throws SQLException {
+        for (int i = 0; i < USERS.length; i++) {
+            Connection c = ds.getConnection(USERS[i][0], USERS[i][1]);
+            c.close();
+        }
+    }
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java?rev=1221666&r1=1221665&r2=1221666&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java
Wed Dec 21 10:33:01 2011
@@ -74,7 +74,7 @@ class UpgradeRun extends UpgradeClassLoa
         new AdditionalDb("NO_ENCRYPT_10_2", true),
         new AdditionalDb("ENCRYPT_10_2",  true),
         new AdditionalDb("ROLES_10_5", false),
-        new AdditionalDb("BUILTIN_10_6", false),
+        new AdditionalDb("BUILTIN_10_9", false),
     };
     
     public final static Test suite(final int[] version, boolean useCreateOnUpgrade) {



Mime
View raw message