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