1 /* 2 * Copyright (c) 2000, 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 26 package com.sun.security.sasl.digest; 27 28 import java.util.Map; 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.logging.Level; 32 import java.math.BigInteger; 33 import java.util.Random; 34 35 import java.io.ByteArrayOutputStream; 36 import java.io.UnsupportedEncodingException; 37 import java.io.IOException; 38 39 import java.security.MessageDigest; 40 import java.security.NoSuchAlgorithmException; 41 import java.security.InvalidKeyException; 42 import java.security.spec.KeySpec; 43 import java.security.spec.InvalidKeySpecException; 44 import java.security.InvalidAlgorithmParameterException; 45 46 import javax.crypto.Cipher; 47 import javax.crypto.SecretKey; 48 import javax.crypto.Mac; 49 import javax.crypto.SecretKeyFactory; 50 import javax.crypto.NoSuchPaddingException; 51 import javax.crypto.IllegalBlockSizeException; 52 import javax.crypto.spec.IvParameterSpec; 53 import javax.crypto.spec.SecretKeySpec; 54 import javax.crypto.spec.DESKeySpec; 55 import javax.crypto.spec.DESedeKeySpec; 56 57 import javax.security.sasl.*; 58 import com.sun.security.sasl.util.AbstractSaslImpl; 59 60 import javax.security.auth.callback.CallbackHandler; 61 62 /** 63 * Utility class for DIGEST-MD5 mechanism. Provides utility methods 64 * and contains two inner classes which implement the SecurityCtx 65 * interface. The inner classes provide the funtionality to allow 66 * for quality-of-protection (QOP) with integrity checking and 67 * privacy. 68 * 69 * @author Jonathan Bruce 70 * @author Rosanna Lee 71 */ 72 abstract class DigestMD5Base extends AbstractSaslImpl { 73 /* ------------------------- Constants ------------------------ */ 74 75 // Used for logging 76 private static final String DI_CLASS_NAME = DigestIntegrity.class.getName(); 77 private static final String DP_CLASS_NAME = DigestPrivacy.class.getName(); 78 79 /* Constants - defined in RFC2831 */ 80 protected static final int MAX_CHALLENGE_LENGTH = 2048; 81 protected static final int MAX_RESPONSE_LENGTH = 4096; 82 protected static final int DEFAULT_MAXBUF = 65536; 83 84 /* Supported ciphers for 'auth-conf' */ 85 protected static final int DES3 = 0; 86 protected static final int RC4 = 1; 87 protected static final int DES = 2; 88 protected static final int RC4_56 = 3; 89 protected static final int RC4_40 = 4; 90 protected static final String[] CIPHER_TOKENS = { "3des", 91 "rc4", 92 "des", 93 "rc4-56", 94 "rc4-40" }; 95 private static final String[] JCE_CIPHER_NAME = { 96 "DESede/CBC/NoPadding", 97 "RC4", 98 "DES/CBC/NoPadding", 99 }; 100 101 /* 102 * If QOP is set to 'auth-conf', a DIGEST-MD5 mechanism must have 103 * support for the DES and Triple DES cipher algorithms (optionally, 104 * support for RC4 [128/56/40 bit keys] ciphers) to provide for 105 * confidentiality. See RFC 2831 for details. This implementation 106 * provides support for DES, Triple DES and RC4 ciphers. 107 * 108 * The value of strength effects the strength of cipher used. The mappings 109 * of 'high', 'medium', and 'low' give the following behaviour. 110 * 111 * HIGH_STRENGTH - Triple DES 112 * - RC4 (128bit) 113 * MEDIUM_STRENGTH - DES 114 * - RC4 (56bit) 115 * LOW_SRENGTH - RC4 (40bit) 116 */ 117 protected static final byte DES_3_STRENGTH = HIGH_STRENGTH; 118 protected static final byte RC4_STRENGTH = HIGH_STRENGTH; 119 protected static final byte DES_STRENGTH = MEDIUM_STRENGTH; 120 protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH; 121 protected static final byte RC4_40_STRENGTH = LOW_STRENGTH; 122 protected static final byte UNSET = (byte)0; 123 protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH, 124 RC4_STRENGTH, 125 DES_STRENGTH, 126 RC4_56_STRENGTH, 127 RC4_40_STRENGTH }; 128 129 private static final String SECURITY_LAYER_MARKER = 130 ":00000000000000000000000000000000"; 131 132 protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 133 134 /* ------------------- Variable Fields ----------------------- */ 135 136 /* Used to track progress of authentication; step numbers from RFC 2831 */ 137 protected int step; 138 139 /* Used to get username/password, choose realm for client */ 140 /* Used to obtain authorization, pw info, canonicalized authzid for server */ 141 protected CallbackHandler cbh; 142 143 protected SecurityCtx secCtx; 144 protected byte[] H_A1; // component of response-value 145 146 protected byte[] nonce; // server generated nonce 147 148 /* Variables set when parsing directives in digest challenge/response. */ 149 protected String negotiatedStrength; 150 protected String negotiatedCipher; 151 protected String negotiatedQop; 152 protected String negotiatedRealm; 153 protected boolean useUTF8 = false; 154 protected String encoding = "8859_1"; // default unless server specifies utf-8 155 156 protected String digestUri; 157 protected String authzid; // authzid or canonicalized authzid 158 159 /** 160 * Constucts an instance of DigestMD5Base. Calls super constructor 161 * to parse properties for mechanism. 162 * 163 * @param props A map of property/value pairs 164 * @param className name of class to use for logging 165 * @param firstStep number of first step in authentication state machine 166 * @param digestUri digestUri used in authentication 167 * @param cbh callback handler used to get info required for auth 168 * 169 * @throws SaslException If invalid value found in props. 170 */ 171 protected DigestMD5Base(Map<String, ?> props, String className, 172 int firstStep, String digestUri, CallbackHandler cbh) 173 throws SaslException { 174 super(props, className); // sets QOP, STENGTH and BUFFER_SIZE 175 176 step = firstStep; 177 this.digestUri = digestUri; 178 this.cbh = cbh; 179 } 180 181 /** 182 * Retrieves the SASL mechanism IANA name. 183 * 184 * @return The String "DIGEST-MD5" 185 */ 186 public String getMechanismName() { 187 return "DIGEST-MD5"; 188 } 189 190 /** 191 * Unwrap the incoming message using the wrap method of the secCtx object 192 * instance. 193 * 194 * @param incoming The byte array containing the incoming bytes. 195 * @param start The offset from which to read the byte array. 196 * @param len The number of bytes to read from the offset. 197 * @return The unwrapped message according to either the integrity or 198 * privacy quality-of-protection specifications. 199 * @throws SaslException if an error occurs when unwrapping the incoming 200 * message 201 */ 202 public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException { 203 if (!completed) { 204 throw new IllegalStateException( 205 "DIGEST-MD5 authentication not completed"); 206 } 207 208 if (secCtx == null) { 209 throw new IllegalStateException( 210 "Neither integrity nor privacy was negotiated"); 211 } 212 213 return (secCtx.unwrap(incoming, start, len)); 214 } 215 216 /** 217 * Wrap outgoing bytes using the wrap method of the secCtx object 218 * instance. 219 * 220 * @param outgoing The byte array containing the outgoing bytes. 221 * @param start The offset from which to read the byte array. 222 * @param len The number of bytes to read from the offset. 223 * @return The wrapped message according to either the integrity or 224 * privacy quality-of-protection specifications. 225 * @throws SaslException if an error occurs when wrapping the outgoing 226 * message 227 */ 228 public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException { 229 if (!completed) { 230 throw new IllegalStateException( 231 "DIGEST-MD5 authentication not completed"); 232 } 233 234 if (secCtx == null) { 235 throw new IllegalStateException( 236 "Neither integrity nor privacy was negotiated"); 237 } 238 239 return (secCtx.wrap(outgoing, start, len)); 240 } 241 242 public void dispose() throws SaslException { 243 if (secCtx != null) { 244 secCtx = null; 245 } 246 } 247 248 public Object getNegotiatedProperty(String propName) { 249 if (completed) { 250 if (propName.equals(Sasl.STRENGTH)) { 251 return negotiatedStrength; 252 } else if (propName.equals(Sasl.BOUND_SERVER_NAME)) { 253 return digestUri.substring(digestUri.indexOf('/') + 1); 254 } else { 255 return super.getNegotiatedProperty(propName); 256 } 257 } else { 258 throw new IllegalStateException( 259 "DIGEST-MD5 authentication not completed"); 260 } 261 } 262 263 /* ----------------- Digest-MD5 utilities ---------------- */ 264 /** 265 * Generate random-string used for digest-response. 266 * This method uses Random to get random bytes and then 267 * base64 encodes the bytes. Could also use binaryToHex() but this 268 * is slightly faster and a more compact representation of the same info. 269 * @return A non-null byte array containing the nonce value for the 270 * digest challenge or response. 271 * Could use SecureRandom to be more secure but it is very slow. 272 */ 273 274 /** This array maps the characters to their 6 bit values */ 275 private final static char pem_array[] = { 276 // 0 1 2 3 4 5 6 7 277 'A','B','C','D','E','F','G','H', // 0 278 'I','J','K','L','M','N','O','P', // 1 279 'Q','R','S','T','U','V','W','X', // 2 280 'Y','Z','a','b','c','d','e','f', // 3 281 'g','h','i','j','k','l','m','n', // 4 282 'o','p','q','r','s','t','u','v', // 5 283 'w','x','y','z','0','1','2','3', // 6 284 '4','5','6','7','8','9','+','/' // 7 285 }; 286 287 // Make sure that this is a multiple of 3 288 private static final int RAW_NONCE_SIZE = 30; 289 290 // Base 64 encoding turns each 3 bytes into 4 291 private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3; 292 293 protected static final byte[] generateNonce() { 294 295 // SecureRandom random = new SecureRandom(); 296 Random random = new Random(); 297 byte[] randomData = new byte[RAW_NONCE_SIZE]; 298 random.nextBytes(randomData); 299 300 byte[] nonce = new byte[ENCODED_NONCE_SIZE]; 301 302 // Base64-encode bytes 303 byte a, b, c; 304 int j = 0; 305 for (int i = 0; i < randomData.length; i += 3) { 306 a = randomData[i]; 307 b = randomData[i+1]; 308 c = randomData[i+2]; 309 nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]); 310 nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); 311 nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); 312 nonce[j++] = (byte)(pem_array[c & 0x3F]); 313 } 314 315 return nonce; 316 317 // %%% For testing using RFC 2831 example, uncomment the following 2 lines 318 // System.out.println("!!!Using RFC 2831's cnonce for testing!!!"); 319 // return "OA6MHXh6VqTrRk".getBytes(); 320 } 321 322 /** 323 * Checks if a byte[] contains characters that must be quoted 324 * and write the resulting, possibly escaped, characters to out. 325 */ 326 protected static void writeQuotedStringValue(ByteArrayOutputStream out, 327 byte[] buf) { 328 329 int len = buf.length; 330 byte ch; 331 for (int i = 0; i < len; i++) { 332 ch = buf[i]; 333 if (needEscape((char)ch)) { 334 out.write('\\'); 335 } 336 out.write(ch); 337 } 338 } 339 340 // See Section 7.2 of RFC 2831; double-quote character is not allowed 341 // unless escaped; also escape the escape character and CTL chars except LWS 342 private static boolean needEscape(String str) { 343 int len = str.length(); 344 for (int i = 0; i < len; i++) { 345 if (needEscape(str.charAt(i))) { 346 return true; 347 } 348 } 349 return false; 350 } 351 352 // Determines whether a character needs to be escaped in a quoted string 353 private static boolean needEscape(char ch) { 354 return ch == '"' || // escape char 355 ch == '\\' || // quote 356 ch == 127 || // DEL 357 358 // 0 <= ch <= 31 except CR, HT and LF 359 (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10); 360 } 361 362 protected static String quotedStringValue(String str) { 363 if (needEscape(str)) { 364 int len = str.length(); 365 char[] buf = new char[len+len]; 366 int j = 0; 367 char ch; 368 for (int i = 0; i < len; i++) { 369 ch = str.charAt(i); 370 if (needEscape(ch)) { 371 buf[j++] = '\\'; 372 } 373 buf[j++] = ch; 374 } 375 return new String(buf, 0, j); 376 } else { 377 return str; 378 } 379 } 380 381 /** 382 * Convert a byte array to hexadecimal string. 383 * 384 * @param a non-null byte array 385 * @return a non-null String contain the HEX value 386 */ 387 protected byte[] binaryToHex(byte[] digest) throws 388 UnsupportedEncodingException { 389 390 StringBuilder digestString = new StringBuilder(); 391 392 for (int i = 0; i < digest.length; i ++) { 393 if ((digest[i] & 0x000000ff) < 0x10) { 394 digestString.append("0"+ 395 Integer.toHexString(digest[i] & 0x000000ff)); 396 } else { 397 digestString.append( 398 Integer.toHexString(digest[i] & 0x000000ff)); 399 } 400 } 401 return digestString.toString().getBytes(encoding); 402 } 403 404 /** 405 * Used to convert username-value, passwd or realm to 8859_1 encoding 406 * if all chars in string are within the 8859_1 (Latin 1) encoding range. 407 * 408 * @param a non-null String 409 * @return a non-nuill byte array containing the correct character encoding 410 * for username, paswd or realm. 411 */ 412 protected byte[] stringToByte_8859_1(String str) throws SaslException { 413 414 char[] buffer = str.toCharArray(); 415 416 try { 417 if (useUTF8) { 418 for( int i = 0; i< buffer.length; i++ ) { 419 if( buffer[i] > '\u00FF' ) { 420 return str.getBytes("UTF8"); 421 } 422 } 423 } 424 return str.getBytes("8859_1"); 425 } catch (UnsupportedEncodingException e) { 426 throw new SaslException( 427 "cannot encode string in UTF8 or 8859-1 (Latin-1)", e); 428 } 429 } 430 431 protected static byte[] getPlatformCiphers() { 432 byte[] ciphers = new byte[CIPHER_TOKENS.length]; 433 434 for (int i = 0; i < JCE_CIPHER_NAME.length; i++) { 435 try { 436 // Checking whether the transformation is available from the 437 // current installed providers. 438 Cipher.getInstance(JCE_CIPHER_NAME[i]); 439 440 logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]); 441 ciphers[i] |= CIPHER_MASKS[i]; 442 } catch (NoSuchAlgorithmException e) { 443 // no implementation found for requested algorithm. 444 } catch (NoSuchPaddingException e) { 445 // no implementation found for requested algorithm. 446 } 447 } 448 449 if (ciphers[RC4] != UNSET) { 450 ciphers[RC4_56] |= CIPHER_MASKS[RC4_56]; 451 ciphers[RC4_40] |= CIPHER_MASKS[RC4_40]; 452 } 453 454 return ciphers; 455 } 456 457 /** 458 * Assembles response-value for digest-response. 459 * 460 * @param authMethod "AUTHENTICATE" for client-generated response; 461 * "" for server-generated response 462 * @return A non-null byte array containing the repsonse-value. 463 * @throws NoSuchAlgorithmException if the platform does not have MD5 464 * digest support. 465 * @throws UnsupportedEncodingException if a an error occurs 466 * encoding a string into either Latin-1 or UTF-8. 467 * @throws IOException if an error occurs writing to the output 468 * byte array buffer. 469 */ 470 protected byte[] generateResponseValue( 471 String authMethod, 472 String digestUriValue, 473 String qopValue, 474 String usernameValue, 475 String realmValue, 476 char[] passwdValue, 477 byte[] nonceValue, 478 byte[] cNonceValue, 479 int nonceCount, 480 byte[] authzidValue 481 ) throws NoSuchAlgorithmException, 482 UnsupportedEncodingException, 483 IOException { 484 485 MessageDigest md5 = MessageDigest.getInstance("MD5"); 486 byte[] hexA1, hexA2; 487 ByteArrayOutputStream A2, beginA1, A1, KD; 488 489 // A2 490 // -- 491 // A2 = { "AUTHENTICATE:", digest-uri-value, 492 // [:00000000000000000000000000000000] } // if auth-int or auth-conf 493 // 494 A2 = new ByteArrayOutputStream(); 495 A2.write((authMethod + ":" + digestUriValue).getBytes(encoding)); 496 if (qopValue.equals("auth-conf") || 497 qopValue.equals("auth-int")) { 498 499 logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue); 500 501 A2.write(SECURITY_LAYER_MARKER.getBytes(encoding)); 502 } 503 504 if (logger.isLoggable(Level.FINE)) { 505 logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString()); 506 } 507 508 md5.update(A2.toByteArray()); 509 byte[] digest = md5.digest(); 510 hexA2 = binaryToHex(digest); 511 512 if (logger.isLoggable(Level.FINE)) { 513 logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2)); 514 } 515 516 // A1 517 // -- 518 // H(user-name : realm-value : passwd) 519 // 520 beginA1 = new ByteArrayOutputStream(); 521 beginA1.write(stringToByte_8859_1(usernameValue)); 522 beginA1.write(':'); 523 // if no realm, realm will be an empty string 524 beginA1.write(stringToByte_8859_1(realmValue)); 525 beginA1.write(':'); 526 beginA1.write(stringToByte_8859_1(new String(passwdValue))); 527 528 md5.update(beginA1.toByteArray()); 529 digest = md5.digest(); 530 531 if (logger.isLoggable(Level.FINE)) { 532 logger.log(Level.FINE, "DIGEST07:H({0}) = {1}", 533 new Object[]{beginA1.toString(), new String(binaryToHex(digest))}); 534 } 535 536 // A1 537 // -- 538 // A1 = { H ( {user-name : realm-value : passwd } ), 539 // : nonce-value, : cnonce-value : authzid-value 540 // 541 A1 = new ByteArrayOutputStream(); 542 A1.write(digest); 543 A1.write(':'); 544 A1.write(nonceValue); 545 A1.write(':'); 546 A1.write(cNonceValue); 547 548 if (authzidValue != null) { 549 A1.write(':'); 550 A1.write(authzidValue); 551 } 552 md5.update(A1.toByteArray()); 553 digest = md5.digest(); 554 H_A1 = digest; // Record H(A1). Use for integrity & privacy. 555 hexA1 = binaryToHex(digest); 556 557 if (logger.isLoggable(Level.FINE)) { 558 logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1)); 559 } 560 561 // 562 // H(k, : , s); 563 // 564 KD = new ByteArrayOutputStream(); 565 KD.write(hexA1); 566 KD.write(':'); 567 KD.write(nonceValue); 568 KD.write(':'); 569 KD.write(nonceCountToHex(nonceCount).getBytes(encoding)); 570 KD.write(':'); 571 KD.write(cNonceValue); 572 KD.write(':'); 573 KD.write(qopValue.getBytes(encoding)); 574 KD.write(':'); 575 KD.write(hexA2); 576 577 if (logger.isLoggable(Level.FINE)) { 578 logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString()); 579 } 580 581 md5.update(KD.toByteArray()); 582 digest = md5.digest(); 583 584 byte[] answer = binaryToHex(digest); 585 586 if (logger.isLoggable(Level.FINE)) { 587 logger.log(Level.FINE, "DIGEST10:response-value: {0}", 588 new String(answer)); 589 } 590 return (answer); 591 } 592 593 /** 594 * Takes 'nonceCount' value and returns HEX value of the value. 595 * 596 * @return A non-null String representing the current NONCE-COUNT 597 */ 598 protected static String nonceCountToHex(int count) { 599 600 String str = Integer.toHexString(count); 601 StringBuilder pad = new StringBuilder(); 602 603 if (str.length() < 8) { 604 for (int i = 0; i < 8-str.length(); i ++) { 605 pad.append("0"); 606 } 607 } 608 609 return pad.toString() + str; 610 } 611 612 /** 613 * Parses digest-challenge string, extracting each token 614 * and value(s) 615 * 616 * @param buf A non-null digest-challenge string. 617 * @param multipleAllowed true if multiple qop or realm or QOP directives 618 * are allowed. 619 * @throws SaslException if the buf cannot be parsed according to RFC 2831 620 */ 621 protected static byte[][] parseDirectives(byte[] buf, 622 String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException { 623 624 byte[][] valueTable = new byte[keyTable.length][]; 625 626 ByteArrayOutputStream key = new ByteArrayOutputStream(10); 627 ByteArrayOutputStream value = new ByteArrayOutputStream(10); 628 boolean gettingKey = true; 629 boolean gettingQuotedValue = false; 630 boolean expectSeparator = false; 631 byte bch; 632 633 int i = skipLws(buf, 0); 634 while (i < buf.length) { 635 bch = buf[i]; 636 637 if (gettingKey) { 638 if (bch == ',') { 639 if (key.size() != 0) { 640 throw new SaslException("Directive key contains a ',':" + 641 key); 642 } 643 // Empty element, skip separator and lws 644 i = skipLws(buf, i+1); 645 646 } else if (bch == '=') { 647 if (key.size() == 0) { 648 throw new SaslException("Empty directive key"); 649 } 650 gettingKey = false; // Termination of key 651 i = skipLws(buf, i+1); // Skip to next nonwhitespace 652 653 // Check whether value is quoted 654 if (i < buf.length) { 655 if (buf[i] == '"') { 656 gettingQuotedValue = true; 657 ++i; // Skip quote 658 } 659 } else { 660 throw new SaslException( 661 "Valueless directive found: " + key.toString()); 662 } 663 } else if (isLws(bch)) { 664 // LWS that occurs after key 665 i = skipLws(buf, i+1); 666 667 // Expecting '=' 668 if (i < buf.length) { 669 if (buf[i] != '=') { 670 throw new SaslException("'=' expected after key: " + 671 key.toString()); 672 } 673 } else { 674 throw new SaslException( 675 "'=' expected after key: " + key.toString()); 676 } 677 } else { 678 key.write(bch); // Append to key 679 ++i; // Advance 680 } 681 } else if (gettingQuotedValue) { 682 // Getting a quoted value 683 if (bch == '\\') { 684 // quoted-pair = "\" CHAR ==> CHAR 685 ++i; // Skip escape 686 if (i < buf.length) { 687 value.write(buf[i]); 688 ++i; // Advance 689 } else { 690 // Trailing escape in a quoted value 691 throw new SaslException( 692 "Unmatched quote found for directive: " 693 + key.toString() + " with value: " + value.toString()); 694 } 695 } else if (bch == '"') { 696 // closing quote 697 ++i; // Skip closing quote 698 gettingQuotedValue = false; 699 expectSeparator = true; 700 } else { 701 value.write(bch); 702 ++i; // Advance 703 } 704 705 } else if (isLws(bch) || bch == ',') { 706 // Value terminated 707 708 extractDirective(key.toString(), value.toByteArray(), 709 keyTable, valueTable, realmChoices, realmIndex); 710 key.reset(); 711 value.reset(); 712 gettingKey = true; 713 gettingQuotedValue = expectSeparator = false; 714 i = skipLws(buf, i+1); // Skip separator and LWS 715 716 } else if (expectSeparator) { 717 throw new SaslException( 718 "Expecting comma or linear whitespace after quoted string: \"" 719 + value.toString() + "\""); 720 } else { 721 value.write(bch); // Unquoted value 722 ++i; // Advance 723 } 724 } 725 726 if (gettingQuotedValue) { 727 throw new SaslException( 728 "Unmatched quote found for directive: " + key.toString() + 729 " with value: " + value.toString()); 730 } 731 732 // Get last pair 733 if (key.size() > 0) { 734 extractDirective(key.toString(), value.toByteArray(), 735 keyTable, valueTable, realmChoices, realmIndex); 736 } 737 738 return valueTable; 739 } 740 741 // Is character a linear white space? 742 // LWS = [CRLF] 1*( SP | HT ) 743 // %%% Note that we're checking individual bytes instead of CRLF 744 private static boolean isLws(byte b) { 745 switch (b) { 746 case 13: // US-ASCII CR, carriage return 747 case 10: // US-ASCII LF, linefeed 748 case 32: // US-ASCII SP, space 749 case 9: // US-ASCII HT, horizontal-tab 750 return true; 751 } 752 return false; 753 } 754 755 // Skip all linear white spaces 756 private static int skipLws(byte[] buf, int start) { 757 int i; 758 for (i = start; i < buf.length; i++) { 759 if (!isLws(buf[i])) { 760 return i; 761 } 762 } 763 return i; 764 } 765 766 /** 767 * Processes directive/value pairs from the digest-challenge and 768 * fill out the challengeVal array. 769 * 770 * @param key A non-null String challenge token name. 771 * @param value A non-null String token value. 772 * @throws SaslException if a either the key or the value is null 773 */ 774 private static void extractDirective(String key, byte[] value, 775 String[] keyTable, byte[][] valueTable, 776 List<byte[]> realmChoices, int realmIndex) throws SaslException { 777 778 for (int i = 0; i < keyTable.length; i++) { 779 if (key.equalsIgnoreCase(keyTable[i])) { 780 if (valueTable[i] == null) { 781 valueTable[i] = value; 782 if (logger.isLoggable(Level.FINE)) { 783 logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}", 784 new Object[]{ 785 keyTable[i], 786 new String(valueTable[i])}); 787 } 788 } else if (realmChoices != null && i == realmIndex) { 789 // > 1 realm specified 790 if (realmChoices.isEmpty()) { 791 realmChoices.add(valueTable[i]); // add existing one 792 } 793 realmChoices.add(value); // add new one 794 } else { 795 throw new SaslException( 796 "DIGEST-MD5: peer sent more than one " + 797 key + " directive: " + new String(value)); 798 } 799 800 break; // end search 801 } 802 } 803 } 804 805 806 /** 807 * Implementation of the SecurityCtx interface allowing for messages 808 * between the client and server to be integrity checked. After a 809 * successful DIGEST-MD5 authentication, integtrity checking is invoked 810 * if the SASL QOP (quality-of-protection) is set to 'auth-int'. 811 * <p> 812 * Further details on the integrity-protection mechanism can be found 813 * at section 2.3 - Integrity protection in the 814 * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition. 815 * 816 * @author Jonathan Bruce 817 */ 818 class DigestIntegrity implements SecurityCtx { 819 /* Used for generating integrity keys - specified in RFC 2831*/ 820 static final private String CLIENT_INT_MAGIC = "Digest session key to " + 821 "client-to-server signing key magic constant"; 822 static final private String SVR_INT_MAGIC = "Digest session key to " + 823 "server-to-client signing key magic constant"; 824 825 /* Key pairs for integrity checking */ 826 protected byte[] myKi; // == Kic for client; == Kis for server 827 protected byte[] peerKi; // == Kis for client; == Kic for server 828 829 protected int mySeqNum = 0; 830 protected int peerSeqNum = 0; 831 832 // outgoing messageType and sequenceNum 833 protected final byte[] messageType = new byte[2]; 834 protected final byte[] sequenceNum = new byte[4]; 835 836 /** 837 * Initializes DigestIntegrity implementation of SecurityCtx to 838 * enable DIGEST-MD5 integrity checking. 839 * 840 * @throws SaslException if an error is encountered generating the 841 * key-pairs for integrity checking. 842 */ 843 DigestIntegrity(boolean clientMode) throws SaslException { 844 /* Initialize magic strings */ 845 846 try { 847 generateIntegrityKeyPair(clientMode); 848 849 } catch (UnsupportedEncodingException e) { 850 throw new SaslException( 851 "DIGEST-MD5: Error encoding strings into UTF-8", e); 852 853 } catch (IOException e) { 854 throw new SaslException("DIGEST-MD5: Error accessing buffers " + 855 "required to create integrity key pairs", e); 856 857 } catch (NoSuchAlgorithmException e) { 858 throw new SaslException("DIGEST-MD5: Unsupported digest " + 859 "algorithm used to create integrity key pairs", e); 860 } 861 862 /* Message type is a fixed value */ 863 intToNetworkByteOrder(1, messageType, 0, 2); 864 } 865 866 /** 867 * Generate client-server, server-client key pairs for DIGEST-MD5 868 * integrity checking. 869 * 870 * @throws UnsupportedEncodingException if the UTF-8 encoding is not 871 * supported on the platform. 872 * @throws IOException if an error occurs when writing to or from the 873 * byte array output buffers. 874 * @throws NoSuchAlgorithmException if the MD5 message digest algorithm 875 * cannot loaded. 876 */ 877 private void generateIntegrityKeyPair(boolean clientMode) 878 throws UnsupportedEncodingException, IOException, 879 NoSuchAlgorithmException { 880 881 byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding); 882 byte[] simagic = SVR_INT_MAGIC.getBytes(encoding); 883 884 MessageDigest md5 = MessageDigest.getInstance("MD5"); 885 886 // Both client-magic-keys and server-magic-keys are the same length 887 byte[] keyBuffer = new byte[H_A1.length + cimagic.length]; 888 889 // Kic: Key for protecting msgs from client to server. 890 System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length); 891 System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length); 892 md5.update(keyBuffer); 893 byte[] Kic = md5.digest(); 894 895 // Kis: Key for protecting msgs from server to client 896 // No need to recopy H_A1 897 System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length); 898 899 md5.update(keyBuffer); 900 byte[] Kis = md5.digest(); 901 902 if (logger.isLoggable(Level.FINER)) { 903 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair", 904 "DIGEST12:Kic: ", Kic); 905 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair", 906 "DIGEST13:Kis: ", Kis); 907 } 908 909 if (clientMode) { 910 myKi = Kic; 911 peerKi = Kis; 912 } else { 913 myKi = Kis; 914 peerKi = Kic; 915 } 916 } 917 918 /** 919 * Append MAC onto outgoing message. 920 * 921 * @param outgoing A non-null byte array containing the outgoing message. 922 * @param start The offset from which to read the byte array. 923 * @param len The non-zero number of bytes for be read from the offset. 924 * @return The message including the integrity MAC 925 * @throws SaslException if an error is encountered converting a string 926 * into a UTF-8 byte encoding, or if the MD5 message digest algorithm 927 * cannot be found or if there is an error writing to the byte array 928 * output buffers. 929 */ 930 public byte[] wrap(byte[] outgoing, int start, int len) 931 throws SaslException { 932 933 if (len == 0) { 934 return EMPTY_BYTE_ARRAY; 935 } 936 937 /* wrapped = message, MAC, message type, sequence number */ 938 byte[] wrapped = new byte[len+10+2+4]; 939 940 /* Start with message itself */ 941 System.arraycopy(outgoing, start, wrapped, 0, len); 942 943 incrementSeqNum(); 944 945 /* Calculate MAC */ 946 byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len); 947 948 if (logger.isLoggable(Level.FINEST)) { 949 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ", 950 outgoing, start, len); 951 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ", 952 sequenceNum); 953 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac); 954 } 955 956 /* Add MAC[0..9] to message */ 957 System.arraycopy(mac, 0, wrapped, len, 10); 958 959 /* Add message type [0..1] */ 960 System.arraycopy(messageType, 0, wrapped, len+10, 2); 961 962 /* Add sequence number [0..3] */ 963 System.arraycopy(sequenceNum, 0, wrapped, len+12, 4); 964 if (logger.isLoggable(Level.FINEST)) { 965 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped); 966 } 967 return wrapped; 968 } 969 970 /** 971 * Return verified message without MAC - only if the received MAC 972 * and re-generated MAC are the same. 973 * 974 * @param incoming A non-null byte array containing the incoming 975 * message. 976 * @param start The offset from which to read the byte array. 977 * @param len The non-zero number of bytes to read from the offset 978 * position. 979 * @return The verified message or null if integrity checking fails. 980 * @throws SaslException if an error is encountered converting a string 981 * into a UTF-8 byte encoding, or if the MD5 message digest algorithm 982 * cannot be found or if there is an error writing to the byte array 983 * output buffers 984 */ 985 public byte[] unwrap(byte[] incoming, int start, int len) 986 throws SaslException { 987 988 if (len == 0) { 989 return EMPTY_BYTE_ARRAY; 990 } 991 992 // shave off last 16 bytes of message 993 byte[] mac = new byte[10]; 994 byte[] msg = new byte[len - 16]; 995 byte[] msgType = new byte[2]; 996 byte[] seqNum = new byte[4]; 997 998 /* Get Msg, MAC, msgType, sequenceNum */ 999 System.arraycopy(incoming, start, msg, 0, msg.length); 1000 System.arraycopy(incoming, start+msg.length, mac, 0, 10); 1001 System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2); 1002 System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4); 1003 1004 /* Calculate MAC to ensure integrity */ 1005 byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length); 1006 1007 if (logger.isLoggable(Level.FINEST)) { 1008 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ", 1009 msg); 1010 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ", 1011 mac); 1012 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ", 1013 msgType); 1014 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ", 1015 seqNum); 1016 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ", 1017 expectedMac); 1018 } 1019 1020 /* First, compare MAC's before updating any of our state */ 1021 if (!Arrays.equals(mac, expectedMac)) { 1022 // Discard message and do not increment sequence number 1023 logger.log(Level.INFO, "DIGEST23:Unmatched MACs"); 1024 return EMPTY_BYTE_ARRAY; 1025 } 1026 1027 /* Ensure server-sequence numbers are correct */ 1028 if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) { 1029 throw new SaslException("DIGEST-MD5: Out of order " + 1030 "sequencing of messages from server. Got: " + 1031 networkByteOrderToInt(seqNum, 0, 4) + 1032 " Expected: " + peerSeqNum); 1033 } 1034 1035 if (!Arrays.equals(messageType, msgType)) { 1036 throw new SaslException("DIGEST-MD5: invalid message type: " + 1037 networkByteOrderToInt(msgType, 0, 2)); 1038 } 1039 1040 // Increment sequence number and return message 1041 peerSeqNum++; 1042 return msg; 1043 } 1044 1045 /** 1046 * Generates MAC to be appended onto out-going messages. 1047 * 1048 * @param Ki A non-null byte array containing the key for the digest 1049 * @param SeqNum A non-null byte array contain the sequence number 1050 * @param msg The message to be digested 1051 * @param start The offset from which to read the msg byte array 1052 * @param len The non-zero number of bytes to be read from the offset 1053 * @return The MAC of a message. 1054 * 1055 * @throws SaslException if an error occurs when generating MAC. 1056 */ 1057 protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg, 1058 int start, int len) throws SaslException { 1059 1060 byte[] seqAndMsg = new byte[4+len]; 1061 System.arraycopy(seqnum, 0, seqAndMsg, 0, 4); 1062 System.arraycopy(msg, start, seqAndMsg, 4, len); 1063 1064 try { 1065 SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5"); 1066 Mac m = Mac.getInstance("HmacMD5"); 1067 m.init(keyKi); 1068 m.update(seqAndMsg); 1069 byte[] hMAC_MD5 = m.doFinal(); 1070 1071 /* First 10 bytes of HMAC_MD5 digest */ 1072 byte macBuffer[] = new byte[10]; 1073 System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10); 1074 1075 return macBuffer; 1076 } catch (InvalidKeyException e) { 1077 throw new SaslException("DIGEST-MD5: Invalid bytes used for " + 1078 "key of HMAC-MD5 hash.", e); 1079 } catch (NoSuchAlgorithmException e) { 1080 throw new SaslException("DIGEST-MD5: Error creating " + 1081 "instance of MD5 digest algorithm", e); 1082 } 1083 } 1084 1085 /** 1086 * Increment own sequence number and set answer in NBO sequenceNum field. 1087 */ 1088 protected void incrementSeqNum() { 1089 intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4); 1090 } 1091 } 1092 1093 /** 1094 * Implementation of the SecurityCtx interface allowing for messages 1095 * between the client and server to be integrity checked and encrypted. 1096 * After a successful DIGEST-MD5 authentication, privacy is invoked if the 1097 * SASL QOP (quality-of-protection) is set to 'auth-conf'. 1098 * <p> 1099 * Further details on the integrity-protection mechanism can be found 1100 * at section 2.4 - Confidentiality protection in 1101 * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition. 1102 * 1103 * @author Jonathan Bruce 1104 */ 1105 final class DigestPrivacy extends DigestIntegrity implements SecurityCtx { 1106 /* Used for generating privacy keys - specified in RFC 2831 */ 1107 static final private String CLIENT_CONF_MAGIC = 1108 "Digest H(A1) to client-to-server sealing key magic constant"; 1109 static final private String SVR_CONF_MAGIC = 1110 "Digest H(A1) to server-to-client sealing key magic constant"; 1111 1112 private Cipher encCipher; 1113 private Cipher decCipher; 1114 1115 /** 1116 * Initializes the cipher object instances for encryption and decryption. 1117 * 1118 * @throws SaslException if an error occurs with the Key 1119 * initialization, or a string cannot be encoded into a byte array 1120 * using the UTF-8 encoding, or an error occurs when writing to a 1121 * byte array output buffers or the mechanism cannot load the MD5 1122 * message digest algorithm or invalid initialization parameters are 1123 * passed to the cipher object instances. 1124 */ 1125 DigestPrivacy(boolean clientMode) throws SaslException { 1126 1127 super(clientMode); // generate Kic, Kis keys for integrity-checking. 1128 1129 try { 1130 generatePrivacyKeyPair(clientMode); 1131 1132 } catch (SaslException e) { 1133 throw e; 1134 1135 } catch (UnsupportedEncodingException e) { 1136 throw new SaslException( 1137 "DIGEST-MD5: Error encoding string value into UTF-8", e); 1138 1139 } catch (IOException e) { 1140 throw new SaslException("DIGEST-MD5: Error accessing " + 1141 "buffers required to generate cipher keys", e); 1142 } catch (NoSuchAlgorithmException e) { 1143 throw new SaslException("DIGEST-MD5: Error creating " + 1144 "instance of required cipher or digest", e); 1145 } 1146 } 1147 1148 /** 1149 * Generates client-server and server-client keys to encrypt and 1150 * decrypt messages. Also generates IVs for DES ciphers. 1151 * 1152 * @throws IOException if an error occurs when writing to or from the 1153 * byte array output buffers. 1154 * @throws NoSuchAlgorithmException if the MD5 message digest algorithm 1155 * cannot loaded. 1156 * @throws UnsupportedEncodingException if an UTF-8 encoding is not 1157 * supported on the platform. 1158 * @throw SaslException if an error occurs initializing the keys and 1159 * IVs for the chosen cipher. 1160 */ 1161 private void generatePrivacyKeyPair(boolean clientMode) 1162 throws IOException, UnsupportedEncodingException, 1163 NoSuchAlgorithmException, SaslException { 1164 1165 byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding); 1166 byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding); 1167 1168 /* Kcc = MD5{H(A1)[0..n], "Digest ... client-to-server"} */ 1169 MessageDigest md5 = MessageDigest.getInstance("MD5"); 1170 1171 int n; 1172 if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) { 1173 n = 5; /* H(A1)[0..5] */ 1174 } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) { 1175 n = 7; /* H(A1)[0..7] */ 1176 } else { // des and 3des and rc4 1177 n = 16; /* H(A1)[0..16] */ 1178 } 1179 1180 /* {H(A1)[0..n], "Digest ... client-to-server..."} */ 1181 // Both client-magic-keys and server-magic-keys are the same length 1182 byte[] keyBuffer = new byte[n + ccmagic.length]; 1183 System.arraycopy(H_A1, 0, keyBuffer, 0, n); // H(A1)[0..n] 1184 1185 /* Kcc: Key for encrypting messages from client->server */ 1186 System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length); 1187 md5.update(keyBuffer); 1188 byte[] Kcc = md5.digest(); 1189 1190 /* Kcs: Key for decrypting messages from server->client */ 1191 // No need to copy H_A1 again since it hasn't changed 1192 System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length); 1193 md5.update(keyBuffer); 1194 byte[] Kcs = md5.digest(); 1195 1196 if (logger.isLoggable(Level.FINER)) { 1197 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1198 "DIGEST24:Kcc: ", Kcc); 1199 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1200 "DIGEST25:Kcs: ", Kcs); 1201 } 1202 1203 byte[] myKc; 1204 byte[] peerKc; 1205 1206 if (clientMode) { 1207 myKc = Kcc; 1208 peerKc = Kcs; 1209 } else { 1210 myKc = Kcs; 1211 peerKc = Kcc; 1212 } 1213 1214 try { 1215 SecretKey encKey; 1216 SecretKey decKey; 1217 1218 /* Initialize cipher objects */ 1219 if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) { 1220 encCipher = Cipher.getInstance("RC4"); 1221 decCipher = Cipher.getInstance("RC4"); 1222 1223 encKey = new SecretKeySpec(myKc, "RC4"); 1224 decKey = new SecretKeySpec(peerKc, "RC4"); 1225 1226 encCipher.init(Cipher.ENCRYPT_MODE, encKey); 1227 decCipher.init(Cipher.DECRYPT_MODE, decKey); 1228 1229 } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) || 1230 (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) { 1231 1232 // DES or 3DES 1233 String cipherFullname, cipherShortname; 1234 1235 // Use "NoPadding" when specifying cipher names 1236 // RFC 2831 already defines padding rules for producing 1237 // 8-byte aligned blocks 1238 if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) { 1239 cipherFullname = "DES/CBC/NoPadding"; 1240 cipherShortname = "des"; 1241 } else { 1242 /* 3DES */ 1243 cipherFullname = "DESede/CBC/NoPadding"; 1244 cipherShortname = "desede"; 1245 } 1246 1247 encCipher = Cipher.getInstance(cipherFullname); 1248 decCipher = Cipher.getInstance(cipherFullname); 1249 1250 encKey = makeDesKeys(myKc, cipherShortname); 1251 decKey = makeDesKeys(peerKc, cipherShortname); 1252 1253 // Set up the DES IV, which is the last 8 bytes of Kcc/Kcs 1254 IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8); 1255 IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8); 1256 1257 // Initialize cipher objects 1258 encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv); 1259 decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv); 1260 1261 if (logger.isLoggable(Level.FINER)) { 1262 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1263 "DIGEST26:" + negotiatedCipher + " IVcc: ", 1264 encIv.getIV()); 1265 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1266 "DIGEST27:" + negotiatedCipher + " IVcs: ", 1267 decIv.getIV()); 1268 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1269 "DIGEST28:" + negotiatedCipher + " encryption key: ", 1270 encKey.getEncoded()); 1271 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair", 1272 "DIGEST29:" + negotiatedCipher + " decryption key: ", 1273 decKey.getEncoded()); 1274 } 1275 } 1276 } catch (InvalidKeySpecException e) { 1277 throw new SaslException("DIGEST-MD5: Unsupported key " + 1278 "specification used.", e); 1279 } catch (InvalidAlgorithmParameterException e) { 1280 throw new SaslException("DIGEST-MD5: Invalid cipher " + 1281 "algorithem parameter used to create cipher instance", e); 1282 } catch (NoSuchPaddingException e) { 1283 throw new SaslException("DIGEST-MD5: Unsupported " + 1284 "padding used for chosen cipher", e); 1285 } catch (InvalidKeyException e) { 1286 throw new SaslException("DIGEST-MD5: Invalid data " + 1287 "used to initialize keys", e); 1288 } 1289 } 1290 1291 // ------------------------------------------------------------------- 1292 1293 /** 1294 * Encrypt out-going message. 1295 * 1296 * @param outgoing A non-null byte array containing the outgoing message. 1297 * @param start The offset from which to read the byte array. 1298 * @param len The non-zero number of bytes to be read from the offset. 1299 * @return The encrypted message. 1300 * 1301 * @throws SaslException if an error occurs when writing to or from the 1302 * byte array output buffers or if the MD5 message digest algorithm 1303 * cannot loaded or if an UTF-8 encoding is not supported on the 1304 * platform. 1305 */ 1306 public byte[] wrap(byte[] outgoing, int start, int len) 1307 throws SaslException { 1308 1309 if (len == 0) { 1310 return EMPTY_BYTE_ARRAY; 1311 } 1312 1313 /* HMAC(Ki, {SeqNum, msg})[0..9] */ 1314 incrementSeqNum(); 1315 byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len); 1316 1317 if (logger.isLoggable(Level.FINEST)) { 1318 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ", 1319 outgoing, start, len); 1320 traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ", 1321 sequenceNum); 1322 traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac); 1323 } 1324 1325 // Calculate padding 1326 int bs = encCipher.getBlockSize(); 1327 byte[] padding; 1328 if (bs > 1 ) { 1329 int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9] 1330 padding = new byte[pad]; 1331 for (int i=0; i < pad; i++) { 1332 padding[i] = (byte)pad; 1333 } 1334 } else { 1335 padding = EMPTY_BYTE_ARRAY; 1336 } 1337 1338 byte[] toBeEncrypted = new byte[len+padding.length+10]; 1339 1340 /* {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])} */ 1341 System.arraycopy(outgoing, start, toBeEncrypted, 0, len); 1342 System.arraycopy(padding, 0, toBeEncrypted, len, padding.length); 1343 System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10); 1344 1345 if (logger.isLoggable(Level.FINEST)) { 1346 traceOutput(DP_CLASS_NAME, "wrap", 1347 "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted); 1348 } 1349 1350 /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */ 1351 byte[] cipherBlock; 1352 try { 1353 // Do CBC (chaining) across packets 1354 cipherBlock = encCipher.update(toBeEncrypted); 1355 1356 if (cipherBlock == null) { 1357 // update() can return null 1358 throw new IllegalBlockSizeException(""+toBeEncrypted.length); 1359 } 1360 } catch (IllegalBlockSizeException e) { 1361 throw new SaslException( 1362 "DIGEST-MD5: Invalid block size for cipher", e); 1363 } 1364 1365 byte[] wrapped = new byte[cipherBlock.length+2+4]; 1366 System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length); 1367 System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2); 1368 System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4); 1369 1370 if (logger.isLoggable(Level.FINEST)) { 1371 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped); 1372 } 1373 1374 return wrapped; 1375 } 1376 1377 /* 1378 * Decrypt incoming messages and verify their integrity. 1379 * 1380 * @param incoming A non-null byte array containing the incoming 1381 * encrypted message. 1382 * @param start The offset from which to read the byte array. 1383 * @param len The non-zero number of bytes to read from the offset 1384 * position. 1385 * @return The decrypted, verified message or null if integrity 1386 * checking 1387 * fails. 1388 * @throws SaslException if there are the SASL buffer is empty or if 1389 * if an error occurs reading the SASL buffer. 1390 */ 1391 public byte[] unwrap(byte[] incoming, int start, int len) 1392 throws SaslException { 1393 1394 if (len == 0) { 1395 return EMPTY_BYTE_ARRAY; 1396 } 1397 1398 byte[] encryptedMsg = new byte[len - 6]; 1399 byte[] msgType = new byte[2]; 1400 byte[] seqNum = new byte[4]; 1401 1402 /* Get cipherMsg; msgType; sequenceNum */ 1403 System.arraycopy(incoming, start, 1404 encryptedMsg, 0, encryptedMsg.length); 1405 System.arraycopy(incoming, start+encryptedMsg.length, 1406 msgType, 0, 2); 1407 System.arraycopy(incoming, start+encryptedMsg.length+2, 1408 seqNum, 0, 4); 1409 1410 if (logger.isLoggable(Level.FINEST)) { 1411 logger.log(Level.FINEST, 1412 "DIGEST33:Expecting sequence num: {0}", 1413 peerSeqNum); 1414 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ", 1415 encryptedMsg); 1416 } 1417 1418 // Decrypt message 1419 /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */ 1420 byte[] decryptedMsg; 1421 1422 try { 1423 // Do CBC (chaining) across packets 1424 decryptedMsg = decCipher.update(encryptedMsg); 1425 1426 if (decryptedMsg == null) { 1427 // update() can return null 1428 throw new IllegalBlockSizeException(""+encryptedMsg.length); 1429 } 1430 } catch (IllegalBlockSizeException e) { 1431 throw new SaslException("DIGEST-MD5: Illegal block " + 1432 "sizes used with chosen cipher", e); 1433 } 1434 1435 byte[] msgWithPadding = new byte[decryptedMsg.length - 10]; 1436 byte[] mac = new byte[10]; 1437 1438 System.arraycopy(decryptedMsg, 0, 1439 msgWithPadding, 0, msgWithPadding.length); 1440 System.arraycopy(decryptedMsg, msgWithPadding.length, 1441 mac, 0, 10); 1442 1443 if (logger.isLoggable(Level.FINEST)) { 1444 traceOutput(DP_CLASS_NAME, "unwrap", 1445 "DIGEST35:Unwrapped (w/padding): ", msgWithPadding); 1446 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac); 1447 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ", 1448 msgType); 1449 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ", 1450 seqNum); 1451 } 1452 1453 int msgLength = msgWithPadding.length; 1454 int blockSize = decCipher.getBlockSize(); 1455 if (blockSize > 1) { 1456 // get value of last octet of the byte array 1457 msgLength -= (int)msgWithPadding[msgWithPadding.length - 1]; 1458 if (msgLength < 0) { 1459 // Discard message and do not increment sequence number 1460 if (logger.isLoggable(Level.INFO)) { 1461 logger.log(Level.INFO, 1462 "DIGEST39:Incorrect padding: {0}", 1463 msgWithPadding[msgWithPadding.length - 1]); 1464 } 1465 return EMPTY_BYTE_ARRAY; 1466 } 1467 } 1468 1469 /* Re-calculate MAC to ensure integrity */ 1470 byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding, 1471 0, msgLength); 1472 1473 if (logger.isLoggable(Level.FINEST)) { 1474 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ", 1475 expectedMac); 1476 } 1477 1478 // First, compare MACs before updating state 1479 if (!Arrays.equals(mac, expectedMac)) { 1480 // Discard message and do not increment sequence number 1481 logger.log(Level.INFO, "DIGEST41:Unmatched MACs"); 1482 return EMPTY_BYTE_ARRAY; 1483 } 1484 1485 /* Ensure sequence number is correct */ 1486 if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) { 1487 throw new SaslException("DIGEST-MD5: Out of order " + 1488 "sequencing of messages from server. Got: " + 1489 networkByteOrderToInt(seqNum, 0, 4) + " Expected: " + 1490 peerSeqNum); 1491 } 1492 1493 /* Check message type */ 1494 if (!Arrays.equals(messageType, msgType)) { 1495 throw new SaslException("DIGEST-MD5: invalid message type: " + 1496 networkByteOrderToInt(msgType, 0, 2)); 1497 } 1498 1499 // Increment sequence number and return message 1500 peerSeqNum++; 1501 1502 if (msgLength == msgWithPadding.length) { 1503 return msgWithPadding; // no padding 1504 } else { 1505 // Get a copy of the message without padding 1506 byte[] clearMsg = new byte[msgLength]; 1507 System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength); 1508 return clearMsg; 1509 } 1510 } 1511 } 1512 1513 // ---------------- DES and 3 DES key manipulation routines 1514 1515 private static final BigInteger MASK = new BigInteger("7f", 16); 1516 1517 /** 1518 * Sets the parity bit (0th bit) in each byte so that each byte 1519 * contains an odd number of 1's. 1520 */ 1521 private static void setParityBit(byte[] key) { 1522 for (int i = 0; i < key.length; i++) { 1523 int b = key[i] & 0xfe; 1524 b |= (Integer.bitCount(b) & 1) ^ 1; 1525 key[i] = (byte) b; 1526 } 1527 } 1528 1529 /** 1530 * Expands a 7-byte array into an 8-byte array that contains parity bits 1531 * The binary format of a cryptographic key is: 1532 * (B1,B2,...,B7,P1,B8,...B14,P2,B15,...,B49,P7,B50,...,B56,P8) 1533 * where (B1,B2,...,B56) are the independent bits of a DES key and 1534 * (PI,P2,...,P8) are reserved for parity bits computed on the preceding 1535 * seven independent bits and set so that the parity of the octet is odd, 1536 * i.e., there is an odd number of "1" bits in the octet. 1537 */ 1538 private static byte[] addDesParity(byte[] input, int offset, int len) { 1539 if (len != 7) 1540 throw new IllegalArgumentException( 1541 "Invalid length of DES Key Value:" + len); 1542 1543 byte[] raw = new byte[7]; 1544 System.arraycopy(input, offset, raw, 0, len); 1545 1546 byte[] result = new byte[8]; 1547 BigInteger in = new BigInteger(raw); 1548 1549 // Shift 7 bits each time into a byte 1550 for (int i=result.length-1; i>=0; i--) { 1551 result[i] = in.and(MASK).toByteArray()[0]; 1552 result[i] <<= 1; // make room for parity bit 1553 in = in.shiftRight(7); 1554 } 1555 setParityBit(result); 1556 return result; 1557 } 1558 1559 /** 1560 * Create parity-adjusted keys suitable for DES / DESede encryption. 1561 * 1562 * @param input A non-null byte array containing key material for 1563 * DES / DESede. 1564 * @param desStrength A string specifying eithe a DES or a DESede key. 1565 * @return SecretKey An instance of either DESKeySpec or DESedeKeySpec. 1566 * 1567 * @throws NoSuchAlgorithmException if the either the DES or DESede 1568 * algorithms cannote be lodaed by JCE. 1569 * @throws InvalidKeyException if an invalid array of bytes is used 1570 * as a key for DES or DESede. 1571 * @throws InvalidKeySpecException in an invalid parameter is passed 1572 * to either te DESKeySpec of the DESedeKeySpec constructors. 1573 */ 1574 private static SecretKey makeDesKeys(byte[] input, String desStrength) 1575 throws NoSuchAlgorithmException, InvalidKeyException, 1576 InvalidKeySpecException { 1577 1578 // Generate first subkey using first 7 bytes 1579 byte[] subkey1 = addDesParity(input, 0, 7); 1580 1581 KeySpec spec = null; 1582 SecretKeyFactory desFactory = 1583 SecretKeyFactory.getInstance(desStrength); 1584 switch (desStrength) { 1585 case "des": 1586 spec = new DESKeySpec(subkey1, 0); 1587 if (logger.isLoggable(Level.FINEST)) { 1588 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1589 "DIGEST42:DES key input: ", input); 1590 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1591 "DIGEST43:DES key parity-adjusted: ", subkey1); 1592 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1593 "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey()); 1594 logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}", 1595 Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0))); 1596 } 1597 break; 1598 case "desede": 1599 // Generate second subkey using second 7 bytes 1600 byte[] subkey2 = addDesParity(input, 7, 7); 1601 // Construct 24-byte encryption-decryption-encryption sequence 1602 byte[] ede = new byte[subkey1.length*2+subkey2.length]; 1603 System.arraycopy(subkey1, 0, ede, 0, subkey1.length); 1604 System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length); 1605 System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length, 1606 subkey1.length); 1607 spec = new DESedeKeySpec(ede, 0); 1608 if (logger.isLoggable(Level.FINEST)) { 1609 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1610 "DIGEST46:3DES key input: ", input); 1611 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1612 "DIGEST47:3DES key ede: ", ede); 1613 traceOutput(DP_CLASS_NAME, "makeDesKeys", 1614 "DIGEST48:3DES key material: ", 1615 ((DESedeKeySpec)spec).getKey()); 1616 logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ", 1617 Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0))); 1618 } 1619 break; 1620 default: 1621 throw new IllegalArgumentException("Invalid DES strength:" + 1622 desStrength); 1623 } 1624 return desFactory.generateSecret(spec); 1625 } 1626 }