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

dugan
01.16.2008 d048be119faafbb9d83bb2f0f8995d6070b16d52
These changes implement a new ACI bind rule keyword "ssf".
This keyword allows users to control the level of access based on the
security level of the connection.

issue: https://opends.dev.java.net/issues/show_bug.cgi?id=3497
Web page: https://www.opends.org/wiki/page/SSFBindRule
2 files added
12 files modified
756 ■■■■ changed files
opends/src/messages/messages/access_control.properties 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ConnectionSecurityProvider.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java 91 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/BindRule.java 5 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleKeyword.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/SSF.java 120 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/authorization/dseecompat/TimeOfDay.java 9 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLContext.java 102 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLSecurityProvider.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/TLSConnectionSecurityProvider.java 51 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTestCase.java 36 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/SSFTestCase.java 294 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/access_control.properties
@@ -364,3 +364,12 @@
SEVERE_WARN_ACI_ATTRIBUTE_NOT_INDEXED_96=Backend %s does not have a \
 presence index defined for attribute type %s.  Access control initialization \
 may take a very long time to complete in this backend
 SEVERE_WARN_ACI_SYNTAX_INVALID_SSF_FORMAT_97=The provided Access Control \
 Instruction (ACI) bind rule SSF expression "%s" failed to parse for \
 the following reason: "%s"
 SEVERE_WARN_ACI_SYNTAX_INVALID_SSF_RANGE_98=The provided Access Control \
 Instruction (ACI) bind rule ssf expression value "%s" is not in the \
 valid range. A valid ssf value is in the range of 1 to 1024
 SEVERE_WARN_ACI_SYNTAX_INVALID_TIMEOFDAY_FORMAT_99=The provided Access Control \
 Instruction (ACI) bind rule timeofday expression "%s" failed to parse for \
 the following reason: "%s"
opends/src/server/org/opends/server/api/ConnectionSecurityProvider.java
@@ -223,5 +223,12 @@
   *          already disconnected the client.
   */
  public abstract boolean writeData(ByteBuffer clearData);
  /**
   * Return the Security Strength Factor.
   *
   * @return The SSF.
   */
  public abstract int getSSF();
}
opends/src/server/org/opends/server/authorization/dseecompat/AciContainer.java
@@ -980,4 +980,11 @@
    else
      evalAllAttributes &= ~v;
  }
  /**
   * {@inheritDoc}
   */
  public int getCurrentSSF() {
      return clientConnection.getConnectionSecurityProvider().getSSF();
  }
}
opends/src/server/org/opends/server/authorization/dseecompat/AciEvalContext.java
@@ -87,7 +87,7 @@
    /**
     * Check if the remote client is bound anonymously.
     * @return True if client is bound anonymously.
     * @return {@code true} if client is bound anonymously.
     */
    public boolean isAnonymousUser();
@@ -118,6 +118,7 @@
     * @param authMethod The required authentication method.
     * @param saslMech The required SASL mechanism if the authentication method
     * is SASL.
     *
     * @return An evaluation result indicating whether the client connection
     * has been authenticated using the required authentication method.
     */
@@ -126,14 +127,15 @@
    /**
     * Get the  address of the bound connection.
     * @return The  address of the bound connection.
     * @return The address of the bound connection.
     */
    public InetAddress getRemoteAddress();
    /**
     * Return true if this is an add operation, needed by the userattr
     * Return true if this is an add operation needed by the userattr
     * USERDN parent inheritance level 0 processing.
     * @return True if this is an add operation.
     *
     * @return {@code true} if this is an add operation.
     */
    public boolean isAddOperation();
@@ -143,60 +145,61 @@
     * ClientConnection.isMemberOf() method, which checks authorization
     * DN membership in the specified group.
     * @param group The group to check membership in.
     * @return True if the authorization DN of the operation is a
     * @return {@code true} if the authorization DN of the operation is a
     * member of the specified group.
     */
    public boolean isMemberOf(Group<?> group);
  /**
   * Returns true if the hashtable of ACIs that matched the targattrfilters
   * keyword evaluation is empty.  Used by geteffectiverights evaluation to
   * determine the access value to put in the "write" rights evaluation field.
   * keyword evaluation is empty.  Used in a geteffectiverights control
   * evaluation to determine the access value to put in the "write" rights
   * evaluation field.
   *
   * @return True if there were not any ACIs that matched targattrfilters
   *         keyword evaluation.
   * @return {@code true} if there were not any ACIs that matched
   *         targattrfilters keyword evaluation.
   */
    public boolean isTargAttrFilterMatchAciEmpty();
  /**
   * The context maintains a hashtable of ACIs that matched the targattrfilters
   * keyword evaluation.  The hasTargAttrFiltersMatchAci method returns true if
   * the specified ACI is contained in that hashtable. Used by
   * geteffectiverights evaluation to determine the access value to put in the
   * "write" rights evaluation field.
   * the specified ACI is contained in that hashtable. Used in a
   * geteffectiverights control evaluation to determine the access value to put
   * in the "write" rights evaluation field.
   *
   * @param aci The ACI that to evaluate if it contains a match during
   *            targattrfilters keyword evaluation.
   *
   * @return True if a specified ACI matched targattrfilters evaluation.
   * @return {@code true} if a specified ACI matched targattrfilters evaluation.
   */
    public boolean hasTargAttrFiltersMatchAci(Aci aci);
  /**
   * Return true if an ACI that evaluated to deny or allow has an
   * targattrfilters keyword. Used by geteffectiverights
   * targattrfilters keyword. Used by geteffectiverights control
   * evaluation to determine the access value to put in the "write" rights
   * evaluation field.
   *
   * @param flag  The integer value specifying either a deny or allow, but not
   * both.
   *
   * @return   True if the ACI that evaluated to
   * @return  {@code true} if the ACI has an targetattrfilters keyword.
   */
    public boolean hasTargAttrFiltersMatchOp(int flag);
  /**
   * Returns true if the evaluation context is being used in a
   * geteffectiverights evaluation.
   * Returns {@code true} if the evaluation context is being used in a
   * geteffectiverights control evaluation.
   *
   * @return  True if the evaluation context is being used in a
   * geteffectiverights evaluation.
   * @return  {@code true} if the evaluation context is being used in a
   * geteffectiverights control evaluation.
   */
    public boolean isGetEffectiveRightsEval();
  /**
   * Set the name of the ACI that last matched a targattrfilters rule. Used
   * in geteffectiverights targattrfilters "write" rights evaluation.
   * in geteffectiverights control targattrfilters "write" evaluation.
   *
   * @param name The ACI name string matching the targattrfilters rule.
   */
