| /* ************************************************************************** |
| * $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 |
| |