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

Ludovic Poitou
26.47.2015 fc4cbe6632b0725d76a8aab87c0aa0bafda84990
Fix for OPENDJ-2151. Remove contention around the SecretKeyFactory allowing more parallelism in the use of the storage schemes.
CR-7342.
2 files modified
239 ■■■■■ changed files
opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PBKDF2PasswordStorageScheme.java 118 ●●●●● patch | view | raw | blame | history
opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java 121 ●●●●● patch | view | raw | blame | history
opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PBKDF2PasswordStorageScheme.java
@@ -68,25 +68,14 @@
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The fully-qualified name of this class. */
  private static final String CLASS_NAME =
      "org.opends.server.extensions.PBKDF2PasswordStorageScheme";
  private static final String CLASS_NAME = "org.opends.server.extensions.PBKDF2PasswordStorageScheme";
  /**
   * The number of bytes of random data to use as the salt when generating the
   * hashes.
   */
  /** The number of bytes of random data to use as the salt when generating the hashes. */
  private static final int NUM_SALT_BYTES = 8;
  /** The number of bytes the SHA-1 algorithm produces. */
  private static final int SHA1_LENGTH = 20;
  /** The factory used to generate the PBKDF2 hashes. */
  private SecretKeyFactory factory;
  /** 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. */
  private SecureRandom random;
@@ -105,14 +94,14 @@
  /** {@inheritDoc} */
  @Override
  public void initializePasswordStorageScheme(
      PBKDF2PasswordStorageSchemeCfg configuration)
  public void initializePasswordStorageScheme(PBKDF2PasswordStorageSchemeCfg configuration)
      throws ConfigException, InitializationException
  {
    try
    {
      random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
      factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      // Just try to verify if the algorithm is supported.
      SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
    }
    catch (NoSuchAlgorithmException e)
    {
@@ -125,17 +114,15 @@
  /** {@inheritDoc} */
  @Override
  public boolean isConfigurationChangeAcceptable(
                      PBKDF2PasswordStorageSchemeCfg configuration,
                      List<LocalizableMessage> unacceptableReasons)
  public boolean isConfigurationChangeAcceptable(PBKDF2PasswordStorageSchemeCfg configuration,
                                                 List<LocalizableMessage> unacceptableReasons)
  {
    return true;
  }
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(
      PBKDF2PasswordStorageSchemeCfg configuration)
  public ConfigChangeResult applyConfigurationChange(PBKDF2PasswordStorageSchemeCfg configuration)
  {
    this.config = configuration;
    return new ConfigChangeResult();
@@ -156,7 +143,7 @@
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    int    iterations     = config.getPBKDF2Iterations();
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations);
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations,random);
    byte[] hashPlusSalt = concatenateHashPlusSalt(saltBytes, digestBytes);
    return ByteString.valueOf(iterations + ":" + Base64.encode(hashPlusSalt));
@@ -167,15 +154,12 @@
  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
      throws DirectoryException
  {
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PBKDF2 + '}'
        + encodePassword(plaintext));
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + encodePassword(plaintext));
  }
  /** {@inheritDoc} */
  @Override
  public boolean passwordMatches(ByteSequence plaintextPassword,
                                 ByteSequence storedPassword)
  {
  public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword) {
    // Split the iterations from the stored value (separated by a ':')
    // Base64-decode the remaining value and take the last 8 bytes as the salt.
    try
@@ -232,18 +216,16 @@
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    int    iterations     = config.getPBKDF2Iterations();
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations);
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes, iterations,random);
    // Encode and return the value.
    return ByteString.valueOf(AUTH_PASSWORD_SCHEME_NAME_PBKDF2 + '$'
        + iterations + ':' + Base64.encode(saltBytes) + '$'
        + Base64.encode(digestBytes));
        + iterations + ':' + Base64.encode(saltBytes) + '$' + Base64.encode(digestBytes));
  }
  /** {@inheritDoc} */
  @Override
  public boolean authPasswordMatches(ByteSequence plaintextPassword,
                                     String authInfo, String authValue)
  public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue)
  {
    try
    {
@@ -276,19 +258,16 @@
  public ByteString getPlaintextValue(ByteSequence storedPassword)
      throws DirectoryException
  {
    LocalizableMessage message =
        ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PBKDF2);
    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PBKDF2);
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /** {@inheritDoc} */
  @Override
  public ByteString getAuthPasswordPlaintextValue(String authInfo,
                                                  String authValue)
  public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue)
      throws DirectoryException
  {
    LocalizableMessage message =
        ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PBKDF2);
    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PBKDF2);
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
@@ -300,16 +279,13 @@
  }
  /**
   * Generates an encoded password string from the given clear-text password.
   * This method is primarily intended for use when it is necessary to generate
   * a password with the server offline (e.g., when setting the initial root
   * user password).
   * This method is primarily intended for use when it is necessary to generate a password with the server
   * offline (e.g., when setting the initial root user password).
   *
   * @param  passwordBytes  The bytes that make up the clear-text password.
   * @return  The encoded password string, including the scheme name in curly
   *          braces.
   * @return  The encoded password string, including the scheme name in curly braces.
   * @throws  DirectoryException  If a problem occurs during processing.
   */
  public static String encodeOffline(byte[] passwordBytes)
