mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
12.15.2014 e93ab9d4d1bcaf18042d569e17909cc724af35be
Code cleanup for password storage schemes.
Used AutoRefactor.
Reduced variable scopes.


PBKDF2PasswordStorageScheme.java:
Extracted methods getDigestBytes(), encodeAndMatch(),
Removed unnecessary use of StringBuilders.
In passwordMatches() and authPasswordMatches(), used String.indexOf().

PKCS5S2PasswordStorageScheme.java:
See general changes.

PKCS5S2PasswordStorageSchemeTestCase.java:
In getTestPasswords(), reused the super method and filtered entries.
Fix test failures with Java 6 due to limitation in JDK.
1 files added
3 files modified
708 ■■■■■ changed files
opendj3-server-dev/src/server/org/opends/server/extensions/PBKDF2PasswordStorageScheme.java 372 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java 211 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java 87 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java 38 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/extensions/PBKDF2PasswordStorageScheme.java
@@ -21,35 +21,37 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2013-2014 ForgeRock AS.
 *      Copyright 2013-2014 ForgeRock AS.
 */
package org.opends.server.extensions;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.List;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.PBKDF2PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.types.*;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteSequence;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.util.Base64;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a Directory Server password storage scheme based on the
@@ -65,9 +67,7 @@
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /**
   * The fully-qualified name of this class.
   */
  /** The fully-qualified name of this class. */
  private static final String CLASS_NAME =
       "org.opends.server.extensions.PBKDF2PasswordStorageScheme";
@@ -78,19 +78,19 @@
   */
  private static final int NUM_SALT_BYTES = 8;
  // The number of bytes the SHA-1 algorithm produces
  /** The number of bytes the SHA-1 algorithm produces. */
  private static final int SHA1_LENGTH = 20;
  // The factory used to generate the PBKDF2 hashes.
  /** The factory used to generate the PBKDF2 hashes. */
  private SecretKeyFactory factory;
  // The lock used to provide threadsafe access to the message digest.
  private Object factoryLock;
  /** The lock used to provide thread-safe access to the message digest. */
  private final Object factoryLock = new Object();
  // The secure random number generator to use to generate the salt values.
  /** The secure random number generator to use to generate the salt values. */
  private SecureRandom random;
  // The current configuration for this storage scheme.
  /** The current configuration for this storage scheme. */
  private volatile PBKDF2PasswordStorageSchemeCfg config;
