aboutsummaryrefslogtreecommitdiff
path: root/src/com/novell/sasl/client/DigestMD5SaslClient.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/novell/sasl/client/DigestMD5SaslClient.java')
-rw-r--r--src/com/novell/sasl/client/DigestMD5SaslClient.java820
1 files changed, 820 insertions, 0 deletions
diff --git a/src/com/novell/sasl/client/DigestMD5SaslClient.java b/src/com/novell/sasl/client/DigestMD5SaslClient.java
new file mode 100644
index 0000000..141c96b
--- /dev/null
+++ b/src/com/novell/sasl/client/DigestMD5SaslClient.java
@@ -0,0 +1,820 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import org.apache.harmony.javax.security.sasl.*;
+import org.apache.harmony.javax.security.auth.callback.*;
+import java.security.SecureRandom;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.UnsupportedEncodingException;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Implements the Client portion of DigestMD5 Sasl mechanism.
+ */
+public class DigestMD5SaslClient implements SaslClient
+{
+ private String m_authorizationId = "";
+ private String m_protocol = "";
+ private String m_serverName = "";
+ private Map m_props;
+ private CallbackHandler m_cbh;
+ private int m_state;
+ private String m_qopValue = "";
+ private char[] m_HA1 = null;
+ private String m_digestURI;
+ private DigestChallenge m_dc;
+ private String m_clientNonce = "";
+ private String m_realm = "";
+ private String m_name = "";
+
+ private static final int STATE_INITIAL = 0;
+ private static final int STATE_DIGEST_RESPONSE_SENT = 1;
+ private static final int STATE_VALID_SERVER_RESPONSE = 2;
+ private static final int STATE_INVALID_SERVER_RESPONSE = 3;
+ private static final int STATE_DISPOSED = 4;
+
+ private static final int NONCE_BYTE_COUNT = 32;
+ private static final int NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT;
+
+ private static final String DIGEST_METHOD = "AUTHENTICATE";
+
+ /**
+ * Creates an DigestMD5SaslClient object using the parameters supplied.
+ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+ * contained in props
+ *
+ * @param authorizationId The possibly null protocol-dependent
+ * identification to be used for authorization. If
+ * null or empty, the server derives an authorization
+ * ID from the client's authentication credentials.
+ * When the SASL authentication completes
+ * successfully, the specified entity is granted
+ * access.
+ *
+ * @param protocol The non-null string name of the protocol for which
+ * the authentication is being performed (e.g. "ldap")
+ *
+ * @param serverName The non-null fully qualified host name of the server
+ * to authenticate to
+ *
+ * @param props The possibly null set of properties used to select
+ * the SASL mechanism and to configure the
+ * authentication exchange of the selected mechanism.
+ * See the Sasl class for a list of standard properties.
+ * Other, possibly mechanism-specific, properties can
+ * be included. Properties not relevant to the selected
+ * mechanism are ignored.
+ *
+ * @param cbh The possibly null callback handler to used by the
+ * SASL mechanisms to get further information from the
+ * application/library to complete the authentication.
+ * For example, a SASL mechanism might require the
+ * authentication ID, password and realm from the
+ * caller. The authentication ID is requested by using
+ * a NameCallback. The password is requested by using
+ * a PasswordCallback. The realm is requested by using
+ * a RealmChoiceCallback if there is a list of realms
+ * to choose from, and by using a RealmCallback if the
+ * realm must be entered.
+ *
+ * @return A possibly null SaslClient created using the
+ * parameters supplied. If null, this factory cannot
+ * produce a SaslClient using the parameters supplied.
+ *
+ * @exception SaslException If a SaslClient instance cannot be created
+ * because of an error
+ */
+ public static SaslClient getClient(
+ String authorizationId,
+ String protocol,
+ String serverName,
+ Map props,
+ CallbackHandler cbh)
+ {
+ String desiredQOP = (String)props.get(Sasl.QOP);
+ String desiredStrength = (String)props.get(Sasl.STRENGTH);
+ String serverAuth = (String)props.get(Sasl.SERVER_AUTH);
+
+ //only support qop equal to auth
+ if ((desiredQOP != null) && !"auth".equals(desiredQOP))
+ return null;
+
+ //doesn't support server authentication
+ if ((serverAuth != null) && !"false".equals(serverAuth))
+ return null;
+
+ //need a callback handler to get the password
+ if (cbh == null)
+ return null;
+
+ return new DigestMD5SaslClient(authorizationId, protocol,
+ serverName, props, cbh);
+ }
+
+ /**
+ * Creates an DigestMD5SaslClient object using the parameters supplied.
+ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+ * contained in props
+ *
+ * @param authorizationId The possibly null protocol-dependent
+ * identification to be used for authorization. If
+ * null or empty, the server derives an authorization
+ * ID from the client's authentication credentials.
+ * When the SASL authentication completes
+ * successfully, the specified entity is granted
+ * access.
+ *
+ * @param protocol The non-null string name of the protocol for which
+ * the authentication is being performed (e.g. "ldap")
+ *
+ * @param serverName The non-null fully qualified host name of the server
+ * to authenticate to
+ *
+ * @param props The possibly null set of properties used to select
+ * the SASL mechanism and to configure the
+ * authentication exchange of the selected mechanism.
+ * See the Sasl class for a list of standard properties.
+ * Other, possibly mechanism-specific, properties can
+ * be included. Properties not relevant to the selected
+ * mechanism are ignored.
+ *
+ * @param cbh The possibly null callback handler to used by the
+ * SASL mechanisms to get further information from the
+ * application/library to complete the authentication.
+ * For example, a SASL mechanism might require the
+ * authentication ID, password and realm from the
+ * caller. The authentication ID is requested by using
+ * a NameCallback. The password is requested by using
+ * a PasswordCallback. The realm is requested by using
+ * a RealmChoiceCallback if there is a list of realms
+ * to choose from, and by using a RealmCallback if the
+ * realm must be entered.
+ *
+ */
+ private DigestMD5SaslClient(
+ String authorizationId,
+ String protocol,
+ String serverName,
+ Map props,
+ CallbackHandler cbh)
+ {
+ m_authorizationId = authorizationId;
+ m_protocol = protocol;
+ m_serverName = serverName;
+ m_props = props;
+ m_cbh = cbh;
+
+ m_state = STATE_INITIAL;
+ }
+
+ /**
+ * Determines if this mechanism has an optional initial response. If true,
+ * caller should call evaluateChallenge() with an empty array to get the
+ * initial response.
+ *
+ * @return true if this mechanism has an initial response
+ */
+ public boolean hasInitialResponse()
+ {
+ return false;
+ }
+
+ /**
+ * Determines if the authentication exchange has completed. This method
+ * may be called at any time, but typically, it will not be called until
+ * the caller has received indication from the server (in a protocol-
+ * specific manner) that the exchange has completed.
+ *
+ * @return true if the authentication exchange has completed;
+ * false otherwise.
+ */
+ public boolean isComplete()
+ {
+ if ((m_state == STATE_VALID_SERVER_RESPONSE) ||
+ (m_state == STATE_INVALID_SERVER_RESPONSE) ||
+ (m_state == STATE_DISPOSED))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Unwraps a byte array received from the server. This method can be called
+ * only after the authentication exchange has completed (i.e., when
+ * isComplete() returns true) and only if the authentication exchange has
+ * negotiated integrity and/or privacy as the quality of protection;
+ * otherwise, an IllegalStateException is thrown.
+ *
+ * incoming is the contents of the SASL buffer as defined in RFC 2222
+ * without the leading four octet field that represents the length.
+ * offset and len specify the portion of incoming to use.
+ *
+ * @param incoming A non-null byte array containing the encoded bytes
+ * from the server
+ * @param offset The starting position at incoming of the bytes to use
+ *
+ * @param len The number of bytes from incoming to use
+ *
+ * @return A non-null byte array containing the decoded bytes
+ *
+ */
+ public byte[] unwrap(
+ byte[] incoming,
+ int offset,
+ int len)
+ throws SaslException
+ {
+ throw new IllegalStateException(
+ "unwrap: QOP has neither integrity nor privacy>");
+ }
+
+ /**
+ * Wraps a byte array to be sent to the server. This method can be called
+ * only after the authentication exchange has completed (i.e., when
+ * isComplete() returns true) and only if the authentication exchange has
+ * negotiated integrity and/or privacy as the quality of protection;
+ * otherwise, an IllegalStateException is thrown.
+ *
+ * The result of this method will make up the contents of the SASL buffer as
+ * defined in RFC 2222 without the leading four octet field that represents
+ * the length. offset and len specify the portion of outgoing to use.
+ *
+ * @param outgoing A non-null byte array containing the bytes to encode
+ * @param offset The starting position at outgoing of the bytes to use
+ * @param len The number of bytes from outgoing to use
+ *
+ * @return A non-null byte array containing the encoded bytes
+ *
+ * @exception SaslException if incoming cannot be successfully unwrapped.
+ *
+ * @exception IllegalStateException if the authentication exchange has
+ * not completed, or if the negotiated quality of
+ * protection has neither integrity nor privacy.
+ */
+ public byte[] wrap(
+ byte[] outgoing,
+ int offset,
+ int len)
+ throws SaslException
+ {
+ throw new IllegalStateException(
+ "wrap: QOP has neither integrity nor privacy>");
+ }
+
+ /**
+ * Retrieves the negotiated property. This method can be called only after
+ * the authentication exchange has completed (i.e., when isComplete()
+ * returns true); otherwise, an IllegalStateException is thrown.
+ *
+ * @param propName The non-null property name
+ *
+ * @return The value of the negotiated property. If null, the property was
+ * not negotiated or is not applicable to this mechanism.
+ *
+ * @exception IllegalStateException if this authentication exchange has
+ * not completed
+ */
+ public Object getNegotiatedProperty(
+ String propName)
+ {
+ if (m_state != STATE_VALID_SERVER_RESPONSE)
+ throw new IllegalStateException(
+ "getNegotiatedProperty: authentication exchange not complete.");
+
+ if (Sasl.QOP.equals(propName))
+ return "auth";
+ else
+ return null;
+ }
+
+ /**
+ * Disposes of any system resources or security-sensitive information the
+ * SaslClient might be using. Invoking this method invalidates the
+ * SaslClient instance. This method is idempotent.
+ *
+ * @exception SaslException if a problem was encountered while disposing
+ * of the resources
+ */
+ public void dispose()
+ throws SaslException
+ {
+ if (m_state != STATE_DISPOSED)
+ {
+ m_state = STATE_DISPOSED;
+ }
+ }
+
+ /**
+ * Evaluates the challenge data and generates a response. If a challenge
+ * is received from the server during the authentication process, this
+ * method is called to prepare an appropriate next response to submit to
+ * the server.
+ *
+ * @param challenge The non-null challenge sent from the server. The
+ * challenge array may have zero length.
+ *
+ * @return The possibly null reponse to send to the server. It is null
+ * if the challenge accompanied a "SUCCESS" status and the
+ * challenge only contains data for the client to update its
+ * state and no response needs to be sent to the server.
+ * The response is a zero-length byte array if the client is to
+ * send a response with no data.
+ *
+ * @exception SaslException If an error occurred while processing the
+ * challenge or generating a response.
+ */
+ public byte[] evaluateChallenge(
+ byte[] challenge)
+ throws SaslException
+ {
+ byte[] response = null;
+
+ //printState();
+ switch (m_state)
+ {
+ case STATE_INITIAL:
+ if (challenge.length == 0)
+ throw new SaslException("response = byte[0]");
+ else
+ try
+ {
+ response = createDigestResponse(challenge).
+ getBytes("UTF-8");
+ m_state = STATE_DIGEST_RESPONSE_SENT;
+ }
+ catch (java.io.UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not suppported by platform", e);
+ }
+ break;
+ case STATE_DIGEST_RESPONSE_SENT:
+ if (checkServerResponseAuth(challenge))
+ m_state = STATE_VALID_SERVER_RESPONSE;
+ else
+ {
+ m_state = STATE_INVALID_SERVER_RESPONSE;
+ throw new SaslException("Could not validate response-auth " +
+ "value from server");
+ }
+ break;
+ case STATE_VALID_SERVER_RESPONSE:
+ case STATE_INVALID_SERVER_RESPONSE:
+ throw new SaslException("Authentication sequence is complete");
+ case STATE_DISPOSED:
+ throw new SaslException("Client has been disposed");
+ default:
+ throw new SaslException("Unknown client state.");
+ }
+
+ return response;
+ }
+
+ /**
+ * This function takes a 16 byte binary md5-hash value and creates a 32
+ * character (plus a terminating null character) hex-digit
+ * representation of binary data.
+ *
+ * @param hash 16 byte binary md5-hash value in bytes
+ *
+ * @return 32 character (plus a terminating null character) hex-digit
+ * representation of binary data.
+ */
+ char[] convertToHex(
+ byte[] hash)
+ {
+ int i;
+ byte j;
+ byte fifteen = 15;
+ char[] hex = new char[32];
+
+ for (i = 0; i < 16; i++)
+ {
+ //convert value of top 4 bits to hex char
+ hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4));
+ //convert value of bottom 4 bits to hex char
+ hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f));
+ }
+
+ return hex;
+ }
+
+ /**
+ * Calculates the HA1 portion of the response
+ *
+ * @param algorithm Algorith to use.
+ * @param userName User being authenticated
+ * @param realm realm information
+ * @param password password of teh user
+ * @param nonce nonce value
+ * @param clientNonce Clients Nonce value
+ *
+ * @return HA1 portion of the response in a character array
+ *
+ * @exception SaslException If an error occurs
+ */
+ char[] DigestCalcHA1(
+ String algorithm,
+ String userName,
+ String realm,
+ String password,
+ String nonce,
+ String clientNonce) throws SaslException
+ {
+ byte[] hash;
+
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ md.update(userName.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(realm.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(password.getBytes("UTF-8"));
+ hash = md.digest();
+
+ if ("md5-sess".equals(algorithm))
+ {
+ md.update(hash);
+ md.update(":".getBytes("UTF-8"));
+ md.update(nonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(clientNonce.getBytes("UTF-8"));
+ hash = md.digest();
+ }
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No provider found for MD5 hash", e);
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not supported by platform.", e);
+ }
+
+ return convertToHex(hash);
+ }
+
+
+ /**
+ * This function calculates the response-value of the response directive of
+ * the digest-response as documented in RFC 2831
+ *
+ * @param HA1 H(A1)
+ * @param serverNonce nonce from server
+ * @param nonceCount 8 hex digits
+ * @param clientNonce client nonce
+ * @param qop qop-value: "", "auth", "auth-int"
+ * @param method method from the request
+ * @param digestUri requested URL
+ * @param clientResponseFlag request-digest or response-digest
+ *
+ * @return Response-value of the response directive of the digest-response
+ *
+ * @exception SaslException If an error occurs
+ */
+ char[] DigestCalcResponse(
+ char[] HA1, /* H(A1) */
+ String serverNonce, /* nonce from server */
+ String nonceCount, /* 8 hex digits */
+ String clientNonce, /* client nonce */
+ String qop, /* qop-value: "", "auth", "auth-int" */
+ String method, /* method from the request */
+ String digestUri, /* requested URL */
+ boolean clientResponseFlag) /* request-digest or response-digest */
+ throws SaslException
+ {
+ byte[] HA2;
+ byte[] respHash;
+ char[] HA2Hex;
+
+ // calculate H(A2)
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ if (clientResponseFlag)
+ md.update(method.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(digestUri.getBytes("UTF-8"));
+ if ("auth-int".equals(qop))
+ {
+ md.update(":".getBytes("UTF-8"));
+ md.update("00000000000000000000000000000000".getBytes("UTF-8"));
+ }
+ HA2 = md.digest();
+ HA2Hex = convertToHex(HA2);
+
+ // calculate response
+ md.update(new String(HA1).getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(serverNonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ if (qop.length() > 0)
+ {
+ md.update(nonceCount.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(clientNonce.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ md.update(qop.getBytes("UTF-8"));
+ md.update(":".getBytes("UTF-8"));
+ }
+ md.update(new String(HA2Hex).getBytes("UTF-8"));
+ respHash = md.digest();
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No provider found for MD5 hash", e);
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ throw new SaslException(
+ "UTF-8 encoding not supported by platform.", e);
+ }
+
+ return convertToHex(respHash);
+ }
+
+
+ /**
+ * Creates the intial response to be sent to the server.
+ *
+ * @param challenge Challenge in bytes recived form the Server
+ *
+ * @return Initial response to be sent to the server
+ */
+ private String createDigestResponse(
+ byte[] challenge)
+ throws SaslException
+ {
+ char[] response;
+ StringBuffer digestResponse = new StringBuffer(512);
+ int realmSize;
+
+ m_dc = new DigestChallenge(challenge);
+
+ m_digestURI = m_protocol + "/" + m_serverName;
+
+ if ((m_dc.getQop() & DigestChallenge.QOP_AUTH)
+ == DigestChallenge.QOP_AUTH )
+ m_qopValue = "auth";
+ else
+ throw new SaslException("Client only supports qop of 'auth'");
+
+ //get call back information
+ Callback[] callbacks = new Callback[3];
+ ArrayList realms = m_dc.getRealms();
+ realmSize = realms.size();
+ if (realmSize == 0)
+ {
+ callbacks[0] = new RealmCallback("Realm");
+ }
+ else if (realmSize == 1)
+ {
+ callbacks[0] = new RealmCallback("Realm", (String)realms.get(0));
+ }
+ else
+ {
+ callbacks[0] =
+ new RealmChoiceCallback(
+ "Realm",
+ (String[])realms.toArray(new String[realmSize]),
+ 0, //the default choice index
+ false); //no multiple selections
+ }
+
+ callbacks[1] = new PasswordCallback("Password", false);
+ //false = no echo
+
+ if (m_authorizationId == null || m_authorizationId.length() == 0)
+ callbacks[2] = new NameCallback("Name");
+ else
+ callbacks[2] = new NameCallback("Name", m_authorizationId);
+
+ try
+ {
+ m_cbh.handle(callbacks);
+ }
+ catch(UnsupportedCallbackException e)
+ {
+ throw new SaslException("Handler does not support" +
+ " necessary callbacks",e);
+ }
+ catch(IOException e)
+ {
+ throw new SaslException("IO exception in CallbackHandler.", e);
+ }
+
+ if (realmSize > 1)
+ {
+ int[] selections =
+ ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes();
+
+ if (selections.length > 0)
+ m_realm =
+ ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]];
+ else
+ m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0];
+ }
+ else
+ m_realm = ((RealmCallback)callbacks[0]).getText();
+
+ m_clientNonce = getClientNonce();
+
+ m_name = ((NameCallback)callbacks[2]).getName();
+ if (m_name == null)
+ m_name = ((NameCallback)callbacks[2]).getDefaultName();
+ if (m_name == null)
+ throw new SaslException("No user name was specified.");
+
+ m_HA1 = DigestCalcHA1(
+ m_dc.getAlgorithm(),
+ m_name,
+ m_realm,
+ new String(((PasswordCallback)callbacks[1]).getPassword()),
+ m_dc.getNonce(),
+ m_clientNonce);
+
+ response = DigestCalcResponse(m_HA1,
+ m_dc.getNonce(),
+ "00000001",
+ m_clientNonce,
+ m_qopValue,
+ "AUTHENTICATE",
+ m_digestURI,
+ true);
+
+ digestResponse.append("username=\"");
+ digestResponse.append(m_authorizationId);
+ if (0 != m_realm.length())
+ {
+ digestResponse.append("\",realm=\"");
+ digestResponse.append(m_realm);
+ }
+ digestResponse.append("\",cnonce=\"");
+ digestResponse.append(m_clientNonce);
+ digestResponse.append("\",nc=");
+ digestResponse.append("00000001"); //nounce count
+ digestResponse.append(",qop=");
+ digestResponse.append(m_qopValue);
+ digestResponse.append(",digest-uri=\"");
+ digestResponse.append(m_digestURI);
+ digestResponse.append("\",response=");
+ digestResponse.append(response);
+ digestResponse.append(",charset=utf-8,nonce=\"");
+ digestResponse.append(m_dc.getNonce());
+ digestResponse.append("\"");
+
+ return digestResponse.toString();
+ }
+
+
+ /**
+ * This function validates the server response. This step performs a
+ * modicum of mutual authentication by verifying that the server knows
+ * the user's password
+ *
+ * @param serverResponse Response recived form Server
+ *
+ * @return true if the mutual authentication succeeds;
+ * else return false
+ *
+ * @exception SaslException If an error occurs
+ */
+ boolean checkServerResponseAuth(
+ byte[] serverResponse) throws SaslException
+ {
+ char[] response;
+ ResponseAuth responseAuth = null;
+ String responseStr;
+
+ responseAuth = new ResponseAuth(serverResponse);
+
+ response = DigestCalcResponse(m_HA1,
+ m_dc.getNonce(),
+ "00000001",
+ m_clientNonce,
+ m_qopValue,
+ DIGEST_METHOD,
+ m_digestURI,
+ false);
+
+ responseStr = new String(response);
+
+ return responseStr.equals(responseAuth.getResponseValue());
+ }
+
+
+ /**
+ * This function returns hex character representing the value of the input
+ *
+ * @param value Input value in byte
+ *
+ * @return Hex value of the Input byte value
+ */
+ private static char getHexChar(
+ byte value)
+ {
+ switch (value)
+ {
+ case 0:
+ return '0';
+ case 1:
+ return '1';
+ case 2:
+ return '2';
+ case 3:
+ return '3';
+ case 4:
+ return '4';
+ case 5:
+ return '5';
+ case 6:
+ return '6';
+ case 7:
+ return '7';
+ case 8:
+ return '8';
+ case 9:
+ return '9';
+ case 10:
+ return 'a';
+ case 11:
+ return 'b';
+ case 12:
+ return 'c';
+ case 13:
+ return 'd';
+ case 14:
+ return 'e';
+ case 15:
+ return 'f';
+ default:
+ return 'Z';
+ }
+ }
+
+ /**
+ * Calculates the Nonce value of the Client
+ *
+ * @return Nonce value of the client
+ *
+ * @exception SaslException If an error Occurs
+ */
+ String getClientNonce() throws SaslException
+ {
+ byte[] nonceBytes = new byte[NONCE_BYTE_COUNT];
+ SecureRandom prng;
+ byte nonceByte;
+ char[] hexNonce = new char[NONCE_HEX_COUNT];
+
+ try
+ {
+ prng = SecureRandom.getInstance("SHA1PRNG");
+ prng.nextBytes(nonceBytes);
+ for(int i=0; i<NONCE_BYTE_COUNT; i++)
+ {
+ //low nibble
+ hexNonce[i*2] = getHexChar((byte)(nonceBytes[i] & 0x0f));
+ //high nibble
+ hexNonce[(i*2)+1] = getHexChar((byte)((nonceBytes[i] & 0xf0)
+ >> 4));
+ }
+ return new String(hexNonce);
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new SaslException("No random number generator available", e);
+ }
+ }
+
+ /**
+ * Returns the IANA-registered mechanism name of this SASL client.
+ * (e.g. "CRAM-MD5", "GSSAPI")
+ *
+ * @return "DIGEST-MD5"the IANA-registered mechanism name of this SASL
+ * client.
+ */
+ public String getMechanismName()
+ {
+ return "DIGEST-MD5";
+ }
+
+} //end class DigestMD5SaslClient
+