db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kahat...@apache.org
Subject svn commit: r922304 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/impl/jdbc/authentication/ engine/org/apache/derby/loc/ shared/org/apache/derby/shared/common/reference/ testing/org/apache/derbyTesting/...
Date Fri, 12 Mar 2010 16:01:21 GMT
Author: kahatlen
Date: Fri Mar 12 16:01:20 2010
New Revision: 922304

URL: http://svn.apache.org/viewvc?rev=922304&view=rev
Log:
DERBY-4483: Provide a way to change the hash algorithm used by BUILTIN authentication

Added a database property, derby.authentication.builtin.algorithm,
that specifies which message digest algorithm to use when storing user
credentials in the database.

Added functional tests and upgrade tests.

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/engine/org/apache/derby/loc/messages.xml
    db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.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/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=922304&r1=922303&r2=922304&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 Fri Mar
12 16:01:20 2010
@@ -711,6 +711,13 @@ public interface Property { 
 	public static final String AUTHENTICATION_SERVER_PARAMETER =
 								"derby.authentication.server";
 
+    /**
+     * Property that specifies the name of the hash algorithm to use with
+     * the configurable hash authentication scheme.
+     */
+    public static final String AUTHENTICATION_BUILTIN_ALGORITHM =
+            "derby.authentication.builtin.algorithm";
+
 	/*
 	** 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=922304&r1=922303&r2=922304&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
Fri Mar 12 16:01:20 2010
@@ -28,7 +28,6 @@ import org.apache.derby.iapi.jdbc.Authen
 import org.apache.derby.iapi.reference.Limits;
 
 import org.apache.derby.iapi.error.StandardException;
-import org.apache.derby.iapi.services.i18n.MessageService;
 
 import org.apache.derby.iapi.services.context.ContextService;
 import org.apache.derby.iapi.services.daemon.Serviceable;
@@ -48,32 +47,75 @@ import org.apache.derby.iapi.reference.A
 import org.apache.derby.iapi.services.property.PropertyUtil;
 import org.apache.derby.iapi.util.StringUtil;
 
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;
+
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
 import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
 import java.util.Dictionary;
 import java.util.Properties;
-import java.util.Date;
+import org.apache.derby.iapi.reference.SQLState;
 
 /**
+ * <p>
  * This is the authentication service base class.
+ * </p>
+
  * <p>
  * There can be 1 Authentication Service for the whole Derby
  * system and/or 1 authentication per database.
  * In a near future, we intend to allow multiple authentication services
  * per system and/or per database.
+ * </p>
+ *
  * <p>
  * It should be extended by the specialized authentication services.
+ * </p>
  *
- * IMPORTANT NOTE:
- * --------------
- * User passwords are encrypted using SHA-1 message digest algorithm
+ * <p><strong>IMPORTANT NOTE:</strong></p>
+ *
+ * <p>
+ * User passwords are encrypted using a message digest algorithm
  * if they're stored in the database; otherwise they are not encrypted
  * if they were defined at the system level.
- * SHA-1 digest is single hash (one way) digest and is considered very
- * secure (160 bits).
+ * </p>
+ *
+ * <p>
+ * The passwords can be encrypted using two different schemes:
+ * </p>
+ *
+ * <ul>
+ * <li>The SHA-1 authentication scheme, which was the only available scheme
+ * in Derby 10.5 and earlier. This scheme uses the SHA-1 message digest
+ * algorithm.</li>
+ * <li>The configurable hash authentication scheme, which allows the users to
+ * specify which message digest algorithm to use.</li>
+ * </ul>
+ *
+ * <p>
+ * In order to use the configurable hash authentication scheme, the users have
+ * to set the {@code derby.authentication.builtin.algorithm} property (on
+ * system level or database level) to the name of an algorithm that's available
+ * in one of the security providers registered on the system. If this property
+ * is not set, or if it's set to NULL or an empty string, the SHA-1
+ * authentication scheme is used.
+ * </p>
  *
+ * <p>
+ * Which scheme to use is decided when a password is about to be stored in the
+ * database. One database may therefore contain passwords stored using
+ * different schemes. In order to determine which scheme to use when comparing
+ * a user's credentials with those stored in the database, the stored password
+ * is prefixed with an identifier that tells which scheme is being used.
+ * Passwords stored using the SHA-1 authentication scheme are prefixed with
+ * {@link #ID_PATTERN_SHA1_SCHEME}. Passwords that are stored using the
+ * configurable hash authentication scheme are prefixed with
+ * {@link #ID_PATTERN_CONFIGURABLE_HASH_SCHEME} and suffixed with the name of
+ * the message digest algorithm.
+ * </p>
  */
 public abstract class AuthenticationServiceBase
 	implements AuthenticationService, ModuleControl, ModuleSupportable, PropertySetCallback
{
@@ -88,21 +130,36 @@ public abstract class AuthenticationServ
 	*/
 	public static final String AuthenticationTrace =
 						SanityManager.DEBUG ? "AuthenticationTrace" : null;
-	/**
-		Pattern that is prefixed to the stored password in the new authentication scheme
-	*/
-	public static final String ID_PATTERN_NEW_SCHEME = "3b60";
+
+    /**
+     * Pattern that is prefixed to the stored password in the SHA-1
+     * authentication scheme.
+     */
+    public static final String ID_PATTERN_SHA1_SCHEME = "3b60";
+
+    /**
+     * Pattern that is prefixed to the stored password in the configurable
+     * hash authentication scheme.
+     */
+    public static final String ID_PATTERN_CONFIGURABLE_HASH_SCHEME = "3b61";
 
     /**
         Userid with Strong password substitute DRDA security mechanism
     */
     protected static final int SECMEC_USRSSBPWD = 8;
 
-	/**
-		Length of the encrypted password in the new authentication scheme
-		See Beetle4601
-	*/
-	public static final int MAGICLEN_NEWENCRYPT_SCHEME=44;
+    /**
+     * The encoding to use when converting the credentials to a byte array
+     * that can be passed to the hash function in the configurable hash scheme.
+     */
+    private static final String ENCODING = "UTF-8";
+
+    /**
+     * Character that separates the hash value from the name of the hash
+     * algorithm in the stored password generated by the configurable hash
+     * authentication scheme.
+     */
+    static final char SEPARATOR_CHAR = ':';
 
 	//
 	// constructor
@@ -349,7 +406,10 @@ public abstract class AuthenticationServ
 		if (userPassword != null) {
 			// encrypt (digest) the password
 			// the caller will retrieve the new value
-			userPassword = encryptPassword(userPassword);
+            String userName =
+                    key.substring(Property.USER_PROPERTY_PREFIX.length());
+            userPassword =
+                    encryptUsingDefaultAlgorithm(userName, userPassword, p);
 		}
 
 		return userPassword;
@@ -373,17 +433,26 @@ public abstract class AuthenticationServ
 	}
 
 	/**
+     * <p>
 	 * This method encrypts a clear user password using a
 	 * Single Hash algorithm such as SHA-1 (SHA equivalent)
 	 * (it is a 160 bits digest)
+     * </p>
 	 *
+     * <p>
 	 * The digest is returned as an object string.
+     * </p>
+     *
+     * <p>
+     * This method is only used by the SHA-1 authentication scheme.
+     * </p>
 	 *
 	 * @param plainTxtUserPassword Plain text user password
 	 *
 	 * @return encrypted user password (digest) as a String object
+     *         or {@code null} if the plaintext password is {@code null}
 	 */
-	protected String encryptPassword(String plainTxtUserPassword)
+	protected String encryptPasswordSHA1Scheme(String plainTxtUserPassword)
 	{
 		if (plainTxtUserPassword == null)
 			return null;
@@ -403,13 +472,126 @@ public abstract class AuthenticationServ
                 plainTxtUserPassword,0,plainTxtUserPassword.length());
 		algorithm.update(bytePasswd);
 		byte[] encryptVal = algorithm.digest();
-        String hexString = ID_PATTERN_NEW_SCHEME +
+        String hexString = ID_PATTERN_SHA1_SCHEME +
                 StringUtil.toHexString(encryptVal,0,encryptVal.length);
 		return (hexString);
 
 	}
 
     /**
+     * <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.
+     * </p>
+     *
+     * <p>
+     * This method is only used by the configurable hash authentication scheme.
+     * </p>
+     *
+     * @param user the user whose password to encrypt
+     * @param password the plain text password
+     * @param algorithm the hash algorithm to use
+     * @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)
+            throws StandardException
+    {
+        if (password == null) {
+            return null;
+        }
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw StandardException.newException(
+                    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 = md.digest();
+
+        return ID_PATTERN_CONFIGURABLE_HASH_SCHEME +
+                StringUtil.toHexString(digest, 0, digest.length) +
+                SEPARATOR_CHAR + algorithm;
+    }
+
+    /**
+     * <p>
+     * Encrypt a password using the default hash algorithm for this system
+     * before it's stored in the database.
+     * </p>
+     *
+     * <p>
+     * If the data dictionary supports the configurable hash authentication
+     * scheme, and the property {@code derby.authentication.builtin.algorithm}
+     * is a non-empty string, the password will be encrypted using the
+     * algorithm specified by that property. Otherwise, we fall back to the new
+     * authentication scheme based on SHA-1. The algorithm used is encoded in
+     * the returned token so that the code that validates a user's credentials
+     * knows which algorithm to use.
+     * </p>
+     *
+     * @param user the user whose password to encrypt
+     * @param password the plain text password
+     * @param props database properties
+     * @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
+     */
+    private String encryptUsingDefaultAlgorithm(String user,
+                                                String password,
+                                                Dictionary props)
+            throws StandardException {
+
+        // 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);
+
+        if (supportConfigurableHash) {
+            String algorithm = (String)
+                    PropertyUtil.getPropertyFromSet(
+                        props,
+                        Property.AUTHENTICATION_BUILTIN_ALGORITHM);
+
+            if (algorithm != null && algorithm.length() > 0) {
+                return encryptPasswordConfigurableScheme(
+                        user, password, algorithm);
+            }
+        }
+
+        return encryptPasswordSHA1Scheme(password);
+    }
+
+    /**
+     * Find the data dictionary for the current connection.
+     *
+     * @return the {@code DataDictionary} for the current connection
+     */
+    private static DataDictionary getDataDictionary() {
+        LanguageConnectionContext lcc = (LanguageConnectionContext)
+            ContextService.getContext(LanguageConnectionContext.CONTEXT_ID);
+        return lcc.getDataDictionary();
+    }
+
+    /**
      * Strong Password Substitution (USRSSBPWD).
      *
      * This method generate a password subtitute to authenticate a client

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=922304&r1=922303&r2=922304&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
Fri Mar 12 16:01:20 2010
@@ -21,26 +21,20 @@
 
 package org.apache.derby.impl.jdbc.authentication;
 
-import org.apache.derby.iapi.reference.MessageId;
 import org.apache.derby.iapi.reference.Attribute;
 import org.apache.derby.authentication.UserAuthenticator;
 import org.apache.derby.iapi.services.property.PropertyUtil;
-import org.apache.derby.iapi.services.daemon.Serviceable;
-import org.apache.derby.iapi.services.monitor.ModuleFactory;
 import org.apache.derby.iapi.services.monitor.Monitor;
 import org.apache.derby.iapi.services.sanity.SanityManager;
 import org.apache.derby.iapi.error.StandardException;
-import org.apache.derby.iapi.services.i18n.MessageService;
-import org.apache.derby.iapi.store.access.TransactionController;
-import org.apache.derby.iapi.jdbc.AuthenticationService;
 import org.apache.derby.iapi.util.StringUtil;
+import org.apache.derby.impl.jdbc.Util;
 
 import java.util.Properties;
 // security imports - for SHA-1 digest
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.io.Serializable;
-import java.util.Dictionary;
+import java.sql.SQLException;
 
 /**
  * This authentication service is the basic Derby user authentication
@@ -147,6 +141,7 @@ public final class BasicAuthenticationSe
 								 String databaseName,
 								 Properties info
 									)
+            throws SQLException
 	{
         // Client security mechanism if any specified
         // Note: Right now it is only used to handle clients authenticating
@@ -199,7 +194,14 @@ public final class BasicAuthenticationSe
             if (secMec != SECMEC_USRSSBPWD)
             {
                 // encrypt passed-in password
-                passedUserPassword = encryptPassword(userPassword);
+                try {
+                    passedUserPassword = encryptPasswordUsingStoredAlgorithm(
+                            userName, userPassword, definedUserPassword);
+                } catch (StandardException se) {
+                    // The UserAuthenticator interface does not allow us to
+                    // throw a StandardException, so convert to SQLException.
+                    throw Util.generateCsSQLException(se);
+                }
             }
             else
             {
@@ -246,4 +248,37 @@ public final class BasicAuthenticationSe
 		// We do have a valid user
 		return true;
 	}
+
+    /**
+     * Encrypt a password using the same algorithm as we used to generate the
+     * stored password token.
+     *
+     * @param user the user whose password to encrypt
+     * @param password the plaintext password
+     * @param storedPassword the password token that's stored in the database
+     * @return a digest of the password created the same way as the stored
+     *         password
+     * @throws StandardException if the password cannot be encrypted with the
+     *         requested algorithm
+     */
+    private String encryptPasswordUsingStoredAlgorithm(
+                String user, String password, String storedPassword)
+            throws StandardException
+    {
+        if (storedPassword.startsWith(ID_PATTERN_SHA1_SCHEME)) {
+            return encryptPasswordSHA1Scheme(password);
+        } else if (storedPassword.startsWith(
+                        ID_PATTERN_CONFIGURABLE_HASH_SCHEME)) {
+            String algorithm = storedPassword.substring(
+                    storedPassword.indexOf(SEPARATOR_CHAR) + 1);
+            return encryptPasswordConfigurableScheme(user, password, algorithm);
+        } else {
+            if (SanityManager.DEBUG) {
+                SanityManager.THROWASSERT(
+                        "Unknown authentication scheme for token " +
+                        storedPassword);
+            }
+            return null;
+        }
+    }
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml?rev=922304&r1=922303&r2=922304&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml Fri Mar 12 16:01:20
2010
@@ -3624,6 +3624,12 @@ Guide.
                 <arg>failureMessage</arg>
             </msg>
 
