You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by rh...@apache.org on 2012/01/04 14:30:22 UTC
svn commit: r1227150 - in /db/derby/code/trunk/java:
engine/org/apache/derby/catalog/ engine/org/apache/derby/iapi/sql/dictionary/
engine/org/apache/derby/impl/jdbc/authentication/
engine/org/apache/derby/impl/sql/catalog/ engine/org/apache/derby/loc/ ...
Author: rhillegas
Date: Wed Jan 4 13:30:22 2012
New Revision: 1227150
URL: http://svn.apache.org/viewvc?rev=1227150&view=rev
Log:
DERBY-866: Wire the new NATIVE password-management procedures to the recently checked-in password hashing improvements.
Added:
db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java (with props)
Modified:
db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.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/impl/sql/catalog/DataDictionaryImpl.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/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthProcs.java
db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
Modified: db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java Wed Jan 4 13:30:22 2012
@@ -66,6 +66,7 @@ import org.apache.derby.iapi.sql.diction
import org.apache.derby.iapi.sql.dictionary.SystemColumn;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.dictionary.UserDescriptor;
@@ -2066,7 +2067,7 @@ public class SystemProcedures {
*/
dd.startWriting(lcc);
- UserDescriptor userDescriptor = makeUserDescriptor( lcc, userName, password.toCharArray() );
+ UserDescriptor userDescriptor = makeUserDescriptor( lcc, userName, password );
dd.addDescriptor( userDescriptor, null, DataDictionary.SYSUSERS_CATALOG_NUM, false, tc );
@@ -2076,24 +2077,26 @@ public class SystemProcedures {
(
LanguageConnectionContext lcc,
String userName,
- char[] password
+ String password
)
throws StandardException
{
DataDictionary dd = lcc.getDataDictionary();
DataDescriptorGenerator ddg = dd.getDataDescriptorGenerator();
+ PasswordHasher hasher = dd.makePasswordHasher( lcc.getTransactionExecute().getProperties() );
+
+ if ( hasher == null )
+ {
+ throw StandardException.newException( SQLState.WEAK_AUTHENTICATION );
+ }
- //
- // FIXME: DERBY-866 Proper values need to be computed for the
- // following variables once Knut is done reworking builtin hashing.
- //
- String hashingScheme = "???????";
- char[] hashedPassword = password;
+ String hashingScheme = hasher.encodeHashingScheme();
+ String hashedPassword = hasher.hashPasswordIntoString( userName, password );
Timestamp currentTimestamp = new Timestamp( (new java.util.Date()).getTime() );
UserDescriptor userDescriptor = ddg.newUserDescriptor
- ( userName, hashingScheme, hashedPassword, currentTimestamp );
+ ( userName, hashingScheme, hashedPassword.toCharArray(), currentTimestamp );
return userDescriptor;
}
@@ -2124,7 +2127,7 @@ public class SystemProcedures {
*/
dd.startWriting(lcc);
- UserDescriptor userDescriptor = makeUserDescriptor( lcc, userName, password.toCharArray() );
+ UserDescriptor userDescriptor = makeUserDescriptor( lcc, userName, password );
dd.updateUser( userDescriptor, tc );
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Wed Jan 4 13:30:22 2012
@@ -41,6 +41,7 @@ import org.apache.derby.catalog.UUID;
import org.apache.derby.iapi.services.uuid.UUIDFactory;
import java.sql.Types;
+import java.util.Dictionary;
import java.util.List;
import java.util.Hashtable;
@@ -402,6 +403,15 @@ public interface DataDictionary
throws StandardException;
/**
+ * Get the default password hasher for this database level. Returns null
+ * if the system is at rev level 10.5 or earlier.
+ *
+ * @param props The persistent properties used to configure password hashing.
+ */
+ public PasswordHasher makePasswordHasher( Dictionary props )
+ throws StandardException;
+
+ /**
* Get the descriptor for the system schema. Schema descriptors include
* authorization ids and schema ids.
*
Added: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java?rev=1227150&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java Wed Jan 4 13:30:22 2012
@@ -0,0 +1,260 @@
+/*
+
+ Derby - Class org.apache.derby.iapi.sql.dictionary.PasswordHasher
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to you under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+package org.apache.derby.iapi.sql.dictionary;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.services.sanity.SanityManager;
+import org.apache.derby.iapi.util.StringUtil;
+import org.apache.derby.shared.common.reference.SQLState;
+
+/**
+ * <p>
+ * This machine performs the hashing of Derby passwords.
+ * </p>
+ */
+public class PasswordHasher
+{
+ ///////////////////////////////////////////////////////////////////////////////////
+ //
+ // CONSTANTS
+ //
+ ///////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * 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";
+
+ /**
+ * 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";
+
+ /**
+ * Pattern that is prefixed to the stored password in the configurable
+ * hash authentication scheme if key stretching has been applied. This
+ * scheme extends the configurable hash scheme by adding a random salt and
+ * applying the hash function multiple times when generating the hashed
+ * token.
+ */
+ public static final String
+ ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME = "3b62";
+
+ /**
+ * Character that separates the hash value from the name of the hash
+ * algorithm in the stored password generated by the configurable hash
+ * authentication scheme.
+ */
+ private static final char SEPARATOR_CHAR = ':';
+
+ ///////////////////////////////////////////////////////////////////////////////////
+ //
+ // STATE
+ //
+ ///////////////////////////////////////////////////////////////////////////////////
+
+ private String _messageDigestAlgorithm;
+ private byte[] _salt; // can be null
+ private int _iterations;
+
+
+ ///////////////////////////////////////////////////////////////////////////////////
+ //
+ // CONSTRUCTORS
+ //
+ ///////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * <p>
+ * Construct from pieces. Used for databases at rev level 10.6 or later.
+ * </p>
+ */
+ public PasswordHasher
+ (
+ String messageDigestAlgorithm,
+ byte[] salt,
+ int iterations
+ )
+ {
+ _messageDigestAlgorithm = messageDigestAlgorithm;
+ _salt = salt;
+ _iterations = iterations;
+ }
+
+ /**
+ * <p>
+ * Construct from a hashed BUILTIN password stored in the PropertyConglomerate
+ * or from a SYSUSERS.HASHINGSCHEME column.
+ * </p>
+ */
+ public PasswordHasher( String hashingScheme )
+ {
+ if ( hashingScheme.startsWith( ID_PATTERN_CONFIGURABLE_HASH_SCHEME) )
+ {
+ _messageDigestAlgorithm = hashingScheme.substring
+ ( hashingScheme.indexOf( SEPARATOR_CHAR) + 1);
+ _salt = null;
+ _iterations = 1;
+ }
+ else if ( hashingScheme.startsWith( ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME) )
+ {
+ int saltPos = hashingScheme.indexOf(SEPARATOR_CHAR) + 1;
+ int iterPos = hashingScheme.indexOf(SEPARATOR_CHAR, saltPos) + 1;
+ int algoPos = hashingScheme.indexOf(SEPARATOR_CHAR, iterPos) + 1;
+
+ _salt = StringUtil.fromHexString
+ ( hashingScheme, saltPos, iterPos - saltPos - 1 );
+ _iterations = Integer.parseInt
+ ( hashingScheme.substring(iterPos, algoPos - 1) );
+ _messageDigestAlgorithm = hashingScheme.substring(algoPos);
+ }
+ else
+ {
+ if (SanityManager.DEBUG)
+ {
+ SanityManager.THROWASSERT
+ (
+ "Unknown authentication scheme for token " +
+ hashingScheme
+ );
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////
+ //
+ // PUBLIC BEHAVIOR
+ //
+ ///////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * <p>
+ * Produce a hashed password using a plaintext username and password. Turn it into
+ * a printable string.
+ * </p>
+ */
+ public String hashPasswordIntoString( String userName, String password )
+ throws StandardException
+ {
+ if (password == null) {
+ return null;
+ }
+
+ byte[] userBytes;
+ byte[] passwordBytes;
+ try {
+ userBytes = userName.getBytes(ENCODING);
+ passwordBytes = password.getBytes(ENCODING);
+ } catch (UnsupportedEncodingException uee) {
+ // UTF-8 should always be available, so this should never happen.
+ throw StandardException.plainWrapException(uee);
+ }
+
+ MessageDigest md = getEmptyMessageDigest();
+
+ byte[] digest = null;
+ for (int i = 0; i < _iterations; i++)
+ {
+ md.reset();
+ if (digest != null) {
+ md.update(digest);
+ }
+ md.update(userBytes);
+ md.update(passwordBytes);
+ if ( _salt != null) {
+ md.update( _salt );
+ }
+ digest = md.digest();
+ }
+
+ return StringUtil.toHexString( digest, 0, digest.length );
+ }
+ private MessageDigest getEmptyMessageDigest()
+ throws StandardException
+ {
+ if ( _messageDigestAlgorithm == null ) { throw badMessageDigest( null ); }
+ try {
+ return MessageDigest.getInstance( _messageDigestAlgorithm );
+ } catch (NoSuchAlgorithmException nsae) { throw badMessageDigest( nsae ); }
+ }
+ private StandardException badMessageDigest( Throwable t )
+ {
+ String digestName = (_messageDigestAlgorithm == null) ? "NULL" : _messageDigestAlgorithm;
+
+ return StandardException.newException( SQLState.DIGEST_NO_SUCH_ALGORITHM, t, digestName );
+ }
+
+ /**
+ * <p>
+ * Encodes the hashing algorithm in a string suitable for storing in SYSUSERS.HASHINGSCHEME.
+ * </p>
+ */
+ public String encodeHashingScheme( )
+ {
+ return hashAndEncode( "" );
+ }
+
+ /**
+ * <p>
+ * Hash a username/password pair and return an encoded representation suitable
+ * for storing as a BUILTIN password value in the PropertyConglomerate.
+ * </p>
+ */
+ public String hashAndEncode( String userName, String password )
+ throws StandardException
+ {
+ String stringDigest = hashPasswordIntoString( userName, password );
+
+ return hashAndEncode( stringDigest );
+ }
+ private String hashAndEncode( String stringDigest )
+ {
+ if (( _salt == null || _salt.length == 0) && _iterations == 1)
+ {
+ // No salt was used, and only a single iteration, which is
+ // identical to the default hashing scheme in 10.6-10.8. Generate
+ // a token on a format compatible with those old versions.
+ return ID_PATTERN_CONFIGURABLE_HASH_SCHEME +
+ stringDigest +
+ SEPARATOR_CHAR + _messageDigestAlgorithm;
+ } else {
+ // Salt and/or multiple iterations was used, so we need to add
+ // those parameters to the token in order to verify the credentials
+ // later.
+ return ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME +
+ stringDigest +
+ SEPARATOR_CHAR + StringUtil.toHexString( _salt, 0, _salt.length) +
+ SEPARATOR_CHAR + _iterations + SEPARATOR_CHAR + _messageDigestAlgorithm;
+ }
+ }
+}
Propchange: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/PasswordHasher.java
------------------------------------------------------------------------------
svn:eol-style = native
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=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java Wed Jan 4 13:30:22 2012
@@ -49,6 +49,7 @@ import org.apache.derby.iapi.util.String
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -112,9 +113,9 @@ import org.apache.derby.iapi.reference.S
* 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
+ * {@link PasswordHasher#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
+ * {@link PasswordHasher#ID_PATTERN_CONFIGURABLE_HASH_SCHEME} and suffixed with the name of
* the message digest algorithm.
* </p>
*/
@@ -133,45 +134,10 @@ public abstract class AuthenticationServ
SanityManager.DEBUG ? "AuthenticationTrace" : null;
/**
- * 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";
-
- /**
- * Pattern that is prefixed to the stored password in the configurable
- * hash authentication scheme if key stretching has been applied. This
- * scheme extends the configurable hash scheme by adding a random salt and
- * applying the hash function multiple times when generating the hashed
- * token.
- */
- public static final String
- ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME = "3b62";
-
- /**
Userid with Strong password substitute DRDA security mechanism
*/
protected static final int SECMEC_USRSSBPWD = 8;
- /**
- * 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
//
@@ -482,7 +448,7 @@ public abstract class AuthenticationServ
bytePasswd = toHexByte(plainTxtUserPassword);
algorithm.update(bytePasswd);
byte[] encryptVal = algorithm.digest();
- String hexString = ID_PATTERN_SHA1_SCHEME +
+ String hexString = PasswordHasher.ID_PATTERN_SHA1_SCHEME +
StringUtil.toHexString(encryptVal,0,encryptVal.length);
return (hexString);
@@ -531,90 +497,6 @@ public abstract class AuthenticationServ
/**
* <p>
- * Hash credentials using the specified hash algorithm, possibly performing
- * key stretching by adding random salt and applying the hash function
- * multiple times.
- * </p>
- *
- * <p>
- * The algorithm must be supported by one of the registered security
- * providers in the JVM.
- * </p>
- *
- * <p>
- * 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
- * @param salt random salt to add to the credentials (possibly {@code null})
- * @param iterations the number of times to apply the hash function
- * @return a digest of the user name and password formatted as a string,
- * or {@code null} if {@code password} is {@code null}
- * @throws StandardException if the specified algorithm is not supported
- */
- String encryptPasswordConfigurableScheme(
- String user, String password, String algorithm,
- byte[] salt, int iterations)
- throws StandardException
- {
- if (password == null) {
- return null;
- }
-
- byte[] userBytes;
- byte[] passwordBytes;
- try {
- userBytes = user.getBytes(ENCODING);
- passwordBytes = password.getBytes(ENCODING);
- } catch (UnsupportedEncodingException uee) {
- // UTF-8 should always be available, so this should never happen.
- throw StandardException.plainWrapException(uee);
- }
-
- MessageDigest md;
- try {
- md = MessageDigest.getInstance(algorithm);
- } catch (NoSuchAlgorithmException nsae) {
- throw StandardException.newException(
- SQLState.DIGEST_NO_SUCH_ALGORITHM, nsae, algorithm);
- }
-
- byte[] digest = null;
- for (int i = 0; i < iterations; i++) {
- md.reset();
- if (digest != null) {
- md.update(digest);
- }
- md.update(userBytes);
- md.update(passwordBytes);
- if (salt != null) {
- md.update(salt);
- }
- digest = md.digest();
- }
-
- if ((salt == null || salt.length == 0) && iterations == 1) {
- // No salt was used, and only a single iteration, which is
- // identical to the default hashing scheme in 10.6-10.8. Generate
- // a token on a format compatible with those old versions.
- return ID_PATTERN_CONFIGURABLE_HASH_SCHEME +
- StringUtil.toHexString(digest, 0, digest.length) +
- SEPARATOR_CHAR + algorithm;
- } else {
- // Salt and/or multiple iterations was used, so we need to add
- // those parameters to the token in order to verify the credentials
- // later.
- return ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME +
- StringUtil.toHexString(digest, 0, digest.length) +
- SEPARATOR_CHAR + StringUtil.toHexString(salt, 0, salt.length) +
- SEPARATOR_CHAR + iterations + SEPARATOR_CHAR + algorithm;
- }
- }
-
- /**
- * <p>
* Encrypt a password using the default hash algorithm for this system
* before it's stored in the database.
* </p>
@@ -639,100 +521,14 @@ public abstract class AuthenticationServ
private String encryptUsingDefaultAlgorithm(String user,
String password,
Dictionary props)
- throws StandardException {
- DataDictionary dd = getDataDictionary();
-
- // Support for configurable hash algorithm was added in Derby 10.6, so
- // we don't want to store a hash using the new scheme if the database
- // is running in soft upgrade and may be used with an older version
- // later.
- boolean supportConfigurableHash =
- dd.checkVersion(DataDictionary.DD_VERSION_DERBY_10_6, null);
-
- // Support for key stretching was added in Derby 10.9, so don't use it
- // if the database may still be used with an older version.
- boolean supportKeyStretching =
- dd.checkVersion(DataDictionary.DD_VERSION_DERBY_10_9, null);
-
- if (supportConfigurableHash) {
- String algorithm = (String)
- PropertyUtil.getPropertyFromSet(
- props,
- Property.AUTHENTICATION_BUILTIN_ALGORITHM);
-
- if (algorithm != null && algorithm.length() > 0) {
- byte[] salt = null;
- int iterations = 1;
-
- if (supportKeyStretching) {
- salt = generateRandomSalt(props);
- iterations = getIntProperty(
- props,
- Property.AUTHENTICATION_BUILTIN_ITERATIONS,
- Property.AUTHENTICATION_BUILTIN_ITERATIONS_DEFAULT,
- 1, Integer.MAX_VALUE);
- }
-
- return encryptPasswordConfigurableScheme(
- user, password, algorithm, salt, iterations);
- }
- }
-
- return encryptPasswordSHA1Scheme(password);
- }
-
- /**
- * Get the value of an integer property.
- *
- * @param props database properties
- * @param key the key of the property
- * @param defaultValue which value to return if the property is not set,
- * or if the property value is not in the valid range
- * @param minValue lowest property value to accept
- * @param maxValue highest property value to accept
- * @return the value of the property
- */
- private int getIntProperty(
- Dictionary props, String key,
- int defaultValue, int minValue, int maxValue) {
-
- String sVal = (String) PropertyUtil.getPropertyFromSet(props, key);
-
- if (sVal != null) {
- try {
- int i = Integer.parseInt(sVal);
- if (i >= minValue && i <= maxValue) {
- return i;
- }
- } catch (NumberFormatException nfe) {
- // By convention, Derby ignores property values that cannot be
- // parsed. Use the default value instead.
- }
- }
-
- return defaultValue;
- }
+ throws StandardException
+ {
+ if ( password == null ) { return null; }
- /**
- * Generate an array of random bytes to use as salt when hashing
- * credentials.
- *
- * @param props database properties that possibly specify the desired
- * length of the salt
- * @return random bytes
- */
- private byte[] generateRandomSalt(Dictionary props) {
- int saltLength = getIntProperty(
- props,
- Property.AUTHENTICATION_BUILTIN_SALT_LENGTH,
- Property.AUTHENTICATION_BUILTIN_SALT_LENGTH_DEFAULT,
- 0, Integer.MAX_VALUE);
-
- SecureRandom random = new SecureRandom();
- byte[] salt = new byte[saltLength];
- random.nextBytes(salt);
+ PasswordHasher hasher = getDataDictionary().makePasswordHasher( props );
- return salt;
+ if ( hasher != null ) { return hasher.hashAndEncode( user, password ); }
+ else { return encryptPasswordSHA1Scheme(password); }
}
/**
@@ -761,10 +557,10 @@ public abstract class AuthenticationServ
* 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
+ * SHA-1 authentication scheme ({@link PasswordHasher#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}
+ * hash authentication scheme {@link PasswordHasher#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
@@ -857,7 +653,7 @@ public abstract class AuthenticationServ
bytePasswd = toHexByte(password);
messageDigest.update(bytePasswd);
byte[] encryptVal = messageDigest.digest();
- hexString = ID_PATTERN_SHA1_SCHEME +
+ hexString = PasswordHasher.ID_PATTERN_SHA1_SCHEME +
StringUtil.toHexString(encryptVal, 0, encryptVal.length);
}
else
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=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/BasicAuthenticationServiceImpl.java Wed Jan 4 13:30:22 2012
@@ -21,6 +21,7 @@
package org.apache.derby.impl.jdbc.authentication;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
import org.apache.derby.iapi.reference.Attribute;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.authentication.UserAuthenticator;
@@ -270,34 +271,14 @@ public final class BasicAuthenticationSe
String user, String password, String storedPassword)
throws StandardException
{
- if (storedPassword.startsWith(ID_PATTERN_SHA1_SCHEME)) {
+ if (storedPassword.startsWith( PasswordHasher.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, null, 1);
- } else if (storedPassword.startsWith(
- ID_PATTERN_CONFIGURABLE_STRETCHED_SCHEME)) {
- int saltPos = storedPassword.indexOf(SEPARATOR_CHAR) + 1;
- int iterPos = storedPassword.indexOf(SEPARATOR_CHAR, saltPos) + 1;
- int algoPos = storedPassword.indexOf(SEPARATOR_CHAR, iterPos) + 1;
-
- byte[] salt = StringUtil.fromHexString(
- storedPassword, saltPos, iterPos - saltPos - 1);
- int iterations = Integer.parseInt(
- storedPassword.substring(iterPos, algoPos - 1));
- String algorithm = storedPassword.substring(algoPos);
- return encryptPasswordConfigurableScheme(
- user, password, algorithm, salt, iterations);
- } else {
- if (SanityManager.DEBUG) {
- SanityManager.THROWASSERT(
- "Unknown authentication scheme for token " +
- storedPassword);
- }
- return null;
+ }
+ else
+ {
+ PasswordHasher hasher = new PasswordHasher( storedPassword );
+
+ return hasher.hashAndEncode( user, password );
}
}
}
Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java Wed Jan 4 13:30:22 2012
@@ -46,6 +46,7 @@ import org.apache.derby.iapi.sql.diction
import org.apache.derby.iapi.sql.dictionary.DependencyDescriptor;
import org.apache.derby.iapi.sql.dictionary.ForeignKeyConstraintDescriptor;
import org.apache.derby.iapi.sql.dictionary.GenericDescriptorList;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
import org.apache.derby.iapi.sql.dictionary.TupleDescriptor;
import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
import org.apache.derby.iapi.sql.dictionary.KeyConstraintDescriptor;
@@ -153,10 +154,13 @@ import org.apache.derby.iapi.services.lo
import org.apache.derby.iapi.services.locks.ShExQual;
import org.apache.derby.iapi.util.IdUtil;
+import java.security.SecureRandom;
+
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Dictionary;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.HashMap;
@@ -1459,6 +1463,104 @@ public final class DataDictionaryImpl
SchemaDescriptor.SYSCS_UTIL_SCHEMA_UUID);
}
+ // returns null if database is at rev level 10.5 or earlier
+ public PasswordHasher makePasswordHasher( 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 = checkVersion(DataDictionary.DD_VERSION_DERBY_10_6, null);
+
+ // Support for key stretching was added in Derby 10.9, so don't use it
+ // if the database may still be used with an older version.
+ boolean supportKeyStretching = checkVersion(DataDictionary.DD_VERSION_DERBY_10_9, null);
+
+ if ( !supportConfigurableHash ) { return null; }
+ else
+ {
+ String algorithm = (String)
+ PropertyUtil.getPropertyFromSet(
+ props,
+ Property.AUTHENTICATION_BUILTIN_ALGORITHM);
+
+ if ( algorithm == null ) { return null; }
+
+ byte[] salt = null;
+ int iterations = 1;
+
+ if (algorithm != null && algorithm.length() > 0) {
+
+ if (supportKeyStretching) {
+ salt = generateRandomSalt(props);
+ iterations = getIntProperty(
+ props,
+ Property.AUTHENTICATION_BUILTIN_ITERATIONS,
+ Property.AUTHENTICATION_BUILTIN_ITERATIONS_DEFAULT,
+ 1, Integer.MAX_VALUE);
+ }
+ }
+
+ return new PasswordHasher( algorithm, salt, iterations );
+ }
+ }
+
+ /**
+ * Generate an array of random bytes to use as salt when hashing
+ * credentials.
+ *
+ * @param props database properties that possibly specify the desired
+ * length of the salt
+ * @return random bytes
+ */
+ private byte[] generateRandomSalt(Dictionary props) {
+ int saltLength = getIntProperty(
+ props,
+ Property.AUTHENTICATION_BUILTIN_SALT_LENGTH,
+ Property.AUTHENTICATION_BUILTIN_SALT_LENGTH_DEFAULT,
+ 0, Integer.MAX_VALUE);
+
+ SecureRandom random = new SecureRandom();
+ byte[] salt = new byte[saltLength];
+ random.nextBytes(salt);
+
+ return salt;
+ }
+ /**
+ * Get the value of an integer property.
+ *
+ * @param props database properties
+ * @param key the key of the property
+ * @param defaultValue which value to return if the property is not set,
+ * or if the property value is not in the valid range
+ * @param minValue lowest property value to accept
+ * @param maxValue highest property value to accept
+ * @return the value of the property
+ */
+ private int getIntProperty(
+ Dictionary props, String key,
+ int defaultValue, int minValue, int maxValue) {
+
+ String sVal = (String) PropertyUtil.getPropertyFromSet(props, key);
+
+ if (sVal != null) {
+ try {
+ int i = Integer.parseInt(sVal);
+ if (i >= minValue && i <= maxValue) {
+ return i;
+ }
+ } catch (NumberFormatException nfe) {
+ // By convention, Derby ignores property values that cannot be
+ // parsed. Use the default value instead.
+ }
+ }
+
+ return defaultValue;
+ }
+
+
+
/**
* Get the descriptor for the system schema. Schema descriptors include
* authorization ids and schema ids.
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=1227150&r1=1227149&r2=1227150&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 Wed Jan 4 13:30:22 2012
@@ -1234,6 +1234,11 @@ Guide.
</msg>
<msg>
+ <name>4251G</name>
+ <text>Please set derby.authentication.builtin.algorithm to a valid message digest algorithm. The current authentication scheme is too weak to be used by NATIVE authentication.</text>
+ </msg>
+
+ <msg>
<name>42601</name>
<text>In an ALTER TABLE statement, the column '{0}' has been specified as NOT NULL and either the DEFAULT clause was not specified or was specified as DEFAULT NULL.</text>
<arg>columnName</arg>
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=1227150&r1=1227149&r2=1227150&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 Wed Jan 4 13:30:22 2012
@@ -779,6 +779,7 @@ public interface SQLState {
String DBO_ONLY = "4251D";
String HIDDEN_COLUMN = "4251E";
String CANT_DROP_DBO = "4251F";
+ String WEAK_AUTHENTICATION = "4251G";
String LANG_DB2_NOT_NULL_COLUMN_INVALID_DEFAULT = "42601";
String LANG_DB2_INVALID_HEXADECIMAL_CONSTANT = "42606";
Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java (original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java Wed Jan 4 13:30:22 2012
@@ -20,6 +20,7 @@
*/
package org.apache.derby.impl.storeless;
+import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
@@ -47,6 +48,7 @@ import org.apache.derby.iapi.sql.diction
import org.apache.derby.iapi.sql.dictionary.DependencyDescriptor;
import org.apache.derby.iapi.sql.dictionary.FileInfoDescriptor;
import org.apache.derby.iapi.sql.dictionary.GenericDescriptorList;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
import org.apache.derby.iapi.sql.dictionary.PermissionsDescriptor;
import org.apache.derby.iapi.sql.dictionary.RoutinePermsDescriptor;
import org.apache.derby.iapi.sql.dictionary.SPSDescriptor;
@@ -162,6 +164,13 @@ public class EmptyDictionary implements
return false;
}
+ public PasswordHasher makePasswordHasher( Dictionary props )
+ throws StandardException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
public SchemaDescriptor getSystemSchemaDescriptor()
throws StandardException {
// TODO Auto-generated method stub
Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthProcs.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthProcs.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthProcs.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthProcs.java Wed Jan 4 13:30:22 2012
@@ -54,6 +54,10 @@ public class NativeAuthProcs extends Gen
private static final String NO_EXECUTE_PERMISSION = "42504";
private static final String DUPLICATE_USER = "X0Y68";
private static final String CANT_DROP_DBO = "4251F";
+ private static final String WEAK_AUTHENTICATION = "4251G";
+
+ private static final String HASHING_FORMAT_10_9 = "3b62";
+ private static final int HEX_CHARS_PER_BYTE = 2;
///////////////////////////////////////////////////////////////////////////////////
//
@@ -179,6 +183,14 @@ public class NativeAuthProcs extends Gen
Connection dboConnection = openUserConnection( TEST_DBO );
Connection janetConnection = openUserConnection( JANET );
+ // set a message digest algorithm in case it was unset by a previous test.
+ // a message digest algorithm must be set in order to use NATIVE authentication
+ String defaultDigestAlgorithm = getDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm" );
+ if ( defaultDigestAlgorithm == null )
+ {
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm", "SHA-1" );
+ }
+
if ( !dboExists( dboConnection ) )
{
goodStatement( dboConnection, "call syscs_util.syscs_create_user( 'TEST_DBO', 'test_dbopassword' )" );
@@ -189,6 +201,14 @@ public class NativeAuthProcs extends Gen
resetPasswordTests( dboConnection, janetConnection );
modifyPasswordTests( dboConnection, janetConnection );
if ( authorizationIsOn() ) { grantRevokeTests( dboConnection, janetConnection ); }
+
+ passwordHashingTests( dboConnection );
+
+ // reset the message digest algorithm in order to not disrupt the existing tests
+ if ( defaultDigestAlgorithm == null )
+ {
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm", null );
+ }
}
private boolean dboExists( Connection conn )
throws Exception
@@ -449,4 +469,118 @@ public class NativeAuthProcs extends Gen
( janetConnection, NO_EXECUTE_PERMISSION, "call syscs_util.syscs_drop_user( 'JOE' )" );
}
+ //
+ // Create/Drop User
+ //
+
+ private void passwordHashingTests
+ ( Connection dboConnection )
+ throws Exception
+ {
+ String defaultDigestAlgorithm = getDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm" );
+
+ goodStatement( dboConnection, "call syscs_util.syscs_create_user( 'pht', 'phtpassword' )" );
+
+ vetHashingScheme( dboConnection, "pht", HASHING_FORMAT_10_9, 16, 1000, defaultDigestAlgorithm );
+
+ int saltLength = 5;
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.saltLength", Integer.toString( saltLength ) );
+ goodStatement( dboConnection, "call syscs_util.syscs_reset_password( 'pht', 'newsaltlength' )" );
+ vetHashingScheme( dboConnection, "pht", HASHING_FORMAT_10_9, saltLength, 1000, defaultDigestAlgorithm );
+
+ int iterations = 10;
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.iterations", Integer.toString( iterations ) );
+ goodStatement( dboConnection, "call syscs_util.syscs_reset_password( 'pht', 'newiterations' )" );
+ vetHashingScheme( dboConnection, "pht", HASHING_FORMAT_10_9, saltLength, iterations, defaultDigestAlgorithm );
+
+ String digestAlgorithm = "SHA-1";
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm", digestAlgorithm );
+ goodStatement( dboConnection, "call syscs_util.syscs_reset_password( 'pht', 'newiterations' )" );
+ vetHashingScheme( dboConnection, "pht", HASHING_FORMAT_10_9, saltLength, iterations, digestAlgorithm );
+
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm", null );
+ expectExecutionError( dboConnection, WEAK_AUTHENTICATION, "call syscs_util.syscs_reset_password( 'pht', 'badalgorithm' )" );
+
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.saltLength", null );
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.iterations", null );
+ setDatabaseProperty( dboConnection, "derby.authentication.builtin.algorithm", defaultDigestAlgorithm );
+ goodStatement( dboConnection, "call syscs_util.syscs_drop_user( 'pht' )" );
+ }
+ private void setDatabaseProperty( Connection conn, String key, String value )
+ throws Exception
+ {
+ if ( value == null ) { value = "cast ( null as varchar( 32672 ) )"; }
+ else { value = "'" + value + "'"; }
+ String command = "call syscs_util.syscs_set_database_property( '" + key + "', " + value + " )";
+
+ goodStatement( conn, command );
+ }
+ private String getDatabaseProperty( Connection conn, String key )
+ throws Exception
+ {
+ PreparedStatement ps = chattyPrepare( conn, "values( syscs_util.syscs_get_database_property( '" + key + "' ) )" );
+ ResultSet rs = ps.executeQuery();
+
+ try {
+ rs.next();
+ return rs.getString( 1 );
+ }
+ finally
+ {
+ rs.close();
+ ps.close();
+ }
+ }
+ private void vetHashingScheme
+ (
+ Connection conn,
+ String userName,
+ String expectedHashingFormat,
+ int expectedSaltLength,
+ int expectedIterations,
+ String expectedDigestAlgorithm
+ )
+ throws Exception
+ {
+ String hashingScheme = getHashingScheme( conn, userName );
+ int firstColonPosition = hashingScheme.indexOf( ":" );
+ int secondColonPosition = hashingScheme.indexOf( ":", firstColonPosition + 1 );
+ int thirdColonPosition = hashingScheme.indexOf( ":", secondColonPosition + 1 );
+
+ String actualHashingFormat = hashingScheme.substring( 0, firstColonPosition );
+ String salt = hashingScheme.substring( firstColonPosition + 1, secondColonPosition );
+ String iterationString = hashingScheme.substring( secondColonPosition + 1, thirdColonPosition );
+ String actualDigestAlgorithm = hashingScheme.substring( thirdColonPosition + 1 );
+
+ int actualSaltLength = salt.length();
+ int actualIterations = Integer.parseInt( iterationString );
+
+ assertEquals( expectedHashingFormat, actualHashingFormat );
+ assertEquals( expectedSaltLength * HEX_CHARS_PER_BYTE, actualSaltLength );
+ assertEquals( expectedIterations, actualIterations );
+ assertEquals( expectedDigestAlgorithm, actualDigestAlgorithm );
+ }
+ private String getHashingScheme( Connection conn, String userName )
+ throws Exception
+ {
+ PreparedStatement ps = conn.prepareStatement( "select userName, hashingScheme from sys.sysusers" );
+ ResultSet rs = ps.executeQuery();
+
+ try {
+ while ( rs.next() )
+ {
+ if ( userName.equals( rs.getString( 1 ) ) ) { return rs.getString( 2 ); }
+ }
+ } finally
+ {
+ rs.close();
+ ps.close();
+ }
+
+ fail( "Could not find credentials for " + userName );
+
+ // never get here
+ return null;
+ }
+
}
Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java?rev=1227150&r1=1227149&r2=1227150&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/upgradeTests/Changes10_9.java Wed Jan 4 13:30:22 2012
@@ -204,6 +204,13 @@ public class Changes10_9 extends Upgrade
}
private void vetNativeProcs( Statement s, boolean shouldExist ) throws Exception
{
+ // make sure that an authentication algorithm has been set
+ String defaultDigestAlgorithm = getDatabaseProperty( s, "derby.authentication.builtin.algorithm" );
+ if ( defaultDigestAlgorithm == null )
+ {
+ setDatabaseProperty( s, "derby.authentication.builtin.algorithm", "SHA-1" );
+ }
+
try {
s.execute( "call syscs_util.syscs_create_user( 'fred', 'fredpassword' )" );
@@ -236,7 +243,35 @@ public class Changes10_9 extends Upgrade
}
assertSQLState( "42Y03", se );
}
-
+
+ // restore the authentication algorithm if we changed it
+ if ( defaultDigestAlgorithm == null )
+ {
+ setDatabaseProperty( s, "derby.authentication.builtin.algorithm", null );
+ }
+ }
+ private void setDatabaseProperty( Statement s, String key, String value )
+ throws Exception
+ {
+ if ( value == null ) { value = "cast ( null as varchar( 32672 ) )"; }
+ else { value = "'" + value + "'"; }
+ String command = "call syscs_util.syscs_set_database_property( '" + key + "', " + value + " )";
+
+ s.execute( command );
+ }
+ private String getDatabaseProperty( Statement s, String key )
+ throws Exception
+ {
+ ResultSet rs = s.executeQuery( "values( syscs_util.syscs_get_database_property( '" + key + "' ) )" );
+
+ try {
+ rs.next();
+ return rs.getString( 1 );
+ }
+ finally
+ {
+ rs.close();
+ }
}
/**