From derby-commits-return-12495-apmail-db-derby-commits-archive=db.apache.org@db.apache.org Fri Mar 12 16:02:22 2010 Return-Path: Delivered-To: apmail-db-derby-commits-archive@www.apache.org Received: (qmail 23159 invoked from network); 12 Mar 2010 16:02:22 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 12 Mar 2010 16:02:22 -0000 Received: (qmail 51795 invoked by uid 500); 12 Mar 2010 16:01:45 -0000 Delivered-To: apmail-db-derby-commits-archive@db.apache.org Received: (qmail 51725 invoked by uid 500); 12 Mar 2010 16:01:45 -0000 Mailing-List: contact derby-commits-help@db.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: "Derby Development" List-Id: Delivered-To: mailing list derby-commits@db.apache.org Received: (qmail 51717 invoked by uid 99); 12 Mar 2010 16:01:44 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 12 Mar 2010 16:01:44 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 12 Mar 2010 16:01:41 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id B23ED23889B8; Fri, 12 Mar 2010 16:01:21 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: derby-commits@db.apache.org From: kahatlen@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100312160121.B23ED23889B8@eris.apache.org> 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; /** + *

* This is the authentication service base class. + *

+ *

* 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. + *

+ * *

* It should be extended by the specialized authentication services. + *

* - * IMPORTANT NOTE: - * -------------- - * User passwords are encrypted using SHA-1 message digest algorithm + *

IMPORTANT NOTE:

+ * + *

+ * 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). + *

+ * + *

+ * The passwords can be encrypted using two different schemes: + *

+ * + *
    + *
  • 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.
  • + *
  • The configurable hash authentication scheme, which allows the users to + * specify which message digest algorithm to use.
  • + *
+ * + *

+ * 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. + *

* + *

+ * 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. + *

*/ 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 } /** + *

* This method encrypts a clear user password using a * Single Hash algorithm such as SHA-1 (SHA equivalent) * (it is a 160 bits digest) + *

* + *

* The digest is returned as an object string. + *

+ * + *

+ * This method is only used by the SHA-1 authentication scheme. + *

* * @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); } /** + *

+ * 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. + *

+ * + *

+ * This method is only used by the configurable hash authentication scheme. + *

+ * + * @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; + } + + /** + *

+ * Encrypt a password using the default hash algorithm for this system + * before it's stored in the database. + *

+ * + *

+ * 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. + *

+ * + * @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. failureMessage + + XBCXW.S + 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. + algorithmName + + 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) {