@@ -322,18 +298,16 @@
    byte[] digestBytes = encodeWithRandomSalt(password, saltBytes, iterations);
    byte[] hashPlusSalt = concatenateHashPlusSalt(saltBytes, digestBytes);
    return '{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + iterations + ':'
        + Base64.encode(hashPlusSalt);
    return '{' + STORAGE_SCHEME_NAME_PBKDF2 + '}' + iterations + ':' + Base64.encode(hashPlusSalt);
  }
  private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes,
      int iterations) throws DirectoryException
  private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes, int iterations)
      throws DirectoryException
  {
    try
    {
      final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
      final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      return encodeWithRandomSalt(plaintext, saltBytes, iterations, random, factory);
      return encodeWithRandomSalt(plaintext, saltBytes, iterations, random);
    }
    catch (DirectoryException e)
    {
@@ -345,14 +319,14 @@
    }
  }
  private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes,
      int iterations, final SecretKeyFactory factory) throws DirectoryException
  private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations)
      throws DirectoryException
  {
    final char[] plaintextChars = plaintext.toString().toCharArray();
    try
    {
      KeySpec spec =
          new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8);
      final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8);
      return factory.generateSecret(spec).getEncoded();
    }
    catch (Exception e)
@@ -361,45 +335,29 @@
    }
    finally
    {
      if (plaintextChars != null)
      {
        Arrays.fill(plaintextChars, '0');
      }
      Arrays.fill(plaintextChars, '0');
    }
  }
  private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes,
      byte[] digestBytes, int iterations)
  private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations)
  {
    synchronized (factoryLock)
    try
    {
      try
      {
        final byte[] userDigestBytes =
            encodeWithSalt(plaintext, saltBytes, iterations, factory);
        return Arrays.equals(digestBytes, userDigestBytes);
      }
      catch (Exception e)
      {
        return false;
      }
      final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
      return Arrays.equals(digestBytes, userDigestBytes);
    }
  }
  private byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes,
      int iterations) throws DirectoryException
  {
    synchronized (factoryLock)
    catch (Exception e)
    {
      return encodeWithRandomSalt(plaintext, saltBytes, iterations, random, factory);
      return false;
    }
  }
  private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes,
      int iterations, SecureRandom random, final SecretKeyFactory factory) throws DirectoryException
                                             int iterations, SecureRandom random)
      throws DirectoryException
  {
    random.nextBytes(saltBytes);
    return encodeWithSalt(plaintext, saltBytes, iterations, factory);
    return encodeWithSalt(plaintext, saltBytes, iterations);
  }
  private static DirectoryException cannotEncodePassword(Exception e)
