1/* 2 * Copyright (c) 2003, 2012, 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 26package com.sun.security.sasl.digest; 27 28import java.security.NoSuchAlgorithmException; 29import java.io.ByteArrayOutputStream; 30import java.io.IOException; 31import java.io.UnsupportedEncodingException; 32import java.util.StringTokenizer; 33import java.util.ArrayList; 34import java.util.List; 35import java.util.Map; 36import java.util.Arrays; 37 38import java.util.logging.Level; 39 40import javax.security.sasl.*; 41import javax.security.auth.callback.*; 42 43/** 44 * An implementation of the DIGEST-MD5 server SASL mechanism. 45 * (<a href="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</a>) 46 * <p> 47 * The DIGEST-MD5 SASL mechanism specifies two modes of authentication. 48 * <ul><li>Initial Authentication 49 * <li>Subsequent Authentication - optional, (currently not supported) 50 * </ul> 51 * 52 * Required callbacks: 53 * - RealmCallback 54 * used as key by handler to fetch password 55 * - NameCallback 56 * used as key by handler to fetch password 57 * - PasswordCallback 58 * handler must enter password for username/realm supplied 59 * - AuthorizeCallback 60 * handler must verify that authid/authzids are allowed and set 61 * authorized ID to be the canonicalized authzid (if applicable). 62 * 63 * Environment properties that affect the implementation: 64 * javax.security.sasl.qop: 65 * specifies list of qops; default is "auth"; typically, caller should set 66 * this to "auth, auth-int, auth-conf". 67 * javax.security.sasl.strength 68 * specifies low/medium/high strength of encryption; default is all available 69 * ciphers [high,medium,low]; high means des3 or rc4 (128); medium des or 70 * rc4-56; low is rc4-40. 71 * javax.security.sasl.maxbuf 72 * specifies max receive buf size; default is 65536 73 * javax.security.sasl.sendmaxbuffer 74 * specifies max send buf size; default is 65536 (min of this and client's max 75 * recv size) 76 * 77 * com.sun.security.sasl.digest.utf8: 78 * "true" means to use UTF-8 charset; "false" to use ISO-8859-1 encoding; 79 * default is "true". 80 * com.sun.security.sasl.digest.realm: 81 * space-separated list of realms; default is server name (fqdn parameter) 82 * 83 * @author Rosanna Lee 84 */ 85 86final class DigestMD5Server extends DigestMD5Base implements SaslServer { 87 private static final String MY_CLASS_NAME = DigestMD5Server.class.getName(); 88 89 private static final String UTF8_DIRECTIVE = "charset=utf-8,"; 90 private static final String ALGORITHM_DIRECTIVE = "algorithm=md5-sess"; 91 92 /* 93 * Always expect nonce count value to be 1 because we support only 94 * initial authentication. 95 */ 96 private static final int NONCE_COUNT_VALUE = 1; 97 98 /* "true" means use UTF8; "false" ISO 8859-1; default is "true" */ 99 private static final String UTF8_PROPERTY = 100 "com.sun.security.sasl.digest.utf8"; 101 102 /* List of space-separated realms used for authentication */ 103 private static final String REALM_PROPERTY = 104 "com.sun.security.sasl.digest.realm"; 105 106 /* Directives encountered in responses sent by the client. */ 107 private static final String[] DIRECTIVE_KEY = { 108 "username", // exactly once 109 "realm", // exactly once if sent by server 110 "nonce", // exactly once 111 "cnonce", // exactly once 112 "nonce-count", // atmost once; default is 00000001 113 "qop", // atmost once; default is "auth" 114 "digest-uri", // atmost once; (default?) 115 "response", // exactly once 116 "maxbuf", // atmost once; default is 65536 117 "charset", // atmost once; default is ISO-8859-1 118 "cipher", // exactly once if qop is "auth-conf" 119 "authzid", // atmost once; default is none 120 "auth-param", // >= 0 times (ignored) 121 }; 122 123 /* Indices into DIRECTIVE_KEY */ 124 private static final int USERNAME = 0; 125 private static final int REALM = 1; 126 private static final int NONCE = 2; 127 private static final int CNONCE = 3; 128 private static final int NONCE_COUNT = 4; 129 private static final int QOP = 5; 130 private static final int DIGEST_URI = 6; 131 private static final int RESPONSE = 7; 132 private static final int MAXBUF = 8; 133 private static final int CHARSET = 9; 134 private static final int CIPHER = 10; 135 private static final int AUTHZID = 11; 136 private static final int AUTH_PARAM = 12; 137 138 /* Server-generated/supplied information */ 139 private String specifiedQops; 140 private byte[] myCiphers; 141 private List<String> serverRealms; 142 143 DigestMD5Server(String protocol, String serverName, Map<String, ?> props, 144 CallbackHandler cbh) throws SaslException { 145 super(props, MY_CLASS_NAME, 1, 146 protocol + "/" + (serverName==null?"*":serverName), 147 cbh); 148 149 serverRealms = new ArrayList<String>(); 150 151 useUTF8 = true; // default 152 153 if (props != null) { 154 specifiedQops = (String) props.get(Sasl.QOP); 155 if ("false".equals((String) props.get(UTF8_PROPERTY))) { 156 useUTF8 = false; 157 logger.log(Level.FINE, "DIGEST80:Server supports ISO-Latin-1"); 158 } 159 160 String realms = (String) props.get(REALM_PROPERTY); 161 if (realms != null) { 162 StringTokenizer parser = new StringTokenizer(realms, ", \t\n"); 163 int tokenCount = parser.countTokens(); 164 String token = null; 165 for (int i = 0; i < tokenCount; i++) { 166 token = parser.nextToken(); 167 logger.log(Level.FINE, "DIGEST81:Server supports realm {0}", 168 token); 169 serverRealms.add(token); 170 } 171 } 172 } 173 174 encoding = (useUTF8 ? "UTF8" : "8859_1"); 175 176 // By default, use server name as realm 177 if (serverRealms.isEmpty()) { 178 if (serverName == null) { 179 throw new SaslException( 180 "A realm must be provided in props or serverName"); 181 } else { 182 serverRealms.add(serverName); 183 } 184 } 185 } 186 187 public byte[] evaluateResponse(byte[] response) throws SaslException { 188 if (response.length > MAX_RESPONSE_LENGTH) { 189 throw new SaslException( 190 "DIGEST-MD5: Invalid digest response length. Got: " + 191 response.length + " Expected < " + MAX_RESPONSE_LENGTH); 192 } 193 194 byte[] challenge; 195 switch (step) { 196 case 1: 197 if (response.length != 0) { 198 throw new SaslException( 199 "DIGEST-MD5 must not have an initial response"); 200 } 201 202 /* Generate first challenge */ 203 String supportedCiphers = null; 204 if ((allQop&PRIVACY_PROTECTION) != 0) { 205 myCiphers = getPlatformCiphers(); 206 StringBuilder sb = new StringBuilder(); 207 208 // myCipher[i] is a byte that indicates whether CIPHER_TOKENS[i] 209 // is supported 210 for (int i = 0; i < CIPHER_TOKENS.length; i++) { 211 if (myCiphers[i] != 0) { 212 if (sb.length() > 0) { 213 sb.append(','); 214 } 215 sb.append(CIPHER_TOKENS[i]); 216 } 217 } 218 supportedCiphers = sb.toString(); 219 } 220 221 try { 222 challenge = generateChallenge(serverRealms, specifiedQops, 223 supportedCiphers); 224 225 step = 3; 226 return challenge; 227 } catch (UnsupportedEncodingException e) { 228 throw new SaslException( 229 "DIGEST-MD5: Error encoding challenge", e); 230 } catch (IOException e) { 231 throw new SaslException( 232 "DIGEST-MD5: Error generating challenge", e); 233 } 234 235 // Step 2 is performed by client 236 237 case 3: 238 /* Validates client's response and generate challenge: 239 * response-auth = "rspauth" "=" response-value 240 */ 241 try { 242 byte[][] responseVal = parseDirectives(response, DIRECTIVE_KEY, 243 null, REALM); 244 challenge = validateClientResponse(responseVal); 245 } catch (SaslException e) { 246 throw e; 247 } catch (UnsupportedEncodingException e) { 248 throw new SaslException( 249 "DIGEST-MD5: Error validating client response", e); 250 } finally { 251 step = 0; // Set to invalid state 252 } 253 254 completed = true; 255 256 /* Initialize SecurityCtx implementation */ 257 if (integrity && privacy) { 258 secCtx = new DigestPrivacy(false /* not client */); 259 } else if (integrity) { 260 secCtx = new DigestIntegrity(false /* not client */); 261 } 262 263 return challenge; 264 265 default: 266 // No other possible state 267 throw new SaslException("DIGEST-MD5: Server at illegal state"); 268 } 269 } 270 271 /** 272 * Generates challenge to be sent to client. 273 * digest-challenge = 274 * 1#( realm | nonce | qop-options | stale | maxbuf | charset 275 * algorithm | cipher-opts | auth-param ) 276 * 277 * realm = "realm" "=" <"> realm-value <"> 278 * realm-value = qdstr-val 279 * nonce = "nonce" "=" <"> nonce-value <"> 280 * nonce-value = qdstr-val 281 * qop-options = "qop" "=" <"> qop-list <"> 282 * qop-list = 1#qop-value 283 * qop-value = "auth" | "auth-int" | "auth-conf" | 284 * token 285 * stale = "stale" "=" "true" 286 * maxbuf = "maxbuf" "=" maxbuf-value 287 * maxbuf-value = 1*DIGIT 288 * charset = "charset" "=" "utf-8" 289 * algorithm = "algorithm" "=" "md5-sess" 290 * cipher-opts = "cipher" "=" <"> 1#cipher-value <"> 291 * cipher-value = "3des" | "des" | "rc4-40" | "rc4" | 292 * "rc4-56" | token 293 * auth-param = token "=" ( token | quoted-string ) 294 */ 295 private byte[] generateChallenge(List<String> realms, String qopStr, 296 String cipherStr) throws UnsupportedEncodingException, IOException { 297 ByteArrayOutputStream out = new ByteArrayOutputStream(); 298 299 // Realms (>= 0) 300 for (int i = 0; realms != null && i < realms.size(); i++) { 301 out.write("realm=\"".getBytes(encoding)); 302 writeQuotedStringValue(out, realms.get(i).getBytes(encoding)); 303 out.write('"'); 304 out.write(','); 305 } 306 307 // Nonce - required (1) 308 out.write(("nonce=\"").getBytes(encoding)); 309 nonce = generateNonce(); 310 writeQuotedStringValue(out, nonce); 311 out.write('"'); 312 out.write(','); 313 314 // QOP - optional (1) [default: auth] 315 // qop="auth,auth-conf,auth-int" 316 if (qopStr != null) { 317 out.write(("qop=\"").getBytes(encoding)); 318 // Check for quotes in case of non-standard qop options 319 writeQuotedStringValue(out, qopStr.getBytes(encoding)); 320 out.write('"'); 321 out.write(','); 322 } 323 324 // maxbuf - optional (1) [default: 65536] 325 if (recvMaxBufSize != DEFAULT_MAXBUF) { 326 out.write(("maxbuf=\"" + recvMaxBufSize + "\",").getBytes(encoding)); 327 } 328 329 // charset - optional (1) [default: ISO 8859_1] 330 if (useUTF8) { 331 out.write(UTF8_DIRECTIVE.getBytes(encoding)); 332 } 333 334 if (cipherStr != null) { 335 out.write("cipher=\"".getBytes(encoding)); 336 // Check for quotes in case of custom ciphers 337 writeQuotedStringValue(out, cipherStr.getBytes(encoding)); 338 out.write('"'); 339 out.write(','); 340 } 341 342 // algorithm - required (1) 343 out.write(ALGORITHM_DIRECTIVE.getBytes(encoding)); 344 345 return out.toByteArray(); 346 } 347 348 /** 349 * Validates client's response. 350 * digest-response = 1#( username | realm | nonce | cnonce | 351 * nonce-count | qop | digest-uri | response | 352 * maxbuf | charset | cipher | authzid | 353 * auth-param ) 354 * 355 * username = "username" "=" <"> username-value <"> 356 * username-value = qdstr-val 357 * cnonce = "cnonce" "=" <"> cnonce-value <"> 358 * cnonce-value = qdstr-val 359 * nonce-count = "nc" "=" nc-value 360 * nc-value = 8LHEX 361 * qop = "qop" "=" qop-value 362 * digest-uri = "digest-uri" "=" <"> digest-uri-value <"> 363 * digest-uri-value = serv-type "/" host [ "/" serv-name ] 364 * serv-type = 1*ALPHA 365 * host = 1*( ALPHA | DIGIT | "-" | "." ) 366 * serv-name = host 367 * response = "response" "=" response-value 368 * response-value = 32LHEX 369 * LHEX = "0" | "1" | "2" | "3" | 370 * "4" | "5" | "6" | "7" | 371 * "8" | "9" | "a" | "b" | 372 * "c" | "d" | "e" | "f" 373 * cipher = "cipher" "=" cipher-value 374 * authzid = "authzid" "=" <"> authzid-value <"> 375 * authzid-value = qdstr-val 376 * sets: 377 * negotiatedQop 378 * negotiatedCipher 379 * negotiatedRealm 380 * negotiatedStrength 381 * digestUri (checked and set to clients to account for case diffs) 382 * sendMaxBufSize 383 * authzid (gotten from callback) 384 * @return response-value ('rspauth') for client to validate 385 */ 386 private byte[] validateClientResponse(byte[][] responseVal) 387 throws SaslException, UnsupportedEncodingException { 388 389 /* CHARSET: optional atmost once */ 390 if (responseVal[CHARSET] != null) { 391 // The client should send this directive only if the server has 392 // indicated it supports UTF-8. 393 if (!useUTF8 || 394 !"utf-8".equals(new String(responseVal[CHARSET], encoding))) { 395 throw new SaslException("DIGEST-MD5: digest response format " + 396 "violation. Incompatible charset value: " + 397 new String(responseVal[CHARSET])); 398 } 399 } 400 401 // maxbuf: atmost once 402 int clntMaxBufSize = 403 (responseVal[MAXBUF] == null) ? DEFAULT_MAXBUF 404 : Integer.parseInt(new String(responseVal[MAXBUF], encoding)); 405 406 // Max send buf size is min of client's max recv buf size and 407 // server's max send buf size 408 sendMaxBufSize = ((sendMaxBufSize == 0) ? clntMaxBufSize : 409 Math.min(sendMaxBufSize, clntMaxBufSize)); 410 411 /* username: exactly once */ 412 String username; 413 if (responseVal[USERNAME] != null) { 414 username = new String(responseVal[USERNAME], encoding); 415 logger.log(Level.FINE, "DIGEST82:Username: {0}", username); 416 } else { 417 throw new SaslException("DIGEST-MD5: digest response format " + 418 "violation. Missing username."); 419 } 420 421 /* realm: exactly once if sent by server */ 422 negotiatedRealm = ((responseVal[REALM] != null) ? 423 new String(responseVal[REALM], encoding) : ""); 424 logger.log(Level.FINE, "DIGEST83:Client negotiated realm: {0}", 425 negotiatedRealm); 426 427 if (!serverRealms.contains(negotiatedRealm)) { 428 // Server had sent at least one realm 429 // Check that response is one of these 430 throw new SaslException("DIGEST-MD5: digest response format " + 431 "violation. Nonexistent realm: " + negotiatedRealm); 432 } 433 // Else, client specified realm was one of server's or server had none 434 435 /* nonce: exactly once */ 436 if (responseVal[NONCE] == null) { 437 throw new SaslException("DIGEST-MD5: digest response format " + 438 "violation. Missing nonce."); 439 } 440 byte[] nonceFromClient = responseVal[NONCE]; 441 if (!Arrays.equals(nonceFromClient, nonce)) { 442 throw new SaslException("DIGEST-MD5: digest response format " + 443 "violation. Mismatched nonce."); 444 } 445 446 /* cnonce: exactly once */ 447 if (responseVal[CNONCE] == null) { 448 throw new SaslException("DIGEST-MD5: digest response format " + 449 "violation. Missing cnonce."); 450 } 451 byte[] cnonce = responseVal[CNONCE]; 452 453 /* nonce-count: atmost once */ 454 if (responseVal[NONCE_COUNT] != null && 455 NONCE_COUNT_VALUE != Integer.parseInt( 456 new String(responseVal[NONCE_COUNT], encoding), 16)) { 457 throw new SaslException("DIGEST-MD5: digest response format " + 458 "violation. Nonce count does not match: " + 459 new String(responseVal[NONCE_COUNT])); 460 } 461 462 /* qop: atmost once; default is "auth" */ 463 negotiatedQop = ((responseVal[QOP] != null) ? 464 new String(responseVal[QOP], encoding) : "auth"); 465 466 logger.log(Level.FINE, "DIGEST84:Client negotiated qop: {0}", 467 negotiatedQop); 468 469 // Check that QOP is one sent by server 470 byte cQop; 471 switch (negotiatedQop) { 472 case "auth": 473 cQop = NO_PROTECTION; 474 break; 475 case "auth-int": 476 cQop = INTEGRITY_ONLY_PROTECTION; 477 integrity = true; 478 rawSendSize = sendMaxBufSize - 16; 479 break; 480 case "auth-conf": 481 cQop = PRIVACY_PROTECTION; 482 integrity = privacy = true; 483 rawSendSize = sendMaxBufSize - 26; 484 break; 485 default: 486 throw new SaslException("DIGEST-MD5: digest response format " + 487 "violation. Invalid QOP: " + negotiatedQop); 488 } 489 if ((cQop&allQop) == 0) { 490 throw new SaslException("DIGEST-MD5: server does not support " + 491 " qop: " + negotiatedQop); 492 } 493 494 if (privacy) { 495 negotiatedCipher = ((responseVal[CIPHER] != null) ? 496 new String(responseVal[CIPHER], encoding) : null); 497 if (negotiatedCipher == null) { 498 throw new SaslException("DIGEST-MD5: digest response format " + 499 "violation. No cipher specified."); 500 } 501 502 int foundCipher = -1; 503 logger.log(Level.FINE, "DIGEST85:Client negotiated cipher: {0}", 504 negotiatedCipher); 505 506 // Check that cipher is one that we offered 507 for (int j = 0; j < CIPHER_TOKENS.length; j++) { 508 if (negotiatedCipher.equals(CIPHER_TOKENS[j]) && 509 myCiphers[j] != 0) { 510 foundCipher = j; 511 break; 512 } 513 } 514 if (foundCipher == -1) { 515 throw new SaslException("DIGEST-MD5: server does not " + 516 "support cipher: " + negotiatedCipher); 517 } 518 // Set negotiatedStrength 519 if ((CIPHER_MASKS[foundCipher]&HIGH_STRENGTH) != 0) { 520 negotiatedStrength = "high"; 521 } else if ((CIPHER_MASKS[foundCipher]&MEDIUM_STRENGTH) != 0) { 522 negotiatedStrength = "medium"; 523 } else { 524 // assume default low 525 negotiatedStrength = "low"; 526 } 527 528 logger.log(Level.FINE, "DIGEST86:Negotiated strength: {0}", 529 negotiatedStrength); 530 } 531 532 // atmost once 533 String digestUriFromResponse = ((responseVal[DIGEST_URI]) != null ? 534 new String(responseVal[DIGEST_URI], encoding) : null); 535 536 if (digestUriFromResponse != null) { 537 logger.log(Level.FINE, "DIGEST87:digest URI: {0}", 538 digestUriFromResponse); 539 } 540 541 // serv-type "/" host [ "/" serv-name ] 542 // e.g.: smtp/mail3.example.com/example.com 543 // e.g.: ftp/ftp.example.com 544 // e.g.: ldap/ldapserver.example.com 545 546 // host should match one of service's configured service names 547 // Check against digest URI that mech was created with 548 549 if (uriMatches(digestUri, digestUriFromResponse)) { 550 digestUri = digestUriFromResponse; // account for case-sensitive diffs 551 } else { 552 throw new SaslException("DIGEST-MD5: digest response format " + 553 "violation. Mismatched URI: " + digestUriFromResponse + 554 "; expecting: " + digestUri); 555 } 556 557 // response: exactly once 558 byte[] responseFromClient = responseVal[RESPONSE]; 559 if (responseFromClient == null) { 560 throw new SaslException("DIGEST-MD5: digest response format " + 561 " violation. Missing response."); 562 } 563 564 // authzid: atmost once 565 byte[] authzidBytes; 566 String authzidFromClient = ((authzidBytes=responseVal[AUTHZID]) != null? 567 new String(authzidBytes, encoding) : username); 568 569 if (authzidBytes != null) { 570 logger.log(Level.FINE, "DIGEST88:Authzid: {0}", 571 new String(authzidBytes)); 572 } 573 574 // Ignore auth-param 575 576 // Get password need to generate verifying response 577 char[] passwd; 578 try { 579 // Realm and Name callbacks are used to provide info 580 RealmCallback rcb = new RealmCallback("DIGEST-MD5 realm: ", 581 negotiatedRealm); 582 NameCallback ncb = new NameCallback("DIGEST-MD5 authentication ID: ", 583 username); 584 585 // PasswordCallback is used to collect info 586 PasswordCallback pcb = 587 new PasswordCallback("DIGEST-MD5 password: ", false); 588 589 cbh.handle(new Callback[] {rcb, ncb, pcb}); 590 passwd = pcb.getPassword(); 591 pcb.clearPassword(); 592 593 } catch (UnsupportedCallbackException e) { 594 throw new SaslException( 595 "DIGEST-MD5: Cannot perform callback to acquire password", e); 596 597 } catch (IOException e) { 598 throw new SaslException( 599 "DIGEST-MD5: IO error acquiring password", e); 600 } 601 602 if (passwd == null) { 603 throw new SaslException( 604 "DIGEST-MD5: cannot acquire password for " + username + 605 " in realm : " + negotiatedRealm); 606 } 607 608 try { 609 // Validate response value sent by client 610 byte[] expectedResponse; 611 612 try { 613 expectedResponse = generateResponseValue("AUTHENTICATE", 614 digestUri, negotiatedQop, username, negotiatedRealm, 615 passwd, nonce /* use own nonce */, 616 cnonce, NONCE_COUNT_VALUE, authzidBytes); 617 618 } catch (NoSuchAlgorithmException e) { 619 throw new SaslException( 620 "DIGEST-MD5: problem duplicating client response", e); 621 } catch (IOException e) { 622 throw new SaslException( 623 "DIGEST-MD5: problem duplicating client response", e); 624 } 625 626 if (!Arrays.equals(responseFromClient, expectedResponse)) { 627 throw new SaslException("DIGEST-MD5: digest response format " + 628 "violation. Mismatched response."); 629 } 630 631 // Ensure that authzid mapping is OK 632 try { 633 AuthorizeCallback acb = 634 new AuthorizeCallback(username, authzidFromClient); 635 cbh.handle(new Callback[]{acb}); 636 637 if (acb.isAuthorized()) { 638 authzid = acb.getAuthorizedID(); 639 } else { 640 throw new SaslException("DIGEST-MD5: " + username + 641 " is not authorized to act as " + authzidFromClient); 642 } 643 } catch (SaslException e) { 644 throw e; 645 } catch (UnsupportedCallbackException e) { 646 throw new SaslException( 647 "DIGEST-MD5: Cannot perform callback to check authzid", e); 648 } catch (IOException e) { 649 throw new SaslException( 650 "DIGEST-MD5: IO error checking authzid", e); 651 } 652 653 return generateResponseAuth(username, passwd, cnonce, 654 NONCE_COUNT_VALUE, authzidBytes); 655 } finally { 656 // Clear password 657 for (int i = 0; i < passwd.length; i++) { 658 passwd[i] = 0; 659 } 660 } 661 } 662 663 private static boolean uriMatches(String thisUri, String incomingUri) { 664 // Full match 665 if (thisUri.equalsIgnoreCase(incomingUri)) { 666 return true; 667 } 668 // Unbound match 669 if (thisUri.endsWith("/*")) { 670 int protoAndSlash = thisUri.length() - 1; 671 String thisProtoAndSlash = thisUri.substring(0, protoAndSlash); 672 String incomingProtoAndSlash = incomingUri.substring(0, protoAndSlash); 673 return thisProtoAndSlash.equalsIgnoreCase(incomingProtoAndSlash); 674 } 675 return false; 676 } 677 678 /** 679 * Server sends a message formatted as follows: 680 * response-auth = "rspauth" "=" response-value 681 * where response-value is calculated as above, using the values sent in 682 * step two, except that if qop is "auth", then A2 is 683 * 684 * A2 = { ":", digest-uri-value } 685 * 686 * And if qop is "auth-int" or "auth-conf" then A2 is 687 * 688 * A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" } 689 * 690 * Clears password afterwards. 691 */ 692 private byte[] generateResponseAuth(String username, char[] passwd, 693 byte[] cnonce, int nonceCount, byte[] authzidBytes) throws SaslException { 694 695 // Construct response value 696 697 try { 698 byte[] responseValue = generateResponseValue("", 699 digestUri, negotiatedQop, username, negotiatedRealm, 700 passwd, nonce, cnonce, nonceCount, authzidBytes); 701 702 byte[] challenge = new byte[responseValue.length + 8]; 703 System.arraycopy("rspauth=".getBytes(encoding), 0, challenge, 0, 8); 704 System.arraycopy(responseValue, 0, challenge, 8, 705 responseValue.length ); 706 707 return challenge; 708 709 } catch (NoSuchAlgorithmException e) { 710 throw new SaslException("DIGEST-MD5: problem generating response", e); 711 } catch (IOException e) { 712 throw new SaslException("DIGEST-MD5: problem generating response", e); 713 } 714 } 715 716 public String getAuthorizationID() { 717 if (completed) { 718 return authzid; 719 } else { 720 throw new IllegalStateException( 721 "DIGEST-MD5 server negotiation not complete"); 722 } 723 } 724} 725