package com.hwlcn.ldap.ldap.sdk; import java.util.HashMap; 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.RealmCallback; import javax.security.sasl.RealmChoiceCallback; 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 DIGEST-MD5 bind request implementation as * described in <A HREF="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</A>. The * DIGEST-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 CRAM-MD5, but provides * better security by combining random data from both the client and the server, * and allows for greater security and functionality, including the ability to * specify an alternate authorization identity and the ability to use data * integrity or confidentiality protection. At present, however, this * implementation may only be used for authentication, as it does not yet * support integrity or confidentiality. * <BR><BR> * Elements included in a DIGEST-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>Authorization ID -- An optional string which specifies an alternate * authorization identity that should be used for subsequent operations * requested on the connection. Like the authentication ID, the * authorization ID should use the "authzId" syntax.</LI> * <LI>Realm -- An optional string which specifies the realm into which the * user should authenticate.</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 DIGEST-MD5 * bind against a directory server with a username of "john.doe" and a password * of "password": * <PRE> * DIGESTMD5BindRequest bindRequest = * new DIGESTMD5BindRequest("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 DIGESTMD5BindRequest extends SASLBindRequest implements CallbackHandler { public static final String DIGESTMD5_MECHANISM_NAME = "DIGEST-MD5"; private static final long serialVersionUID = 867592367640540593L; private final ASN1OctetString password; private int messageID = -1; private final String authenticationID; private final String authorizationID; private final String realm; public DIGESTMD5BindRequest(final String authenticationID, final String password) { this(authenticationID, null, new ASN1OctetString(password), null, NO_CONTROLS); ensureNotNull(password); } public DIGESTMD5BindRequest(final String authenticationID, final byte[] password) { this(authenticationID, null, new ASN1OctetString(password), null, NO_CONTROLS); ensureNotNull(password); } public DIGESTMD5BindRequest(final String authenticationID, final ASN1OctetString password) { this(authenticationID, null, password, null, NO_CONTROLS); } public DIGESTMD5BindRequest(final String authenticationID, final String authorizationID, final String password, final String realm, final Control... controls) { this(authenticationID, authorizationID, new ASN1OctetString(password), realm, controls); ensureNotNull(password); } public DIGESTMD5BindRequest(final String authenticationID, final String authorizationID, final byte[] password, final String realm, final Control... controls) { this(authenticationID, authorizationID, new ASN1OctetString(password), realm, controls); ensureNotNull(password); } public DIGESTMD5BindRequest(final String authenticationID, final String authorizationID, final ASN1OctetString password, final String realm, final Control... controls) { super(controls); ensureNotNull(authenticationID, password); this.authenticationID = authenticationID; this.authorizationID = authorizationID; this.password = password; this.realm = realm; } @Override() public String getSASLMechanismName() { return DIGESTMD5_MECHANISM_NAME; } public String getAuthenticationID() { return authenticationID; } public String getAuthorizationID() { return authorizationID; } public String getPasswordString() { return password.stringValue(); } public byte[] getPasswordBytes() { return password.getValue(); } public String getRealm() { return realm; } @Override() protected BindResult process(final LDAPConnection connection, final int depth) throws LDAPException { final String[] mechanisms = { DIGESTMD5_MECHANISM_NAME }; final HashMap<String,Object> saslProperties = new HashMap<String,Object>(); saslProperties.put(Sasl.QOP, "auth"); saslProperties.put(Sasl.SERVER_AUTH, "false"); final SaslClient saslClient; try { saslClient = Sasl.createSaslClient(mechanisms, authorizationID, "ldap", connection.getConnectedAddress(), saslProperties, this); } catch (Exception e) { debugException(e); throw new LDAPException(ResultCode.LOCAL_ERROR, ERR_DIGESTMD5_CANNOT_CREATE_SASL_CLIENT.get(getExceptionMessage(e)), e); } final SASLHelper helper = new SASLHelper(this, connection, DIGESTMD5_MECHANISM_NAME, saslClient, getControls(), getResponseTimeoutMillis(connection)); try { return helper.processSASLBind(); } finally { messageID = helper.getMessageID(); } } @Override() public DIGESTMD5BindRequest getRebindRequest(final String host, final int port) { return new DIGESTMD5BindRequest(authenticationID, authorizationID, password, realm, 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 if (callback instanceof RealmCallback) { if (realm != null) { ((RealmCallback) callback).setText(realm); } } else if (callback instanceof RealmChoiceCallback) { if (realm != null) { final RealmChoiceCallback rcc = (RealmChoiceCallback) callback; final String[] choices = rcc.getChoices(); for (int i=0; i < choices.length; i++) { if (choices[i].equals(realm)) { rcc.setSelectedIndex(i); break; } } } } else { if (debugEnabled(DebugType.LDAP)) { debug(Level.WARNING, DebugType.LDAP, "Unexpected DIGEST-MD5 SASL callback of type " + callback.getClass().getName()); } } } } @Override() public int getLastMessageID() { return messageID; } @Override() public DIGESTMD5BindRequest duplicate() { return duplicate(getControls()); } @Override() public DIGESTMD5BindRequest duplicate(final Control[] controls) { final DIGESTMD5BindRequest bindRequest = new DIGESTMD5BindRequest(authenticationID, authorizationID, password, realm, controls); bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); return bindRequest; } @Override() public void toString(final StringBuilder buffer) { buffer.append("DIGESTMD5BindRequest(authenticationID='"); buffer.append(authenticationID); buffer.append('\''); if (authorizationID != null) { buffer.append(", authorizationID='"); buffer.append(authorizationID); buffer.append('\''); } if (realm != null) { buffer.append(", realm='"); buffer.append(realm); 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(')'); } }