package com.hwlcn.ldap.ldap.sdk; import java.util.logging.Level; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.util.DebugType; import com.hwlcn.core.annotation.InternalUseOnly; import com.hwlcn.core.annotation.NotMutable; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*; import static com.hwlcn.ldap.util.Debug.*; import static com.hwlcn.ldap.util.StaticUtils.*; import static com.hwlcn.ldap.util.Validator.*; /** * This class provides a SASL CRAM-MD5 bind request implementation as described * in draft-ietf-sasl-crammd5. The CRAM-MD5 mechanism can be used to * authenticate over an insecure channel without exposing the credentials * (although it requires that the server have access to the clear-text * password). It is similar to DIGEST-MD5, but does not provide as many * options, and provides slightly weaker protection because the client does not * contribute any of the random data used during bind processing. * <BR><BR> * Elements included in a CRAM-MD5 bind request include: * <UL> * <LI>Authentication ID -- A string which identifies the user that is * attempting to authenticate. It should be an "authzId" value as * described in section 5.2.1.8 of * <A HREF="http://www.ietf.org/rfc/rfc4513.txt">RFC 4513</A>. That is, * it should be either "dn:" followed by the distinguished name of the * target user, or "u:" followed by the username. If the "u:" form is * used, then the mechanism used to resolve the provided username to an * entry may vary from server to server.</LI> * <LI>Password -- The clear-text password for the target user.</LI> * </UL> * <H2>Example</H2> * The following example demonstrates the process for performing a CRAM-MD5 * bind against a directory server with a username of "john.doe" and a password * of "password": * <PRE> * CRAMMD5BindRequest bindRequest = * new CRAMMD5BindRequest("u:john.doe", "password"); * try * { * BindResult bindResult = connection.bind(bindRequest); * // If we get here, then the bind was successful. * } * catch (LDAPException le) * { * // The bind failed for some reason. * } * </PRE> */ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class CRAMMD5BindRequest extends SASLBindRequest implements CallbackHandler { public static final String CRAMMD5_MECHANISM_NAME = "CRAM-MD5"; private static final long serialVersionUID = -4556570436768136483L; private final ASN1OctetString password; private int messageID = -1; private final String authenticationID; public CRAMMD5BindRequest(final String authenticationID, final String password) { this(authenticationID, new ASN1OctetString(password), NO_CONTROLS); ensureNotNull(password); } public CRAMMD5BindRequest(final String authenticationID, final byte[] password) { this(authenticationID, new ASN1OctetString(password), NO_CONTROLS); ensureNotNull(password); } public CRAMMD5BindRequest(final String authenticationID, final ASN1OctetString password) { this(authenticationID, password, NO_CONTROLS); } public CRAMMD5BindRequest(final String authenticationID, final String password, final Control... controls) { this(authenticationID, new ASN1OctetString(password), controls); ensureNotNull(password); } public CRAMMD5BindRequest(final String authenticationID, final byte[] password, final Control... controls) { this(authenticationID, new ASN1OctetString(password), controls); ensureNotNull(password); } public CRAMMD5BindRequest(final String authenticationID, final ASN1OctetString password, final Control... controls) { super(controls); ensureNotNull(authenticationID, password); this.authenticationID = authenticationID; this.password = password; } @Override() public String getSASLMechanismName() { return CRAMMD5_MECHANISM_NAME; } public String getAuthenticationID() { return authenticationID; } public String getPasswordString() { return password.stringValue(); } public byte[] getPasswordBytes() { return password.getValue(); } @Override() protected BindResult process(final LDAPConnection connection, final int depth) throws LDAPException { final SaslClient saslClient; final String[] mechanisms = { CRAMMD5_MECHANISM_NAME }; try { saslClient = Sasl.createSaslClient(mechanisms, null, "ldap", connection.getConnectedAddress(), null, this); } catch (Exception e) { debugException(e); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_CRAMMD5_CANNOT_CREATE_SASL_CLIENT.get(getExceptionMessage(e)), e); } final SASLHelper helper = new SASLHelper(this, connection, CRAMMD5_MECHANISM_NAME, saslClient, getControls(), getResponseTimeoutMillis(connection)); try { return helper.processSASLBind(); } finally { messageID = helper.getMessageID(); } } @Override() public CRAMMD5BindRequest getRebindRequest(final String host, final int port) { return new CRAMMD5BindRequest(authenticationID, password, getControls()); } @InternalUseOnly() public void handle(final Callback[] callbacks) { for (final Callback callback : callbacks) { if (callback instanceof NameCallback) { ((NameCallback) callback).setName(authenticationID); } else if (callback instanceof PasswordCallback) { ((PasswordCallback) callback).setPassword( password.stringValue().toCharArray()); } else { // This is an unexpected callback. if (debugEnabled(DebugType.LDAP)) { debug(Level.WARNING, DebugType.LDAP, "Unexpected CRAM-MD5 SASL callback of type " + callback.getClass().getName()); } } } } @Override() public int getLastMessageID() { return messageID; } @Override() public CRAMMD5BindRequest duplicate() { return duplicate(getControls()); } @Override() public CRAMMD5BindRequest duplicate(final Control[] controls) { final CRAMMD5BindRequest bindRequest = new CRAMMD5BindRequest(authenticationID, password, controls); bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); return bindRequest; } @Override() public void toString(final StringBuilder buffer) { buffer.append("CRAMMD5BindRequest(authenticationID='"); buffer.append(authenticationID); buffer.append('\''); final Control[] controls = getControls(); if (controls.length > 0) { buffer.append(", controls={"); for (int i=0; i < controls.length; i++) { if (i > 0) { buffer.append(", "); } buffer.append(controls[i]); } buffer.append('}'); } buffer.append(')'); } }