From 9e1ac7af03d6655724654082188ccb7c3f0cf48d Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Mon, 01 Sep 2014 09:13:35 +0000
Subject: [PATCH] Port to the DJ3 dev branch the fix for OPENDJ-1510 - New Password Storage Scheme for PKCS5S2.
---
opendj3-server-dev/src/admin/messages/PKCS5S2PasswordStorageSchemeCfgDefn.properties | 6
opendj3-server-dev/resource/config/config.ldif | 8
opendj3-server-dev/src/server/org/opends/server/extensions/ExtensionsConstants.java | 18 +
opendj3-server-dev/resource/admin/abbreviations.xsl | 4
opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/PKCS5S2PasswordStorageSchemeConfiguration.xml | 58 ++++
opendj3-server-dev/resource/schema/02-config.ldif | 5
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java | 161 ++++++++++++
opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java | 478 ++++++++++++++++++++++++++++++++++++
8 files changed, 733 insertions(+), 5 deletions(-)
diff --git a/opendj3-server-dev/resource/admin/abbreviations.xsl b/opendj3-server-dev/resource/admin/abbreviations.xsl
index eb8b113..e4563bc 100644
--- a/opendj3-server-dev/resource/admin/abbreviations.xsl
+++ b/opendj3-server-dev/resource/admin/abbreviations.xsl
@@ -22,7 +22,7 @@
!
!
! Copyright 2008-2009 Sun Microsystems, Inc.
- ! Portions copyright 2011-2013 ForgeRock AS
+ ! Portions copyright 2011-2014 ForgeRock AS
! -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
@@ -54,7 +54,7 @@
or $value = 'des' or $value = 'aes' or $value = 'rc4'
or $value = 'db' or $value = 'snmp' or $value = 'qos'
or $value = 'ecl' or $value = 'ttl' or $value = 'jpeg'
- or $value = 'pbkdf2'
+ or $value = 'pbkdf2' or $value = 'pkcs5s2'
"/>
</xsl:template>
</xsl:stylesheet>
diff --git a/opendj3-server-dev/resource/config/config.ldif b/opendj3-server-dev/resource/config/config.ldif
index 08ee1e0..d552c3a 100644
--- a/opendj3-server-dev/resource/config/config.ldif
+++ b/opendj3-server-dev/resource/config/config.ldif
@@ -1656,6 +1656,14 @@
ds-cfg-java-class: org.opends.server.extensions.PBKDF2PasswordStorageScheme
ds-cfg-enabled: true
+dn: cn=PKCS5S2,cn=Password Storage Schemes,cn=config
+objectClass: top
+objectClass: ds-cfg-password-storage-scheme
+objectClass: ds-cfg-pkcs5s2-password-storage-scheme
+cn: PKCS5S2
+ds-cfg-java-class: org.opends.server.extensions.PKCS5S2PasswordStorageScheme
+ds-cfg-enabled: true
+
dn: cn=SHA-1,cn=Password Storage Schemes,cn=config
objectClass: top
objectClass: ds-cfg-password-storage-scheme
diff --git a/opendj3-server-dev/resource/schema/02-config.ldif b/opendj3-server-dev/resource/schema/02-config.ldif
index b3d67ec..89374c6 100644
--- a/opendj3-server-dev/resource/schema/02-config.ldif
+++ b/opendj3-server-dev/resource/schema/02-config.ldif
@@ -5730,3 +5730,8 @@
ds-cfg-allow-zero-length-values-directory-string $
ds-cfg-strip-syntax-min-upper-bound-attribute-type-description )
X-ORIGIN 'OpenDJ Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.21
+ NAME 'ds-cfg-pkcs5s2-password-storage-scheme'
+ SUP ds-cfg-password-storage-scheme
+ STRUCTURAL
+ X-ORIGIN 'OpenDJ Directory Server' )
diff --git a/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/PKCS5S2PasswordStorageSchemeConfiguration.xml b/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/PKCS5S2PasswordStorageSchemeConfiguration.xml
new file mode 100644
index 0000000..5ab318d
--- /dev/null
+++ b/opendj3-server-dev/src/admin/defn/org/opends/server/admin/std/PKCS5S2PasswordStorageSchemeConfiguration.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ! 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.
+ ! -->
+<adm:managed-object name="pkcs5s2-password-storage-scheme"
+ plural-name="pkcs5s2-password-storage-schemes"
+ package="org.opends.server.admin.std"
+ extends="password-storage-scheme"
+ xmlns:adm="http://www.opends.org/admin"
+ xmlns:ldap="http://www.opends.org/admin-ldap">
+ <adm:synopsis>
+ The
+ <adm:user-friendly-name />
+ provides a mechanism for encoding user passwords using the
+ Atlassian PBKDF2-based message digest algorithm.
+ </adm:synopsis>
+ <adm:description>
+ This scheme contains an implementation for the user password syntax,
+ with a storage scheme name of "PKCS5S2".
+ </adm:description>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>ds-cfg-pkcs5s2-password-storage-scheme</ldap:name>
+ <ldap:superior>ds-cfg-password-storage-scheme</ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:property-override name="java-class" advanced="true">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>
+ org.opends.server.extensions.PKCS5S2PasswordStorageScheme
+ </adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+</adm:managed-object>
diff --git a/opendj3-server-dev/src/admin/messages/PKCS5S2PasswordStorageSchemeCfgDefn.properties b/opendj3-server-dev/src/admin/messages/PKCS5S2PasswordStorageSchemeCfgDefn.properties
new file mode 100644
index 0000000..41fd0aa
--- /dev/null
+++ b/opendj3-server-dev/src/admin/messages/PKCS5S2PasswordStorageSchemeCfgDefn.properties
@@ -0,0 +1,6 @@
+user-friendly-name=PKCS5S2 Password Storage Scheme
+user-friendly-plural-name=PKCS5S2 Password Storage Schemes
+synopsis=The PKCS5S2 Password Storage Scheme provides a mechanism for encoding user passwords using the Atlassian PBKDF2-based message digest algorithm.
+description=This scheme contains an implementation for the user password syntax, with a storage scheme name of "PKCS5S2".
+property.enabled.synopsis=Indicates whether the PKCS5S2 Password Storage Scheme is enabled for use.
+property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the PKCS5S2 Password Storage Scheme implementation.
diff --git a/opendj3-server-dev/src/server/org/opends/server/extensions/ExtensionsConstants.java b/opendj3-server-dev/src/server/org/opends/server/extensions/ExtensionsConstants.java
index 78994e6..1aa7b0e 100644
--- a/opendj3-server-dev/src/server/org/opends/server/extensions/ExtensionsConstants.java
+++ b/opendj3-server-dev/src/server/org/opends/server/extensions/ExtensionsConstants.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2006-2008 Sun Microsystems, Inc.
- * Portions copyright 2013 ForgeRock AS.
+ * Portions copyright 2013-2014 ForgeRock AS.
*/
package org.opends.server.extensions;
@@ -81,10 +81,15 @@
* The authentication password scheme name for use with passwords encoded in a
* PBKDF2 representation.
*/
- public static final String AUTH_PASSWORD_SCHEME_NAME_PBKDF2 =
- "PBKDF2";
+ public static final String AUTH_PASSWORD_SCHEME_NAME_PBKDF2 = "PBKDF2";
+ /**
+ * The authentication password scheme name for use with passwords encoded in a
+ * PKCS5S2 representation.
+ */
+ public static final String AUTH_PASSWORD_SCHEME_NAME_PKCS5S2 = "PKCS5S2";
+
/**
* The name of the message digest algorithm that should be used to generate
@@ -325,6 +330,13 @@
public static final String STORAGE_SCHEME_NAME_PBKDF2 = "PBKDF2";
+ /**
+ * The password storage scheme name that will be used for passwords stored in
+ * a PKCS5S2 representation.
+ */
+ public static final String STORAGE_SCHEME_NAME_PKCS5S2 = "PKCS5S2";
+
+
/**
* The password storage scheme name that will be used for passwords stored in
diff --git a/opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java b/opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
new file mode 100644
index 0000000..1f1da98
--- /dev/null
+++ b/opendj3-server-dev/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageScheme.java
@@ -0,0 +1,478 @@
+/*
+ * 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.
+ * Portions Copyright Emidio Stani & Andrea Stani
+ */
+package org.opends.server.extensions;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.PKCS5S2PasswordStorageSchemeCfg;
+import org.opends.server.api.PasswordStorageScheme;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.*;
+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.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+/**
+ * This class defines a Directory Server password storage scheme based on the
+ * Atlassian PBKF2-base hash algorithm. This is a one-way digest algorithm
+ * so there is no way to retrieve the original clear-text version of the
+ * password from the hashed value (although this means that it is not suitable
+ * for things that need the clear-text password like DIGEST-MD5). Unlike
+ * the other PBKF2-base scheme, this implementation uses a fixed number of
+ * iterations.
+ */
+public class PKCS5S2PasswordStorageScheme
+ extends PasswordStorageScheme<PKCS5S2PasswordStorageSchemeCfg>
+{
+ /**
+ * The tracer object for the debug logger.
+ */
+ private static final DebugTracer TRACER = getTracer();
+
+ /**
+ * The fully-qualified name of this class.
+ */
+ 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.
+ */
+ private static final int NUM_SALT_BYTES = 16;
+
+ /**
+ * The number of bytes the SHA-1 algorithm produces.
+ */
+ private static final int SHA1_LENGTH = 32;
+
+ /**
+ * 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;
+
+
+ /**
+ * Creates a new instance of this password storage scheme. Note that no
+ * initialization should be performed here, as all initialization should be
+ * done in the <CODE>initializePasswordStorageScheme</CODE> method.
+ */
+ public PKCS5S2PasswordStorageScheme()
+ {
+ super();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public void initializePasswordStorageScheme(
+ PKCS5S2PasswordStorageSchemeCfg configuration)
+ throws ConfigException, InitializationException
+ {
+ try
+ {
+ random = SecureRandom.getInstance(SECURE_PRNG_SHA1);
+ factory = SecretKeyFactory.getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new InitializationException(null);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public String getStorageSchemeName()
+ {
+ return STORAGE_SCHEME_NAME_PKCS5S2;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public ByteString encodePassword(ByteSequence plaintext)
+ throws DirectoryException
+ {
+ byte[] saltBytes = new byte[NUM_SALT_BYTES];
+ byte[] digestBytes = createRandomSaltAndEncode(plaintext, saltBytes);
+ // Append the hashed value to the salt and base64-the whole thing.
+ byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes);
+
+ return ByteString.valueOf(Base64.encode(hashPlusSalt));
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public ByteString encodePasswordWithScheme(ByteSequence plaintext)
+ throws DirectoryException
+ {
+ return ByteString.valueOf("{" + STORAGE_SCHEME_NAME_PKCS5S2 + '}'
+ + encodePassword(plaintext));
+ }
+
+
+
+ /**
+ * {@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();
+
+ byte[] decodedBytes = Base64.decode(stored);
+
+ if (decodedBytes.length != NUM_SALT_BYTES + SHA1_LENGTH)
+ {
+ Message message =
+ ERR_PWSCHEME_INVALID_BASE64_DECODED_STORED_PASSWORD.get(
+ storedPassword.toString());
+ ErrorLogger.logError(message);
+ return false;
+ }
+ System.arraycopy(decodedBytes, 0, saltBytes, 0, saltLength);
+ System.arraycopy(decodedBytes, saltLength, digestBytes, 0,
+ SHA1_LENGTH);
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ Message message = ERR_PWSCHEME_CANNOT_BASE64_DECODE_STORED_PASSWORD.get(
+ storedPassword.toString(), String.valueOf(e));
+ ErrorLogger.logError(message);
+ return false;
+ }
+
+ return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean supportsAuthPasswordSyntax()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public String getAuthPasswordSchemeName()
+ {
+ return AUTH_PASSWORD_SCHEME_NAME_PKCS5S2;
+ }
+
+
+
+ /**
+ * {@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));
+ }
+
+
+
+ /**
+ * {@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);
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ return false;
+ }
+
+ return encodeAndMatch(plaintextPassword, saltBytes, digestBytes, iterations);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean isReversible()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public ByteString getPlaintextValue(ByteSequence storedPassword)
+ throws DirectoryException
+ {
+ Message 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)
+ throws DirectoryException
+ {
+ Message message =
+ ERR_PWSCHEME_NOT_REVERSIBLE.get(AUTH_PASSWORD_SCHEME_NAME_PKCS5S2);
+ throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override()
+ public boolean isStorageSchemeSecure()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * 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).
+ *
+ * @param passwordBytes The bytes that make up the clear-text password.
+ *
+ * @return The encoded password string, including the scheme name in curly
+ * braces.
+ *
+ * @throws org.opends.server.types.DirectoryException If a problem occurs during processing.
+ */
+ public static String encodeOffline(byte[] passwordBytes)
+ throws DirectoryException
+ {
+ byte[] saltBytes = new byte[NUM_SALT_BYTES];
+ byte[] digestBytes;
+
+ try
+ {
+ SecureRandom.getInstance(SECURE_PRNG_SHA1).nextBytes(saltBytes);
+
+ char[] plaintextChars = Arrays.toString(passwordBytes).toCharArray();
+ KeySpec spec = new PBEKeySpec(plaintextChars, saltBytes,iterations,
+ SHA1_LENGTH * 8);
+ digestBytes = SecretKeyFactory
+ .getInstance(MESSAGE_DIGEST_ALGORITHM_PBKDF2)
+ .generateSecret(spec).getEncoded();
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
+ CLASS_NAME, getExceptionMessage(e));
+ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+ message, e);
+ }
+
+ // Append the hashed value to the salt and base64-the whole thing.
+ byte[] hashPlusSalt = concatenateSaltPlusHash(saltBytes, digestBytes);
+
+ return "{" + STORAGE_SCHEME_NAME_PKCS5S2 + "}" +
+ Base64.encode(hashPlusSalt);
+ }
+
+
+ private boolean encodeAndMatch(ByteSequence plaintext,
+ byte[] saltBytes, byte[] digestBytes, int iterations)
+ {
+ byte[] userDigestBytes;
+
+ try
+ {
+ userDigestBytes = encodeWithSalt(plaintext, saltBytes, iterations);
+ }
+ catch (Exception e)
+ {
+ 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 digestBytes;
+ }
+
+ private byte[] encodeWithSalt(ByteSequence plaintext, byte[] saltBytes, int iterations) throws DirectoryException {
+ byte[] digestBytes;
+ char[] plaintextChars = null;
+ try
+ {
+ plaintextChars = plaintext.toString().toCharArray();
+ KeySpec spec = new PBEKeySpec(
+ plaintextChars, saltBytes,
+ iterations, SHA1_LENGTH * 8);
+ digestBytes = factory.generateSecret(spec).getEncoded();
+ }
+ catch (Exception e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ Message 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');
+ }
+ }
+ return digestBytes;
+ }
+
+ private static byte[] concatenateSaltPlusHash(byte[] saltBytes, byte[] digestBytes) {
+ byte[] hashPlusSalt = new byte[digestBytes.length + NUM_SALT_BYTES];
+
+ System.arraycopy(saltBytes, 0, hashPlusSalt, 0, NUM_SALT_BYTES);
+ System.arraycopy(digestBytes, 0, hashPlusSalt, NUM_SALT_BYTES,
+ digestBytes.length);
+ return hashPlusSalt;
+ }
+
+}
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
new file mode 100644
index 0000000..dfac4e3
--- /dev/null
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/extensions/PKCS5S2PasswordStorageSchemeTestCase.java
@@ -0,0 +1,161 @@
+/*
+ * 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.TestCaseUtils;
+import org.opends.server.admin.server.AdminTestCaseUtils;
+import org.opends.server.admin.std.meta.PKCS5S2PasswordStorageSchemeCfgDefn;
+import org.opends.server.admin.std.server.PKCS5S2PasswordStorageSchemeCfg;
+import org.opends.server.api.PasswordStorageScheme;
+import org.opends.server.types.Entry;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertTrue;
+
+
+/**
+ * A set of test cases for the PKCS5S2 password storage scheme.
+ */
+public class PKCS5S2PasswordStorageSchemeTestCase
+ extends PasswordStorageSchemeTestCase
+{
+ /**
+ * Creates a new instance of this storage scheme test case.
+ */
+ public PKCS5S2PasswordStorageSchemeTestCase()
+ {
+ super("cn=PKCS5S2,cn=Password Storage Schemes,cn=config");
+ }
+
+
+
+ /**
+ * 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
+ {
+ PKCS5S2PasswordStorageScheme scheme =
+ new PKCS5S2PasswordStorageScheme();
+
+ PKCS5S2PasswordStorageSchemeCfg configuration =
+ AdminTestCaseUtils.getConfiguration(
+ PKCS5S2PasswordStorageSchemeCfgDefn.getInstance(),
+ configEntry.getEntry()
+ );
+
+ scheme.initializePasswordStorageScheme(configuration);
+ return scheme;
+ }
+
+ /**
+ * Retrieves a set of passwords (plain and PKCS5S2 encrypted) that may
+ * be used to test the compatibility of PKCS5S2 passwords.
+ * The encrypted versions have been provided by external tools or
+ * users
+ *
+ * @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
+ {
+ return new Object[][]
+ {
+ // Sample from public forum...
+ new Object[] { "admin", "{PKCS5S2}siTdcDkChqeSDGVnIMILINUGSzhublIyp1KDvI0CJQ3HuQurEHyN7itWI6rpIzN4" },
+ // Sample from Crowd support forums
+ new Object[] { "admin", "{PKCS5S2}4PCXluhV1YoY3yGgp77MfHjoFoS7GwNxif4gQLpwIfqLs9n/3seRLlECMu2CWGtm" },
+ // Sample from Apache DS implementation test
+ new Object[] {"tempo", "{PKCS5S2}ggkzUKrzLIxti+aFlhPbfXFiIZbw9TGm/Pru/eVqMgWupaxbIt70xqWXpqS9Q9XZ" },
+ // Sample from passlib library http://pythonhosted.org/passlib/lib/passlib.hash.atlassian_pbkdf2_sha1.html
+ new Object[] { "password", "{PKCS5S2}DQIXJU038u4P7FdsuFTY/+35bm41kfjZa57UrdxHp2Mu3qF2uy+ooD+jF5t1tb8J" },
+ // Samples from https://eikonal.wordpress.com/tag/magic-string/
+ new Object[] { "password", "{PKCS5S2}1Nq7N2YM4ZyTstZaSynlnGGh2rgAG+b7SB+9xreszUhrE39BnfwNg2RGm6tqvDg2" },
+ new Object[] { "password", "{PKCS5S2}fU8ppRTCuJeS8n7PGYOQMhVqZ4hUidTIiWI4K8R8IBOXm/lYywaouSLtvlTeTr3V" },
+ new Object[] { "password", "{PKCS5S2}+X+PMcYYAwBAKIWwFsJY639EipU1NXJfc1jKC5VYHZV7zoDI4zTEpKO4xZQoegg1" },
+ new Object[] { "password", "{PKCS5S2}bu1dK0WotXYuBaB0bo2RslxMAp4JawLofUFw4S5fZdAtfsm3Ats6kO6j5NaHZCdt" },
+ new Object[] { "password", "{PKCS5S2}z/mfc47xvjcm5Ny7dw7BeExB68Oc4XiTJvUS5HRAadKr4/Aomn1WOMMrMWtikUPK" },
+ // Sample from Sage platform JIRA - PLFM-2205
+ new Object[] { "password", "{PKCS5S2}cnDeuXJkUW+sQwdTw4YlBaV0PMYvZQKc69lHAamznecCeEX9IPqpp7TjhEdJlNkV" },
+ // Samples from Emidio Stani, contributor of original PKCS5S2 extension for OpenDJ
+ new Object[] { "test2", "{PKCS5S2}A0o7i4Typ0wVnME334K2Od2oyFUNBCwryGBa6g/5s2NDFc+E4ewNiV22KaTDKOqB" },
+ new Object[] { "test1", "{PKCS5S2}999tlQor9kNRXuIiHv2MhiL3zlReDlfWS9nOzO1Le/HeawYuhYuL/2SOug67T+Aq" },
+ // Sample from bitbucket cwdapache pull request
+ new Object[] { "password", "{PKCS5S2}aCE+yLkHgdZ7DQxM37/5nY3NFFYhQfDrkNUoEE6eUItQJoS4Z+jKFj+2OkySTboT" },
+ // Sample from Atlassian JIRA test suite
+ // https://github.com/atlassian/jira-suite-utilities/blob/master/src/test/xml/test1.xml
+ new Object[] { "developer", "{PKCS5S2}IcisOH+L07K8RAgqQJsp7IGXLUL0jRhCOSVrvAq8sprymJvEcNHT/LMaL+6ZOcCh" }
+ };
+ }
+
+ @Test(dataProvider = "testPKCS5S2Passwords")
+ public void testAuthPKCS5S2Passwords(
+ String plaintextPassword,
+ String encodedPassword) throws Exception
+ {
+ // Start/clear-out the memory backend
+ TestCaseUtils.initializeTestBackend(true);
+
+ boolean allowPreencodedDefault = setAllowPreencodedPasswords(true);
+
+ try {
+
+ Entry userEntry = TestCaseUtils.makeEntry(
+ "dn: uid=testPKCS5S2.user,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: testPKCS5S2.user",
+ "givenName: TestPKCS5S2",
+ "sn: User",
+ "cn: TestPKCS5S2 User",
+ "userPassword: " + encodedPassword);
+
+ TestCaseUtils.addEntry(userEntry);
+
+ assertTrue(TestCaseUtils.canBind("uid=testPKCS5S2.user,o=test",
+ plaintextPassword),
+ "Failed to bind when pre-encoded password = \"" +
+ encodedPassword + "\" and " +
+ "plaintext password = \"" +
+ plaintextPassword + "\"" );
+ } finally {
+ setAllowPreencodedPasswords(allowPreencodedDefault);
+ }
+ }
+
+}
+
--
Gitblit v1.10.0