@@ -205,8 +208,8 @@
  /**
   * Set a flag that specifies that a ACI that evaluated to either deny or
   * allow contains a targattrfilters keyword. Used by geteffectiverights
   * evaluation to determine the access value to put in the "write" rights
   * evaluation field.
   * control evaluation to determine the access value to put in the "write"
   * rights evaluation field.
   *
   * @param flag Either the integer value representing an allow or a deny,
   *             but not both.
@@ -215,7 +218,7 @@
  /**
   * Set the reason the last access evaluation was evaluated the way it
   * was. Used by geteffectiverights evaluation to eventually build the
   * was. Used by geteffectiverights control evaluation to eventually build the
   * summary string.
   *
   * @param reason  The enumeration representing the reason of the last access
@@ -225,7 +228,8 @@
  /**
   * Return the reason the last access evaluation was evaluated the way it
   * was. Used by geteffectiverights evaluation to build the summary string.
   * was. Used by geteffectiverights control evaluation to build the summary
   * string.
   *
   * @return The enumeration representing the reason of the last access
   * evaluation.
@@ -234,7 +238,7 @@
  /**
   * Set the ACI that decided that last access evaluation. Used by
   * geteffectiverights evaluation to the build summary string.
   * geteffectiverights control evaluation to the build summary string.
   *
   * @param aci The ACI that decided the last access evaluation.
   */
@@ -245,13 +249,13 @@
   *
   * @param rights The rights mask to check.
   *
   * @return True if the evaluation context contains a access right set.
   * @return {@code true} if the evaluation context contains a access right set.
   */
    public boolean hasRights(int rights);
  /**
   * Return the name of the ACI that decided the last access evaluation. Used
   * by geteffectiverights evaluation to build the summmary string.
   * by geteffectiverights control evaluation to build the summary string.
   *
   * @return The name of the ACI that decided the last access evaluation.
   */
@@ -259,10 +263,10 @@
  /**
   * Return true if a evaluation context is being used in proxied authorization
   * evaluation.
   * control evaluation.
   *
   * @return  True if evaluation context is being used in proxied authorization
   * evaluation.
   * @return  {@code true} if evaluation context is being used in proxied
   *          authorization control evaluation.
   */
    public boolean isProxiedAuthorization();
@@ -275,15 +279,16 @@
  /**
   * Set the value of the summary string to the specified string.
   * Used in geteffectiverights evaluation to build summary string.
   * Used in get effective rights evaluation to build summary string.
   *
   * @param summary The string to set the summary string to
   */
    public void setEvalSummary(String summary);
  /**
   * Return the access evaluation summary string. Used by the geteffectiverights
   * evaluation when a aclRightsInfo attribute was specified in a search.
   * Return the access evaluation summary string. Used in a geteffectiverights
   * control evaluation when an aclRightsInfo attribute was specified in a
   * search request.
   *
   * @return   The string describing the access evaluation.
   */
@@ -291,7 +296,7 @@
  /**
   * Return a string representation of the current right being evaluated.
   * Used in geteffectiverights evaluation to build summary string.
   * Used in geteffectiverights control evaluation to build summary string.
   *
   * @return  String representation of the current right being evaluated.
   */
@@ -299,9 +304,9 @@
    /**
   * Return the name of the ACI that last matched a targattrfilters rule. Used
   * in geteffectiverights evaluation.
   * in geteffectiverights control evaluation.
   *
   * @return   The name of the ACI that last matched a targattrfilters rule.
   * @return The name of the ACI that last matched a targattrfilters rule.
   */
    public String getTargAttrFiltersAciName();