+            <msg>
+                <name>XBCXW.S</name>
+                <text>The message digest algorithm '{0}' is not supported by any of
the available cryptography providers. Please install a cryptography provider that supports
that algorithm, or specify another algorithm in the derby.authentication.builtin.algorithm
property.</text>
+                <arg>algorithmName</arg>
+            </msg>
+
         </family>
 
 

Modified: db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java?rev=922304&r1=922303&r2=922304&view=diff
==============================================================================
--- db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
(original)
+++ db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
Fri Mar 12 16:01:20 2010
@@ -244,6 +244,7 @@ public interface SQLState {
     String CANNOT_REENCRYPT_LOG_ARCHIVED_DATABASE     = "XBCXT.S";
     String DATABASE_ENCRYPTION_FAILED                 = "XBCXU.S";
     String DATABASE_REENCRYPTION_FAILED               = "XBCXV.S";
+    String DIGEST_NO_SUCH_ALGORITHM                   = "XBCXW.S";
 
 	/*
 	** Cache Service

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=922304&r1=922303&r2=922304&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
Fri Mar 12 16:01:20 2010
@@ -22,13 +22,14 @@
 
 package org.apache.derbyTesting.functionTests.tests.jdbcapi;
 
-import java.security.AccessController;
 import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Properties;
 
@@ -57,6 +58,13 @@ public class AuthenticationTest extends 
 
     private static final String zeus = "\u0396\u0395\u03A5\u03A3";
     private static final String apollo = "\u0391\u09A0\u039F\u039B\u039B\u039A\u0390";
+
+    private static final String BUILTIN_ALGO_PROP =
+            "derby.authentication.builtin.algorithm";
+
+    private static final String USER_PREFIX = "derby.user.";
+
+    private static final String NO_SUCH_ALGO = "XBCXW";
     
     /** Creates a new instance of the Test */
     public AuthenticationTest(String name) {
@@ -107,7 +115,26 @@ public class AuthenticationTest extends 
 
         test = new AuthenticationTest("testSystemShutdown");
         setBaseProps(suite, test);
+
+        // The test cases below test the configurable hash authentication
+        // mechanism added in DERBY-4483. Set the property that specifies the
+        // hash algorithm to some valid value for these tests. Not all tests
+        // depend on the property being set prior to their invocation, but by
+        // setting it in a decorator we ensure that it will be automatically
+        // cleared on tear down, so that it will be safe for all of these tests
+        // to change the property without worrying about resetting it later.
+        Properties confHashProps = new Properties();
+        confHashProps.setProperty(BUILTIN_ALGO_PROP, "MD5");
+
+        test = new AuthenticationTest("testVariousBuiltinAlgorithms");
+        setBaseProps(suite, test, confHashProps);
         
+        test = new AuthenticationTest("testNoCollisionsWithConfigurableHash");
+        setBaseProps(suite, test, confHashProps);
+
+        test = new AuthenticationTest("testInvalidAlgorithmName");
+        setBaseProps(suite, test, confHashProps);
+
         // This test needs to run in a new single use database as we're setting
         // a number of properties
         return TestConfiguration.singleUseDatabaseDecorator(suite);
@@ -115,6 +142,12 @@ public class AuthenticationTest extends 
     
     protected static void setBaseProps(TestSuite suite, Test test) 
     {
+        setBaseProps(suite, test, null);
+    }
+
+    private static void setBaseProps(
+            TestSuite suite, Test test, Properties extraDbProps)
+    {
         // Use DatabasePropertyTestSetup.builtinAuthentication decorator
         // to set the user properties required by this test (and shutdown 
         // the database for the property to take effect).
@@ -126,6 +159,11 @@ public class AuthenticationTest extends 
         Properties props = new Properties();
         props.setProperty("derby.infolog.append", "true");
         props.setProperty("derby.debug.true", "AuthenticationTrace");
+
+        if (extraDbProps != null) {
+            props.putAll(extraDbProps);
+        }
+
         Properties sysprops = new Properties();
         sysprops.put("derby.user.system", "admin");
         sysprops.put("derby.user.mickey", "mouse");
@@ -1058,6 +1096,95 @@ public class AuthenticationTest extends 
         assertSystemShutdownOK("", "system", "admin");
         openDefaultConnection("system", "admin").close(); // just so teardown works.
     }
+
+    /**
+     * DERBY-4483: Test that setting the property
+     * {@code derby.authentication.builtin.algorithm} changes which hash
+     * algorithm is used to protect the stored password token.
+     */
+    public void testVariousBuiltinAlgorithms() throws SQLException {
+        setAutoCommit(true);
+        String[] algorithms = { null, "MD5", "SHA-1", "SHA-256", "SHA-512" };
+        for (int i = 0; i < algorithms.length; i++) {
+            String algo = algorithms[i];
+            setDatabaseProperty(BUILTIN_ALGO_PROP, algo);
+
+            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"));
+                } else {
+                    assertTrue("Expected configurable hash schema: " + 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();
+            }
+        }
+    }
+
+    /**
+     * DERBY-4483: Test that slightly different passwords result in different
+     * hashes, and also that using the same password for different users
+     * results in a unique hashes with the configurable hash authentication
+     * scheme.
+     */
+    public void testNoCollisionsWithConfigurableHash() throws SQLException {
+        assertNotNull("hash algorithm not set up",
+                      getDatabaseProperty(BUILTIN_ALGO_PROP));
+
+        // Store a set of generated password tokens to detect collisions
+        HashSet tokens = new HashSet();
+
+        for (int i = 0; i < USERS.length; i++) {
+            String user = USERS[i];
+            String userProp = USER_PREFIX + user;
+            assertNotNull("missing user " + user,
+                          getDatabaseProperty(userProp));
+
+            // Start with the password "testing", and then change one of the
+            // characters
+            char[] pw = new char[] { 't', 'e', 's', 't', 'i', 'n', 'g' };
+            for (int j = 0; j < 100; j++) {
+                String pass = new String(pw);
+                setDatabaseProperty(userProp, pass);
+
+                assertTrue("collision detected",
+                           tokens.add(getDatabaseProperty(userProp)));
+                pw[pw.length / 2]++;
+            }
+        }
+    }
+
+    /**
+     * DERBY-4483: Test that we fail gracefully if an invalid algorithm name
+     * is specified in {@code derby.authentication.builtin.algorithm}.
+     */
+    public void testInvalidAlgorithmName() throws SQLException {
+        setDatabaseProperty(BUILTIN_ALGO_PROP, "not-a-valid-name");
+
+        for (int i = 0; i < USERS.length; i++) {
+            try {
+                setDatabaseProperty(USER_PREFIX + USERS[i], "abcdef");
+                fail();
+            } catch (SQLException sqle) {
+                assertSQLState(NO_SUCH_ALGO, sqle);
+            }
+        }
+    }
     
     protected void assertFailSetDatabaseProperty(
         String propertyName, String value, Connection conn) 
@@ -1081,6 +1208,15 @@ public class AuthenticationTest extends 
         setDBP.execute();
         setDBP.close();
     }
+
+    /**
+     * Set a database property in the default connection.
+     */
+    void setDatabaseProperty(String propertyName, String value)
+            throws SQLException
+    {
+        setDatabaseProperty(propertyName, value, getConnection());
+    }
     
     protected void useUserValue(int expectedUpdateCount, String user, String sql)
     throws SQLException

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=922304&r1=922303&r2=922304&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
Fri Mar 12 16:01:20 2010
@@ -28,6 +28,7 @@ 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;
@@ -37,6 +38,7 @@ import junit.framework.TestSuite;
 
 import org.apache.derby.catalog.types.RoutineAliasInfo;
 import org.apache.derby.catalog.TypeDescriptor;
+import org.apache.derbyTesting.junit.JDBC;
 
 
 /**
@@ -317,5 +319,147 @@ public class Changes10_6 extends Upgrade
 
         return ((Integer) meth.invoke( typeDescriptor, null )).intValue();
     }
-    
+
+    /**
+     * 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-256",
+                  "3b609e5173cfa03620061518adc92f2a58c7b15cf04f",
+                  "3b61aff1a3f161b6c0ce856c4ce99ce6d779bad9cc1" +
+                  "44136099bc4b2b0742ed87899:SHA-256"
+        },
+    };
+
+    /**
+     * 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, "derby.authentication.builtin.algorithm");
+            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/UpgradeRun.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java?rev=922304&r1=922303&r2=922304&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
Fri Mar 12 16:01:20 2010
@@ -73,7 +73,8 @@ class UpgradeRun extends UpgradeClassLoa
                                                     // based collation
         new AdditionalDb("NO_ENCRYPT_10_2", true),
         new AdditionalDb("ENCRYPT_10_2",  true),
-        new AdditionalDb("ROLES_10_5", false)
+        new AdditionalDb("ROLES_10_5", false),
+        new AdditionalDb("BUILTIN_10_6", false),
     };
     
     public final static Test suite(final int[] version) {



Mime
View raw message