Home | History | Annotate | Download | only in client
      1 /* **************************************************************************
      2  * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
      3  *
      4  * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
      5  *
      6  * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
      7  * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
      8  * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
      9  * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
     10  * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
     11  * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
     12  * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
     13  * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
     14  ******************************************************************************/
     15 package com.novell.sasl.client;
     16 
     17 import org.apache.harmony.javax.security.sasl.*;
     18 import org.apache.harmony.javax.security.auth.callback.*;
     19 import java.security.SecureRandom;
     20 import java.security.MessageDigest;
     21 import java.security.NoSuchAlgorithmException;
     22 import java.io.UnsupportedEncodingException;
     23 import java.io.IOException;
     24 import java.util.*;
     25 
     26 /**
     27  * Implements the Client portion of DigestMD5 Sasl mechanism.
     28  */
     29 public class DigestMD5SaslClient implements SaslClient
     30 {
     31     private String           m_authorizationId = "";
     32     private String           m_protocol = "";
     33     private String           m_serverName = "";
     34     private Map              m_props;
     35     private CallbackHandler  m_cbh;
     36     private int              m_state;
     37     private String           m_qopValue = "";
     38     private char[]              m_HA1 = null;
     39     private String           m_digestURI;
     40     private DigestChallenge  m_dc;
     41     private String           m_clientNonce = "";
     42     private String           m_realm = "";
     43     private String           m_name = "";
     44 
     45     private static final int   STATE_INITIAL = 0;
     46     private static final int   STATE_DIGEST_RESPONSE_SENT = 1;
     47     private static final int   STATE_VALID_SERVER_RESPONSE = 2;
     48     private static final int   STATE_INVALID_SERVER_RESPONSE = 3;
     49     private static final int   STATE_DISPOSED = 4;
     50 
     51     private static final int   NONCE_BYTE_COUNT = 32;
     52     private static final int   NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT;
     53 
     54     private static final String DIGEST_METHOD = "AUTHENTICATE";
     55 
     56     /**
     57      * Creates an DigestMD5SaslClient object using the parameters supplied.
     58      * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
     59      * contained in props
     60      *
     61      * @param authorizationId  The possibly null protocol-dependent
     62      *                     identification to be used for authorization. If
     63      *                     null or empty, the server derives an authorization
     64      *                     ID from the client's authentication credentials.
     65      *                     When the SASL authentication completes
     66      *                     successfully, the specified entity is granted
     67      *                     access.
     68      *
     69      * @param protocol     The non-null string name of the protocol for which
     70      *                     the authentication is being performed (e.g. "ldap")
     71      *
     72      * @param serverName   The non-null fully qualified host name of the server
     73      *                     to authenticate to
     74      *
     75      * @param props        The possibly null set of properties used to select
     76      *                     the SASL mechanism and to configure the
     77      *                     authentication exchange of the selected mechanism.
     78      *                     See the Sasl class for a list of standard properties.
     79      *                     Other, possibly mechanism-specific, properties can
     80      *                     be included. Properties not relevant to the selected
     81      *                     mechanism are ignored.
     82      *
     83      * @param cbh          The possibly null callback handler to used by the
     84      *                     SASL mechanisms to get further information from the
     85      *                     application/library to complete the authentication.
     86      *                     For example, a SASL mechanism might require the
     87      *                     authentication ID, password and realm from the
     88      *                     caller. The authentication ID is requested by using
     89      *                     a NameCallback. The password is requested by using
     90      *                     a PasswordCallback. The realm is requested by using
     91      *                     a RealmChoiceCallback if there is a list of realms
     92      *                     to choose from, and by using a RealmCallback if the
     93      *                     realm must be entered.
     94      *
     95      * @return            A possibly null SaslClient created using the
     96      *                     parameters supplied. If null, this factory cannot
     97      *                     produce a SaslClient using the parameters supplied.
     98      *
     99      * @exception SaslException  If a SaslClient instance cannot be created
    100      *                     because of an error
    101      */
    102     public static SaslClient getClient(
    103         String          authorizationId,
    104         String          protocol,
    105         String          serverName,
    106         Map             props,
    107         CallbackHandler cbh)
    108     {
    109         String desiredQOP = (String)props.get(Sasl.QOP);
    110         String desiredStrength = (String)props.get(Sasl.STRENGTH);
    111         String serverAuth = (String)props.get(Sasl.SERVER_AUTH);
    112 
    113         //only support qop equal to auth
    114         if ((desiredQOP != null) && !"auth".equals(desiredQOP))
    115             return null;
    116 
    117         //doesn't support server authentication
    118         if ((serverAuth != null) && !"false".equals(serverAuth))
    119             return null;
    120 
    121         //need a callback handler to get the password
    122         if (cbh == null)
    123             return null;
    124 
    125         return new DigestMD5SaslClient(authorizationId, protocol,
    126                                        serverName, props, cbh);
    127     }
    128 
    129     /**
    130      * Creates an DigestMD5SaslClient object using the parameters supplied.
    131      * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
    132      * contained in props
    133      *
    134      * @param authorizationId  The possibly null protocol-dependent
    135      *                     identification to be used for authorization. If
    136      *                     null or empty, the server derives an authorization
    137      *                     ID from the client's authentication credentials.
    138      *                     When the SASL authentication completes
    139      *                     successfully, the specified entity is granted
    140      *                     access.
    141      *
    142      * @param protocol     The non-null string name of the protocol for which
    143      *                     the authentication is being performed (e.g. "ldap")
    144      *
    145      * @param serverName   The non-null fully qualified host name of the server
    146      *                     to authenticate to
    147      *
    148      * @param props        The possibly null set of properties used to select
    149      *                     the SASL mechanism and to configure the
    150      *                     authentication exchange of the selected mechanism.
    151      *                     See the Sasl class for a list of standard properties.
    152      *                     Other, possibly mechanism-specific, properties can
    153      *                     be included. Properties not relevant to the selected
    154      *                     mechanism are ignored.
    155      *
    156      * @param cbh          The possibly null callback handler to used by the
    157      *                     SASL mechanisms to get further information from the
    158      *                     application/library to complete the authentication.
    159      *                     For example, a SASL mechanism might require the
    160      *                     authentication ID, password and realm from the
    161      *                     caller. The authentication ID is requested by using
    162      *                     a NameCallback. The password is requested by using
    163      *                     a PasswordCallback. The realm is requested by using
    164      *                     a RealmChoiceCallback if there is a list of realms
    165      *                     to choose from, and by using a RealmCallback if the
    166      *                     realm must be entered.
    167      *
    168      */
    169     private  DigestMD5SaslClient(
    170         String          authorizationId,
    171         String          protocol,
    172         String          serverName,
    173         Map             props,
    174         CallbackHandler cbh)
    175     {
    176         m_authorizationId = authorizationId;
    177         m_protocol = protocol;
    178         m_serverName = serverName;
    179         m_props = props;
    180         m_cbh = cbh;
    181 
    182         m_state = STATE_INITIAL;
    183     }
    184 
    185     /**
    186      * Determines if this mechanism has an optional initial response. If true,
    187      * caller should call evaluateChallenge() with an empty array to get the
    188      * initial response.
    189      *
    190      * @return  true if this mechanism has an initial response
    191      */
    192     public boolean hasInitialResponse()
    193     {
    194         return false;
    195     }
    196 
    197     /**
    198      * Determines if the authentication exchange has completed. This method
    199      * may be called at any time, but typically, it will not be called until
    200      * the caller has received indication from the server (in a protocol-
    201      * specific manner) that the exchange has completed.
    202      *
    203      * @return  true if the authentication exchange has completed;
    204      *           false otherwise.
    205      */
    206     public boolean isComplete()
    207     {
    208         if ((m_state == STATE_VALID_SERVER_RESPONSE) ||
    209             (m_state == STATE_INVALID_SERVER_RESPONSE) ||
    210             (m_state == STATE_DISPOSED))
    211             return true;
    212         else
    213             return false;
    214     }
    215 
    216     /**
    217      * Unwraps a byte array received from the server. This method can be called
    218      * only after the authentication exchange has completed (i.e., when
    219      * isComplete() returns true) and only if the authentication exchange has
    220      * negotiated integrity and/or privacy as the quality of protection;
    221      * otherwise, an IllegalStateException is thrown.
    222      *
    223      * incoming is the contents of the SASL buffer as defined in RFC 2222
    224      * without the leading four octet field that represents the length.
    225      * offset and len specify the portion of incoming to use.
    226      *
    227      * @param incoming   A non-null byte array containing the encoded bytes
    228      *                   from the server
    229      * @param offset     The starting position at incoming of the bytes to use
    230      *
    231      * @param len        The number of bytes from incoming to use
    232      *
    233      * @return           A non-null byte array containing the decoded bytes
    234      *
    235      */
    236     public byte[] unwrap(
    237         byte[] incoming,
    238         int    offset,
    239         int    len)
    240             throws SaslException
    241     {
    242         throw new IllegalStateException(
    243          "unwrap: QOP has neither integrity nor privacy>");
    244     }
    245 
    246     /**
    247      * Wraps a byte array to be sent to the server. This method can be called
    248      * only after the authentication exchange has completed (i.e., when
    249      * isComplete() returns true) and only if the authentication exchange has
    250      * negotiated integrity and/or privacy as the quality of protection;
    251      * otherwise, an IllegalStateException is thrown.
    252      *
    253      * The result of this method will make up the contents of the SASL buffer as
    254      * defined in RFC 2222 without the leading four octet field that represents
    255      * the length. offset and len specify the portion of outgoing to use.
    256      *
    257      * @param outgoing   A non-null byte array containing the bytes to encode
    258      * @param offset     The starting position at outgoing of the bytes to use
    259      * @param len        The number of bytes from outgoing to use
    260      *
    261      * @return A non-null byte array containing the encoded bytes
    262      *
    263      * @exception SaslException  if incoming cannot be successfully unwrapped.
    264      *
    265      * @exception IllegalStateException   if the authentication exchange has
    266      *                   not completed, or if the negotiated quality of
    267      *                   protection has neither integrity nor privacy.
    268      */
    269     public byte[] wrap(
    270         byte[]  outgoing,
    271         int     offset,
    272         int     len)
    273             throws SaslException
    274     {
    275         throw new IllegalStateException(
    276          "wrap: QOP has neither integrity nor privacy>");
    277     }
    278 
    279     /**
    280      * Retrieves the negotiated property. This method can be called only after
    281      * the authentication exchange has completed (i.e., when isComplete()
    282      * returns true); otherwise, an IllegalStateException is thrown.
    283      *
    284      * @param propName   The non-null property name
    285      *
    286      * @return  The value of the negotiated property. If null, the property was
    287      *          not negotiated or is not applicable to this mechanism.
    288      *
    289      * @exception IllegalStateException   if this authentication exchange has
    290      *                                    not completed
    291      */
    292     public Object getNegotiatedProperty(
    293         String propName)
    294     {
    295         if (m_state != STATE_VALID_SERVER_RESPONSE)
    296             throw new IllegalStateException(
    297              "getNegotiatedProperty: authentication exchange not complete.");
    298 
    299         if (Sasl.QOP.equals(propName))
    300             return "auth";
    301         else
    302             return null;
    303     }
    304 
    305     /**
    306      * Disposes of any system resources or security-sensitive information the
    307      * SaslClient might be using. Invoking this method invalidates the
    308      * SaslClient instance. This method is idempotent.
    309      *
    310      * @exception SaslException  if a problem was encountered while disposing
    311      *                           of the resources
    312      */
    313     public void dispose()
    314             throws SaslException
    315     {
    316         if (m_state != STATE_DISPOSED)
    317         {
    318             m_state = STATE_DISPOSED;
    319         }
    320     }
    321 
    322     /**
    323      * Evaluates the challenge data and generates a response. If a challenge
    324      * is received from the server during the authentication process, this
    325      * method is called to prepare an appropriate next response to submit to
    326      * the server.
    327      *
    328      * @param challenge  The non-null challenge sent from the server. The
    329      *                   challenge array may have zero length.
    330      *
    331      * @return    The possibly null reponse to send to the server. It is null
    332      *            if the challenge accompanied a "SUCCESS" status and the
    333      *            challenge only contains data for the client to update its
    334      *            state and no response needs to be sent to the server.
    335      *            The response is a zero-length byte array if the client is to
    336      *            send a response with no data.
    337      *
    338      * @exception SaslException   If an error occurred while processing the
    339      *                            challenge or generating a response.
    340      */
    341     public byte[] evaluateChallenge(
    342         byte[] challenge)
    343             throws SaslException
    344     {
    345         byte[] response = null;
    346 
    347         //printState();
    348         switch (m_state)
    349         {
    350         case STATE_INITIAL:
    351             if (challenge.length == 0)
    352                 throw new SaslException("response = byte[0]");
    353             else
    354                 try
    355                 {
    356                     response = createDigestResponse(challenge).
    357                                                            getBytes("UTF-8");
    358                     m_state = STATE_DIGEST_RESPONSE_SENT;
    359                 }
    360                 catch (java.io.UnsupportedEncodingException e)
    361                 {
    362                     throw new SaslException(
    363                      "UTF-8 encoding not suppported by platform", e);
    364                 }
    365             break;
    366         case STATE_DIGEST_RESPONSE_SENT:
    367             if (checkServerResponseAuth(challenge))
    368                 m_state = STATE_VALID_SERVER_RESPONSE;
    369             else
    370             {
    371                 m_state = STATE_INVALID_SERVER_RESPONSE;
    372                 throw new SaslException("Could not validate response-auth " +
    373                                         "value from server");
    374             }
    375             break;
    376         case STATE_VALID_SERVER_RESPONSE:
    377         case STATE_INVALID_SERVER_RESPONSE:
    378             throw new SaslException("Authentication sequence is complete");
    379         case STATE_DISPOSED:
    380             throw new SaslException("Client has been disposed");
    381         default:
    382             throw new SaslException("Unknown client state.");
    383         }
    384 
    385         return response;
    386     }
    387 
    388     /**
    389      * This function takes a 16 byte binary md5-hash value and creates a 32
    390      * character (plus    a terminating null character) hex-digit
    391      * representation of binary data.
    392      *
    393      * @param hash  16 byte binary md5-hash value in bytes
    394      *
    395      * @return   32 character (plus    a terminating null character) hex-digit
    396      *           representation of binary data.
    397      */
    398     char[] convertToHex(
    399         byte[] hash)
    400     {
    401         int          i;
    402         byte         j;
    403         byte         fifteen = 15;
    404         char[]      hex = new char[32];
    405 
    406         for (i = 0; i < 16; i++)
    407         {
    408             //convert value of top 4 bits to hex char
    409             hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4));
    410             //convert value of bottom 4 bits to hex char
    411             hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f));
    412         }
    413 
    414         return hex;
    415     }
    416 
    417     /**
    418      * Calculates the HA1 portion of the response
    419      *
    420      * @param  algorithm   Algorith to use.
    421      * @param  userName    User being authenticated
    422      * @param  realm       realm information
    423      * @param  password    password of teh user
    424      * @param  nonce       nonce value
    425      * @param  clientNonce Clients Nonce value
    426      *
    427      * @return  HA1 portion of the response in a character array
    428      *
    429      * @exception SaslException  If an error occurs
    430      */
    431     char[] DigestCalcHA1(
    432         String   algorithm,
    433         String   userName,
    434         String   realm,
    435         String   password,
    436         String   nonce,
    437         String   clientNonce) throws SaslException
    438     {
    439         byte[]        hash;
    440 
    441         try
    442         {
    443             MessageDigest md = MessageDigest.getInstance("MD5");
    444 
    445             md.update(userName.getBytes("UTF-8"));
    446             md.update(":".getBytes("UTF-8"));
    447             md.update(realm.getBytes("UTF-8"));
    448             md.update(":".getBytes("UTF-8"));
    449             md.update(password.getBytes("UTF-8"));
    450             hash = md.digest();
    451 
    452             if ("md5-sess".equals(algorithm))
    453             {
    454                 md.update(hash);
    455                 md.update(":".getBytes("UTF-8"));
    456                 md.update(nonce.getBytes("UTF-8"));
    457                 md.update(":".getBytes("UTF-8"));
    458                 md.update(clientNonce.getBytes("UTF-8"));
    459                 hash = md.digest();
    460             }
    461         }
    462         catch(NoSuchAlgorithmException e)
    463         {
    464             throw new SaslException("No provider found for MD5 hash", e);
    465         }
    466         catch(UnsupportedEncodingException e)
    467         {
    468             throw new SaslException(
    469              "UTF-8 encoding not supported by platform.", e);
    470         }
    471 
    472         return convertToHex(hash);
    473     }
    474 
    475 
    476     /**
    477      * This function calculates the response-value of the response directive of
    478      * the digest-response as documented in RFC 2831
    479      *
    480      * @param  HA1           H(A1)
    481      * @param  serverNonce   nonce from server
    482      * @param  nonceCount    8 hex digits
    483      * @param  clientNonce   client nonce
    484      * @param  qop           qop-value: "", "auth", "auth-int"
    485      * @param  method        method from the request
    486      * @param  digestUri     requested URL
    487      * @param  clientResponseFlag request-digest or response-digest
    488      *
    489      * @return Response-value of the response directive of the digest-response
    490      *
    491      * @exception SaslException  If an error occurs
    492      */
    493     char[] DigestCalcResponse(
    494         char[]      HA1,            /* H(A1) */
    495         String      serverNonce,    /* nonce from server */
    496         String      nonceCount,     /* 8 hex digits */
    497         String      clientNonce,    /* client nonce */
    498         String      qop,            /* qop-value: "", "auth", "auth-int" */
    499         String      method,         /* method from the request */
    500         String      digestUri,      /* requested URL */
    501         boolean     clientResponseFlag) /* request-digest or response-digest */
    502             throws SaslException
    503     {
    504         byte[]             HA2;
    505         byte[]             respHash;
    506         char[]             HA2Hex;
    507 
    508         // calculate H(A2)
    509         try
    510         {
    511             MessageDigest md = MessageDigest.getInstance("MD5");
    512             if (clientResponseFlag)
    513                   md.update(method.getBytes("UTF-8"));
    514             md.update(":".getBytes("UTF-8"));
    515             md.update(digestUri.getBytes("UTF-8"));
    516             if ("auth-int".equals(qop))
    517             {
    518                 md.update(":".getBytes("UTF-8"));
    519                 md.update("00000000000000000000000000000000".getBytes("UTF-8"));
    520             }
    521             HA2 = md.digest();
    522             HA2Hex = convertToHex(HA2);
    523 
    524             // calculate response
    525             md.update(new String(HA1).getBytes("UTF-8"));
    526             md.update(":".getBytes("UTF-8"));
    527             md.update(serverNonce.getBytes("UTF-8"));
    528             md.update(":".getBytes("UTF-8"));
    529             if (qop.length() > 0)
    530             {
    531                 md.update(nonceCount.getBytes("UTF-8"));
    532                 md.update(":".getBytes("UTF-8"));
    533                 md.update(clientNonce.getBytes("UTF-8"));
    534                 md.update(":".getBytes("UTF-8"));
    535                 md.update(qop.getBytes("UTF-8"));
    536                 md.update(":".getBytes("UTF-8"));
    537             }
    538             md.update(new String(HA2Hex).getBytes("UTF-8"));
    539             respHash = md.digest();
    540         }
    541         catch(NoSuchAlgorithmException e)
    542         {
    543             throw new SaslException("No provider found for MD5 hash", e);
    544         }
    545         catch(UnsupportedEncodingException e)
    546         {
    547             throw new SaslException(
    548              "UTF-8 encoding not supported by platform.", e);
    549         }
    550 
    551         return convertToHex(respHash);
    552     }
    553 
    554 
    555     /**
    556      * Creates the intial response to be sent to the server.
    557      *
    558      * @param challenge  Challenge in bytes recived form the Server
    559      *
    560      * @return Initial response to be sent to the server
    561      */
    562     private String createDigestResponse(
    563         byte[] challenge)
    564             throws SaslException
    565     {
    566         char[]            response;
    567         StringBuffer    digestResponse = new StringBuffer(512);
    568         int             realmSize;
    569 
    570         m_dc = new DigestChallenge(challenge);
    571 
    572         m_digestURI = m_protocol + "/" + m_serverName;
    573 
    574         if ((m_dc.getQop() & DigestChallenge.QOP_AUTH)
    575             == DigestChallenge.QOP_AUTH )
    576             m_qopValue = "auth";
    577         else
    578             throw new SaslException("Client only supports qop of 'auth'");
    579 
    580         //get call back information
    581         Callback[] callbacks = new Callback[3];
    582         ArrayList realms = m_dc.getRealms();
    583         realmSize = realms.size();
    584         if (realmSize == 0)
    585         {
    586             callbacks[0] = new RealmCallback("Realm");
    587         }
    588         else if (realmSize == 1)
    589         {
    590             callbacks[0] = new RealmCallback("Realm", (String)realms.get(0));
    591         }
    592         else
    593         {
    594             callbacks[0] =
    595              new RealmChoiceCallback(
    596                          "Realm",
    597                          (String[])realms.toArray(new String[realmSize]),
    598                           0,      //the default choice index
    599                           false); //no multiple selections
    600         }
    601 
    602         callbacks[1] = new PasswordCallback("Password", false);
    603         //false = no echo
    604 
    605         if (m_authorizationId == null || m_authorizationId.length() == 0)
    606             callbacks[2] = new NameCallback("Name");
    607         else
    608             callbacks[2] = new NameCallback("Name", m_authorizationId);
    609 
    610         try
    611         {
    612             m_cbh.handle(callbacks);
    613         }
    614         catch(UnsupportedCallbackException e)
    615         {
    616             throw new SaslException("Handler does not support" +
    617                                           " necessary callbacks",e);
    618         }
    619         catch(IOException e)
    620         {
    621             throw new SaslException("IO exception in CallbackHandler.", e);
    622         }
    623 
    624         if (realmSize > 1)
    625         {
    626             int[] selections =
    627              ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes();
    628 
    629             if (selections.length > 0)
    630                 m_realm =
    631                 ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]];
    632             else
    633                 m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0];
    634         }
    635         else
    636             m_realm = ((RealmCallback)callbacks[0]).getText();
    637 
    638         m_clientNonce = getClientNonce();
    639 
    640         m_name = ((NameCallback)callbacks[2]).getName();
    641         if (m_name == null)
    642             m_name = ((NameCallback)callbacks[2]).getDefaultName();
    643         if (m_name == null)
    644             throw new SaslException("No user name was specified.");
    645 
    646         m_HA1 = DigestCalcHA1(
    647                       m_dc.getAlgorithm(),
    648                       m_name,
    649                       m_realm,
    650                       new String(((PasswordCallback)callbacks[1]).getPassword()),
    651                       m_dc.getNonce(),
    652                       m_clientNonce);
    653 
    654         response = DigestCalcResponse(m_HA1,
    655                                       m_dc.getNonce(),
    656                                       "00000001",
    657                                       m_clientNonce,
    658                                       m_qopValue,
    659                                       "AUTHENTICATE",
    660                                       m_digestURI,
    661                                       true);
    662 
    663         digestResponse.append("username=\"");
    664         digestResponse.append(m_authorizationId);
    665         if (0 != m_realm.length())
    666         {
    667             digestResponse.append("\",realm=\"");
    668             digestResponse.append(m_realm);
    669         }
    670         digestResponse.append("\",cnonce=\"");
    671         digestResponse.append(m_clientNonce);
    672         digestResponse.append("\",nc=");
    673         digestResponse.append("00000001"); //nounce count
    674         digestResponse.append(",qop=");
    675         digestResponse.append(m_qopValue);
    676         digestResponse.append(",digest-uri=\"ldap/");
    677         digestResponse.append(m_serverName);
    678         digestResponse.append("\",response=");
    679         digestResponse.append(response);
    680         digestResponse.append(",charset=utf-8,nonce=\"");
    681         digestResponse.append(m_dc.getNonce());
    682         digestResponse.append("\"");
    683 
    684         return digestResponse.toString();
    685      }
    686 
    687 
    688     /**
    689      * This function validates the server response. This step performs a
    690      * modicum of mutual authentication by verifying that the server knows
    691      * the user's password
    692      *
    693      * @param  serverResponse  Response recived form Server
    694      *
    695      * @return  true if the mutual authentication succeeds;
    696      *          else return false
    697      *
    698      * @exception SaslException  If an error occurs
    699      */
    700     boolean checkServerResponseAuth(
    701             byte[]  serverResponse) throws SaslException
    702     {
    703         char[]           response;
    704         ResponseAuth  responseAuth = null;
    705         String        responseStr;
    706 
    707         responseAuth = new ResponseAuth(serverResponse);
    708 
    709         response = DigestCalcResponse(m_HA1,
    710                                   m_dc.getNonce(),
    711                                   "00000001",
    712                                   m_clientNonce,
    713                                   m_qopValue,
    714                                   DIGEST_METHOD,
    715                                   m_digestURI,
    716                                   false);
    717 
    718         responseStr = new String(response);
    719 
    720         return responseStr.equals(responseAuth.getResponseValue());
    721     }
    722 
    723 
    724     /**
    725      * This function returns hex character representing the value of the input
    726      *
    727      * @param value Input value in byte
    728      *
    729      * @return Hex value of the Input byte value
    730      */
    731     private static char getHexChar(
    732         byte    value)
    733     {
    734         switch (value)
    735         {
    736         case 0:
    737             return '0';
    738         case 1:
    739             return '1';
    740         case 2:
    741             return '2';
    742         case 3:
    743             return '3';
    744         case 4:
    745             return '4';
    746         case 5:
    747             return '5';
    748         case 6:
    749             return '6';
    750         case 7:
    751             return '7';
    752         case 8:
    753             return '8';
    754         case 9:
    755             return '9';
    756         case 10:
    757             return 'a';
    758         case 11:
    759             return 'b';
    760         case 12:
    761             return 'c';
    762         case 13:
    763             return 'd';
    764         case 14:
    765             return 'e';
    766         case 15:
    767             return 'f';
    768         default:
    769             return 'Z';
    770         }
    771     }
    772 
    773     /**
    774      * Calculates the Nonce value of the Client
    775      *
    776      * @return   Nonce value of the client
    777      *
    778      * @exception   SaslException If an error Occurs
    779      */
    780     String getClientNonce() throws SaslException
    781     {
    782         byte[]          nonceBytes = new byte[NONCE_BYTE_COUNT];
    783         SecureRandom    prng;
    784         byte            nonceByte;
    785         char[]          hexNonce = new char[NONCE_HEX_COUNT];
    786 
    787         try
    788         {
    789             prng = SecureRandom.getInstance("SHA1PRNG");
    790             prng.nextBytes(nonceBytes);
    791             for(int i=0; i<NONCE_BYTE_COUNT; i++)
    792             {
    793                 //low nibble
    794                 hexNonce[i*2] = getHexChar((byte)(nonceBytes[i] & 0x0f));
    795                 //high nibble
    796                 hexNonce[(i*2)+1] = getHexChar((byte)((nonceBytes[i] & 0xf0)
    797                                                                       >> 4));
    798             }
    799             return new String(hexNonce);
    800         }
    801         catch(NoSuchAlgorithmException e)
    802         {
    803             throw new SaslException("No random number generator available", e);
    804         }
    805     }
    806 
    807     /**
    808      * Returns the IANA-registered mechanism name of this SASL client.
    809      *  (e.g. "CRAM-MD5", "GSSAPI")
    810      *
    811      * @return  "DIGEST-MD5"the IANA-registered mechanism name of this SASL
    812      *          client.
    813      */
    814     public String getMechanismName()
    815     {
    816         return "DIGEST-MD5";
    817     }
    818 
    819 } //end class DigestMD5SaslClient
    820 
    821