@@ -315,9 +320,19 @@
   * This method is used to replace the current resource entry with that saved
   * entry and back.
   *
   * @param val Specifies if the saved entry should be used or not. True if it
   * should be used, false if the original resource entry should be used.
   * @param val Specifies if the saved entry should be used or not. {@code true}
   * if it should be used, {@code false} if the original resource entry should
   * be used.
   *
   */
    public void useFullResourceEntry(boolean val);
    /**
     * Return the current SSF (Security Strength Factor) of the underlying
     * connection.
     *
     * @return The current SSF of the connection.
     */
    public int getCurrentSSF();
}
opends/src/server/org/opends/server/authorization/dseecompat/BindRule.java
@@ -534,6 +534,11 @@
                rule = UserAttr.decode(expr, op);
                break;
            }
            case SSF:
            {
                rule = SSF.decode(expr, op);
                break;
            }
            default:  {
                Message message = WARN_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD.get(
                    keyword.toString());
opends/src/server/org/opends/server/authorization/dseecompat/EnumBindRuleKeyword.java
@@ -77,7 +77,12 @@
     * The enumeration type when the bind rule has specified keyword of
     * authmethod.
     */
    AUTHMETHOD ("authmethod");
    AUTHMETHOD ("authmethod"),
    /**
     * The enumeration type when the bind rule has specified keyword of
     * ssf.
     */
    SSF("ssf");
    /*
     * The keyword name.
opends/src/server/org/opends/server/authorization/dseecompat/SSF.java
New file
@@ -0,0 +1,120 @@
/*
 * 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
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.authorization.dseecompat;
import org.opends.messages.Message;
import static org.opends.messages.AccessControlMessages.*;
/**
 * The class represents the ssf keyword in a bind rule.SSF stands for
 * security strength factor.
 *
 */
public class SSF implements KeywordBindRule {
    /*
     *  Enumeration representing the bind rule operation type.
     */
    private EnumBindRuleType type=null;
    private static final int MAX_KEY_BITS=1024;
    private int ssf;
    private SSF(int ssf, EnumBindRuleType type) {
        this.ssf = ssf;
        this.type = type;
    }
    /**
     * Create SSF instance using the specified expression string and bind rule
     * type enumeration.
     * @param expr The expression string.
     * @param type The bind rule type enumeration.
     * @return A SSF instance.
     * @throws AciException If the SSF instance cannot be created.
     */
    static SSF
    decode(String expr, EnumBindRuleType type) throws AciException  {
        int valueAsInt = 0;
        try {
            valueAsInt = Integer.parseInt(expr);
        } catch (NumberFormatException nfe) {
            Message message =
                 WARN_ACI_SYNTAX_INVALID_SSF_FORMAT.get(expr, nfe.getMessage());
            throw new AciException(message);
        }
        if ((valueAsInt <= 0) || (valueAsInt > MAX_KEY_BITS)) {
            Message message = WARN_ACI_SYNTAX_INVALID_SSF_RANGE.get(expr);
            throw new AciException(message);
        }
        return new SSF(valueAsInt, type);
    }
    /**
     * Evaluate the specified evaluation context.
     * @param evalCtx The evaluation context to evaluate.
     *
     * @return An evaluation result enumeration containing the result of the
     *         context evaluation.
     */
    public EnumEvalResult evaluate(AciEvalContext evalCtx) {
        EnumEvalResult matched=EnumEvalResult.FALSE;
        int currentSSF = evalCtx.getCurrentSSF();
        switch (type) {
        case EQUAL_BINDRULE_TYPE:
            if (currentSSF == ssf)
                matched=EnumEvalResult.TRUE;
            break;
        case NOT_EQUAL_BINDRULE_TYPE:
            if (currentSSF != ssf)
                matched=EnumEvalResult.TRUE;
            break;
        case LESS_OR_EQUAL_BINDRULE_TYPE:
            if (currentSSF <= ssf)
                matched=EnumEvalResult.TRUE;
            break;
        case LESS_BINDRULE_TYPE:
            if (currentSSF < ssf)
                matched=EnumEvalResult.TRUE;
            break;
        case GREATER_OR_EQUAL_BINDRULE_TYPE:
            if (currentSSF >= ssf)
                matched=EnumEvalResult.TRUE;
            break;
        case GREATER_BINDRULE_TYPE:
            if (currentSSF > ssf)
                matched=EnumEvalResult.TRUE;
        }
        return matched.getRet(type, false);
    }
}
opends/src/server/org/opends/server/authorization/dseecompat/TimeOfDay.java
@@ -71,12 +71,19 @@
     */
    public static TimeOfDay decode(String expr,  EnumBindRuleType type)
    throws AciException  {
        int valueAsInt = 0;
        if (!Pattern.matches(timeofdayRegex, expr))
        {
            Message message = WARN_ACI_SYNTAX_INVALID_TIMEOFDAY.get(expr);
            throw new AciException(message);
         }
        int valueAsInt = Integer.parseInt(expr);
        try {
            valueAsInt = Integer.parseInt(expr);
        } catch (NumberFormatException nfe) {
          Message message =
           WARN_ACI_SYNTAX_INVALID_TIMEOFDAY_FORMAT.get(expr, nfe.getMessage());
            throw new AciException(message);
        }
        if ((valueAsInt < 0) || (valueAsInt > 2359))
        {
            Message message = WARN_ACI_SYNTAX_INVALID_TIMEOFDAY_RANGE.get(expr);
opends/src/server/org/opends/server/extensions/NullConnectionSecurityProvider.java
@@ -498,5 +498,13 @@
      }
    }
  }
  /**
   * Always returns 0, there is no cipher used.
   * @return Returns 0 always.
   */
  public int getSSF() {
      return 0;
  }
}
opends/src/server/org/opends/server/extensions/SASLContext.java
@@ -86,7 +86,7 @@
    private String serverFQDN;
    //The SASL mechanism name.
    private final String mech;
    private final String mechanism;
    //The authorization entry used in the authentication.
    private Entry authEntry=null;
