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

neil_a_wilson
21.01.2006 34451f14f9d4ef9efb313ecbdd22633eec5fd1ff
Update the PLAIN, CRAM-MD5, and DIGEST-MD5 SASL mechanism handlers to use the
password policy during the process of verifying the credentials rather than
just assuming that the password is held in the userPassword attribute.

Also, fix a bug in the DIGEST-MD5 implementation that prevented authentication
from completing successfully when the "u:" form of the authentication ID was
provided.

OpenDS Issue Numbers: 698, 699
4 files modified
432 ■■■■ changed files
opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java 130 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java 160 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java 99 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 43 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
@@ -41,7 +41,6 @@
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.IdentityMapper;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
@@ -52,17 +51,13 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
@@ -511,87 +506,40 @@
    }
    // Get the password attribute from the user entry and iterate through the
    // available values.  We can only look at reversible values, so all
    // non-reversible values will be ignored.  For each reversible value, see
    // if we can use it in conjunction with the challenge to construct the
    // provided digest.
    // FIXME -- Determine the attribute based on the user's password policy.
    AttributeType pwType = DirectoryServer.getAttributeType(ATTR_USER_PASSWORD);
    if (pwType == null)
    // Get the clear-text passwords from the user entry, if there are any.
    List<ByteString> clearPasswords;
    try
    {
      pwType = DirectoryServer.getDefaultAttributeType(ATTR_USER_PASSWORD);
    }
    List<Attribute> pwAttr = userEntry.getAttribute(pwType);
    if ((pwAttr == null) || pwAttr.isEmpty())
      PasswordPolicyState pwPolicyState =
           new PasswordPolicyState(userEntry, false, false);
      clearPasswords = pwPolicyState.getClearPasswords();
      if ((clearPasswords == null) || clearPasswords.isEmpty())
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLCRAMMD5_NO_PW_ATTR;
      String message = getMessage(msgID, pwType.getNameOrOID());
        int msgID = MSGID_SASLCRAMMD5_NO_REVERSIBLE_PASSWORDS;
        String message = getMessage(msgID, String.valueOf(userEntry.getDN()));
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
    }
    catch (Exception e)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS;
      String message = getMessage(msgID, String.valueOf(userEntry.getDN()),
                                  String.valueOf(e));
      bindOperation.setAuthFailureReason(msgID, message);
      return;
    }
    boolean reversibleFound = false;
    // Iterate through the clear-text values and see if any of them can be used
    // in conjunction with the challenge to construct the provided digest.
    boolean matchFound       = false;
    for (Attribute a : pwAttr)
    for (ByteString clearPassword : clearPasswords)
    {
      for (AttributeValue v : a.getValues())
      {
        String valueStr = v.getStringValue();
        int closePos;
        if (valueStr.startsWith(STORAGE_SCHEME_PREFIX) &&
            (closePos = valueStr.indexOf(STORAGE_SCHEME_SUFFIX, 2)) > 0)
        {
          String schemeName =
               toLowerCase(valueStr.substring(1, closePos));
          PasswordStorageScheme scheme =
               DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            // We can't do anything with this.  Append a message to the
            // error message to include in the response and continue.
            int    msgID   = MSGID_SASLCRAMMD5_UNKNOWN_STORAGE_SCHEME;
            String message = getMessage(msgID,
                                        String.valueOf(userEntry.getDN()),
                                        schemeName);
            logError(ErrorLogCategory.EXTENSIONS,
                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
            continue;
          }
          else if (! scheme.isReversible())
          {
            // It's not a reversible scheme, so we can't get the clear-text
            // password to test.  Skip it and go on.
            continue;
          }
          else
          {
            ASN1OctetString encodedPassword =
                 new ASN1OctetString(valueStr.substring(closePos+1));
            ByteString clearPassword;
            try
            {
              clearPassword   = scheme.getPlaintextValue(encodedPassword);
              reversibleFound = true;
            }
            catch (DirectoryException de)
            {
              assert debugException(CLASS_NAME, "processSASLBind", de);
              int    msgID   = MSGID_SASLCRAMMD5_CANNOT_GET_CLEAR_PASSWORD;
              String message = getMessage(msgID,
                                          String.valueOf(userEntry.getDN()),
                                          schemeName, de.getErrorMessage());
              logError(ErrorLogCategory.EXTENSIONS,
                       ErrorLogSeverity.SEVERE_WARNING, message, msgID);
              continue;
            }
            byte[] generatedDigest = generateDigest(clearPassword, challenge);
            if (Arrays.equals(digestBytes, generatedDigest))
            {
@@ -599,44 +547,16 @@
              break;
            }
          }
        }
        else
        {
          reversibleFound = true;
          byte[] generatedDigest = generateDigest(v.getValue(), challenge);
          if (Arrays.equals(digestBytes, generatedDigest))
          {
            matchFound = true;
            break;
          }
        }
      }
      if (matchFound)
      {
        break;
      }
    }
    if (! matchFound)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      if (reversibleFound)
      {
        int    msgID   = MSGID_SASLCRAMMD5_INVALID_PASSWORD;
        String message = getMessage(msgID);
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
      else
      {
        int    msgID   = MSGID_SASLCRAMMD5_NO_REVERSIBLE_PASSWORDS;
        String message = getMessage(msgID, String.valueOf(userEntry.getDN()));
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
    }
    // If we've gotten here, then the authentication was successful.
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -45,7 +45,6 @@
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.IdentityMapper;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
@@ -57,10 +56,8 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
@@ -862,6 +859,7 @@
    else
    {
      // Use the identity mapper to resolve the username to an entry.
      String userName = responseUserName;
      if (lowerUserName.startsWith("u:"))
      {
        if (lowerUserName.equals("u:"))
@@ -874,13 +872,13 @@
          return;
        }
        responseUserName = responseUserName.substring(2);
        userName = responseUserName.substring(2);
      }
      try
      {
        userEntry = identityMapper.getEntryForID(responseUserName);
        userEntry = identityMapper.getEntryForID(userName);
      }
      catch (DirectoryException de)
      {
@@ -913,87 +911,41 @@
    }
    // Get the password attribute from the user entry and iterate through the
    // available values.  We can only look at reversible values, so all
    // non-reversible values will be ignored.  For each reversible value, see
    // if we can use it in conjunction with the challenge to construct the
    // provided digest.
    // FIXME -- Determine the attribute based on the user's password policy.
    AttributeType pwType = DirectoryServer.getAttributeType(ATTR_USER_PASSWORD);
    if (pwType == null)
    // Get the clear-text passwords from the user entry, if there are any.
    List<ByteString> clearPasswords;
    try
    {
      pwType = DirectoryServer.getDefaultAttributeType(ATTR_USER_PASSWORD);
    }
    List<Attribute> pwAttr = userEntry.getAttribute(pwType);
    if ((pwAttr == null) || pwAttr.isEmpty())
      PasswordPolicyState pwPolicyState =
           new PasswordPolicyState(userEntry, false, false);
      clearPasswords = pwPolicyState.getClearPasswords();
      if ((clearPasswords == null) || clearPasswords.isEmpty())
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLDIGESTMD5_NO_PW_ATTR;
      String message = getMessage(msgID, pwType.getNameOrOID());
        int msgID = MSGID_SASLDIGESTMD5_NO_REVERSIBLE_PASSWORDS;
        String message = getMessage(msgID, String.valueOf(userEntry.getDN()));
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
    }
    catch (Exception e)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS;
      String message = getMessage(msgID, String.valueOf(userEntry.getDN()),
                                  String.valueOf(e));
      bindOperation.setAuthFailureReason(msgID, message);
      return;
    }
    boolean reversibleFound = false;
    // Iterate through the clear-text values and see if any of them can be used
    // in conjunction with the challenge to construct the provided digest.
    boolean matchFound       = false;
    byte[]  passwordBytes    = null;
    for (Attribute a : pwAttr)
    for (ByteString clearPassword : clearPasswords)
    {
      for (AttributeValue v : a.getValues())
      {
        String valueStr = v.getStringValue();
        int closePos;
        if (valueStr.startsWith(STORAGE_SCHEME_PREFIX) &&
            (closePos = valueStr.indexOf(STORAGE_SCHEME_SUFFIX, 2)) > 0)
        {
          String schemeName =
               toLowerCase(valueStr.substring(1, closePos));
          PasswordStorageScheme scheme =
               DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            // We can't do anything with this.  Append a message to the
            // error message to include in the response and continue.
            int    msgID   = MSGID_SASLDIGESTMD5_UNKNOWN_STORAGE_SCHEME;
            String message = getMessage(msgID,
                                        String.valueOf(userEntry.getDN()),
                                        schemeName);
            logError(ErrorLogCategory.EXTENSIONS,
                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
            continue;
          }
          else if (! scheme.isReversible())
          {
            // It's not a reversible scheme, so we can't get the clear-text
            // password to test.  Skip it and go on.
            continue;
          }
          else
          {
            ASN1OctetString encodedPassword =
                 new ASN1OctetString(valueStr.substring(closePos+1));
            ByteString clearPassword;
            try
            {
              clearPassword   = scheme.getPlaintextValue(encodedPassword);
              reversibleFound = true;
            }
            catch (DirectoryException de)
            {
              assert debugException(CLASS_NAME, "processSASLBind", de);
              int    msgID   = MSGID_SASLDIGESTMD5_CANNOT_GET_CLEAR_PASSWORD;
              String message = getMessage(msgID,
                                          String.valueOf(userEntry.getDN()),
                                          schemeName, de.getErrorMessage());
              logError(ErrorLogCategory.EXTENSIONS,
                       ErrorLogSeverity.SEVERE_WARNING, message, msgID);
              continue;
            }
            byte[] generatedDigest;
            try
            {
@@ -1001,9 +953,8 @@
                   generateResponseDigest(responseUserName, responseAuthzID,
                                          clearPassword.value(), responseRealm,
                                          responseNonce, responseCNonce,
                                          responseNonceCountStr,
                                          responseDigestURI, responseQoP,
                                          responseCharset);
                                    responseNonceCountStr, responseDigestURI,
                                    responseQoP, responseCharset);
            }
            catch (Exception e)
            {
@@ -1016,7 +967,6 @@
              continue;
            }
            if (Arrays.equals(responseDigest, generatedDigest))
            {
              matchFound    = true;
@@ -1024,66 +974,16 @@
              break;
            }
          }
        }
        else
        {
          reversibleFound = true;
          byte[] generatedDigest;
          try
          {
            generatedDigest =
                 generateResponseDigest(responseUserName, responseAuthzID,
                                        v.getValue().value(), responseRealm,
                                        responseNonce, responseCNonce,
                                        responseNonceCountStr,
                                        responseDigestURI, responseQoP,
                                        responseCharset);
          }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "processSASLBind", e);
            logError(ErrorLogCategory.EXTENSIONS,
                     ErrorLogSeverity.SEVERE_WARNING,
                     MSGID_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_DIGEST,
                     stackTraceToSingleLineString(e));
            continue;
          }
          if (Arrays.equals(responseDigest, generatedDigest))
          {
            matchFound    = true;
            passwordBytes = v.getValue().value();
            break;
          }
        }
      }
      if (matchFound)
      {
        break;
      }
    }
    if (! matchFound)
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      if (reversibleFound)
      {
        int    msgID   = MSGID_SASLDIGESTMD5_INVALID_CREDENTIALS;
        String message = getMessage(msgID);
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
      else
      {
        int    msgID   = MSGID_SASLDIGESTMD5_NO_REVERSIBLE_PASSWORDS;
        String message = getMessage(msgID);
        bindOperation.setAuthFailureReason(msgID, message);
        return;
      }
    }
    // FIXME -- Need to do something with the authzid.
opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
@@ -35,7 +35,6 @@
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.IdentityMapper;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
@@ -46,17 +45,13 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
@@ -421,80 +416,13 @@
    }
    // Get the password attribute from the user entry and see if any of the
    // values match the provided clear-text password.
    // FIXME -- Determine the attribute based on the user's password policy.
    AttributeType pwType = DirectoryServer.getAttributeType(ATTR_USER_PASSWORD);
    if (pwType == null)
    // Get the password policy for the user and use it to determine if the
    // provided password was correct.
    try
    {
      pwType = DirectoryServer.getDefaultAttributeType(ATTR_USER_PASSWORD);
    }
    List<Attribute> pwAttr = userEntry.getAttribute(pwType);
    if ((pwAttr == null) || pwAttr.isEmpty())
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLPLAIN_NO_PW_ATTR;
      String message = getMessage(msgID, pwType.getNameOrOID());
      bindOperation.setAuthFailureReason(msgID, message);
      return;
    }
    ASN1OctetString passwordOS = new ASN1OctetString(password);
    boolean matchFound = false;
    for (Attribute a : pwAttr)
    {
      for (AttributeValue v : a.getValues())
      {
        String valueStr = v.getStringValue();
        int closePos;
        if (valueStr.startsWith(STORAGE_SCHEME_PREFIX) &&
            (closePos = valueStr.indexOf(STORAGE_SCHEME_SUFFIX, 2)) > 0)
        {
          String schemeName =
               toLowerCase(valueStr.substring(1, closePos));
          PasswordStorageScheme scheme =
               DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            // We can't do anything with this.  Append a message to the
            // error message to include in the response and continue.
            int    msgID   = MSGID_SASLPLAIN_UNKNOWN_STORAGE_SCHEME;
            String message = getMessage(msgID,
                                        String.valueOf(userEntry.getDN()),
                                        schemeName);
            logError(ErrorLogCategory.EXTENSIONS,
                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
          }
          else
          {
            ASN1OctetString storedPassword =
                 new ASN1OctetString(valueStr.substring(closePos+1));
            if (scheme.passwordMatches(passwordOS, storedPassword))
            {
              matchFound = true;
              break;
            }
          }
        }
        else
        {
          matchFound = passwordOS.equalsIgnoreType(v.getValue());
          if (matchFound)
          {
            break;
          }
        }
      }
      if (matchFound)
      {
        break;
      }
    }
    if (! matchFound)
      PasswordPolicyState pwPolicyState =
           new PasswordPolicyState(userEntry, false, false);
      if (! pwPolicyState.passwordMatches(new ASN1OctetString(password)))
    {
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
@@ -503,6 +431,19 @@
      bindOperation.setAuthFailureReason(msgID, message);
      return;
    }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "processSASLBind", e);
      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
      int    msgID   = MSGID_SASLPLAIN_CANNOT_CHECK_PASSWORD_VALIDITY;
      String message = getMessage(msgID, String.valueOf(userEntry.getDN()),
                                  String.valueOf(e));
      bindOperation.setAuthFailureReason(msgID, message);
      return;
    }
    // FIXME -- Figure out what to do with the authzID if one was provided.
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -3972,6 +3972,37 @@
  /**
   * The message ID for the message that will be used if SASL DIGEST-MD5
   * authentication fails because an error occured while trying to get the
   * clear-text password value(s) from a user's entry.
   */
  public static final int MSGID_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 376;
  /**
   * The message ID for the message that will be used if SASL CRAM-MD5
   * authentication fails because an error occured while trying to get the
   * clear-text password value(s) from a user's entry.
   */
  public static final int MSGID_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 377;
  /**
   * The message ID for the message that will be used if SASL PLAIN
   * authentication fails because an error occurred while trying to get the
   * password policy state.  This takes two arguments, which are the user DN and
   * a message explaining the problem that occurred.
   */
  public static final int MSGID_SASLPLAIN_CANNOT_CHECK_PASSWORD_VALIDITY =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 378;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -4901,6 +4932,10 @@
                    "unknown storage scheme of %s.");
    registerMessage(MSGID_SASLPLAIN_INVALID_PASSWORD,
                    "The provided password is invalid.");
    registerMessage(MSGID_SASLPLAIN_CANNOT_CHECK_PASSWORD_VALIDITY,
                    "An error occurred while attempting to verify the " +
                    "password for user %s during SASL PLAIN authentication:  " +
                    "%s.");
    registerMessage(MSGID_SASLPLAIN_UPDATED_IDENTITY_MAPPER,
                    "Attribute " + ATTR_IDMAPPER_DN +
                    " in configuration entry %s has been updated.  The " +
@@ -5057,6 +5092,10 @@
                    "SASL CRAM-MD5 authentication is not possible for user " +
                    "%s because none of the passwords in the user entry are " +
                    "stored in a reversible form.");
    registerMessage(MSGID_SASLCRAMMD5_CANNOT_GET_REVERSIBLE_PASSWORDS,
                    "An error occurred while attempting to retrieve the " +
                    "clear-text password(s) for user %s in order to perform " +
                    "SASL CRAM-MD5 authentication:  %s.");
    registerMessage(MSGID_SASLCRAMMD5_UPDATED_IDENTITY_MAPPER,
                    "Attribute " + ATTR_IDMAPPER_DN +
                    " in configuration entry %s has been updated.  The " +
@@ -5288,6 +5327,10 @@
                    "SASL DIGEST-MD5 authentication is not possible for user " +
                    "%s because none of the passwords in the user entry are " +
                    "stored in a reversible form.");
    registerMessage(MSGID_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS,
                    "An error occurred while attempting to retrieve the " +
                    "clear-text password(s) for user %s in order to perform " +
                    "SASL DIGEST-MD5 authentication:  %s.");
    registerMessage(MSGID_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_DIGEST,
                    "An error occurred while attempting to generate a " +
                    "server-side digest to compare with the client " +