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 @@ } }