@@ -116,19 +116,19 @@
     * @param saslProps The properties to use in creating the SASL server.
     * @param serverFQDN The fully qualified domain name to use in creating the
     *                   SASL server.
     * @param mech The SASL mechanism name.
     * @param mechanism The SASL mechanism name.
     * @param identityMapper The identity mapper to use in mapping identities.
     *
     * @throws SaslException If the SASL server can not be instantiated.
     */
    private SASLContext(HashMap<String, String>saslProps, String serverFQDN,
                          String mech, IdentityMapper<?> identityMapper)
                          String mechanism, IdentityMapper<?> identityMapper)
                          throws SaslException {
        this.identityMapper = identityMapper;
        this.mech = mech;
        this.mechanism = mechanism;
        this.saslProps = saslProps;
        this.serverFQDN = serverFQDN;
        if(mech.equals(SASL_MECHANISM_DIGEST_MD5)) {
        if(mechanism.equals(SASL_MECHANISM_DIGEST_MD5)) {
            initSASLServer();
        }
    }
@@ -141,7 +141,7 @@
     * @param saslProps The properties to use in creating the SASL server.
     * @param serverFQDN The fully qualified domain name to use in creating the
     *                   SASL server.
     * @param mech The SASL mechanism name.
     * @param mechanism The SASL mechanism name.
     * @param identityMapper The identity mapper to use in mapping identities.
     * @return A fully instantiated SASL context to use in processing a SASL
     *         bind for the GSSAPI or DIGEST-MD5 mechanisms.
@@ -150,21 +150,19 @@
     */
    public static
    SASLContext createSASLContext(HashMap<String,String>saslProps,
                        String serverFQDN, String mech,
                        String serverFQDN, String mechanism,
                        IdentityMapper<?> identityMapper) throws SaslException {
        return (new SASLContext(saslProps,serverFQDN, mech, identityMapper));
      return (new SASLContext(saslProps,serverFQDN, mechanism, identityMapper));
    }
    /**
     * Initialize the SASL server using parameters specified in the
     * constructor.
     *
     * @throws SaslException If the SASL server can not be instantiated.
     */
    private void initSASLServer() throws SaslException {
        this.saslServer = Sasl.createSaslServer(mech, SASL_DEFAULT_PROTOCOL,
                                                serverFQDN, saslProps, this);
       this.saslServer = Sasl.createSaslServer(mechanism, SASL_DEFAULT_PROTOCOL,
                                               serverFQDN, saslProps, this);
    }
@@ -189,7 +187,7 @@
    /**
     * Unwrap the specified byte array using the provided offset and length
     * values. Used only when the SASL server has negotiated
     * confidentiality/integrity  processing.
     * confidentiality or integrity processing.
     *
     * @param bytes The byte array to unwrap.
     * @param offset The offset in the array.
@@ -230,15 +228,38 @@
          return Integer.parseInt(sizeStr);
    }
    /**
     * Return the Security Strength Factor of the cipher if the QOP property
     * is confidentiality, or, 1 if it is integrity.
     *
     * @return The SSF of the cipher used during confidentiality or
     *         integrity processing.
     */
    int getSSF() {
        int ssf = 0;
        String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
        if(qop.equalsIgnoreCase(integrity)) {
            ssf = 1;
        } else {
            String negStrength =
                (String) saslServer.getNegotiatedProperty(Sasl.STRENGTH);
            if(negStrength.equalsIgnoreCase("low"))
                ssf = 40;
            else if (negStrength.equalsIgnoreCase("medium"))
                ssf = 56;
            else
                ssf = 128;
        }
        return ssf;
    }
    /**
     * Return true if the bind has been completed. If the context is supporting
     * confidentiality/integrity, the security provider will need to check
     * if the context has completed its handshakes with the client and is
     * ready to process confidentiality/integrity messages.
     * Return {@code true} if the bind has been completed. If the context is
     * supporting confidentiality or integrity, the security provider will need
     * to check if the context has completed the handshake with the client
     * and is ready to process confidentiality or integrity messages.
     *
     * @return {@code true} if the handshaking is complete,
     *         {@code false} if further handshaking is needed.
     * @return {@code true} if the handshaking is complete.
     */
    boolean isBindComplete() {
          return saslServer.isComplete();
@@ -247,10 +268,10 @@
    /**
     * Return true if the SASL server has negotiated with the client to support
     * confidentiality/integrity.
     * confidentiality or integrity.
     *
     * @return {@code true} if the context support
     *         confidentiality/integrity, or, {@code false} otherwise.
     * @return {@code true} if the context supports confidentiality or
     *         integrity.
     */
    private boolean isConfidentialIntegrity() {
      boolean ret = false;
@@ -312,7 +333,7 @@
     *
     * @param authInfo The authentication information to use in the check.
     * @return {@code true} if the authentication information has
     *         PROXIED_AUTH privileges, {@code false} otherwise.
     *         PROXIED_AUTH privileges.
     */
    private boolean
    hasPrivilege(AuthenticationInfo authInfo) {
@@ -334,7 +355,7 @@
     *
     * @param authInfo The authentication information to check access on.
     * @return {@code true} if the authentication information has
     *         proxy access, {@code false} otherwise.
     *         proxy access.
     */
    private boolean
    hasPermission(AuthenticationInfo authInfo) {
@@ -388,7 +409,7 @@
                authorizeCallback((AuthorizeCallback) callback);
            } else {
                Message message =
                    INFO_SASL_UNSUPPORTED_CALLBACK.get(mech,
                    INFO_SASL_UNSUPPORTED_CALLBACK.get(mechanism,
                                                      String.valueOf(callback));
                throw new UnsupportedCallbackException(callback,
                        message.toString());
@@ -586,7 +607,7 @@
          clearPasswords = pwPolicyState.getClearPasswords();
          if ((clearPasswords == null) || clearPasswords.isEmpty()) {
              setCallbackMsg(
                 ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mech,
                 ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mechanism,
                                            String.valueOf(authEntry.getDN())));
            return;
          }
@@ -596,7 +617,7 @@
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            setCallbackMsg(ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
                    String.valueOf(authEntry.getDN()),mech,
                    String.valueOf(authEntry.getDN()),mechanism,
                    String.valueOf(e)));
          return;
        }
@@ -625,13 +646,13 @@
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                }
                 setCallbackMsg(ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN.get(
                                      mech,
                                      mechanism,
                                      userName, e.getMessageObject()));
                return;
            }
            if (userDN.isNullDN()) {
              setCallbackMsg(ERR_SASL_USERNAME_IS_NULL_DN.get(
                                                      mech));
                                                      mechanism));
              return;
            }
            DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
