1 /* 2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.security.sasl.digest; 27 28 import java.security.NoSuchAlgorithmException; 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.io.UnsupportedEncodingException; 32 import java.util.StringTokenizer; 33 import java.util.ArrayList; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Arrays; 37 38 import java.util.logging.Level; 39 40 import javax.security.sasl.*; 41 import javax.security.auth.callback.CallbackHandler; 42 import javax.security.auth.callback.PasswordCallback; 43 import javax.security.auth.callback.NameCallback; 44 import javax.security.auth.callback.Callback; 45 import javax.security.auth.callback.UnsupportedCallbackException; 46 47 /** 48 * An implementation of the DIGEST-MD5 49 * (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) SASL 50 * (<a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>) mechanism. 51 * 52 * The DIGEST-MD5 SASL mechanism specifies two modes of authentication. 53 * - Initial Authentication 54 * - Subsequent Authentication - optional, (currently unsupported) 55 * 56 * Required callbacks: 57 * - RealmChoiceCallback 58 * shows user list of realms server has offered; handler must choose one 59 * from list 60 * - RealmCallback 61 * shows user the only realm server has offered or none; handler must 62 * enter realm to use 63 * - NameCallback 64 * handler must enter username to use for authentication 65 * - PasswordCallback 66 * handler must enter password for username to use for authentication 67 * 68 * Environment properties that affect behavior of implementation: 69 * 70 * javax.security.sasl.qop 71 * quality of protection; list of auth, auth-int, auth-conf; default is "auth" 72 * javax.security.sasl.strength 73 * auth-conf strength; list of high, medium, low; default is highest 74 * available on platform ["high,medium,low"]. 75 * high means des3 or rc4 (128); medium des or rc4-56; low is rc4-40; 76 * choice of cipher depends on its availablility on platform 77 * javax.security.sasl.maxbuf 78 * max receive buffer size; default is 65536 79 * javax.security.sasl.sendmaxbuffer 80 * max send buffer size; default is 65536; (min with server max recv size) 81 * 82 * com.sun.security.sasl.digest.cipher 83 * name a specific cipher to use; setting must be compatible with the 84 * setting of the javax.security.sasl.strength property. 85 * 86 * @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a> 87 * - Simple Authentication and Security Layer (SASL) 88 * @see <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a> 89 * - Using Digest Authentication as a SASL Mechanism 90 * @see <a href="http://java.sun.com/products/jce">Java(TM) 91 * Cryptography Extension 1.2.1 (JCE)</a> 92 * @see <a href="http://java.sun.com/products/jaas">Java(TM) 93 * Authentication and Authorization Service (JAAS)</a> 94 * 95 * @author Jonathan Bruce 96 * @author Rosanna Lee 97 */ 98 final class DigestMD5Client extends DigestMD5Base implements SaslClient { 99 private static final String MY_CLASS_NAME = DigestMD5Client.class.getName(); 100 101 // Property for specifying cipher explicitly 102 private static final String CIPHER_PROPERTY = 103 "com.sun.security.sasl.digest.cipher"; 104 105 /* Directives encountered in challenges sent by the server. */ 106 private static final String[] DIRECTIVE_KEY = { 107 "realm", // >= 0 times 108 "qop", // atmost once; default is "auth" 109 "algorithm", // exactly once 110 "nonce", // exactly once 111 "maxbuf", // atmost once; default is 65536 112 "charset", // atmost once; default is ISO 8859-1 113 "cipher", // exactly once if qop is "auth-conf" 114 "rspauth", // exactly once in 2nd challenge 115 "stale", // atmost once for in subsequent auth (not supported) 116 }; 117 118 /* Indices into DIRECTIVE_KEY */ 119 private static final int REALM = 0; 120 private static final int QOP = 1; 121 private static final int ALGORITHM = 2; 122 private static final int NONCE = 3; 123 private static final int MAXBUF = 4; 124 private static final int CHARSET = 5; 125 private static final int CIPHER = 6; 126 private static final int RESPONSE_AUTH = 7; 127 private static final int STALE = 8; 128 129 private int nonceCount; // number of times nonce has been used/seen 130 131 /* User-supplied/generated information */ 132 private String specifiedCipher; // cipher explicitly requested by user 133 private byte[] cnonce; // client generated nonce 134 private String username; 135 private char[] passwd; 136 private byte[] authzidBytes; // byte repr of authzid 137 138 /** 139 * Constructor for DIGEST-MD5 mechanism. 140 * 141 * @param authzid A non-null String representing the principal 142 * for which authorization is being granted.. 143 * @param digestURI A non-null String representing detailing the 144 * combined protocol and host being used for authentication. 145 * @param props The possibly null properties to be used by the SASL 146 * mechanism to configure the authentication exchange. 147 * @param cbh The non-null CallbackHanlder object for callbacks 148 * @throws SaslException if no authentication ID or password is supplied 149 */ 150 DigestMD5Client(String authzid, String protocol, String serverName, 151 Map<String, ?> props, CallbackHandler cbh) throws SaslException { 152 153 super(props, MY_CLASS_NAME, 2, protocol + "/" + serverName, cbh); 154 155 // authzID can only be encoded in UTF8 - RFC 2222 156 if (authzid != null) { 157 this.authzid = authzid; 158 try { 159 authzidBytes = authzid.getBytes("UTF8"); 160 161 } catch (UnsupportedEncodingException e) { 162 throw new SaslException( 163 "DIGEST-MD5: Error encoding authzid value into UTF-8", e); 164 } 165 } 166 167 if (props != null) { 168 specifiedCipher = (String)props.get(CIPHER_PROPERTY); 169 170 logger.log(Level.FINE, "DIGEST60:Explicitly specified cipher: {0}", 171 specifiedCipher); 172 } 173 } 174 175 /** 176 * DIGEST-MD5 has no initial response 177 * 178 * @return false 179 */ 180 public boolean hasInitialResponse() { 181 return false; 182 } 183 184 /** 185 * Process the challenge data. 186 * 187 * The server sends a digest-challenge which the client must reply to 188 * in a digest-response. When the authentication is complete, the 189 * completed field is set to true. 190 * 191 * @param challengeData A non-null byte array containing the challenge 192 * data from the server. 193 * @return A possibly null byte array containing the response to 194 * be sent to the server. 195 * 196 * @throws SaslException If the platform does not have MD5 digest support 197 * or if the server sends an invalid challenge. 198 */ 199 public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { 200 201 if (challengeData.length > MAX_CHALLENGE_LENGTH) { 202 throw new SaslException( 203 "DIGEST-MD5: Invalid digest-challenge length. Got: " + 204 challengeData.length + " Expected < " + MAX_CHALLENGE_LENGTH); 205 } 206 207 /* Extract and process digest-challenge */ 208 byte[][] challengeVal; 209 210 switch (step) { 211 case 2: 212 /* Process server's first challenge (from Step 1) */ 213 /* Get realm, qop, maxbuf, charset, algorithm, cipher, nonce 214 directives */ 215 List<byte[]> realmChoices = new ArrayList<byte[]>(3); 216 challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY, 217 realmChoices, REALM); 218 219 try { 220 processChallenge(challengeVal, realmChoices); 221 checkQopSupport(challengeVal[QOP], challengeVal[CIPHER]); 222 ++step; 223 return generateClientResponse(challengeVal[CHARSET]); 224 } catch (SaslException e) { 225 step = 0; 226 clearPassword(); 227 throw e; // rethrow 228 } catch (IOException e) { 229 step = 0; 230 clearPassword(); 231 throw new SaslException("DIGEST-MD5: Error generating " + 232 "digest response-value", e); 233 } 234 235 case 3: 236 try { 237 /* Process server's step 3 (server response to digest response) */ 238 /* Get rspauth directive */ 239 challengeVal = parseDirectives(challengeData, DIRECTIVE_KEY, 240 null, REALM); 241 validateResponseValue(challengeVal[RESPONSE_AUTH]); 242 243 244 /* Initialize SecurityCtx implementation */ 245 if (integrity && privacy) { 246 secCtx = new DigestPrivacy(true /* client */); 247 } else if (integrity) { 248 secCtx = new DigestIntegrity(true /* client */); 249 } 250 251 return null; // Mechanism has completed. 252 } finally { 253 clearPassword(); 254 step = 0; // Set to invalid state 255 completed = true; 256 } 257 258 default: 259 // No other possible state 260 throw new SaslException("DIGEST-MD5: Client at illegal state"); 261 } 262 } 263 264 265 /** 266 * Record information from the challengeVal array into variables/fields. 267 * Check directive values that are multi-valued and ensure that mandatory 268 * directives not missing from the digest-challenge. 269 * 270 * @throws SaslException if a sasl is a the mechanism cannot 271 * correcly handle a callbacks or if a violation in the 272 * digest challenge format is detected. 273 */ 274 private void processChallenge(byte[][] challengeVal, List<byte[]> realmChoices) 275 throws SaslException, UnsupportedEncodingException { 276 277 /* CHARSET: optional atmost once */ 278 if (challengeVal[CHARSET] != null) { 279 if (!"utf-8".equals(new String(challengeVal[CHARSET], encoding))) { 280 throw new SaslException("DIGEST-MD5: digest-challenge format " + 281 "violation. Unrecognised charset value: " + 282 new String(challengeVal[CHARSET])); 283 } else { 284 encoding = "UTF8"; 285 useUTF8 = true; 286 } 287 } 288 289 /* ALGORITHM: required exactly once */ 290 if (challengeVal[ALGORITHM] == null) { 291 throw new SaslException("DIGEST-MD5: Digest-challenge format " + 292 "violation: algorithm directive missing"); 293 } else if (!"md5-sess".equals(new String(challengeVal[ALGORITHM], encoding))) { 294 throw new SaslException("DIGEST-MD5: Digest-challenge format " + 295 "violation. Invalid value for 'algorithm' directive: " + 296 challengeVal[ALGORITHM]); 297 } 298 299 /* NONCE: required exactly once */ 300 if (challengeVal[NONCE] == null) { 301 throw new SaslException("DIGEST-MD5: Digest-challenge format " + 302 "violation: nonce directive missing"); 303 } else { 304 nonce = challengeVal[NONCE]; 305 } 306 307 try { 308 /* REALM: optional, if multiple, stored in realmChoices */ 309 String[] realmTokens = null; 310 311 if (challengeVal[REALM] != null) { 312 if (realmChoices == null || realmChoices.size() <= 1) { 313 // Only one realm specified 314 negotiatedRealm = new String(challengeVal[REALM], encoding); 315 } else { 316 realmTokens = new String[realmChoices.size()]; 317 for (int i = 0; i < realmTokens.length; i++) { 318 realmTokens[i] = 319 new String(realmChoices.get(i), encoding); 320 } 321 } 322 } 323 324 NameCallback ncb = authzid == null ? 325 new NameCallback("DIGEST-MD5 authentication ID: ") : 326 new NameCallback("DIGEST-MD5 authentication ID: ", authzid); 327 PasswordCallback pcb = 328 new PasswordCallback("DIGEST-MD5 password: ", false); 329 330 if (realmTokens == null) { 331 // Server specified <= 1 realm 332 // If 0, RFC 2831: the client SHOULD solicit a realm from the user. 333 RealmCallback tcb = 334 (negotiatedRealm == null? new RealmCallback("DIGEST-MD5 realm: ") : 335 new RealmCallback("DIGEST-MD5 realm: ", negotiatedRealm)); 336 337 cbh.handle(new Callback[] {tcb, ncb, pcb}); 338 339 /* Acquire realm from RealmCallback */ 340 negotiatedRealm = tcb.getText(); 341 if (negotiatedRealm == null) { 342 negotiatedRealm = ""; 343 } 344 } else { 345 RealmChoiceCallback ccb = new RealmChoiceCallback( 346 "DIGEST-MD5 realm: ", 347 realmTokens, 348 0, false); 349 cbh.handle(new Callback[] {ccb, ncb, pcb}); 350 351 // Acquire realm from RealmChoiceCallback 352 int[] selected = ccb.getSelectedIndexes(); 353 if (selected == null 354 || selected[0] < 0 355 || selected[0] >= realmTokens.length) { 356 throw new SaslException("DIGEST-MD5: Invalid realm chosen"); 357 } 358 negotiatedRealm = realmTokens[selected[0]]; 359 } 360 361 passwd = pcb.getPassword(); 362 pcb.clearPassword(); 363 username = ncb.getName(); 364 365 } catch (SaslException se) { 366 throw se; 367 368 } catch (UnsupportedCallbackException e) { 369 throw new SaslException("DIGEST-MD5: Cannot perform callback to " + 370 "acquire realm, authentication ID or password", e); 371 372 } catch (IOException e) { 373 throw new SaslException( 374 "DIGEST-MD5: Error acquiring realm, authentication ID or password", e); 375 } 376 377 if (username == null || passwd == null) { 378 throw new SaslException( 379 "DIGEST-MD5: authentication ID and password must be specified"); 380 } 381 382 /* MAXBUF: optional atmost once */ 383 int srvMaxBufSize = 384 (challengeVal[MAXBUF] == null) ? DEFAULT_MAXBUF 385 : Integer.parseInt(new String(challengeVal[MAXBUF], encoding)); 386 sendMaxBufSize = 387 (sendMaxBufSize == 0) ? srvMaxBufSize 388 : Math.min(sendMaxBufSize, srvMaxBufSize); 389 } 390 391 /** 392 * Parses the 'qop' directive. If 'auth-conf' is specified by 393 * the client and offered as a QOP option by the server, then a check 394 * is client-side supported ciphers is performed. 395 * 396 * @throws IOException 397 */ 398 private void checkQopSupport(byte[] qopInChallenge, byte[] ciphersInChallenge) 399 throws IOException { 400 401 /* QOP: optional; if multiple, merged earlier */ 402 String qopOptions; 403 404 if (qopInChallenge == null) { 405 qopOptions = "auth"; 406 } else { 407 qopOptions = new String(qopInChallenge, encoding); 408 } 409 410 // process 411 String[] serverQopTokens = new String[3]; 412 byte[] serverQop = parseQop(qopOptions, serverQopTokens, 413 true /* ignore unrecognized tokens */); 414 byte serverAllQop = combineMasks(serverQop); 415 416 switch (findPreferredMask(serverAllQop, qop)) { 417 case 0: 418 throw new SaslException("DIGEST-MD5: No common protection " + 419 "layer between client and server"); 420 421 case NO_PROTECTION: 422 negotiatedQop = "auth"; 423 // buffer sizes not applicable 424 break; 425 426 case INTEGRITY_ONLY_PROTECTION: 427 negotiatedQop = "auth-int"; 428 integrity = true; 429 rawSendSize = sendMaxBufSize - 16; 430 break; 431 432 case PRIVACY_PROTECTION: 433 negotiatedQop = "auth-conf"; 434 privacy = integrity = true; 435 rawSendSize = sendMaxBufSize - 26; 436 checkStrengthSupport(ciphersInChallenge); 437 break; 438 } 439 440 if (logger.isLoggable(Level.FINE)) { 441 logger.log(Level.FINE, "DIGEST61:Raw send size: {0}", 442 rawSendSize); 443 } 444 } 445 446 /** 447 * Processes the 'cipher' digest-challenge directive. This allows the 448 * mechanism to check for client-side support against the list of 449 * supported ciphers send by the server. If no match is found, 450 * the mechanism aborts. 451 * 452 * @throws SaslException If an error is encountered in processing 453 * the cipher digest-challenge directive or if no client-side 454 * support is found. 455 */ 456 private void checkStrengthSupport(byte[] ciphersInChallenge) 457 throws IOException { 458 459 /* CIPHER: required exactly once if qop=auth-conf */ 460 if (ciphersInChallenge == null) { 461 throw new SaslException("DIGEST-MD5: server did not specify " + 462 "cipher to use for 'auth-conf'"); 463 } 464 465 // First determine ciphers that server supports 466 String cipherOptions = new String(ciphersInChallenge, encoding); 467 StringTokenizer parser = new StringTokenizer(cipherOptions, ", \t\n"); 468 int tokenCount = parser.countTokens(); 469 String token = null; 470 byte[] serverCiphers = { UNSET, 471 UNSET, 472 UNSET, 473 UNSET, 474 UNSET }; 475 String[] serverCipherStrs = new String[serverCiphers.length]; 476 477 // Parse ciphers in challenge; mark each that server supports 478 for (int i = 0; i < tokenCount; i++) { 479 token = parser.nextToken(); 480 for (int j = 0; j < CIPHER_TOKENS.length; j++) { 481 if (token.equals(CIPHER_TOKENS[j])) { 482 serverCiphers[j] |= CIPHER_MASKS[j]; 483 serverCipherStrs[j] = token; // keep for replay to server 484 logger.log(Level.FINE, "DIGEST62:Server supports {0}", token); 485 } 486 } 487 } 488 489 // Determine which ciphers are available on client 490 byte[] clntCiphers = getPlatformCiphers(); 491 492 // Take intersection of server and client supported ciphers 493 byte inter = 0; 494 for (int i = 0; i < serverCiphers.length; i++) { 495 serverCiphers[i] &= clntCiphers[i]; 496 inter |= serverCiphers[i]; 497 } 498 499 if (inter == UNSET) { 500 throw new SaslException( 501 "DIGEST-MD5: Client supports none of these cipher suites: " + 502 cipherOptions); 503 } 504 505 // now have a clear picture of user / client; client / server 506 // cipher options. Leverage strength array against what is 507 // supported to choose a cipher. 508 negotiatedCipher = findCipherAndStrength(serverCiphers, serverCipherStrs); 509 510 if (negotiatedCipher == null) { 511 throw new SaslException("DIGEST-MD5: Unable to negotiate " + 512 "a strength level for 'auth-conf'"); 513 } 514 logger.log(Level.FINE, "DIGEST63:Cipher suite: {0}", negotiatedCipher); 515 } 516 517 /** 518 * Steps through the ordered 'strength' array, and compares it with 519 * the 'supportedCiphers' array. The cipher returned represents 520 * the best possible cipher based on the strength preference and the 521 * available ciphers on both the server and client environments. 522 * 523 * @param tokens The array of cipher tokens sent by server 524 * @return The agreed cipher. 525 */ 526 private String findCipherAndStrength(byte[] supportedCiphers, 527 String[] tokens) { 528 byte s; 529 for (int i = 0; i < strength.length; i++) { 530 if ((s=strength[i]) != 0) { 531 for (int j = 0; j < supportedCiphers.length; j++) { 532 533 // If user explicitly requested cipher, then it 534 // must be the one we choose 535 536 if (s == supportedCiphers[j] && 537 (specifiedCipher == null || 538 specifiedCipher.equals(tokens[j]))) { 539 switch (s) { 540 case HIGH_STRENGTH: 541 negotiatedStrength = "high"; 542 break; 543 case MEDIUM_STRENGTH: 544 negotiatedStrength = "medium"; 545 break; 546 case LOW_STRENGTH: 547 negotiatedStrength = "low"; 548 break; 549 } 550 551 return tokens[j]; 552 } 553 } 554 } 555 } 556 557 return null; // none found 558 } 559 560 /** 561 * Returns digest-response suitable for an initial authentication. 562 * 563 * The following are qdstr-val (quoted string values) as per RFC 2831, 564 * which means that any embedded quotes must be escaped. 565 * realm-value 566 * nonce-value 567 * username-value 568 * cnonce-value 569 * authzid-value 570 * @returns <tt>digest-response</tt> in a byte array 571 * @throws SaslException if there is an error generating the 572 * response value or the cnonce value. 573 */ 574 private byte[] generateClientResponse(byte[] charset) throws IOException { 575 576 ByteArrayOutputStream digestResp = new ByteArrayOutputStream(); 577 578 if (useUTF8) { 579 digestResp.write("charset=".getBytes(encoding)); 580 digestResp.write(charset); 581 digestResp.write(','); 582 } 583 584 digestResp.write(("username=\"" + 585 quotedStringValue(username) + "\",").getBytes(encoding)); 586 587 if (negotiatedRealm.length() > 0) { 588 digestResp.write(("realm=\"" + 589 quotedStringValue(negotiatedRealm) + "\",").getBytes(encoding)); 590 } 591 592 digestResp.write("nonce=\"".getBytes(encoding)); 593 writeQuotedStringValue(digestResp, nonce); 594 digestResp.write('"'); 595 digestResp.write(','); 596 597 nonceCount = getNonceCount(nonce); 598 digestResp.write(("nc=" + 599 nonceCountToHex(nonceCount) + ",").getBytes(encoding)); 600 601 cnonce = generateNonce(); 602 digestResp.write("cnonce=\"".getBytes(encoding)); 603 writeQuotedStringValue(digestResp, cnonce); 604 digestResp.write("\",".getBytes(encoding)); 605 digestResp.write(("digest-uri=\"" + digestUri + "\",").getBytes(encoding)); 606 607 digestResp.write("maxbuf=".getBytes(encoding)); 608 digestResp.write(String.valueOf(recvMaxBufSize).getBytes(encoding)); 609 digestResp.write(','); 610 611 try { 612 digestResp.write("response=".getBytes(encoding)); 613 digestResp.write(generateResponseValue("AUTHENTICATE", 614 digestUri, negotiatedQop, username, 615 negotiatedRealm, passwd, nonce, cnonce, 616 nonceCount, authzidBytes)); 617 digestResp.write(','); 618 } catch (Exception e) { 619 throw new SaslException( 620 "DIGEST-MD5: Error generating response value", e); 621 } 622 623 digestResp.write(("qop=" + negotiatedQop).getBytes(encoding)); 624 625 if (negotiatedCipher != null) { 626 digestResp.write((",cipher=\"" + negotiatedCipher + "\"").getBytes(encoding)); 627 } 628 629 if (authzidBytes != null) { 630 digestResp.write(",authzid=\"".getBytes(encoding)); 631 writeQuotedStringValue(digestResp, authzidBytes); 632 digestResp.write("\"".getBytes(encoding)); 633 } 634 635 if (digestResp.size() > MAX_RESPONSE_LENGTH) { 636 throw new SaslException ("DIGEST-MD5: digest-response size too " + 637 "large. Length: " + digestResp.size()); 638 } 639 return digestResp.toByteArray(); 640 } 641 642 643 /** 644 * From RFC 2831, Section 2.1.3: Step Three 645 * [Server] sends a message formatted as follows: 646 * response-auth = "rspauth" "=" response-value 647 * where response-value is calculated as above, using the values sent in 648 * step two, except that if qop is "auth", then A2 is 649 * 650 * A2 = { ":", digest-uri-value } 651 * 652 * And if qop is "auth-int" or "auth-conf" then A2 is 653 * 654 * A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" } 655 */ 656 private void validateResponseValue(byte[] fromServer) throws SaslException { 657 if (fromServer == null) { 658 throw new SaslException("DIGEST-MD5: Authenication failed. " + 659 "Expecting 'rspauth' authentication success message"); 660 } 661 662 try { 663 byte[] expected = generateResponseValue("", 664 digestUri, negotiatedQop, username, negotiatedRealm, 665 passwd, nonce, cnonce, nonceCount, authzidBytes); 666 if (!Arrays.equals(expected, fromServer)) { 667 /* Server's rspauth value does not match */ 668 throw new SaslException( 669 "Server's rspauth value does not match what client expects"); 670 } 671 } catch (NoSuchAlgorithmException e) { 672 throw new SaslException( 673 "Problem generating response value for verification", e); 674 } catch (IOException e) { 675 throw new SaslException( 676 "Problem generating response value for verification", e); 677 } 678 } 679 680 /** 681 * Returns the number of requests (including current request) 682 * that the client has sent in response to nonceValue. 683 * This is 1 the first time nonceValue is seen. 684 * 685 * We don't cache nonce values seen, and we don't support subsequent 686 * authentication, so the value is always 1. 687 */ 688 private static int getNonceCount(byte[] nonceValue) { 689 return 1; 690 } 691 692 private void clearPassword() { 693 if (passwd != null) { 694 for (int i = 0; i < passwd.length; i++) { 695 passwd[i] = 0; 696 } 697 passwd = null; 698 } 699 } 700 }