opendj-sdk/opendj-server-legacy/src/main/java/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2014 ForgeRock AS.
 *      Copyright 2014-2015 ForgeRock AS.
 *      Portions Copyright Emidio Stani & Andrea Stani
 */
package org.opends.server.extensions;
@@ -65,14 +65,9 @@
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** The fully-qualified name of this class. */
  private static final String CLASS_NAME =
      "org.opends.server.extensions.PKCS5S2PasswordStorageScheme";
  private static final String CLASS_NAME = "org.opends.server.extensions.PKCS5S2PasswordStorageScheme";
  /**
   * The number of bytes of random data to use as the salt when generating the
   * hashes.
   */
  /** The number of bytes of random data to use as the salt when generating the hashes. */
  private static final int NUM_SALT_BYTES = 16;
  /** The number of bytes the SHA-1 algorithm produces. */
@@ -81,12 +76,6 @@
  /** Atlassian hardcoded the number of iterations to 10000. */
  private static final int iterations = 10000;
  /** The factory used to generate the PKCS5S2 hashes. */
  private SecretKeyFactory factory;
  /** 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. */
  private SecureRandom random;
@@ -102,14 +91,14 @@
  /** {@inheritDoc} */
  @Override
  public void initializePasswordStorageScheme(
      PKCS5S2PasswordStorageSchemeCfg configuration)
  public void initializePasswordStorageScheme(PKCS5S2PasswordStorageSchemeCfg configuration)
      throws InitializationException
  {
    try
    {
      random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
      factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      // Just try to verify if the algorithm is supported
      SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
    }
    catch (NoSuchAlgorithmException e)
    {
@@ -130,7 +119,7 @@
      throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes);
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes,random);
    byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes);
    return ByteString.valueOf(Base64.encode(hashPlusSalt));
@@ -141,14 +130,12 @@
  public ByteString encodePasswordWithScheme(ByteSequence plaintext)
      throws DirectoryException
  {
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}'
        + encodePassword(plaintext));
    return ByteString.valueOf('{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + encodePassword(plaintext));
  }
  /** {@inheritDoc} */
  @Override
  public boolean passwordMatches(ByteSequence plaintextPassword,
                                 ByteSequence storedPassword)
  public boolean passwordMatches(ByteSequence plaintextPassword, ByteSequence storedPassword)
  {
    // Base64-decode the value and take the first 16 bytes as the salt.
    try
@@ -158,8 +145,7 @@
      if (decodedBytes.length != NUM_SALT_BYTES + SHA1_LENGTH)
      {
        logger.error(ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD.get(
            storedPassword.toString()));
        logger.error(ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD.get(storedPassword.toString()));
        return false;
      }
@@ -173,8 +159,7 @@
    catch (Exception e)
    {
      logger.traceException(e);
      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get(
          storedPassword.toString(), String.valueOf(e)));
      logger.error(ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get(storedPassword.toString(), String.valueOf(e)));
      return false;
    }
  }
@@ -199,17 +184,15 @@
      throws DirectoryException
  {
    byte[] saltBytes      = new byte[NUM_SALT_BYTES];
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes);
    byte[] digestBytes = encodeWithRandomSalt(plaintext, saltBytes,random);
    // 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
  public boolean authPasswordMatches(ByteSequence plaintextPassword,
                                     String authInfo, String authValue)
  public boolean authPasswordMatches(ByteSequence plaintextPassword, String authInfo, String authValue)
  {
    try
    {
@@ -242,19 +225,16 @@
  public ByteString getPlaintextValue(ByteSequence storedPassword)
      throws DirectoryException
  {
    LocalizableMessage message =
        ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PKCS5S2);
    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(STORAGE_SCHEME_NAME_PKCS5S2);
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
  /** {@inheritDoc} */
  @Override
  public ByteString getAuthPasswordPlaintextValue(String authInfo,
                                                  String authValue)
  public ByteString getAuthPasswordPlaintextValue(String authInfo, String authValue)
      throws DirectoryException
  {
    LocalizableMessage message =
        ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2);
    LocalizableMessage message = ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2);
    throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
  }