@@ -646,7 +667,7 @@
            if (lowerUserName.startsWith("u:")) {
                if (lowerUserName.equals("u:")) {
                    setCallbackMsg(ERR_SASL_ZERO_LENGTH_USERNAME.get(
                            mech,mech));
                            mechanism,mechanism));
                    return;
                }
                entryID = userName.substring(2);
@@ -707,8 +728,7 @@
    * The method performs all GSSAPI processing. It is run as the context of
    * the login context performed by the GSSAPI mechanism handler. See comments
    * for processing overview.
    * @return {@code true} if the authentication processing was successful,
    *         or, {@code false} otherwise.
    * @return {@code true} if the authentication processing was successful.
    */
   public Boolean run() {
       ClientConnection clientConn = bindOp.getClientConnection();
@@ -751,7 +771,7 @@
               bindOp.setSASLAuthUserEntry(authEntry);
               AuthenticationInfo authInfo =
                    new AuthenticationInfo(authEntry, authzEntry,
                                    mech,
                                    mechanism,
                                   DirectoryServer.isRootDN(authEntry.getDN()));
               bindOp.setAuthenticationInfo(authInfo);
               //If confidentiality/integrity has been negotiated then
@@ -760,7 +780,7 @@
               //negotiated, dispose of the SASL server.
               if(isConfidentialIntegrity()) {
                   SASLSecurityProvider secProvider =
                       new SASLSecurityProvider(clientConn, mech, this);
                       new SASLSecurityProvider(clientConn, mechanism, this);
                   LDAPClientConnection ldapConn =
                       (LDAPClientConnection) clientConn;
                       ldapConn.setSASLConnectionSecurityProvider(secProvider);
@@ -778,7 +798,7 @@
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
           handleError(msg);
           return false;
       }
@@ -806,7 +826,7 @@
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
           handleError(msg);
       }
   }
@@ -832,7 +852,7 @@
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech,getExceptionMessage(e));
               ERR_SASL_PROTOCOL_ERROR.get(mechanism,getExceptionMessage(e));
           handleError(msg);
       }
   }
@@ -850,7 +870,7 @@
       if ((clientCredentials == null) ||
               (clientCredentials.value().length == 0)) {
           Message msg =
               ERR_SASL_NO_CREDENTIALS.get(mech, mech);
               ERR_SASL_NO_CREDENTIALS.get(mechanism, mechanism);
           handleError(msg);
           return;
       }
@@ -866,7 +886,7 @@
           bindOp.setSASLAuthUserEntry(authEntry);
           AuthenticationInfo authInfo =
                new AuthenticationInfo(authEntry, authzEntry,
                                       mech,
                                       mechanism,
                                  DirectoryServer.isRootDN(authEntry.getDN()));
           bindOp.setAuthenticationInfo(authInfo);
           //If confidentiality/integrity has been negotiated, then create a
