From 46824550ba084705c3fbb1475b200da1f84b471a Mon Sep 17 00:00:00 2001
From: Chris Ridd <chris.ridd@forgerock.com>
Date: Thu, 14 Mar 2013 16:20:30 +0000
Subject: [PATCH] CR-1406 Fix OPENDJ-120 Enhancement: Support BSD Crypt SHA256/512 in binding
---
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CryptPasswordStorageSchemeTestCase.java | 366 ++++++------------
opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java | 84 ++++
opends/src/admin/messages/CryptPasswordStorageSchemeCfgDefn.properties | 10
opends/src/admin/defn/org/opends/server/admin/std/CryptPasswordStorageSchemeConfiguration.xml | 42 +
opends/src/server/org/opends/server/extensions/Sha2Crypt.java | 678 +++++++++++++++++++++++++++++++++
5 files changed, 920 insertions(+), 260 deletions(-)
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/CryptPasswordStorageSchemeConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/CryptPasswordStorageSchemeConfiguration.xml
index 85538d4..de76939 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/CryptPasswordStorageSchemeConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/CryptPasswordStorageSchemeConfiguration.xml
@@ -24,7 +24,8 @@
!
!
! Copyright 2007-2008 Sun Microsystems, Inc.
- ! Portions Copyright 2010 ForgeRock AS
+ ! Portions Copyright 2010-2013 ForgeRock AS
+ ! Portions Copyright 2012 Dariusz Janny <dariusz.janny@gmail.com>
! -->
<adm:managed-object name="crypt-password-storage-scheme"
plural-name="crypt-password-storage-schemes"
@@ -36,21 +37,21 @@
The
<adm:user-friendly-name />
provides a mechanism for encoding user passwords like Unix crypt does.
- Like on most Unix systems, the password mq be encrypted using different
- algorithm, either UNIX crypt or md5 (bsd).
+ Like on most Unix systems, the password may be encrypted using different
+ algorithms, either Unix crypt, md5, sha256 or sha512.
</adm:synopsis>
<adm:description>
This implementation contains an implementation for the user
password syntax, with a storage scheme name of "CRYPT". Like on most
- Unix, the "CRYPT" storage scheme has different algorithm, the default
- being the UNIX crypt.
+ Unixes, the "CRYPT" storage scheme has different algorithms, the default
+ being the Unix crypt.
- Even though the UNIX crypt is a one-way digest, it
- is relatively weak by today's standards. Because it supports
- only a 12-bit salt (meaning that there are only 4096 possible ways to
- encode a given password), it is also vulnerable to dictionary attacks.
- You should therefore use this storage scheme only in cases where an
- external application expects to retrieve the password and verify it
+ Even though the Unix crypt is a one-way digest, it
+ is relatively weak by today's standards. Because it supports
+ only a 12-bit salt (meaning that there are only 4096 possible ways to
+ encode a given password), it is also vulnerable to dictionary attacks.
+ You should therefore use this storage scheme only in cases where an
+ external application expects to retrieve the password and verify it
outside of the directory, rather than by performing an LDAP bind.
</adm:description>
<adm:profile name="ldap">
@@ -75,8 +76,11 @@
<adm:description>
Select the crypt algorithm to use to encrypt new passwords.
The value can either be "unix", which means the password is encrypted
- with the UNIX crypt algorithm, or md5 which means the password is
- encrypted with BSD MD5 algorithm and has a $1$ prefix.
+ with the Unix crypt algorithm, or md5 which means the password is
+ encrypted with the BSD MD5 algorithm and has a $1$ prefix,
+ or sha256 which means the password is encrypted with the SHA256 algorithm
+ and has a $5$ prefix, or sha512 which means the password is encrypted with
+ the SHA512 algorithm and has a $6$ prefix.
</adm:description>
<adm:default-behavior>
<adm:defined>
@@ -87,7 +91,7 @@
<adm:enumeration>
<adm:value name="unix">
<adm:synopsis>
- New passwords are encrypted with the UNIX crypt algorithm.
+ New passwords are encrypted with the Unix crypt algorithm.
</adm:synopsis>
</adm:value>
<adm:value name="md5">
@@ -95,6 +99,16 @@
New passwords are encrypted with the BSD MD5 algorithm.
</adm:synopsis>
</adm:value>
+ <adm:value name="sha256">
+ <adm:synopsis>
+ New passwords are encrypted with the Unix crypt SHA256 algorithm.
+ </adm:synopsis>
+ </adm:value>
+ <adm:value name="sha512">
+ <adm:synopsis>
+ New passwords are encrypted with the Unix crypt SHA512 algorithm.
+ </adm:synopsis>
+ </adm:value>
</adm:enumeration>
</adm:syntax>
<adm:profile name="ldap">
diff --git a/opends/src/admin/messages/CryptPasswordStorageSchemeCfgDefn.properties b/opends/src/admin/messages/CryptPasswordStorageSchemeCfgDefn.properties
index 351d95a..369925b 100644
--- a/opends/src/admin/messages/CryptPasswordStorageSchemeCfgDefn.properties
+++ b/opends/src/admin/messages/CryptPasswordStorageSchemeCfgDefn.properties
@@ -1,10 +1,12 @@
user-friendly-name=Crypt Password Storage Scheme
user-friendly-plural-name=Crypt Password Storage Schemes
-synopsis=The Crypt Password Storage Scheme provides a mechanism for encoding user passwords like Unix crypt does. Like on most Unix systems, the password mq be encrypted using different algorithm, either UNIX crypt or md5 (bsd).
-description=This implementation contains an implementation for the user password syntax, with a storage scheme name of "CRYPT". Like on most Unix, the "CRYPT" storage scheme has different algorithm, the default being the UNIX crypt. Even though the UNIX crypt is a one-way digest, it is relatively weak by today's standards. Because it supports only a 12-bit salt (meaning that there are only 4096 possible ways to encode a given password), it is also vulnerable to dictionary attacks. You should therefore use this storage scheme only in cases where an external application expects to retrieve the password and verify it outside of the directory, rather than by performing an LDAP bind.
+synopsis=The Crypt Password Storage Scheme provides a mechanism for encoding user passwords like Unix crypt does. Like on most Unix systems, the password may be encrypted using different algorithms, either Unix crypt, md5, sha256 or sha512.
+description=This implementation contains an implementation for the user password syntax, with a storage scheme name of "CRYPT". Like on most Unixes, the "CRYPT" storage scheme has different algorithms, the default being the Unix crypt. Even though the Unix crypt is a one-way digest, it is relatively weak by today's standards. Because it supports only a 12-bit salt (meaning that there are only 4096 possible ways to encode a given password), it is also vulnerable to dictionary attacks. You should therefore use this storage scheme only in cases where an external application expects to retrieve the password and verify it outside of the directory, rather than by performing an LDAP bind.
property.crypt-password-storage-encryption-algorithm.synopsis=Specifies the algorithm to use to encrypt new passwords.
-property.crypt-password-storage-encryption-algorithm.description=Select the crypt algorithm to use to encrypt new passwords. The value can either be "unix", which means the password is encrypted with the UNIX crypt algorithm, or md5 which means the password is encrypted with BSD MD5 algorithm and has a $1$ prefix.
+property.crypt-password-storage-encryption-algorithm.description=Select the crypt algorithm to use to encrypt new passwords. The value can either be "unix", which means the password is encrypted with the Unix crypt algorithm, or md5 which means the password is encrypted with the BSD MD5 algorithm and has a $1$ prefix, or sha256 which means the password is encrypted with the SHA256 algorithm and has a $5$ prefix, or sha512 which means the password is encrypted with the SHA512 algorithm and has a $6$ prefix.
property.crypt-password-storage-encryption-algorithm.syntax.enumeration.value.md5.synopsis=New passwords are encrypted with the BSD MD5 algorithm.
-property.crypt-password-storage-encryption-algorithm.syntax.enumeration.value.unix.synopsis=New passwords are encrypted with the UNIX crypt algorithm.
+property.crypt-password-storage-encryption-algorithm.syntax.enumeration.value.sha256.synopsis=New passwords are encrypted with the Unix crypt SHA256 algorithm.
+property.crypt-password-storage-encryption-algorithm.syntax.enumeration.value.sha512.synopsis=New passwords are encrypted with the Unix crypt SHA512 algorithm.
+property.crypt-password-storage-encryption-algorithm.syntax.enumeration.value.unix.synopsis=New passwords are encrypted with the Unix crypt algorithm.
property.enabled.synopsis=Indicates whether the Crypt Password Storage Scheme is enabled for use.
property.java-class.synopsis=Specifies the fully-qualified name of the Java class that provides the Crypt Password Storage Scheme implementation.
diff --git a/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java b/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
index 348a945..fdfa06e 100644
--- a/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
+++ b/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
@@ -23,7 +23,8 @@
*
*
* Copyright 2008 Sun Microsystems, Inc.
- * Portions Copyright 2010 ForgeRock AS
+ * Portions Copyright 2010-2013 ForgeRock AS
+ * Portions Copyright 2012 Dariusz Janny <dariusz.janny@gmail.com>
*
*/
@@ -34,6 +35,7 @@
import java.util.ArrayList;
import java.util.Random;
+
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.PasswordStorageSchemeCfg;
@@ -50,7 +52,6 @@
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
-
/**
* This class defines a Directory Server password storage scheme based on the
* UNIX Crypt algorithm. This is a legacy one-way digest algorithm
@@ -63,6 +64,7 @@
extends PasswordStorageScheme<CryptPasswordStorageSchemeCfg>
implements ConfigurationChangeListener<CryptPasswordStorageSchemeCfg>
{
+
/**
* The fully-qualified name of this class for debugging purposes.
*/
@@ -185,6 +187,40 @@
return ByteString.valueOf(output);
}
+ private ByteString sha256CryptEncodePassword(ByteSequence plaintext)
+ throws DirectoryException {
+ String output;
+ try
+ {
+ output = Sha2Crypt.sha256Crypt(plaintext.toByteArray());
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
+ CLASS_NAME, stackTraceToSingleLineString(e));
+ throw new DirectoryException(
+ DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ return ByteString.valueOf(output);
+ }
+
+ private ByteString sha512CryptEncodePassword(ByteSequence plaintext)
+ throws DirectoryException {
+ String output;
+ try
+ {
+ output = Sha2Crypt.sha512Crypt(plaintext.toByteArray());
+ }
+ catch (Exception e)
+ {
+ Message message = ERR_PWSCHEME_CANNOT_ENCODE_PASSWORD.get(
+ CLASS_NAME, stackTraceToSingleLineString(e));
+ throw new DirectoryException(
+ DirectoryServer.getServerErrorResultCode(), message, e);
+ }
+ return ByteString.valueOf(output);
+ }
+
/**
* {@inheritDoc}
*/
@@ -201,6 +237,12 @@
case MD5:
bytes = md5CryptEncodePassword(plaintext);
break;
+ case SHA256:
+ bytes = sha256CryptEncodePassword(plaintext);
+ break;
+ case SHA512:
+ bytes = sha512CryptEncodePassword(plaintext);
+ break;
}
return bytes;
}
@@ -267,6 +309,36 @@
}
}
+ private boolean sha256CryptPasswordMatches(ByteSequence plaintextPassword,
+ ByteSequence storedPassword) {
+ String storedString = storedPassword.toString();
+ try
+ {
+ String userString = Sha2Crypt.sha256Crypt(
+ plaintextPassword.toByteArray(), storedString);
+ return userString.equals(storedString);
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+ }
+
+ private boolean sha512CryptPasswordMatches(ByteSequence plaintextPassword,
+ ByteSequence storedPassword) {
+ String storedString = storedPassword.toString();
+ try
+ {
+ String userString = Sha2Crypt.sha512Crypt(
+ plaintextPassword.toByteArray(), storedString);
+ return userString.equals(storedString);
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -279,6 +351,14 @@
{
return md5CryptPasswordMatches(plaintextPassword, storedPassword);
}
+ else if (storedString.startsWith(Sha2Crypt.getMagicSHA256Prefix()))
+ {
+ return sha256CryptPasswordMatches(plaintextPassword, storedPassword);
+ }
+ else if (storedString.startsWith(Sha2Crypt.getMagicSHA512Prefix()))
+ {
+ return sha512CryptPasswordMatches(plaintextPassword, storedPassword);
+ }
else
{
return unixCryptPasswordMatches(plaintextPassword, storedPassword);
diff --git a/opends/src/server/org/opends/server/extensions/Sha2Crypt.java b/opends/src/server/org/opends/server/extensions/Sha2Crypt.java
new file mode 100644
index 0000000..251b59f
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/Sha2Crypt.java
@@ -0,0 +1,678 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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
+ *
+ * Portions Copyright 2012 Dariusz Janny <dariusz.janny@gmail.com>
+ * Copyright 2013 ForgeRock AS
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opends.server.extensions;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * SHA2-based Unix crypt implementation.
+ *
+ * <p>
+ * Based on the C implementation released into the Public Domain by Ulrich
+ * Drepper <drepper@redhat.com>
+ * http://www.akkadia.org/drepper/SHA-crypt.txt
+ * </p>
+ *
+ * <p>
+ * Conversion to Kotlin and from there to Java in 2012 by Christian Hammers
+ * <ch@lathspell.de> and likewise put into the Public Domain.
+ * </p>
+ *
+ * <p>
+ * This class is immutable and thread-safe.
+ * </p>
+ *
+ * <p>
+ * Note this class was originally in the
+ * <code>org.apache.commons.codec.digest</code> package, but was moved into
+ * <code>org.opends.server.extensions</code> for convenience.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+final class Sha2Crypt {
+
+ /**
+ * Base64 like method to convert binary bytes into ASCII chars.
+ *
+ * <p>This class is immutable and thread-safe.</p>
+ *
+ * <p>
+ * Note this class was originally in the
+ * <code>org.apache.commons.codec.digest</code> package, but was moved into an
+ * inner class here for convenience. It is <b>not</b> compatible with Base64.
+ * </p>
+ *
+ * @version $Id$
+ * @since 1.7
+ */
+ static class B64 {
+
+ /**
+ * Table with characters for Base64 transformation.
+ */
+ static final String B64T =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ /**
+ * Base64 like conversion of bytes to ASCII chars.
+ *
+ * @param b2
+ * A byte from the result.
+ * @param b1
+ * A byte from the result.
+ * @param b0
+ * A byte from the result.
+ * @param outLen
+ * The number of expected output chars.
+ * @param buffer
+ * Where the output chars is appended to.
+ */
+ static void b64from24bit(byte b2, byte b1, byte b0, int outLen,
+ StringBuilder buffer) {
+ // The bit masking is necessary because the JVM byte type is signed!
+ int w =
+ ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff);
+ // It's effectively a "for" loop but kept to resemble
+ // the original C code.
+ int n = outLen;
+ while (n-- > 0) {
+ buffer.append(B64T.charAt(w & 0x3f));
+ w >>= 6;
+ }
+ }
+
+ /**
+ * Generates a string of random chars from the B64T set.
+ *
+ * @param num
+ * Number of chars to generate.
+ * @return
+ * a string of random chars from the B64T set
+ */
+ static String getRandomSalt(int num) {
+ StringBuilder saltString = new StringBuilder();
+ for (int i = 1; i <= num; i++) {
+ saltString.append(B64T.charAt(new Random().
+ nextInt(B64T.length())));
+ }
+ return saltString.toString();
+ }
+ }
+
+ /**
+ * Default number of rounds if not explicitly specified.
+ */
+ private static final int ROUNDS_DEFAULT = 5000;
+
+ /**
+ * Maximum number of rounds.
+ */
+ private static final int ROUNDS_MAX = 999999999;
+
+ /**
+ * Minimum number of rounds.
+ */
+ private static final int ROUNDS_MIN = 1000;
+
+ /**
+ * Prefix for optional rounds specification.
+ */
+ private static final String ROUNDS_PREFIX = "rounds=";
+
+ /**
+ * The MessageDigest algorithms.
+ */
+ private static final String SHA256_ALGORITHM = "SHA-256";
+
+ private static final String SHA512_ALGORITHM = "SHA-512";
+
+ /**
+ * The number of bytes the final hash value will have.
+ */
+ private static final int SHA256_BLOCKSIZE = 32;
+
+ private static final int SHA512_BLOCKSIZE = 64;
+
+ /**
+ * The prefixes that can be used to identify this crypt() variant.
+ */
+ static final String SHA256_PREFIX = "$5$";
+
+ static final String SHA512_PREFIX = "$6$";
+
+
+ /**
+ * Returns the magic string denoting the SHA-256 scheme is being used.
+ *
+ * @return the magic string
+ */
+ public static String getMagicSHA256Prefix()
+ {
+ return SHA256_PREFIX;
+ }
+
+
+
+ /**
+ * Returns the magic string denoting the SHA-512 scheme is being used.
+ *
+ * @return the magic string
+ */
+ public static String getMagicSHA512Prefix()
+ {
+ return SHA512_PREFIX;
+ }
+
+
+
+ /**
+ * Generates a libc crypt() compatible "$5$" hash value with random salt.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * The plaintext that should be hashed.
+ * @throws Exception exception
+ * @return compatible "$5$" hash value with random sal
+ */
+ public static String sha256Crypt(byte[] keyBytes) throws Exception {
+ return sha256Crypt(keyBytes, null);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$5$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * The plaintext that should be hashed.
+ * @param salt
+ * The real salt value without prefix or "rounds=".
+ * @throws Exception exception
+ * @return compatible "$5$" hash value
+ */
+ public static String sha256Crypt(byte[] keyBytes, String salt)
+ throws Exception {
+ if (salt == null) {
+ salt = SHA256_PREFIX + B64.getRandomSalt(8);
+ }
+ return sha2Crypt(keyBytes, salt, SHA256_PREFIX, SHA256_BLOCKSIZE,
+ SHA256_ALGORITHM);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$5$" or "$6$" SHA2 based hash value.
+ *
+ * This is a nearly line by line conversion of the original C function. The
+ * numbered comments are from the algorithm description, the short C-style
+ * ones from the original C code and the ones with "Remark" from me.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * The plaintext that should be hashed.
+ * @param salt_string
+ * The real salt value without prefix or "rounds=".
+ * @param saltPrefix
+ * Either $5$ or $6$.
+ * @param blocksize
+ * A value that differs between $5$ and $6$.
+ * @param algorithm
+ * The MessageDigest algorithm identifier string.
+ * @throws Exception exception
+ * @return The complete hash value including prefix and salt.
+ */
+ private static String sha2Crypt(byte[] keyBytes, String salt,
+ String saltPrefix, int blocksize, String algorithm) throws Exception {
+ int keyLen = keyBytes.length;
+
+ // Extracts effective salt and the number of rounds from the given salt.
+ int rounds = ROUNDS_DEFAULT;
+ boolean roundsCustom = false;
+ if (salt == null) {
+ throw new IllegalArgumentException("Invalid salt value: null");
+ }
+ Pattern p = Pattern
+ .compile("^\\$([56])\\$(rounds=(\\d+)\\$)?([\\.\\/a-zA-Z0-9]{1,16}).*");
+ Matcher m = p.matcher(salt);
+ if (m == null || !m.find()) {
+ throw new IllegalArgumentException("Invalid salt value: " + salt);
+ }
+ if (m.group(3) != null) {
+ rounds = Integer.parseInt(m.group(3));
+ rounds = Math.max(ROUNDS_MIN, Math.min(ROUNDS_MAX, rounds));
+ roundsCustom = true;
+ }
+ String saltString = m.group(4);
+ byte[] saltBytes = saltString.getBytes("UTF-8");
+ int saltLen = saltBytes.length;
+
+ // 1. start digest A
+ // Prepare for the real work.
+ MessageDigest ctx = MessageDigest.getInstance(algorithm);
+
+ // 2. the password string is added to digest A
+ /*
+ * Add the key string.
+ */
+ ctx.update(keyBytes);
+
+ // 3. the salt string is added to digest A. This is just the salt string
+ // itself without the enclosing '$', without the magic salt_prefix $5$ and
+ // $6$ respectively and without the rounds=<N> specification.
+ //
+ // NB: the MD5 algorithm did add the $1$ salt_prefix. This is not deemed
+ // necessary since it is a constant string and does not add security
+ // and /possibly/ allows a plain text attack. Since the rounds=<N>
+ // specification should never be added this would also create an
+ // inconsistency.
+ /*
+ * The last part is the salt string. This must be at most 16 characters and
+ * it ends at the first `$' character (for compatibility with existing
+ * implementations).
+ */
+ ctx.update(saltBytes);
+
+ // 4. start digest B
+ /*
+ * Compute alternate sha512 sum with input KEY, SALT, and KEY. The final
+ * result will be added to the first context.
+ */
+ MessageDigest altCtx = MessageDigest.getInstance(algorithm);
+
+ // 5. add the password to digest B
+ /*
+ * Add key.
+ */
+ altCtx.update(keyBytes);
+
+ // 6. add the salt string to digest B
+ /*
+ * Add salt.
+ */
+ altCtx.update(saltBytes);
+
+ // 7. add the password again to digest B
+ /*
+ * Add key again.
+ */
+ altCtx.update(keyBytes);
+
+ // 8. finish digest B
+ /*
+ * Now get result of this (32 bytes) and add it to the other context.
+ */
+ byte[] altResult = altCtx.digest();
+
+ // 9. For each block of 32 or 64 bytes in the password string (excluding
+ // the terminating NUL in the C representation), add digest B to digest A
+ /*
+ * Add for any character in the key one byte of the alternate sum.
+ */
+ /*
+ * (Remark: the C code comment seems wrong for key length > 32!)
+ */
+ int cnt = keyBytes.length;
+ while (cnt > blocksize) {
+ ctx.update(altResult, 0, blocksize);
+ cnt -= blocksize;
+ }
+
+ // 10. For the remaining N bytes of the password string add the first
+ // N bytes of digest B to digest A
+ ctx.update(altResult, 0, cnt);
+
+ // 11. For each bit of the binary representation of the length of the
+ // password string up to and including the highest 1-digit, starting
+ // from to lowest bit position (numeric value 1):
+ //
+ // a) for a 1-digit add digest B to digest A
+ //
+ // b) for a 0-digit add the password string
+ //
+ // NB: this step differs significantly from the MD5 algorithm. It
+ // adds more randomness.
+ /*
+ * Take the binary representation of the length of the key and for every 1
+ * add the alternate sum, for every 0 the key.
+ */
+ cnt = keyBytes.length;
+ while (cnt > 0) {
+ if ((cnt & 1) != 0) {
+ ctx.update(altResult, 0, blocksize);
+ } else {
+ ctx.update(keyBytes);
+ }
+ cnt >>= 1;
+ }
+
+ // 12. finish digest A
+ /*
+ * Create intermediate result.
+ */
+ altResult = ctx.digest();
+
+ // 13. start digest DP
+ /*
+ * Start computation of P byte sequence.
+ */
+ altCtx = MessageDigest.getInstance(algorithm);
+
+ // 14. for every byte in the password (excluding the terminating NUL byte
+ // in the C representation of the string)
+ //
+ // add the password to digest DP
+ /*
+ * For every character in the password add the entire password.
+ */
+ for (int i = 1; i <= keyLen; i++) {
+ altCtx.update(keyBytes);
+ }
+
+ // 15. finish digest DP
+ /*
+ * Finish the digest.
+ */
+ byte[] tempResult = altCtx.digest();
+
+ // 16. produce byte sequence P of the same length as the password where
+ //
+ // a) for each block of 32 or 64 bytes of length of the password string
+ // the entire digest DP is used
+ //
+ // b) for the remaining N (up to 31 or 63) bytes use the first N
+ // bytes of digest DP
+ /*
+ * Create byte sequence P.
+ */
+ byte[] pBytes = new byte[keyLen];
+ int cp = 0;
+ while (cp < keyLen - blocksize) {
+ System.arraycopy(tempResult, 0, pBytes, cp, blocksize);
+ cp += blocksize;
+ }
+ System.arraycopy(tempResult, 0, pBytes, cp, keyLen - cp);
+
+ // 17. start digest DS
+ /*
+ * Start computation of S byte sequence.
+ */
+ altCtx = MessageDigest.getInstance(algorithm);
+
+ // 18. repeast the following 16+A[0] times, where A[0] represents the first
+ // byte in digest A interpreted as an 8-bit unsigned value
+ //
+ // add the salt to digest DS
+ /*
+ * For every character in the password add the entire password.
+ */
+ for (int i = 1; i <= 16 + (altResult[0] & 0xff); i++) {
+ altCtx.update(saltBytes);
+ }
+
+ // 19. finish digest DS
+ /*
+ * Finish the digest.
+ */
+ tempResult = altCtx.digest();
+
+ // 20. produce byte sequence S of the same length as the salt string where
+ //
+ // a) for each block of 32 or 64 bytes of length of the salt string
+ // the entire digest DS is used
+ //
+ // b) for the remaining N (up to 31 or 63) bytes use the first N
+ // bytes of digest DS
+ /*
+ * Create byte sequence S.
+ */
+ // Remark: The salt is limited to 16 chars, how does this make sense?
+ byte[] sBytes = new byte[saltLen];
+ cp = 0;
+ while (cp < saltLen - blocksize) {
+ System.arraycopy(tempResult, 0, sBytes, cp, blocksize);
+ cp += blocksize;
+ }
+ System.arraycopy(tempResult, 0, sBytes, cp, saltLen - cp);
+
+ // 21. repeat a loop according to the number specified in the rounds=<N>
+ // specification in the salt (or the default value if none is
+ // present). Each round is numbered, starting with 0 and up to N-1.
+ //
+ // The loop uses a digest as input. In the first round it is the
+ // digest produced in step 12. In the latter steps it is the digest
+ // produced in step 21.h. The following text uses the notation
+ // "digest A/C" to describe this behavior.
+ /*
+ * Repeatedly run the collected hash value through sha512 to burn CPU
+ * cycles.
+ */
+ for (int i = 0; i <= rounds - 1; i++) {
+ // a) start digest C
+ /*
+ * New context.
+ */
+ ctx = MessageDigest.getInstance(algorithm);
+
+ // b) for odd round numbers add the byte sequence P to digest C
+ // c) for even round numbers add digest A/C
+ /*
+ * Add key or last result.
+ */
+ if ((i & 1) != 0) {
+ ctx.update(pBytes, 0, keyLen);
+ } else {
+ ctx.update(altResult, 0, blocksize);
+ }
+
+ // d) for all round numbers not divisible by 3 add the byte sequence S
+ /*
+ * Add salt for numbers not divisible by 3.
+ */
+ if (i % 3 != 0) {
+ ctx.update(sBytes, 0, saltLen);
+ }
+
+ // e) for all round numbers not divisible by 7 add the byte sequence P
+ /*
+ * Add key for numbers not divisible by 7.
+ */
+ if (i % 7 != 0) {
+ ctx.update(pBytes, 0, keyLen);
+ }
+
+ // f) for odd round numbers add digest A/C
+ // g) for even round numbers add the byte sequence P
+ /*
+ * Add key or last result.
+ */
+ if ((i & 1) != 0) {
+ ctx.update(altResult, 0, blocksize);
+ } else {
+ ctx.update(pBytes, 0, keyLen);
+ }
+
+ // h) finish digest C.
+ /*
+ * Create intermediate result.
+ */
+ altResult = ctx.digest();
+ }
+
+ // 22. Produce the output string. This is an ASCII string of the maximum
+ // size specified above, consisting of multiple pieces:
+ //
+ // a) the salt salt_prefix, $5$ or $6$ respectively
+ //
+ // b) the rounds=<N> specification, if one was present in the input
+ // salt string. A trailing '$' is added in this case to separate
+ // the rounds specification from the following text.
+ //
+ // c) the salt string truncated to 16 characters
+ //
+ // d) a '$' character
+ /*
+ * Now we can construct the result string. It consists of three parts.
+ */
+ StringBuilder buffer = new StringBuilder(saltPrefix
+ + (roundsCustom ? ROUNDS_PREFIX + rounds + "$" : "")
+ + saltString + "$");
+
+ // e) the base-64 encoded final C digest. The encoding used is as
+ // follows:
+ // [...]
+ //
+ // Each group of three bytes from the digest produces four
+ // characters as output:
+ //
+ // 1. character: the six low bits of the first byte
+ // 2. character: the two high bits of the first byte and the
+ // four low bytes from the second byte
+ // 3. character: the four high bytes from the second byte and
+ // the two low bits from the third byte
+ // 4. character: the six high bits from the third byte
+ //
+ // The groups of three bytes are as follows (in this sequence).
+ // These are the indices into the byte array containing the
+ // digest, starting with index 0. For the last group there are
+ // not enough bytes left in the digest and the value zero is used
+ // in its place. This group also produces only three or two
+ // characters as output for SHA-512 and SHA-512 respectively.
+
+ // This was just a safeguard in the C implementation:
+ // int buflen = salt_prefix.length() - 1 + ROUNDS_PREFIX.length() + 9 + 1 +
+ // salt_string.length() + 1 + 86 + 1;
+
+ if (blocksize == 32) {
+ B64.b64from24bit(altResult[0], altResult[10], altResult[20], 4, buffer);
+ B64.b64from24bit(altResult[21], altResult[1], altResult[11], 4, buffer);
+ B64.b64from24bit(altResult[12], altResult[22], altResult[2], 4, buffer);
+ B64.b64from24bit(altResult[3], altResult[13], altResult[23], 4, buffer);
+ B64.b64from24bit(altResult[24], altResult[4], altResult[14], 4, buffer);
+ B64.b64from24bit(altResult[15], altResult[25], altResult[5], 4, buffer);
+ B64.b64from24bit(altResult[6], altResult[16], altResult[26], 4, buffer);
+ B64.b64from24bit(altResult[27], altResult[7], altResult[17], 4, buffer);
+ B64.b64from24bit(altResult[18], altResult[28], altResult[8], 4, buffer);
+ B64.b64from24bit(altResult[9], altResult[19], altResult[29], 4, buffer);
+ B64.b64from24bit((byte) 0, altResult[31], altResult[30], 3, buffer);
+ } else {
+ B64.b64from24bit(altResult[0], altResult[21], altResult[42], 4, buffer);
+ B64.b64from24bit(altResult[22], altResult[43], altResult[1], 4, buffer);
+ B64.b64from24bit(altResult[44], altResult[2], altResult[23], 4, buffer);
+ B64.b64from24bit(altResult[3], altResult[24], altResult[45], 4, buffer);
+ B64.b64from24bit(altResult[25], altResult[46], altResult[4], 4, buffer);
+ B64.b64from24bit(altResult[47], altResult[5], altResult[26], 4, buffer);
+ B64.b64from24bit(altResult[6], altResult[27], altResult[48], 4, buffer);
+ B64.b64from24bit(altResult[28], altResult[49], altResult[7], 4, buffer);
+ B64.b64from24bit(altResult[50], altResult[8], altResult[29], 4, buffer);
+ B64.b64from24bit(altResult[9], altResult[30], altResult[51], 4, buffer);
+ B64.b64from24bit(altResult[31], altResult[52], altResult[10], 4, buffer);
+ B64.b64from24bit(altResult[53], altResult[11], altResult[32], 4, buffer);
+ B64.b64from24bit(altResult[12], altResult[33], altResult[54], 4, buffer);
+ B64.b64from24bit(altResult[34], altResult[55], altResult[13], 4, buffer);
+ B64.b64from24bit(altResult[56], altResult[14], altResult[35], 4, buffer);
+ B64.b64from24bit(altResult[15], altResult[36], altResult[57], 4, buffer);
+ B64.b64from24bit(altResult[37], altResult[58], altResult[16], 4, buffer);
+ B64.b64from24bit(altResult[59], altResult[17], altResult[38], 4, buffer);
+ B64.b64from24bit(altResult[18], altResult[39], altResult[60], 4, buffer);
+ B64.b64from24bit(altResult[40], altResult[61], altResult[19], 4, buffer);
+ B64.b64from24bit(altResult[62], altResult[20], altResult[41], 4, buffer);
+ B64.b64from24bit((byte) 0, (byte) 0, altResult[63], 2, buffer);
+ }
+
+ /*
+ * Clear the buffer for the intermediate result so that people attaching to
+ * processes or reading core dumps cannot get any information.
+ */
+ // Is there a better way to do this with the JVM?
+ Arrays.fill(tempResult, (byte) 0);
+ Arrays.fill(pBytes, (byte) 0);
+ Arrays.fill(sBytes, (byte) 0);
+ ctx.reset();
+ altCtx.reset();
+ Arrays.fill(keyBytes, (byte) 0);
+ Arrays.fill(saltBytes, (byte) 0);
+
+ return buffer.toString();
+ }
+
+ /**
+ * Generates a libc crypt() compatible "$6$" hash value with random salt.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * The plaintext that should be hashed.
+ * @throws Exception exception
+ * @return compatible "$6$" hash value with random salt
+ */
+ public static String sha512Crypt(byte[] keyBytes) throws Exception {
+ return sha512Crypt(keyBytes, null);
+ }
+
+ /**
+ * Generates a libc6 crypt() compatible "$6$" hash value.
+ *
+ * See {@link Crypt#crypt(String, String)} for details.
+ *
+ * @param keyBytes
+ * The plaintext that should be hashed.
+ * @param salt
+ * The real salt value without prefix or "rounds=".
+ * @throws Exception exception
+ * @return compatible "$6$" hash value
+ */
+ public static String sha512Crypt(byte[] keyBytes, String salt)
+ throws Exception {
+ if (salt == null) {
+ salt = SHA512_PREFIX + B64.getRandomSalt(8);
+ }
+ return sha2Crypt(keyBytes, salt, SHA512_PREFIX, SHA512_BLOCKSIZE,
+ SHA512_ALGORITHM);
+ }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CryptPasswordStorageSchemeTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CryptPasswordStorageSchemeTestCase.java
index 7d1d9f1..614b718 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CryptPasswordStorageSchemeTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CryptPasswordStorageSchemeTestCase.java
@@ -23,14 +23,15 @@
*
*
* Copyright 2008 Sun Microsystems, Inc.
- * Portions Copyright 2010-2011 ForgeRock AS.
+ * Portions Copyright 2010-2013 ForgeRock AS.
+ * Portions Copyright 2012 Dariusz Janny <dariusz.janny@gmail.com>
*/
package org.opends.server.extensions;
-import static org.testng.Assert.*;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
import java.util.ArrayList;
@@ -53,6 +54,9 @@
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
/**
@@ -69,6 +73,9 @@
private static final String configDNString =
"cn=Crypt,cn=Password Storage Schemes,cn=config";
+ // Names of all the crypt algorithms we want to test.
+ private static final String[] names = { "unix", "md5", "sha256", "sha512" };
+
/**
* Creates a new instance of this crypt password storage scheme test
* case with the provided information.
@@ -134,7 +141,7 @@
/**
- * Creates an instance of the password storage scheme, uses it to encode the
+ * Creates an instance of each password storage scheme, uses it to encode the
* provided password, and ensures that the encoded value is correct.
*
* @param plaintext The plain-text version of the password to encode.
@@ -142,164 +149,85 @@
* @throws Exception If an unexpected problem occurs.
*/
@Test(dataProvider = "testPasswords")
- public void testUnixStorageScheme(ByteString plaintext)
+ public void testUnixStorageSchemes(ByteString plaintext)
throws Exception
{
- CryptPasswordStorageScheme scheme = getScheme("unix");
- assertNotNull(scheme);
- assertNotNull(scheme.getStorageSchemeName());
-
- ByteString encodedPassword = scheme.encodePassword(plaintext);
- assertNotNull(encodedPassword);
- assertTrue(scheme.passwordMatches(plaintext, encodedPassword));
- assertFalse(scheme.passwordMatches(plaintext,
- ByteString.valueOf("garbage")));
-
- ByteString schemeEncodedPassword =
- scheme.encodePasswordWithScheme(plaintext);
- String[] pwComponents = UserPasswordSyntax.decodeUserPassword(
- schemeEncodedPassword.toString());
- assertNotNull(pwComponents);
-
-
- if (scheme.supportsAuthPasswordSyntax())
+ for (String name : names)
{
- assertNotNull(scheme.getAuthPasswordSchemeName());
- ByteString encodedAuthPassword = scheme.encodeAuthPassword(plaintext);
- StringBuilder[] authPWComponents =
- AuthPasswordSyntax.decodeAuthPassword(
- encodedAuthPassword.toString());
- assertTrue(scheme.authPasswordMatches(plaintext,
- authPWComponents[1].toString(),
- authPWComponents[2].toString()));
- assertFalse(scheme.authPasswordMatches(plaintext, ",", "foo"));
- assertFalse(scheme.authPasswordMatches(plaintext, "foo", ","));
- }
- else
- {
- try
+ CryptPasswordStorageScheme scheme = getScheme(name);
+ assertNotNull(scheme);
+ assertNotNull(scheme.getStorageSchemeName());
+
+ ByteString encodedPassword = scheme.encodePassword(plaintext);
+ assertNotNull(encodedPassword);
+ assertTrue(scheme.passwordMatches(plaintext, encodedPassword));
+ assertFalse(scheme.passwordMatches(plaintext,
+ ByteString.valueOf("garbage")));
+
+ ByteString schemeEncodedPassword =
+ scheme.encodePasswordWithScheme(plaintext);
+ String[] pwComponents = UserPasswordSyntax.decodeUserPassword(
+ schemeEncodedPassword.toString());
+ assertNotNull(pwComponents);
+
+
+ if (scheme.supportsAuthPasswordSyntax())
{
- scheme.encodeAuthPassword(plaintext);
- throw new Exception("Expected encodedAuthPassword to fail for scheme " +
- scheme.getStorageSchemeName() +
- " because it doesn't support auth passwords.");
+ assertNotNull(scheme.getAuthPasswordSchemeName());
+ ByteString encodedAuthPassword = scheme.encodeAuthPassword(plaintext);
+ StringBuilder[] authPWComponents =
+ AuthPasswordSyntax.decodeAuthPassword(
+ encodedAuthPassword.toString());
+ assertTrue(scheme.authPasswordMatches(plaintext,
+ authPWComponents[1].toString(),
+ authPWComponents[2].toString()));
+ assertFalse(scheme.authPasswordMatches(plaintext, ",", "foo"));
+ assertFalse(scheme.authPasswordMatches(plaintext, "foo", ","));
}
- catch (DirectoryException de)
+ else
{
- // This was expected.
+ try
+ {
+ scheme.encodeAuthPassword(plaintext);
+ throw new Exception("Expected encodedAuthPassword to fail for scheme " +
+ scheme.getStorageSchemeName() +
+ " because it doesn't support auth passwords.");
+ }
+ catch (DirectoryException de)
+ {
+ // This was expected.
+ }
+
+ assertFalse(scheme.authPasswordMatches(plaintext, "foo", "bar"));
}
- assertFalse(scheme.authPasswordMatches(plaintext, "foo", "bar"));
- }
-
- if (scheme.isReversible())
- {
- assertEquals(scheme.getPlaintextValue(encodedPassword), plaintext);
- }
- else
- {
- try
+ if (scheme.isReversible())
{
- scheme.getPlaintextValue(encodedPassword);
- throw new Exception("Expected getPlaintextValue to fail for scheme " +
- scheme.getStorageSchemeName() +
- " because it is not reversible.");
+ assertEquals(scheme.getPlaintextValue(encodedPassword), plaintext);
}
- catch (DirectoryException de)
+ else
{
- // This was expected.
+ try
+ {
+ scheme.getPlaintextValue(encodedPassword);
+ throw new Exception("Expected getPlaintextValue to fail for scheme " +
+ scheme.getStorageSchemeName() +
+ " because it is not reversible.");
+ }
+ catch (DirectoryException de)
+ {
+ // This was expected.
+ }
}
- }
- scheme.isStorageSchemeSecure();
+ scheme.isStorageSchemeSecure();
+
+ }
}
- /**
- * Creates an instance of the password storage scheme, uses it to encode the
- * provided password, and ensures that the encoded value is correct.
- *
- * @param plaintext The plain-text version of the password to encode.
- *
- * @throws Exception If an unexpected problem occurs.
- */
- @Test(dataProvider = "testPasswords")
- public void testMD5StorageScheme(ByteString plaintext)
- throws Exception
- {
- CryptPasswordStorageScheme scheme = getScheme("md5");
- assertNotNull(scheme);
- assertNotNull(scheme.getStorageSchemeName());
-
- ByteString encodedPassword = scheme.encodePassword(plaintext);
- assertNotNull(encodedPassword);
- assertTrue(scheme.passwordMatches(plaintext, encodedPassword));
- assertFalse(scheme.passwordMatches(plaintext,
- ByteString.valueOf("garbage")));
-
- ByteString schemeEncodedPassword =
- scheme.encodePasswordWithScheme(plaintext);
- String[] pwComponents = UserPasswordSyntax.decodeUserPassword(
- schemeEncodedPassword.toString());
- assertNotNull(pwComponents);
-
-
- if (scheme.supportsAuthPasswordSyntax())
- {
- assertNotNull(scheme.getAuthPasswordSchemeName());
- ByteString encodedAuthPassword = scheme.encodeAuthPassword(plaintext);
- StringBuilder[] authPWComponents =
- AuthPasswordSyntax.decodeAuthPassword(
- encodedAuthPassword.toString());
- assertTrue(scheme.authPasswordMatches(plaintext,
- authPWComponents[1].toString(),
- authPWComponents[2].toString()));
- assertFalse(scheme.authPasswordMatches(plaintext, ",", "foo"));
- assertFalse(scheme.authPasswordMatches(plaintext, "foo", ","));
- }
- else
- {
- try
- {
- scheme.encodeAuthPassword(plaintext);
- throw new Exception("Expected encodedAuthPassword to fail for scheme " +
- scheme.getStorageSchemeName() +
- " because it doesn't support auth passwords.");
- }
- catch (DirectoryException de)
- {
- // This was expected.
- }
-
- assertFalse(scheme.authPasswordMatches(plaintext, "foo", "bar"));
- }
-
-
- if (scheme.isReversible())
- {
- assertEquals(scheme.getPlaintextValue(encodedPassword), plaintext);
- }
- else
- {
- try
- {
- scheme.getPlaintextValue(encodedPassword);
- throw new Exception("Expected getPlaintextValue to fail for scheme " +
- scheme.getStorageSchemeName() +
- " because it is not reversible.");
- }
- catch (DirectoryException de)
- {
- // This was expected.
- }
- }
-
- scheme.isStorageSchemeSecure();
- }
-
-
@DataProvider
public Object[][] passwordsForBinding()
{
@@ -316,6 +244,8 @@
};
}
+
+
/**
* An end-to-end test that verifies that we can set a pre-encoded password
* in a user entry, and then bind as that user using the cleartext password.
@@ -324,15 +254,16 @@
public void testSettingUnixEncodedPassword(ByteString plainPassword)
throws Exception
{
- // Start/clear-out the memory backend
- TestCaseUtils.initializeTestBackend(true);
+ for (String name: names)
+ {
+ // Start/clear-out the memory backend
+ TestCaseUtils.initializeTestBackend(true);
- boolean allowPreencodedDefault = setAllowPreencodedPasswords(true);
+ setAllowPreencodedPasswords(true);
- try {
- CryptPasswordStorageScheme scheme = getScheme("unix");
+ CryptPasswordStorageScheme scheme = getScheme(name);
ByteString schemeEncodedPassword =
- scheme.encodePasswordWithScheme(plainPassword);
+ scheme.encodePasswordWithScheme(plainPassword);
//
// This code creates a user with the encoded password,
@@ -340,79 +271,27 @@
//
Entry userEntry = TestCaseUtils.makeEntry(
- "dn: uid=test.user,o=test",
- "objectClass: top",
- "objectClass: person",
- "objectClass: organizationalPerson",
- "objectClass: inetOrgPerson",
- "uid: test.user",
- "givenName: Test",
- "sn: User",
- "cn: Test User",
- "ds-privilege-name: bypass-acl",
- "userPassword: " + schemeEncodedPassword.toString());
+ "dn: uid=test.user,o=test",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "uid: test.user",
+ "givenName: Test",
+ "sn: User",
+ "cn: Test User",
+ "ds-privilege-name: bypass-acl",
+ "userPassword: " + schemeEncodedPassword.toString());
// Add the entry
TestCaseUtils.addEntry(userEntry);
assertTrue(TestCaseUtils.canBind("uid=test.user,o=test",
- plainPassword.toString()),
- "Failed to bind when pre-encoded password = \"" +
- schemeEncodedPassword.toString() + "\" and " +
- "plaintext password = \"" +
- plainPassword.toString() + "\"");
- } finally {
- setAllowPreencodedPasswords(allowPreencodedDefault);
- }
- }
-
- /**
- * An end-to-end test that verifies that we can set a pre-encoded password
- * in a user entry, and then bind as that user using the cleartext password.
- */
- @Test(dataProvider = "passwordsForBinding")
- public void testSettingMD5EncodedPassword(ByteString plainPassword)
- throws Exception
- {
- // Start/clear-out the memory backend
- TestCaseUtils.initializeTestBackend(true);
-
- boolean allowPreencodedDefault = setAllowPreencodedPasswords(true);
-
- try {
- CryptPasswordStorageScheme scheme = getScheme("md5");
- ByteString schemeEncodedPassword =
- scheme.encodePasswordWithScheme(plainPassword);
-
- //
- // This code creates a user with the encoded password,
- // and then verifies that they can bind with the raw password.
- //
-
- Entry userEntry = TestCaseUtils.makeEntry(
- "dn: uid=test.user,o=test",
- "objectClass: top",
- "objectClass: person",
- "objectClass: organizationalPerson",
- "objectClass: inetOrgPerson",
- "uid: test.user",
- "givenName: Test",
- "sn: User",
- "cn: Test User",
- "ds-privilege-name: bypass-acl",
- "userPassword: " + schemeEncodedPassword.toString());
-
- // Add the entry
- TestCaseUtils.addEntry(userEntry);
-
- assertTrue(TestCaseUtils.canBind("uid=test.user,o=test",
- plainPassword.toString()),
- "Failed to bind when pre-encoded password = \"" +
- schemeEncodedPassword.toString() + "\" and " +
- "plaintext password = \"" +
- plainPassword.toString() + "\"");
- } finally {
- setAllowPreencodedPasswords(allowPreencodedDefault);
+ plainPassword.toString()),
+ "Failed to bind when pre-encoded password = \"" +
+ schemeEncodedPassword.toString() + "\" and " +
+ "plaintext password = \"" +
+ plainPassword.toString() + "\"");
}
}
@@ -460,33 +339,40 @@
}
/**
- * Retrieves a set of passwords (plain and md5 encrypted) that may
- * be used to test the BSDMD5 algorithm of Crypt Password Storage scheme
- * compatibility with Linux versions.
- * The encrypted version has been generated by openssl passwd -1
+ * Retrieves a set of passwords (plain and variously hashed) that may
+ * be used to test the different Unix "crypt" algorithms used by the Crypt
+ * Password Storage scheme.
+ *
+ * The encrypted versions have been generated by the openssl passwd -1
* command on MacOS X.
*
- * @return A set of couple (cleartext, md5 encryped) passwords that
- * may be used to test BSD MD5 algorithm of the Crypt password
- * storage scheme.
+ * @return A set of couple (cleartext, hashed) passwords that
+ * may be used to test the different algorithms used by the Crypt
+ * password storage scheme.
*/
- @DataProvider(name = "testBSDMD5Passwords")
- public Object[][] getTestBSDMD5Passwords()
+ @DataProvider(name = "testCryptPasswords")
+ public Object[][] getTestCryptPasswords()
throws Exception
{
return new Object[][]
{
new Object[] { "secret12", "{CRYPT}$1$X40CcMaA$dd3ndknBLcpkED4/RciyD1" },
new Object[] { "#1 Strong Password!", "{CRYPT}$1$7jHbWKyy$gAmpOSdaYVap55MwsQnK5/" },
- new Object[] { "foo", "{CRYPT}$1$ac/Z7Q3s$5kTVLqMSq9KMqUVyEBfiw0" }
+ new Object[] { "foo", "{CRYPT}$1$ac/Z7Q3s$5kTVLqMSq9KMqUVyEBfiw0" },
+ new Object[] { "secret12", "{CRYPT}$5$miWe9yahchas7aiy$b/6oTh5QF3bqbdIDWmjtdOxD8df75426zTHwF.MJuyB" },
+ new Object[] { "foo", "{CRYPT}$5$aZoothaeDai0nooG$5LDMuhK6gWtH6/mrrqZbRc5aIRROfrKri4Tvl/D6Z.0"},
+ new Object[] { "#1 Strong Password!", "{CRYPT}$5$aZoothaeDai0nooG$6o0Sbx/RtTA4K/A8uflMsSCid3i7TYktcwWxIp5NFy2"},
+ new Object[] { "secret12", "{CRYPT}$6$miWe9yahchas7aiy$RQASn5qZMCu2FDsR69RHk1RoLVi3skFUhS0qGNCo.MymgkYoWAedMji09UzxMFzOj8fW2GnzsXT4RVn9gcNmf0" },
+ new Object[] { "#1 Strong Password!", "{CRYPT}$6$p0NJY6r4$VV2JfNtRaTmy8hBtVpdgeIUYQIAUyfdLyhiH6VxzsDIw.28oCsVeMQ5ARiL/PoOambM9dAU3vk4ll8uEB/nnx0"},
+ new Object[] { "foo", "{CRYPT}$6$aZoothaeDai0nooG$1K9ePro8ujsqRy/Ag77OVuev8Y8hyN1Jp10S2t9S.1RMtkKn/SbxQbl2MezoL0UJFYjrEzL0zVdO8PcfT3yXS."}
};
}
- @Test(dataProvider = "testBSDMD5Passwords")
- public void testAuthBSDMD5Passwords(
+ @Test(dataProvider = "testCryptPasswords")
+ public void testAuthCryptPasswords(
String plaintextPassword,
- String md5EncodedPassword) throws Exception
+ String encodedPassword) throws Exception
{
// Start/clear-out the memory backend
TestCaseUtils.initializeTestBackend(true);
@@ -496,25 +382,25 @@
try {
Entry userEntry = TestCaseUtils.makeEntry(
- "dn: uid=testMD5.user,o=test",
+ "dn: uid=testCrypt.user,o=test",
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
- "uid: testMD5.user",
- "givenName: TestMD5",
+ "uid: testCrypt.user",
+ "givenName: TestCrypt",
"sn: User",
- "cn: TestMD5 User",
- "userPassword: " + md5EncodedPassword);
+ "cn: TestCrypt User",
+ "userPassword: " + encodedPassword);
// Add the entry
TestCaseUtils.addEntry(userEntry);
- assertTrue(TestCaseUtils.canBind("uid=testMD5.user,o=test",
+ assertTrue(TestCaseUtils.canBind("uid=testCrypt.user,o=test",
plaintextPassword),
"Failed to bind when pre-encoded password = \"" +
- md5EncodedPassword + "\" and " +
+ encodedPassword + "\" and " +
"plaintext password = \"" +
plaintextPassword + "\"" );
} finally {
--
Gitblit v1.10.0