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

Jean-Noel Rouvignac
25.23.2014 da280307edefe3dce5d3836f502c9fc3daa4ce6f
opends/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -45,9 +45,8 @@
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines a Directory Server password storage scheme based on the
@@ -61,14 +60,10 @@
public class PKCS5S2PasswordStorageScheme
    extends PasswordStorageScheme<PKCS5S2PasswordStorageSchemeCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  /** The tracer object for the debug logger. */
  private static final DebugTracer TRACER = getTracer();
  /**
   * 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";
@@ -79,29 +74,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;
@@ -115,12 +100,8 @@
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public void initializePasswordStorageScheme(
      PKCS5S2PasswordStorageSchemeCfg configuration)
      throws ConfigException, InitializationException
@@ -136,21 +117,15 @@
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public String getStorageSchemeName()
  {
    return STORAGE_SCHEME_NAME_PKCS5S2;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString encodePassword(ByteSequence plaintext)
      throws DirectoryException
  {
@@ -162,32 +137,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();
@@ -202,9 +166,13 @@
        ErrorLogger.logError(message);
        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)
    {
@@ -218,75 +186,47 @@
      ErrorLogger.logError(message);
      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) != ':')
      {
        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 pos = authInfo.indexOf(':');
      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)
    {
@@ -294,30 +234,19 @@
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, 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
  {
@@ -326,12 +255,8 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public ByteString getAuthPasswordPlaintextValue(String authInfo,
                                                  String authValue)
      throws DirectoryException
@@ -341,12 +266,8 @@
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  /** {@inheritDoc} */
  @Override
  public boolean isStorageSchemeSecure()
  {
    return true;
@@ -365,7 +286,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
@@ -400,7 +321,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);
  }
@@ -408,33 +329,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
    {
@@ -442,7 +357,7 @@
      KeySpec spec = new PBEKeySpec(
          plaintextChars, saltBytes,
          iterations, SHA1_LENGTH * 8);
      digestBytes = factory.generateSecret(spec).getEncoded();
      return factory.generateSecret(spec).getEncoded();
    }
    catch (Exception e)
    {
@@ -463,7 +378,6 @@
        Arrays.fill(plaintextChars, '0');
      }
    }
    return digestBytes;
  }
  private static byte[] concatenateSaltPlusHash(byte[] saltBytes, byte[] digestBytes) {