CVE-2009-4269
Description
The password hash generation algorithm in the BUILTIN authentication functionality for Apache Derby before 10.6.1.0 performs a transformation that reduces the size of the set of inputs to SHA-1, which produces a small search space that makes it easier for local and possibly remote attackers to crack passwords by generating hash collisions, related to password substitution.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.derby:derbyMaven | < 10.6.1.0 | 10.6.1.0 |
Affected products
1Patches
58c305e2f3fadDERBY-4483: Provide a way to change the hash algorithm used by BUILTIN authentication
5 files changed · +52 −3
java/engine/org/apache/derby/iapi/reference/Property.java+7 −0 modified@@ -718,6 +718,13 @@ release. New databases and existing databases (in Derby 10.2) still use legacy public static final String AUTHENTICATION_BUILTIN_ALGORITHM = "derby.authentication.builtin.algorithm"; + /** + * Default value for derby.authentication.builtin.algorithm when creating + * a new database. + */ + public static final String AUTHENTICATION_BUILTIN_ALGORITHM_DEFAULT = + "SHA-256"; + /* ** Log */
java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java+7 −0 modified@@ -761,6 +761,13 @@ public void boot(boolean create, Properties startParams) bootingTC.setProperty(Property.SQL_AUTHORIZATION_PROPERTY,"true",true); usesSqlAuthorization=true; } + + // Set default hash algorithm used to protect passwords stored + // in the database for BUILTIN authentication. + bootingTC.setProperty( + Property.AUTHENTICATION_BUILTIN_ALGORITHM, + Property.AUTHENTICATION_BUILTIN_ALGORITHM_DEFAULT, + false); } else { // Get the ids for non-core tables loadDictionaryTables(bootingTC, ddg, startParams);
java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/NSSecurityMechanismTest.java+9 −1 modified@@ -1106,7 +1106,9 @@ private void assertDerby1080Fixed(String expectedValue) * * We want to test a combination of USRSSBPWD with BUILTIN as password * substitute is only supported with NONE or BUILTIN Derby authentication - * scheme right now (DERBY-528). + * scheme right now (DERBY-528). Also, it doesn't work if passwords are + * hashed with the configurable hash authentication scheme (DERBY-4483) + * before they are stored in the database, so we'll need to disable that. * * @throws Exception if there an unexpected error */ @@ -1122,6 +1124,12 @@ private void assertUSRSSBPWD_with_BUILTIN(String[] expectedValues) CallableStatement cs = conn.prepareCall( "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?, ?)"); + // First, disable the configurable hash authentication scheme so that + // passwords are stored using the old hash algorithm. + cs.setString(1, "derby.authentication.builtin.algorithm"); + cs.setString(2, null); + cs.execute(); + cs.setString(1, "derby.user.neelima"); cs.setString(2, "lee"); cs.execute();
java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java+12 −0 modified@@ -116,6 +116,9 @@ public static Test baseSuite(String name) { test = new AuthenticationTest("testSystemShutdown"); setBaseProps(suite, test); + test = new AuthenticationTest("testDefaultHashAlgorithm"); + 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 @@ -1097,6 +1100,15 @@ public void testSystemShutdown() throws SQLException openDefaultConnection("system", "admin").close(); // just so teardown works. } + /** + * DERBY-4483: Test that the database by default has the configurable + * hash authentication scheme enabled. + */ + public void testDefaultHashAlgorithm() throws SQLException { + // SHA-256 should be the default hash algorithm now + assertEquals("SHA-256", getDatabaseProperty(BUILTIN_ALGO_PROP)); + } + /** * DERBY-4483: Test that setting the property * {@code derby.authentication.builtin.algorithm} changes which hash
java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java+17 −2 modified@@ -70,7 +70,10 @@ public class Changes10_6 extends UpgradeChange { 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 = + "derby.authentication.builtin.algorithm"; + public Changes10_6(String name) { super(name); } @@ -320,6 +323,18 @@ private int getMaximumWidth( Object typeDescriptor ) return ((Integer) meth.invoke( typeDescriptor, null )).intValue(); } + /** + * Verify that we don't enable the configurable hash authentication + * scheme when we upgrade a database. See DERBY-4483. + */ + public void testBuiltinAuthenticationHashNotChangedOnUpgrade() + throws SQLException { + // We enable the configurable hash authentication scheme by setting + // a property, so check that it's NULL in all phases to verify that + // it's not enabled on upgrade. + assertNull(getDatabaseProperty(HASH_ALGORITHM_PROPERTY)); + } + /** * Make sure builtin authentication only uses the new configurable hash * scheme in hard-upgraded databases. See DERBY-4483. @@ -418,7 +433,7 @@ 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(1, HASH_ALGORITHM_PROPERTY); cs.setString(2, USERS[i][2]); cs.execute(); // Set the password.
3b82686e32a8DERBY-4483: Provide a way to change the hash algorithm used by BUILTIN authentication
4 files changed · +22 −17
java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java+15 −10 modified@@ -22,6 +22,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more package org.apache.derby.impl.jdbc.authentication; import org.apache.derby.iapi.reference.Attribute; +import org.apache.derby.iapi.reference.SQLState; import org.apache.derby.authentication.UserAuthenticator; import org.apache.derby.iapi.services.property.PropertyUtil; import org.apache.derby.iapi.services.monitor.Monitor; @@ -233,20 +234,24 @@ public boolean authenticateUser(String userName, } } - if (definedUserPassword == null) - // no such user found - return false; - - // check if the passwords match - if (!definedUserPassword.equals(passedUserPassword)) - return false; - + // Check if the passwords match. // NOTE: We do not look at the passed-in database name value as // we rely on the authorization service that was put in // in 2.0 . (if a database name was passed-in) + boolean passwordsMatch = + (definedUserPassword != null) && + definedUserPassword.equals(passedUserPassword); + + // Provide extra information on mismatch if strong password + // substitution is used, since the problem may be that the stored + // password was stored using the configurable hash authentication + // scheme which is incompatible with strong password substitution. + if (!passwordsMatch && secMec == SECMEC_USRSSBPWD) { + throw Util.generateCsSQLException( + SQLState.NET_CONNECT_SECMEC_INCOMPATIBLE_SCHEME); + } - // We do have a valid user - return true; + return passwordsMatch; } /**
java/engine/org/apache/derby/loc/messages.xml+5 −0 modified@@ -415,6 +415,11 @@ Guide. <arg>exceptionMsg</arg> </msg> + <msg> + <name>08004.C.12</name> + <text>Connection authentication failure occurred. Either the supplied credentials were invalid, or the database uses a password encryption scheme not compatible with the strong password substitution security mechanism. If this error started after upgrade, refer to the release note for DERBY-4483 for options.</text> + </msg> + <msg> <name>08006.C</name> <text>A network protocol error was encountered and the connection has been terminated: {0}</text>
java/shared/org/apache/derby/shared/common/reference/SQLState.java+1 −0 modified@@ -1655,6 +1655,7 @@ public interface SQLState { String AUTH_DATABASE_CREATE_EXCEPTION = "08004.C.10"; //DERBY-2109: new state/msg String AUTH_DATABASE_CREATE_MISSING_PERMISSION = "08004.C.11"; + String NET_CONNECT_SECMEC_INCOMPATIBLE_SCHEME = "08004.C.12"; // There can be multiple causes for 08003, which according // to SQL2003 spec means "connection does not exist"
java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java+1 −7 modified@@ -26,16 +26,9 @@ import org.apache.derbyTesting.junit.BaseJDBCTestCase; import org.apache.derbyTesting.junit.TestConfiguration; -import org.apache.derbyTesting.junit.Utilities; -import java.sql.Connection; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.PreparedStatement; import java.sql.Statement; -import java.sql.CallableStatement; -import java.sql.SQLWarning; -import java.sql.SQLException; import org.apache.derbyTesting.junit.JDBC; public final class ErrorCodeTest extends BaseJDBCTestCase { @@ -127,6 +120,7 @@ public void test_errorcode() throws Exception {"08004","Missing permission for user '{0}' to shutdown system [{1}].","40000"}, {"08004","Cannot check system permission to create database '{0}' [{1}].","40000"}, {"08004","Missing permission for user '{0}' to create database '{1}' [{2}].","40000"}, + {"08004","Connection authentication failure occurred. Either the supplied credentials were invalid, or the database uses a password encryption scheme not compatible with the strong password substitution security mechanism. If this error started after upgrade, refer to the release note for DERBY-4483 for options.","40000"}, {"08006","An error occurred during connect reset and the connection has been terminated. See chained exceptions for details.","40000"}, {"08006","SocketException: '{0}'","40000"}, {"08006","A communications error has been detected: {0}.","40000"},
178ca0cfb796DERBY-4483: Make toHexByte() private to discourage its use in new code
2 files changed · +45 −39
java/engine/org/apache/derby/iapi/util/StringUtil.java+0 −33 modified@@ -227,39 +227,6 @@ public static String toHexString(byte[] data, int offset, int length) return s.toString(); } - /** - - Convert a string into a byte array in hex format. - <BR> - For each character (b) two bytes are generated, the first byte - represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>), - the second byte represents the low nibble (<code>b & 0x0f</code>). - <BR> - The character at <code>str.charAt(0)</code> is represented by the first two bytes - in the returned String. - - @param str string - @param offset starting character (zero based) to convert. - @param length number of characters to convert. - - @return the byte[] (with hexidecimal format) form of the string (str) - */ - public static byte[] toHexByte(String str, int offset, int length) - { - byte[] data = new byte[(length - offset) * 2]; - int end = offset+length; - - for (int i = offset; i < end; i++) - { - char ch = str.charAt(i); - int high_nibble = (ch & 0xf0) >>> 4; - int low_nibble = (ch & 0x0f); - data[i] = (byte)high_nibble; - data[i+1] = (byte)low_nibble; - } - return data; - } - /** Convert a hexidecimal string generated by toHexString() back into a byte array.
java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java+45 −6 modified@@ -468,8 +468,7 @@ protected String encryptPasswordSHA1Scheme(String plainTxtUserPassword) algorithm.reset(); byte[] bytePasswd = null; - bytePasswd = StringUtil.toHexByte( - plainTxtUserPassword,0,plainTxtUserPassword.length()); + bytePasswd = toHexByte(plainTxtUserPassword); algorithm.update(bytePasswd); byte[] encryptVal = algorithm.digest(); String hexString = ID_PATTERN_SHA1_SCHEME + @@ -478,6 +477,47 @@ protected String encryptPasswordSHA1Scheme(String plainTxtUserPassword) } + /** + * <p> + * Convert a string into a byte array in hex format. + * </p> + * + * <p> + * For each character (b) two bytes are generated, the first byte + * represents the high nibble (4 bits) in hexadecimal ({@code b & 0xf0}), + * the second byte represents the low nibble ({@code b & 0x0f}). + * </p> + * + * <p> + * The character at {@code str.charAt(0)} is represented by the first two + * bytes in the returned String. + * </p> + * + * <p> + * New code is encouraged to use {@code String.getBytes(String)} or similar + * methods instead, since this method does not preserve all bits for + * characters whose codepoint exceeds 8 bits. This method is preserved for + * compatibility with the SHA-1 authentication scheme. + * </p> + * + * @param str string + * @return the byte[] (with hexadecimal format) form of the string (str) + */ + private static byte[] toHexByte(String str) + { + byte[] data = new byte[str.length() * 2]; + + for (int i = 0; i < str.length(); i++) + { + char ch = str.charAt(i); + int high_nibble = (ch & 0xf0) >>> 4; + int low_nibble = (ch & 0x0f); + data[i] = (byte)high_nibble; + data[i+1] = (byte)low_nibble; + } + return data; + } + /** * <p> * Encrypt a password using the specified hash algorithm and with the @@ -671,7 +711,7 @@ protected String substitutePassword( messageDigest.reset(); byte[] bytePasswd = null; - byte[] userBytes = StringUtil.toHexByte(userName, 0, userName.length()); + byte[] userBytes = toHexByte(userName); if (SanityManager.DEBUG) { @@ -699,7 +739,7 @@ protected String substitutePassword( // substitute generation right afterwards. if (!databaseUser) { - bytePasswd = StringUtil.toHexByte(password, 0, password.length()); + bytePasswd = toHexByte(password); messageDigest.update(bytePasswd); byte[] encryptVal = messageDigest.digest(); hexString = ID_PATTERN_SHA1_SCHEME + @@ -722,8 +762,7 @@ protected String substitutePassword( // Generate some 20-byte password token messageDigest.update(userBytes); - messageDigest.update( - StringUtil.toHexByte(hexString, 0, hexString.length())); + messageDigest.update(toHexByte(hexString)); byte[] passwordToken = messageDigest.digest(); // Now we generate the 20-byte password substitute
23f97a597716DERBY-4483: Provide a way to change the hash algorithm used by BUILTIN authentication
3 files changed · +34 −6
java/client/org/apache/derby/client/am/EncryptionManager.java+10 −1 modified@@ -525,7 +525,7 @@ public byte[] generateSeed() { /** * Strong Password Substitution (USRSSBPWD). * - * This method generate a password subtitute to send to the target + * This method generates a password substitute to send to the target * server. * * Substitution algorithm works as follow: @@ -587,6 +587,15 @@ public byte[] substitutePassword( // // Encrypt the password as it is done by the derby engine - Note that // this code (logic) is not shared yet - will be in next revision. + // + // Note that this code assumes that the Derby engine has encrypted + // the password using one particular algorithm (based on SHA-1). After + // DERBY-4483, it is possible that the engine uses another algorithm. + // Since the engine has no way to decrypt the encrypted password, it + // has no way to compared the stored password with the hash we send, so + // authentication will fail unless the engine actually uses the SHA-1 + // based scheme. + messageDigest.reset(); messageDigest.update(this.toHexByte(password, 0, password.length()));
java/drda/org/apache/derby/impl/drda/DRDAConnThread.java+2 −0 modified@@ -8464,6 +8464,8 @@ private void finalizeChain() throws DRDAProtocolException { * SECMEC_USRSSBPWD is ONLY supported by the target server if: * - current authentication provider is Derby BUILTIN or * NONE. (database / system level) (Phase I) + * - database-level password must have been encrypted with the + * SHA-1 based authentication scheme * - Application requester is 'DNC' (Derby Network Client) * (Phase I) *
java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java+22 −5 modified@@ -594,7 +594,7 @@ private static DataDictionary getDataDictionary() { /** * Strong Password Substitution (USRSSBPWD). * - * This method generate a password subtitute to authenticate a client + * This method generates a password substitute to authenticate a client * which is using a DRDA security mechanism such as SECMEC_USRSSBPWD. * * Depending how the user is defined in Derby and if BUILTIN @@ -605,6 +605,17 @@ private static DataDictionary getDataDictionary() { * generate a substitute password coming from the store to compare with * the one passed-in. * + * The substitution algorithm used is the same as the one used in the + * SHA-1 authentication scheme ({@link #ID_PATTERN_SHA1_SCHEME}), so in + * the case of database passwords stored using that scheme, we can simply + * compare the received hash with the stored hash. If the configurable + * hash authentication scheme {@link #ID_PATTERN_CONFIGURABLE_HASH_SCHEME} + * is used, we have no way to find out if the received hash matches the + * stored password, since we cannot decrypt the hashed passwords and + * re-apply another hash algorithm. Therefore, strong password substitution + * only works if the database-level passwords are stored with the SHA-1 + * scheme. + * * NOTE: A lot of this logic could be shared with the DRDA decryption * and client encryption managers - This will be done _once_ * code sharing along with its rules are defined between the @@ -633,9 +644,6 @@ protected String substitutePassword( MessageDigest messageDigest = null; - // Pattern that is prefixed to the BUILTIN encrypted password - String ID_PATTERN_NEW_SCHEME = "3b60"; - // PWSEQs's 8-byte value constant - See DRDA Vol 3 byte SECMEC_USRSSBPWD_PWDSEQS[] = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, @@ -694,12 +702,21 @@ protected String substitutePassword( bytePasswd = StringUtil.toHexByte(password, 0, password.length()); messageDigest.update(bytePasswd); byte[] encryptVal = messageDigest.digest(); - hexString = ID_PATTERN_NEW_SCHEME + + hexString = ID_PATTERN_SHA1_SCHEME + StringUtil.toHexString(encryptVal, 0, encryptVal.length); } else + { // Already encrypted from the database store + // NOTE: If the password was stored with the configurable hash + // authentication scheme, the stored password will have been hashed + // with a different algorithm than the hashed password sent from + // the client. Since there's no way to decrypt the stored password + // and rehash it with the algorithm that the client uses, we are + // not able to compare the passwords, and the connection attempt + // will fail. hexString = password; + } // Generate the password substitute now
60edeb0cb29dDERBY-4483: Provide a way to change the hash algorithm used by BUILTIN authentication
8 files changed · +543 −31
java/engine/org/apache/derby/iapi/reference/Property.java+7 −0 modified@@ -711,6 +711,13 @@ release. New databases and existing databases (in Derby 10.2) still use legacy 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 */
java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java+201 −19 modified@@ -28,7 +28,6 @@ Licensed to the Apache Software Foundation (ASF) under one or more 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 @@ Licensed to the Apache Software Foundation (ASF) under one or more 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 AuthenticationServiceBase */ 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 Serializable map(String key, Serializable value, Dictionary p) 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 @@ protected final boolean requireAuthentication(Properties properties) { } /** + * <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,12 +472,125 @@ protected String encryptPassword(String plainTxtUserPassword) 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). *
java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java+44 −9 modified@@ -21,26 +21,20 @@ Licensed to the Apache Software Foundation (ASF) under one or more 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 boolean authenticateUser(String userName, 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 boolean authenticateUser(String userName, 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 boolean authenticateUser(String userName, // 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; + } + } }
java/engine/org/apache/derby/loc/messages.xml+6 −0 modified@@ -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>
java/shared/org/apache/derby/shared/common/reference/SQLState.java+1 −0 modified@@ -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
java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/AuthenticationTest.java+137 −1 modified@@ -22,13 +22,14 @@ Licensed to the Apache Software Foundation (ASF) under one or more 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 BaseJDBCTestCase { 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,13 +115,38 @@ public static Test baseSuite(String name) { 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); } 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 @@ -126,6 +159,11 @@ protected static void setBaseProps(TestSuite suite, Test test) 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 void testSystemShutdown() throws SQLException 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 @@ protected void setDatabaseProperty( 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
java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_6.java+145 −1 modified@@ -28,6 +28,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more 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 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.derby.catalog.types.RoutineAliasInfo; import org.apache.derby.catalog.TypeDescriptor; +import org.apache.derbyTesting.junit.JDBC; /** @@ -317,5 +319,147 @@ private int getMaximumWidth( Object typeDescriptor ) 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(); + } + } }
java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/UpgradeRun.java+2 −1 modified@@ -73,7 +73,8 @@ class UpgradeRun extends UpgradeClassLoader // 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) {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
18- github.com/advisories/GHSA-fh32-35w2-rxccghsaADVISORY
- issues.apache.org/jira/browse/DERBY-4483nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2009-4269ghsaADVISORY
- db.apache.org/derby/releases/release-10.6.1.0.cginvdWEB
- marc.infonvdWEB
- www.oracle.com/technetwork/topics/security/cpujan2011-194091.htmlnvdWEB
- github.com/apache/derby/commit/178ca0cfb796b5a5788d25ded0978773ea254332ghsaWEB
- github.com/apache/derby/commit/23f97a597716ee5b08eff698b7177850ad8e1294ghsaWEB
- github.com/apache/derby/commit/3b82686e32a8d4fa2027350279104f9b243b35d6ghsaWEB
- github.com/apache/derby/commit/60edeb0cb29daf9d28ece1863db779c1af5a3f62ghsaWEB
- github.com/apache/derby/commit/8c305e2f3fad1c3a4f98c06c7f2b53e2bfdd308cghsaWEB
- blogs.sun.com/kah/entry/derby_10_6_1_hasnvd
- marcellmajor.com/derbyhash.htmlnvd
- secunia.com/advisories/42948nvd
- secunia.com/advisories/42970nvd
- www.securityfocus.com/bid/42637nvd
- www.securitytracker.com/idnvd
- www.vupen.com/english/advisories/2011/0149nvd
News mentions
0No linked articles in our index yet.