@@ -874,7 +894,7 @@
           //use in later processing.
           if(isConfidentialIntegrity()) {
               SASLSecurityProvider secProvider =
                   new SASLSecurityProvider(clientConn, mech, this);
                   new SASLSecurityProvider(clientConn, mechanism, this);
               LDAPClientConnection ldapConn =
                   (LDAPClientConnection) clientConn;
               ldapConn.setSASLConnectionSecurityProvider(secProvider);
@@ -887,7 +907,7 @@
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
           }
           Message msg =
               ERR_SASL_PROTOCOL_ERROR.get(mech, getExceptionMessage(e));
               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
           handleError(msg);
       }
   }
opends/src/server/org/opends/server/extensions/SASLSecurityProvider.java
@@ -454,4 +454,14 @@
    public boolean isActive() {
        return saslContext.isBindComplete();
    }
    /**
     * Return the cipher Security Strength Function of the cipher used in the
     * SSAL context.
     *
     * @return The cipher SSF of the cipher used in the SASL context.
     */
    public int getSSF() {
        return saslContext.getSSF();
    }
}
opends/src/server/org/opends/server/extensions/TLSConnectionSecurityProvider.java
@@ -34,6 +34,8 @@
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.security.cert.Certificate;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
@@ -76,16 +78,12 @@
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The SSL context name that should be used for this TLS connection security
   * provider.
   */
  private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
  // The buffer that will be used when reading clear-text data.
  private ByteBuffer clearInBuffer;
@@ -127,6 +125,27 @@
  // The set of protocols to allow.
  private String[] enabledProtocols;
  //Map of cipher phrases to effective key size (bits). Taken from the
  //following RFCs: 5289, 4346, 3268,4132 and 4162.
  private static final Map<String, Integer> cipherMap;
  static {
      cipherMap = new LinkedHashMap<String, Integer>();
      cipherMap.put("_WITH_AES_256_CBC_", new Integer(256));
      cipherMap.put("_WITH_CAMELLIA_256_CBC_", new Integer(256));
      cipherMap.put("_WITH_AES_256_GCM_", new Integer(256));
      cipherMap.put("_WITH_3DES_EDE_CBC_", new Integer(168));
      cipherMap.put("_WITH_AES_128_GCM_", new Integer(128));
      cipherMap.put("_WITH_SEED_CBC_", new Integer(128));
      cipherMap.put("_WITH_CAMELLIA_128_CBC_", new Integer(128));
      cipherMap.put("_WITH_AES_128_CBC_", new Integer(128));
      cipherMap.put("_WITH_IDEA_CBC_", new Integer(128));
      cipherMap.put("_WITH_DES_CBC_", new Integer(56));
      cipherMap.put("_WITH_RC2_CBC_40_", new Integer(40));
      cipherMap.put("_WITH_RC4_40_", new Integer(40));
      cipherMap.put("_WITH_DES40_CBC_", new Integer(40));
      cipherMap.put("_WITH_NULL_", new Integer(0));
  };
  /**
@@ -175,7 +194,7 @@
    // Create an SSL session based on the configured key and trust stores in the
    // Directory Server.
    KeyManagerProvider keyManagerProvider =
    KeyManagerProvider<?> keyManagerProvider =
         DirectoryServer.getKeyManagerProvider(
              clientConnection.getKeyManagerProviderDN());
    if (keyManagerProvider == null)
@@ -183,7 +202,7 @@
      keyManagerProvider = new NullKeyManagerProvider();
    }
    TrustManagerProvider trustManagerProvider =
    TrustManagerProvider<?> trustManagerProvider =
         DirectoryServer.getTrustManagerProvider(
              clientConnection.getTrustManagerProviderDN());
    if (trustManagerProvider == null)
@@ -1039,5 +1058,25 @@
      return null;
    }
  }
  /**
   * Return the Security Strength FActor of the cipher used in the current
   * TLS session.
   *
   * @return The cipher SSF used in the current TLS session.
   */
  public int getSSF() {
      int cipherKeySSF = 0;
      String cipherString = sslEngine.getSession().getCipherSuite();
      for(Map.Entry<String, Integer> mapEntry : cipherMap.entrySet()) {
          if(cipherString.indexOf(mapEntry.getKey()) >= 0) {
              cipherKeySSF = mapEntry.getValue().intValue();
              break;
          }
      }
      return cipherKeySSF;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/AciTestCase.java
@@ -29,6 +29,8 @@
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.LDAPModify;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.tools.LDAPDelete;
@@ -38,10 +40,24 @@
import org.testng.Assert;
import java.io.*;
import java.util.Hashtable;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
@Test(groups = {"precommit", "dseecompat"}, sequential = true)
public abstract class  AciTestCase extends DirectoryServerTestCase {
@@ -335,6 +351,25 @@
    ldapModify(argList.toArray(args), rc);
  }
  protected void JNDIModify(Hashtable<?, ?> env, String name,
                            String attr, String val, int rc) {
      try {
          DirContext ctx = new InitialDirContext(env);
          ModificationItem[] mods = new ModificationItem[1 ];
          mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
                  new BasicAttribute(attr, val));
          ctx.modifyAttributes(name, mods);
          ctx.close();
      } catch (NoPermissionException npe) {
          Assert.assertEquals(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,  rc,
                              "Returned error: " + npe.getMessage());
          return;
      } catch (NamingException ex) {
          Assert.assertEquals(-1,  rc, "Returned error: " + ex.getMessage());
      }
      Assert.assertEquals(LDAPResultCode.SUCCESS, rc, "");
  }
  private void ldapModify(String[] args, int rc) {
    oStream.reset();
    int retVal =LDAPModify.mainModify(args, false, oStream, oStream);
@@ -616,4 +651,5 @@
    }
    return attrMap;
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/authorization/dseecompat/SSFTestCase.java
New file
@@ -0,0 +1,294 @@
/*
 * 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
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
/**
 * Unit test to test the ssf ACI bind rule keyword.
 */
