From a8d1e63227200136e36d7ace664a099e78aaab10 Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Tue, 26 Oct 2010 11:57:44 +0000
Subject: [PATCH] Resolves Enhancement request OpenDJ-5: Support Linux md5 crypt storage for password This changes are adding support for the BSD MD5 crypt hash as part of the CRYPT password storage scheme. A new parameter has been added to the configuration of the storage scheme to select whether new passwords should be hashed with the unix algo (default) or the md5 one. When it comes to authentication, the scheme is able to detect the algo (based on the $1$ prefix) and match appropriately. Unit tests have been added, including test again passwords already hashed on Linux systems.

---
 opendj-sdk/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java |  150 +++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 137 insertions(+), 13 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
index 9692a6f..f9f968f 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/CryptPasswordStorageScheme.java
@@ -23,20 +23,27 @@
  *
  *
  *      Copyright 2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2010 ForgeRock AS
+ *
  */
+
 package org.opends.server.extensions;
 
 
-
+import java.util.List;
+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;
 import org.opends.server.admin.std.server.CryptPasswordStorageSchemeCfg;
 import org.opends.server.api.PasswordStorageScheme;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.types.*;
 import org.opends.server.util.Crypt;
+import org.opends.server.util.BSDMD5Crypt;
 
 import static org.opends.messages.ExtensionMessages.*;
 import static org.opends.server.extensions.ExtensionsConstants.*;
@@ -54,6 +61,7 @@
  */
 public class CryptPasswordStorageScheme
        extends PasswordStorageScheme<CryptPasswordStorageSchemeCfg>
+       implements ConfigurationChangeListener<CryptPasswordStorageSchemeCfg>
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -61,6 +69,11 @@
   private static final String CLASS_NAME =
        "org.opends.server.extensions.CryptPasswordStorageScheme";
 
+  /*
+   * The current configuration for the CryptPasswordStorageScheme
+   */
+  private CryptPasswordStorageSchemeCfg currentConfig;
+
   /**
    * An array of values that can be used to create salt characters
    * when encoding new crypt hashes.
@@ -72,7 +85,7 @@
   private final Random randomSaltIndex = new Random();
   private final Object saltLock = new Object();
   private final Crypt crypt = new Crypt();
-
+  private final BSDMD5Crypt bsdmd5crypt = new BSDMD5Crypt();
 
 
   /**
@@ -93,7 +106,10 @@
   public void initializePasswordStorageScheme(
                    CryptPasswordStorageSchemeCfg configuration)
          throws ConfigException, InitializationException {
-    // Nothing to configure
+
+    configuration.addCryptChangeListener(this);
+
+    currentConfig = configuration;
   }
 
   /**
@@ -107,10 +123,10 @@
 
 
   /**
-   * {@inheritDoc}
+   * Encrypt plaintext password with the Unix Crypt algorithm.
    */
-  @Override()
-  public ByteString encodePassword(ByteSequence plaintext)
+
+  private ByteString unixCryptEncodePassword(ByteSequence plaintext)
          throws DirectoryException
   {
 
@@ -133,7 +149,6 @@
     return ByteString.wrap(digestBytes);
   }
 
-
   /**
    * Return a random 2-byte salt.
    *
@@ -152,6 +167,44 @@
     }
   }
 
+  private ByteString md5CryptEncodePassword(ByteSequence plaintext)
+         throws DirectoryException
+  {
+    String output;
+    try
+    {
+      output = bsdmd5crypt.crypt(plaintext.toString());
+    }
+    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}
+   */
+  @Override()
+  public ByteString encodePassword(ByteSequence plaintext)
+         throws DirectoryException
+  {
+    ByteString bytes = null;
+    switch (currentConfig.getCryptPasswordStorageEncryptionAlgorithm())
+    {
+      case UNIX:
+        bytes = unixCryptEncodePassword(plaintext);
+        break;
+      case MD5:
+        bytes = md5CryptEncodePassword(plaintext);
+        break;
+    }
+    return bytes;
+  }
+
 
   /**
    * {@inheritDoc}
@@ -171,13 +224,10 @@
     return ByteString.valueOf(buffer.toString());
   }
 
-
-
   /**
-   * {@inheritDoc}
+   * Matches passwords encrypted with the Unix Crypt algorithm.
    */
-  @Override()
-  public boolean passwordMatches(ByteSequence plaintextPassword,
+  private boolean unixCryptPasswordMatches(ByteSequence plaintextPassword,
                                  ByteSequence storedPassword)
   {
     // TODO: Can we avoid this copy?
@@ -201,7 +251,39 @@
     return userPWDigestBytes.equals(storedPassword);
   }
 
+  private boolean md5CryptPasswordMatches(ByteSequence plaintextPassword,
+                                 ByteSequence storedPassword)
+  {
+    String storedString = storedPassword.toString();
+    try
+    {
+      String userString   = bsdmd5crypt.crypt(plaintextPassword.toString(),
+        storedString);
+      return userString.equals(storedString);
+    }
+    catch (Exception e)
+    {
+      return false;
+    }
+  }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean passwordMatches(ByteSequence plaintextPassword,
+                                 ByteSequence storedPassword)
+  {
+    String storedString = storedPassword.toString();
+    if (storedString.startsWith(BSDMD5Crypt.getMagicString()))
+    {
+      return md5CryptPasswordMatches(plaintextPassword, storedPassword);
+    }
+    else
+    {
+      return unixCryptPasswordMatches(plaintextPassword, storedPassword);
+    }
+  }
 
   /**
    * {@inheritDoc}
@@ -276,7 +358,7 @@
          throws DirectoryException
   {
     Message message =
-        ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
+      ERR_PWSCHEME_DOES_NOT_SUPPORT_AUTH_PASSWORD.get(getStorageSchemeName());
     throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
   }
 
@@ -303,5 +385,47 @@
 
     return false;
   }
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isConfigurationAcceptable(
+          PasswordStorageSchemeCfg configuration,
+          List<Message> unacceptableReasons)
+  {
+    CryptPasswordStorageSchemeCfg config =
+            (CryptPasswordStorageSchemeCfg) configuration;
+    return isConfigurationChangeAcceptable(config, unacceptableReasons);
 }
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+                      CryptPasswordStorageSchemeCfg configuration,
+                      List<Message> unacceptableReasons)
+  {
+    // If we've gotten this far, then we'll accept the change.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+                      CryptPasswordStorageSchemeCfg configuration)
+  {
+    ResultCode        resultCode          = ResultCode.SUCCESS;
+    boolean           adminActionRequired = false;
+    ArrayList<Message> messages            = new ArrayList<Message>();
+
+
+    currentConfig = configuration;
+
+    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
+}

--
Gitblit v1.10.0