@@ -104,17 +104,12 @@
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public void initializePasswordStorageScheme(
                   PBKDF2PasswordStorageSchemeCfg configuration)
         throws ConfigException, InitializationException
  {
    factoryLock = new Object();
    try
    {
      random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
@@ -129,24 +124,17 @@
    config.addPBKDF2ChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public boolean isConfigurationChangeAcceptable(
                      PBKDF2PasswordStorageSchemeCfg configuration,
                      List<LocalizableMessage> unacceptableReasons)
  {
    // The configuration will always be acceptable.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(
      PBKDF2PasswordStorageSchemeCfg configuration)
  {
@@ -154,57 +142,22 @@
    return new ConfigChangeResult(ResultCode.SUCCESS, false);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public String getStorageSchemeName()
  {
    return STORAGE_SCHEME_NAME_PBKDF2;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodePassword(ByteSequence plaintext)
         throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes;
    char[] plaintextChars = null;
    int    iterations     = config.getPBKDF2Iterations();
    synchronized(factoryLock)
    {
      try
      {
        random.nextBytes(saltBytes);
        plaintextChars = plaintext.toString().toCharArray();
        KeySpec spec = new PBEKeySpec(plaintextChars,
            saltBytes, iterations, SHA1_LENGTH * 8);
        digestBytes = factory.generateSecret(spec).getEncoded();
      }
      catch (Exception e)
      {
        logger.traceException(e);
        LocalizableMessage message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
            CLASS_NAME, getExceptionMessage(e));
        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                     message, e);
      }
      finally
      {
        if (plaintextChars != null)
          Arrays.fill(plaintextChars, '0');
      }
    }
    byte[] digestBytes = getDigestBytes(plaintext, saltBytes, iterations);
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
@@ -212,73 +165,49 @@
    System.arraycopy(saltBytes, 0, hashPlusSalt, digestBytes.length,
                     NUM_SALT_BYTES);
    StringBuilder sb = new StringBuilder();
    sb.append(Integer.toString(iterations));
    sb.append(':');
    sb.append(Base64.encode(hashPlusSalt));
    return ByteString.valueOf(sb.toString());
    return ByteString.valueOf(iterations + ":" + Base64.encode(hashPlusSalt));
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
         throws DirectoryException
  {
    StringBuilder buffer = new StringBuilder();
    buffer.append('{');
    buffer.append(STORAGE_SCHEME_NAME_PBKDF2);
    buffer.append('}');
    buffer.append(encodePassword(plaintext));
    return ByteString.valueOf(buffer.toString());
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PBKDF2 + '}'
        + encodePassword(plaintext));
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean passwordMatches(ByteSequence plaintextPassword,
                                 ByteSequence storedPassword)
  {
    // Split the iterations from the stored value (separated by a ":")
    // Split the iterations from the stored value (separated by a ':')
    // Base64-decode the remaining value and take the last 8 bytes as the salt.
    int iterations;
    byte[] saltBytes;
    byte[] digestBytes = new byte[SHA1_LENGTH];
    int saltLength = 0;
    try
    {
      String stored = storedPassword.toString();
      int stored_length = stored.length();
      int pos = 0;
      while (pos < stored_length && stored.charAt(pos) != ':')
      int pos = stored.indexOf(':');
      if (pos == -1)
      {
        pos++;
      }
      if (pos >= (stored_length - 1) || pos == 0)
        throw new Exception();
      }
      iterations = Integer.parseInt(stored.substring(0, pos));
      final int iterations = Integer.parseInt(stored.substring(0, pos));
      byte[] decodedBytes = Base64.decode(stored.substring(pos + 1));
      saltLength = decodedBytes.length - SHA1_LENGTH;
      final int saltLength = decodedBytes.length - SHA1_LENGTH;
      if (saltLength <= 0)
      {
        logger.error(ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD, storedPassword);
        return false;
      }
      saltBytes = new byte[saltLength];
      final byte[] digestBytes = new byte[SHA1_LENGTH];
      final byte[] saltBytes = new byte[saltLength];
      System.arraycopy(decodedBytes, 0, digestBytes, 0, SHA1_LENGTH);
      System.arraycopy(decodedBytes, SHA1_LENGTH, saltBytes, 0,
                       saltLength);
      System.arraycopy(decodedBytes, SHA1_LENGTH, saltBytes, 0, saltLength);
      return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
    }
    catch (Exception e)
    {
@@ -286,18 +215,19 @@
      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD, storedPassword, e);
      return false;
    }
  }
  private boolean encodeAndMatch(ByteSequence plaintextPassword,
      final byte[] saltBytes, byte[] digestBytes, int iterations)
  {
    // Use the salt to generate a digest based on the provided plain-text value.
    int plainBytesLength = plaintextPassword.length();
    byte[] plainPlusSalt = new byte[plainBytesLength + saltLength];
    byte[] plainPlusSalt = new byte[plainBytesLength + saltBytes.length];
    plaintextPassword.copyTo(plainPlusSalt);
    System.arraycopy(saltBytes, 0, plainPlusSalt, plainBytesLength,
                     saltLength);
    System.arraycopy(saltBytes, 0, plainPlusSalt, plainBytesLength, saltBytes.length);
    byte[] userDigestBytes;
    char[] plaintextChars = null;
    synchronized (factoryLock)
    {
      try
@@ -306,62 +236,58 @@
        KeySpec spec = new PBEKeySpec(
            plaintextChars, saltBytes,
            iterations, SHA1_LENGTH * 8);
        userDigestBytes = factory.generateSecret(spec).getEncoded();
        final byte[] userDigestBytes = factory.generateSecret(spec).getEncoded();
        return Arrays.equals(digestBytes, userDigestBytes);
      }
      catch (Exception e)
      {
        logger.traceException(e);
        return false;
      }
      finally
      {
        if (plaintextChars != null)
        {
          Arrays.fill(plaintextChars, '0');
        }
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean supportsAuthPasswordSyntax()
  {
    // This storage scheme does support the authentication password syntax.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public String getAuthPasswordSchemeName()
  {
    return AUTH_PASSWORD_SCHEME_NAME_PBKDF2;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodeAuthPassword(ByteSequence plaintext)
         throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes;
    char[] plaintextChars = null;
    int    iterations     = config.getPBKDF2Iterations();
    byte[] digestBytes = getDigestBytes(plaintext, saltBytes, iterations);
    synchronized(factoryLock)
    // Encode and return the value.
    return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PBKDF2 + '$'
        + iterations + ':' + Base64.encode(saltBytes) + '$'
        + Base64.encode(digestBytes));
  }
  private byte[] getDigestBytes(ByteSequence plaintext, byte[] saltBytes,
      int iterations) throws DirectoryException
  {
    char[] plaintextChars = null;
    synchronized (factoryLock)
    {
      try
      {
@@ -371,7 +297,7 @@
        KeySpec spec = new PBEKeySpec(
            plaintextChars, saltBytes,
            iterations, SHA1_LENGTH * 8);
        digestBytes = factory.generateSecret(spec).getEncoded();
        return factory.generateSecret(spec).getEncoded();
      }
      catch (Exception e)
      {
@@ -385,109 +311,46 @@
      finally
      {
        if (plaintextChars != null)
        {
          Arrays.fill(plaintextChars, '0');
        }
      }
    }
    // Encode and return the value.
    StringBuilder authPWValue = new StringBuilder();
    authPWValue.append(AUTH_PASSWORD_SCHEME_NAME_PBKDF2);
    authPWValue.append('$');
    authPWValue.append(Integer.toString(iterations));
    authPWValue.append(':');
    authPWValue.append(Base64.encode(saltBytes));
    authPWValue.append('$');
    authPWValue.append(Base64.encode(digestBytes));
    return ByteString.valueOf(authPWValue.toString());
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean authPasswordMatches(ByteSequence plaintextPassword,
                                     String authInfo, String authValue)
  {
    byte[] saltBytes;
    byte[] digestBytes;
    int    iterations;
    try
    {
      int pos = 0;
      int length = authInfo.length();
      while (pos < length && authInfo.charAt(pos) != ':')
      int pos = authInfo.indexOf(':');
      if (pos == -1)
      {
        pos++;
      }
      if (pos >= (length - 1) || pos == 0)
        throw new Exception();
      iterations = Integer.parseInt(authInfo.substring(0, pos));
      saltBytes   = Base64.decode(authInfo.substring(pos + 1));
      digestBytes = Base64.decode(authValue);
      }
      int iterations = Integer.parseInt(authInfo.substring(0, pos));
      byte[] saltBytes   = Base64.decode(authInfo.substring(pos + 1));
      byte[] digestBytes = Base64.decode(authValue);
      return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      return false;
    }
    int plainBytesLength = plaintextPassword.length();
    byte[] plainPlusSalt = new byte[plainBytesLength + saltBytes.length];
    plaintextPassword.copyTo(plainPlusSalt);
    System.arraycopy(saltBytes, 0, plainPlusSalt, plainBytesLength,
                     saltBytes.length);
    byte[] userDigestBytes;
    char[] plaintextChars = null;
    synchronized (factoryLock)
    {
      try
      {
        plaintextChars = plaintextPassword.toString().toCharArray();
        KeySpec spec = new PBEKeySpec(
            plaintextChars, saltBytes,
            iterations, SHA1_LENGTH * 8);
        userDigestBytes = factory.generateSecret(spec).getEncoded();
      }
      catch (Exception e)
      {
        logger.traceException(e);
        return false;
      }
      finally
      {
        if (plaintextChars != null)
          Arrays.fill(plaintextChars, '0');
      }
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean isReversible()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString getPlaintextValue(ByteSequence storedPassword)
         throws DirectoryException
  {
@@ -496,12 +359,8 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString getAuthPasswordPlaintextValue(String authInfo,
                                                  String authValue)
         throws DirectoryException
@@ -511,15 +370,10 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean isStorageSchemeSecure()
  {
    // PBKDF2 should be considered secure.
    return true;
  }
@@ -542,19 +396,33 @@
         throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes;
    char[] plaintextChars = null;
    int    iterations     = 10000;
    byte[] digestBytes = getDigestBytes(passwordBytes, saltBytes, iterations);
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
    System.arraycopy(digestBytes, 0, hashPlusSalt, 0, digestBytes.length);
    System.arraycopy(saltBytes, 0, hashPlusSalt, digestBytes.length,
                     NUM_SALT_BYTES);
    return '{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + iterations + ':' +
      Base64.encode(hashPlusSalt);
  }
  private static byte[] getDigestBytes(byte[] plaintext, byte[] saltBytes,
      int iterations) throws DirectoryException
  {
    char[] plaintextChars = null;
    try
    {
      SecureRandom.getInstance(SECURE_PRNG_SHA1).nextBytes(saltBytes);
      plaintextChars = passwordBytes.toString().toCharArray();
      plaintextChars = plaintext.toString().toCharArray();
      KeySpec spec = new PBEKeySpec(
          plaintextChars, saltBytes,
          iterations, SHA1_LENGTH * 8);
      digestBytes = SecretKeyFactory
      return SecretKeyFactory
          .getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2)
          .generateSecret(spec).getEncoded();
    }
@@ -570,18 +438,10 @@
    finally
    {
      if (plaintextChars != null)
      {
        Arrays.fill(plaintextChars, '0');
      }
    }
    // Append the salt to the hashed value and base64-the whole thing.
    byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
    System.arraycopy(digestBytes, 0, hashPlusSalt, 0, digestBytes.length);
    System.arraycopy(saltBytes, 0, hashPlusSalt, digestBytes.length,
                     NUM_SALT_BYTES);
    return "{" + STORAGE_SCHEME_NAME_PBKDF2 + "}" + iterations + ":" +
      Base64.encode(hashPlusSalt);
  }
}
opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -26,6 +26,14 @@
 */
package org.opends.server.extensions;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteSequence;
@@ -38,16 +46,9 @@
import org.opends.server.types.InitializationException;
import org.opends.server.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a Directory Server password storage scheme based on the
@@ -63,9 +64,7 @@
{
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /**
   * The fully-qualified name of this class.
   */
  /** The fully-qualified name of this class. */
  private static final String CLASS_NAME =
      "org.opends.server.extensions.PKCS5S2PasswordStorageScheme";
@@ -76,29 +75,19 @@
   */
  private static final int NUM_SALT_BYTES = 16;
  /**
   * The number of bytes the SHA-1 algorithm produces.
   */
  /** The number of bytes the SHA-1 algorithm produces. */
  private static final int SHA1_LENGTH = 32;
  /**
   *  Atlassian hardcoded the number of iterations to 10000.
   */
  /** Atlassian hardcoded the number of iterations to 10000. */
  private static final int iterations = 10000;
  /**
   * The factory used to generate the PKCS5S2 hashes.
   */
  /** The factory used to generate the PKCS5S2 hashes. */
  private SecretKeyFactory factory;
  /**
   * The lock used to provide thread-safe access to the message digest.
   */
  /** The lock used to provide thread-safe access to the message digest. */
  private final Object factoryLock = new Object();
  /**
   *  The secure random number generator to use to generate the salt values.
   */
  /** The secure random number generator to use to generate the salt values. */
  private SecureRandom random;
@@ -112,12 +101,8 @@
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public void initializePasswordStorageScheme(
      PKCS5S2PasswordStorageSchemeCfg configuration)
      throws InitializationException
@@ -133,21 +118,15 @@
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public String getStorageSchemeName()
  {
    return STORAGE_SCHEME_NAME_PKCS5S2;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodePassword(ByteSequence plaintext)
      throws DirectoryException
  {
@@ -159,32 +138,21 @@
    return ByteString.valueOf(Base64.encode(hashPlusSalt));
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
      throws DirectoryException
  {
    return ByteString.valueOf("{" + STORAGE_SCHEME_NAME_PKCS5S2 + '}'
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}'
        + encodePassword(plaintext));
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean passwordMatches(ByteSequence plaintextPassword,
                                 ByteSequence storedPassword)
  {
    // Base64-decode the value and take the first 16 bytes as the salt.
    byte[] saltBytes = new byte[NUM_SALT_BYTES];
    final int saltLength = NUM_SALT_BYTES;
    byte[] digestBytes = new byte[SHA1_LENGTH];
    try
    {
      String stored = storedPassword.toString();
@@ -197,9 +165,13 @@
            storedPassword.toString()));
        return false;
      }
      final int saltLength = NUM_SALT_BYTES;
      byte[] saltBytes = new byte[saltLength];
      byte[] digestBytes = new byte[SHA1_LENGTH];
      System.arraycopy(decodedBytes, 0, saltBytes, 0, saltLength);
      System.arraycopy(decodedBytes, saltLength, digestBytes, 0,
          SHA1_LENGTH);
      System.arraycopy(decodedBytes, saltLength, digestBytes, 0, SHA1_LENGTH);
      return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
    }
    catch (Exception e)
    {
@@ -208,102 +180,68 @@
          storedPassword.toString(), String.valueOf(e)));
      return false;
    }
    return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean supportsAuthPasswordSyntax()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public String getAuthPasswordSchemeName()
  {
    return AUTH_PASSWORD_SCHEME_NAME_PKCS5S2;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodeAuthPassword(ByteSequence plaintext)
      throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes = createRandomSaltAndEncode(plaintext, saltBytes);
    // Encode and return the value.
    return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2 + '$' +
        iterations + ':' + Base64.encode(saltBytes) +
        '$' + Base64.encode(digestBytes));
    return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2 + '$'
        + iterations + ':' + Base64.encode(saltBytes) + '$'
        + Base64.encode(digestBytes));
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean authPasswordMatches(ByteSequence plaintextPassword,
                                     String authInfo, String authValue)
  {
    byte[] saltBytes;
    byte[] digestBytes;
    int    iterations;
    try
    {
      int pos = 0;
      int length = authInfo.length();
      while (pos < length && authInfo.charAt(pos) != ':')
      int pos = authInfo.indexOf(':');
      if (pos == -1)
      {
        pos++;
        return false;
      }
      if (pos >= (length - 1) || pos == 0)
        throw new Exception();
      iterations = Integer.parseInt(authInfo.substring(0, pos));
      saltBytes   = Base64.decode(authInfo.substring(pos + 1));
      digestBytes = Base64.decode(authValue);
      int iterations = Integer.parseInt(authInfo.substring(0, pos));
      byte[] saltBytes   = Base64.decode(authInfo.substring(pos + 1));
      byte[] digestBytes = Base64.decode(authValue);
      return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
    }
    catch (Exception e)
    {
        logger.traceException(e);
        return false;
      logger.traceException(e);
      return false;
    }
    return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean isReversible()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString getPlaintextValue(ByteSequence storedPassword)
      throws DirectoryException
  {
@@ -312,12 +250,8 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString getAuthPasswordPlaintextValue(String authInfo,
                                                  String authValue)
      throws DirectoryException
@@ -327,12 +261,8 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean isStorageSchemeSecure()
  {
    return true;
@@ -351,7 +281,7 @@
   * @return  The encoded password string, including the scheme name in curly
   *          braces.
   *
   * @throws  org.opends.server.types.DirectoryException  If a problem occurs during processing.
   * @throws  DirectoryException  If a problem occurs during processing.
   */
  public static String encodeOffline(byte[] passwordBytes)
      throws DirectoryException
@@ -382,7 +312,7 @@
    // Append the hashed value to the salt and base64-the whole thing.
    byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes);
    return "{" + STORAGE_SCHEME_NAME_PKCS5S2 + "}"  +
    return '{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' +
        Base64.encode(hashPlusSalt);
  }
@@ -390,33 +320,27 @@
  private boolean encodeAndMatch(ByteSequence plaintext,
                                 byte[] saltBytes, byte[] digestBytes, int iterations)
  {
    byte[] userDigestBytes;
    try
    {
      userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
      byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
      return Arrays.equals(digestBytes, userDigestBytes);
    }
    catch (Exception e)
    {
        return false;
      return false;
    }
    return Arrays.equals(digestBytes, userDigestBytes);
  }
  private byte[] createRandomSaltAndEncode(ByteSequence plaintext, byte[] saltBytes) throws DirectoryException {
    byte[] digestBytes;
    synchronized(factoryLock)
    {
      random.nextBytes(saltBytes);
      digestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
      return encodeWithSalt(plaintext, saltBytes, iterations);
    }
    return digestBytes;
  }
  private byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations) throws DirectoryException {
    byte[] digestBytes;
    char[] plaintextChars = null;
    try
    {
@@ -424,7 +348,7 @@
      KeySpec spec = new PBEKeySpec(
          plaintextChars, saltBytes,
          iterations, SHA1_LENGTH * 8);
      digestBytes = factory.generateSecret(spec).getEncoded();
      return factory.generateSecret(spec).getEncoded();
    }
    catch (Exception e)
    {
@@ -442,7 +366,6 @@
        Arrays.fill(plaintextChars, '0');
      }
    }
    return digestBytes;
  }
  private static byte[] concatenateSaltPlusHash(byte[] saltBytes, byte[] digestBytes) {
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PBKDF2PasswordStorageSchemeTestCase.java
New file
@@ -0,0 +1,87 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 */
package org.opends.server.extensions;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PBKDF2PasswordStorageSchemeCfgDefn;
import org.opends.server.admin.std.server.PBKDF2PasswordStorageSchemeCfg;
import org.opends.server.api.PasswordStorageScheme;
import org.testng.annotations.DataProvider;
/**
 * A set of test cases for the PBKDF2 password storage scheme.
 */
public class PBKDF2PasswordStorageSchemeTestCase
       extends PasswordStorageSchemeTestCase
{
  /** Creates a new instance of this storage scheme test case.   */
  public PBKDF2PasswordStorageSchemeTestCase()
  {
    super("cn=PBKDF2,cn=Password Storage Schemes,cn=config");
  }
  /**
   * Retrieves a set of passwords that may be used to test the password storage scheme.
   *
   * @return  A set of passwords that may be used to test the password storage scheme.
   */
  @Override
  @DataProvider(name = "testPasswords")
  public Object[][] getTestPasswords()
  {
    final Object[][] testPasswords = super.getTestPasswords();
    // JDK Bug 6879540. Empty passwords are not accepted when generating PBESpecKey.
    // The bug is present in Java 6 and some version of Java 7.
    final int newLength = testPasswords.length - 2;
    final Object[][] results = new Object[newLength][];
    System.arraycopy(testPasswords, 2, results, 0, newLength);
    return results;
  }
  /**
   * Retrieves an initialized instance of this password storage scheme.
   *
   * @return  An initialized instance of this password storage scheme.
   */
  @Override
  protected PasswordStorageScheme<?> getScheme() throws Exception
  {
    PBKDF2PasswordStorageScheme scheme =
         new PBKDF2PasswordStorageScheme();
    PBKDF2PasswordStorageSchemeCfg configuration =
      AdminTestCaseUtils.getConfiguration(
              PBKDF2PasswordStorageSchemeCfgDefn.getInstance(),
          configEntry.getEntry()
          );
    scheme.initializePasswordStorageScheme(configuration);
    return scheme;
  }
}
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
@@ -25,7 +25,6 @@
 */
package org.opends.server.extensions;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.admin.std.meta.PKCS5S2PasswordStorageSchemeCfgDefn;
@@ -35,12 +34,12 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.*;
/**
 * A set of test cases for the PKCS5S2 password storage scheme.
 */
@SuppressWarnings("javadoc")
public class PKCS5S2PasswordStorageSchemeTestCase
       extends PasswordStorageSchemeTestCase
{
@@ -52,17 +51,33 @@
    super("cn=PKCS5S2,cn=Password Storage Schemes,cn=config");
  }
  /**
   * Retrieves a set of passwords that may be used to test the password storage scheme.
   *
   * @return  A set of passwords that may be used to test the password storage scheme.
   */
  @Override
  @DataProvider(name = "testPasswords")
  public Object[][] getTestPasswords()
  {
    final Object[][] testPasswords = super.getTestPasswords();
    // JDK Bug 6879540. Empty passwords are not accepted when generating PBESpecKey.
    // The bug is present in Java 6 and some version of Java 7.
    final int newLength = testPasswords.length - 2;
    final Object[][] results = new Object[newLength][];
    System.arraycopy(testPasswords, 2, results, 0, newLength);
    return results;
  }
  /**
   * Retrieves an initialized instance of this password storage scheme.
   *
   * @return  An initialized instance of this password storage scheme.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  protected PasswordStorageScheme getScheme()
         throws Exception
  @Override
  protected PasswordStorageScheme<?> getScheme() throws Exception
  {
    PKCS5S2PasswordStorageScheme scheme =
         new PKCS5S2PasswordStorageScheme();
@@ -86,10 +101,8 @@
   * @return  A set of couple (cleartext, encrypted) passwords that
   *          may be used to test the PKCS5S2 password storage scheme
   */
  @DataProvider(name = "testPKCS5S2Passwords")
  public Object[][] getTestPKCS5S2Passwords()
         throws Exception
  public Object[][] getTestPKCS5S2Passwords() throws Exception
  {
    return new Object[][]
    {
@@ -131,7 +144,6 @@
    boolean allowPreencodedDefault = setAllowPreencodedPasswords(true);
    try {
      Entry userEntry = TestCaseUtils.makeEntry(
       "dn: uid=testPKCS5S2.user,o=test",
       "objectClass: top",
@@ -146,8 +158,7 @@
      TestCaseUtils.addEntry(userEntry);
      assertTrue(TestCaseUtils.canBind("uid=testPKCS5S2.user,o=test",
                  plaintextPassword),
      assertTrue(TestCaseUtils.canBind("uid=testPKCS5S2.user,o=test", plaintextPassword),
               "Failed to bind when pre-encoded password = \"" +
               encodedPassword + "\" and " +
               "plaintext password = \"" +
@@ -158,4 +169,3 @@
  }
}