@@ -269,13 +249,11 @@
  /**
   * Generates an encoded password string from the given clear-text password.
   * This method is primarily intended for use when it is necessary to generate
   * a password with the server offline (e.g., when setting the initial root
   * user password).
   * This method is primarily intended for use when it is necessary to generate a password with the server
   * offline (e.g., when setting the initial root user password).
   *
   * @param  passwordBytes  The bytes that make up the clear-text password.
   * @return  The encoded password string, including the scheme name in curly
   *          braces.
   * @return  The encoded password string, including the scheme name in curly braces.
   * @throws  DirectoryException  If a problem occurs during processing.
   */
  public static String encodeOffline(byte[] passwordBytes)
@@ -285,8 +263,7 @@
    byte[] digestBytes = encodeWithRandomSalt(ByteString.wrap(passwordBytes), saltBytes);
    byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes);
    return '{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' +
        Base64.encode(hashPlusSalt);
    return '{' + STORAGE_SCHEME_NAME_PKCS5S2 + '}' + Base64.encode(hashPlusSalt);
  }
  private static byte[] encodeWithRandomSalt(ByteString plaintext, byte[] saltBytes)
@@ -295,8 +272,7 @@
    try
    {
      final SecureRandom random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
      final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      return encodeWithRandomSalt(plaintext, saltBytes, random, factory);
      return encodeWithRandomSalt(plaintext, saltBytes, random);
    }
    catch (DirectoryException e)
    {
@@ -308,14 +284,14 @@
    }
  }
  private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes,
      int iterations, final SecretKeyFactory factory) throws DirectoryException
  private static byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations)
      throws DirectoryException
  {
    final char[] plaintextChars = plaintext.toString().toCharArray();
    try
    {
      KeySpec spec =
          new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8);
      final SecretKeyFactory factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
      KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes, iterations, SHA1_LENGTH * 8);
      return factory.generateSecret(spec).getEncoded();
    }
    catch (Exception e)
@@ -324,45 +300,28 @@
    }
    finally
    {
      if (plaintextChars != null)
      {
        Arrays.fill(plaintextChars, '0');
      }
      Arrays.fill(plaintextChars, '0');
    }
  }
  private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes,
      byte[] digestBytes, int iterations)
  private boolean encodeAndMatch(ByteSequence plaintext, byte[] saltBytes, byte[] digestBytes, int iterations)
  {
    synchronized (factoryLock)
    {
      try
      {
        final byte[] userDigestBytes =
            encodeWithSalt(plaintext, saltBytes, iterations, factory);
        return Arrays.equals(digestBytes, userDigestBytes);
      }
      catch (Exception e)
      {
        return false;
      }
    }
     try
     {
       final byte[] userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
       return Arrays.equals(digestBytes, userDigestBytes);
     }
     catch (Exception e)
     {
       return false;
     }
  }
  private byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes)
  private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes, SecureRandom random)
      throws DirectoryException
  {
    synchronized (factoryLock)
    {
      return encodeWithRandomSalt(plaintext, saltBytes, random, factory);
    }
  }
  private static byte[] encodeWithRandomSalt(ByteSequence plaintext, byte[] saltBytes,
      SecureRandom random, final SecretKeyFactory factory) throws DirectoryException
  {
    random.nextBytes(saltBytes);
    return encodeWithSalt(plaintext, saltBytes, iterations, factory);
    return encodeWithSalt(plaintext, saltBytes, iterations);
  }
  private static DirectoryException cannotEncodePassword(Exception e)