package org.opends.server.authorization.dseecompat;
import java.util.Hashtable;
import javax.naming.Context;
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.testng.annotations.*;
import static org.opends.server.config.ConfigConstants.*;
public class SSFTestCase extends AciTestCase {
    private static final String newUser="uid=new.user,ou=People,o=test";
    private static final String descriptionStr = "description of user.1";
    private static final String factory = "com.sun.jndi.ldap.LdapCtxFactory";
    private static final String pwdPolicy = "Aci Temp Policy";
    private static final String pwdPolicyDN =
                       "cn=" + pwdPolicy + ",cn=Password Policies,cn=config";
    private static final String[] newEntry = new String[] {
        "objectClass: top",
        "objectClass: person",
        "objectClass: organizationalPerson",
        "objectClass: inetOrgPerson",
        "uid: new.user",
        "givenName: New",
        "sn: User",
        "cn: New User",
        "mail: new.user@test.com",
        "ds-pwp-password-policy-dn:" + pwdPolicyDN
      };
    private static final
    String integrityACI = "(targetattr=\"" + "*" + "\")" +
            "(version 3.0; acl \"integrity aci\";" +
            "allow(all) (userdn=\"ldap:///self\" and ssf = \"1\");)";
    private static final
    String greaterIntegrityACI = "(targetattr=\"" + "*" + "\")" +
            "(version 3.0; acl \"greater integrity aci\";" +
            "allow(all) (userdn=\"ldap:///self\" and ssf > \"1\");)";
    private static final
    String medStrengthACI = "(targetattr=\"" + "*" + "\")" +
            "(version 3.0; acl \"56 bit key aci\";" +
            "allow(all) (userdn=\"ldap:///self\" and ssf = \"56\");)";
    private static final
    String hiStrengthACI = "(targetattr=\"" + "*" + "\")" +
            "(version 3.0; acl \"128 bit key aci\";" +
            "allow(all) (userdn=\"ldap:///self\" and ssf = \"128\");)";
    private static final
    String hiPlusStrengthACI = "(targetattr=\"" + "*" + "\")" +
            "(version 3.0; acl \"greater 128 bit aci\";" +
            "allow(all) (userdn=\"ldap:///self\" and ssf > \"128\");)";
    @BeforeClass
    public void setupClass() throws Exception {
      TestCaseUtils.startServer();
      TestCaseUtils.dsconfig(
              "create-password-policy",
              "--policy-name", pwdPolicy,
              "--set", "password-attribute:userPassword",
              "--set", "default-password-storage-scheme: Clear"
              );
      TestCaseUtils.dsconfig(
              "set-sasl-mechanism-handler-prop",
              "--handler-name", "DIGEST-MD5",
              "--set", "server-fqdn:localhost");
      deleteAttrFromAdminEntry(ACCESS_HANDLER_DN, ATTR_AUTHZ_GLOBAL_ACI);
      String aciLdif=makeAddLDIF(ATTR_AUTHZ_GLOBAL_ACI, ACCESS_HANDLER_DN,
                    G_SCHEMA, G_DSE, G_USER_OPS, G_CONTROL, E_EXTEND_OP);
      LDIFAdminModify(aciLdif, DIR_MGR_DN, PWD);
      addEntries("o=test");
      String newUserLDIF=makeAddEntryLDIF(newUser, newEntry);
      LDIFAdd(newUserLDIF, DIR_MGR_DN, PWD, null, LDAPResultCode.SUCCESS);
      String pwdILDIF =
          makeAddLDIF("userpassword", newUser, "password");
      LDIFModify(pwdILDIF, DIR_MGR_DN, PWD);
    }
    @AfterClass(alwaysRun = true)
    public void tearDown() throws Exception {
         String aciLdif=makeAddLDIF(ATTR_AUTHZ_GLOBAL_ACI, ACCESS_HANDLER_DN,
                G_READ_ACI, G_SELF_MOD, G_SCHEMA, G_DSE, G_USER_OPS, G_CONTROL,
                E_EXTEND_OP);
         LDIFAdminModify(aciLdif, DIR_MGR_DN, PWD);
         TestCaseUtils.dsconfig(
                 "delete-password-policy",
                 "--policy-name", pwdPolicy
                 );
         TestCaseUtils.dsconfig(
                 "set-sasl-mechanism-handler-prop",
                 "--handler-name", "DIGEST-MD5",
                 "--reset", "server-fqdn",
                 "--reset", "quality-of-protection");
     }
    //Valid ssf statements. Not the complete ACI.
    @DataProvider(name = "validStatements")
    public Object[][] valids() {
      return new Object[][] {
              {"1"},
              {"40"},
              {"56"},
              {"128"},
              {"256"},
              {"129"},
      };
    }
     //Invalid ssf statements. Not the complete ACI.
    @DataProvider(name = "invalidStatements")
    public Object[][] invalids() {
      return new Object[][] {
              {"-1"},
              {"0"},
              {"not valid"},
              {"1025"},
              {"10000"},
      };
    }
    private EnumBindRuleType bindRuleType = EnumBindRuleType.EQUAL_BINDRULE_TYPE;
    /**
     * Test valid ssf statements.
     *
     * @param statement The ssf statement to attempt to decode.
     * @throws AciException  If an unexpected result happens.
     */
    @Test(dataProvider = "validStatements")
    public void testValidStatements(String statement) throws AciException {
        SSF.decode(statement, bindRuleType);
    }
    /**
     * Test invalid ssf statements.
     *
     * @param statement The ssf statement to attempt to decode.
     * @throws Exception  If an unexpected result happens.
     */
    @Test(expectedExceptions= AciException.class,
          dataProvider="invalidStatements")
    public void testInvalidStatements(String statement)  throws Exception {
      try {
        SSF.decode(statement, bindRuleType);
      } catch (AciException e) {
        throw e;
      } catch (Exception e) {
        System.out.println(
                "Invalid ssf  <" + statement +
                "> threw wrong exception type.");
        throw e;
      }
      throw new RuntimeException(
              "Invalid ssf <" + statement +
              "> did not throw an exception.");
    }
    /**
     * Test ssf bind rule using ssf value for integrity.
     *
     * @throws Exception If a test doesn't pass.
     */
    @Test()
    public void testIntegrity() throws Exception {
        //set QOP to integrity.
        TestCaseUtils.dsconfig(
                "set-sasl-mechanism-handler-prop",
                "--handler-name", "DIGEST-MD5",
                "--set", "quality-of-protection:" + "integrity");
        //Configure JNDI props.
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
        int port = TestCaseUtils.getServerLdapPort();
        String url = "ldap://localhost:" + Integer.valueOf(port);
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
        String principal = "dn:" + newUser;
        env.put(Context.SECURITY_PRINCIPAL, principal);
        env.put(Context.SECURITY_CREDENTIALS, "password");
        //Select integrity QOP.
        env.put("javax.security.sasl.qop", "auth-int");
        //Add ACI with ssf > 1, should fail.
        String addACILDIF = makeAddLDIF("aci", newUser, greaterIntegrityACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        JNDIModify(env, newUser, "description", descriptionStr,
                  LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        deleteAttrFromEntry(newUser, "aci");
        //Add ACI with ssf = 1.
         addACILDIF = makeAddLDIF("aci", newUser, integrityACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        //Should succeed.
        JNDIModify(env, newUser, "description", descriptionStr,
                   LDAPResultCode.SUCCESS);
        deleteAttrFromEntry(newUser, "aci");
        deleteAttrFromEntry(newUser, "description");
    }
    /**
     * Test confidentiality settings using DIGEST-MD5.
     * @throws Exception
     */
    @Test()
    public void testConfidentiality() throws Exception {
        //set QOP to integrity.
        TestCaseUtils.dsconfig(
                "set-sasl-mechanism-handler-prop",
                "--handler-name", "DIGEST-MD5",
                "--set", "quality-of-protection:" + "confidentiality");
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
        int port = TestCaseUtils.getServerLdapPort();
        String url = "ldap://localhost:" + Integer.valueOf(port);
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
        String principal = "dn:" + newUser;
        env.put(Context.SECURITY_PRINCIPAL, principal);
        env.put(Context.SECURITY_CREDENTIALS, "password");
        //Select integrity QOP.
        env.put("javax.security.sasl.qop", "auth-conf");
        //Add ACI with ssf > 1, should succeed.
        String addACILDIF = makeAddLDIF("aci", newUser, greaterIntegrityACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        JNDIModify(env, newUser, "description", descriptionStr,
                  LDAPResultCode.SUCCESS);
        deleteAttrFromEntry(newUser, "aci");
        deleteAttrFromEntry(newUser, "description");
        //Test medium strength.
        addACILDIF = makeAddLDIF("aci", newUser, medStrengthACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        env.put("javax.security.sasl.strength", "medium");
        JNDIModify(env, newUser, "description", descriptionStr,
                LDAPResultCode.SUCCESS);
        deleteAttrFromEntry(newUser, "aci");
        deleteAttrFromEntry(newUser, "description");
        //Test high strength.
        addACILDIF = makeAddLDIF("aci", newUser, hiStrengthACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        env.put("javax.security.sasl.strength", "high");
        JNDIModify(env, newUser, "description", descriptionStr,
                LDAPResultCode.SUCCESS);
        deleteAttrFromEntry(newUser, "aci");
        deleteAttrFromEntry(newUser, "description");
        //Fail DIGEST-MD5 only goes to 128.
        addACILDIF = makeAddLDIF("aci", newUser, hiPlusStrengthACI);
        LDIFModify(addACILDIF, DIR_MGR_DN, PWD);
        env.put("javax.security.sasl.strength", "high");
        JNDIModify(env, newUser, "description", descriptionStr,
                   LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        deleteAttrFromEntry(newUser, "aci");
        deleteAttrFromEntry(newUser, "description");
    }
}