• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getClient( String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh)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      */
DigestMD5SaslClient( String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh)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      */
hasInitialResponse()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      */
isComplete()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      */
unwrap( byte[] incoming, int offset, int len)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      */
wrap( byte[] outgoing, int offset, int len)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      */
getNegotiatedProperty( String propName)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      */
dispose()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      */
evaluateChallenge( byte[] challenge)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      */
convertToHex( byte[] hash)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      */
DigestCalcHA1( String algorithm, String userName, String realm, String password, String nonce, String clientNonce)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      */
DigestCalcResponse( char[] HA1, String serverNonce, String nonceCount, String clientNonce, String qop, String method, String digestUri, boolean clientResponseFlag)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      */
createDigestResponse( byte[] challenge)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      */
checkServerResponseAuth( byte[] serverResponse)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      */
getHexChar( byte value)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      */
getClientNonce()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      */
getMechanismName()814     public String getMechanismName()
815     {
816         return "DIGEST-MD5";
817     }
818 
819 } //end class DigestMD5SaslClient
820 
821