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').append(Integer.toHexString(digest[i] & 0x000000ff));
 395             } else {
 396                 digestString.append(
 397                     Integer.toHexString(digest[i] & 0x000000ff));
 398             }
 399         }
 400         return digestString.toString().getBytes(encoding);
 401     }
 402 
 403     /**
 404      * Used to convert username-value, passwd or realm to 8859_1 encoding
 405      * if all chars in string are within the 8859_1 (Latin 1) encoding range.
 406      *
 407      * @param a non-null String
 408      * @return a non-nuill byte array containing the correct character encoding
 409      * for username, paswd or realm.
 410      */
 411     protected byte[] stringToByte_8859_1(String str) throws SaslException {
 412 
 413         char[] buffer = str.toCharArray();
 414 
 415         try {
 416             if (useUTF8) {
 417                 for( int i = 0; i< buffer.length; i++ ) {
 418                     if( buffer[i] > '\u00FF' ) {
 419                         return str.getBytes("UTF8");
 420                     }
 421                 }
 422             }
 423             return str.getBytes("8859_1");
 424         } catch (UnsupportedEncodingException e) {
 425             throw new SaslException(
 426                 "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);
 427         }
 428     }
 429 
 430     protected static byte[] getPlatformCiphers() {
 431         byte[] ciphers = new byte[CIPHER_TOKENS.length];
 432 
 433         for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {
 434             try {
 435                 // Checking whether the transformation is available from the
 436                 // current installed providers.
 437                 Cipher.getInstance(JCE_CIPHER_NAME[i]);
 438 
 439                 logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);
 440                 ciphers[i] |= CIPHER_MASKS[i];
 441             } catch (NoSuchAlgorithmException e) {
 442                 // no implementation found for requested algorithm.
 443             } catch (NoSuchPaddingException e) {
 444                 // no implementation found for requested algorithm.
 445             }
 446         }
 447 
 448         if (ciphers[RC4] != UNSET) {
 449             ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];
 450             ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];
 451         }
 452 
 453         return ciphers;
 454     }
 455 
 456     /**
 457      * Assembles response-value for digest-response.
 458      *
 459      * @param authMethod "AUTHENTICATE" for client-generated response;
 460      *        "" for server-generated response
 461      * @return A non-null byte array containing the repsonse-value.
 462      * @throws NoSuchAlgorithmException if the platform does not have MD5
 463      * digest support.
 464      * @throws UnsupportedEncodingException if a an error occurs
 465      * encoding a string into either Latin-1 or UTF-8.
 466      * @throws IOException if an error occurs writing to the output
 467      * byte array buffer.
 468      */
 469     protected byte[] generateResponseValue(
 470         String authMethod,
 471         String digestUriValue,
 472         String qopValue,
 473         String usernameValue,
 474         String realmValue,
 475         char[] passwdValue,
 476         byte[] nonceValue,
 477         byte[] cNonceValue,
 478         int nonceCount,
 479         byte[] authzidValue
 480         ) throws NoSuchAlgorithmException,
 481             UnsupportedEncodingException,
 482             IOException {
 483 
 484         MessageDigest md5 = MessageDigest.getInstance("MD5");
 485         byte[] hexA1, hexA2;
 486         ByteArrayOutputStream A2, beginA1, A1, KD;
 487 
 488         // A2
 489         // --
 490         // A2 = { "AUTHENTICATE:", digest-uri-value,
 491         // [:00000000000000000000000000000000] }  // if auth-int or auth-conf
 492         //
 493         A2 = new ByteArrayOutputStream();
 494         A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));
 495         if (qopValue.equals("auth-conf") ||
 496             qopValue.equals("auth-int")) {
 497 
 498             logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue);
 499 
 500             A2.write(SECURITY_LAYER_MARKER.getBytes(encoding));
 501         }
 502 
 503         if (logger.isLoggable(Level.FINE)) {
 504             logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString());
 505         }
 506 
 507         md5.update(A2.toByteArray());
 508         byte[] digest = md5.digest();
 509         hexA2 = binaryToHex(digest);
 510 
 511         if (logger.isLoggable(Level.FINE)) {
 512             logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2));
 513         }
 514 
 515         // A1
 516         // --
 517         // H(user-name : realm-value : passwd)
 518         //
 519         beginA1 = new ByteArrayOutputStream();
 520         beginA1.write(stringToByte_8859_1(usernameValue));
 521         beginA1.write(':');
 522         // if no realm, realm will be an empty string
 523         beginA1.write(stringToByte_8859_1(realmValue));
 524         beginA1.write(':');
 525         beginA1.write(stringToByte_8859_1(new String(passwdValue)));
 526 
 527         md5.update(beginA1.toByteArray());
 528         digest = md5.digest();
 529 
 530         if (logger.isLoggable(Level.FINE)) {
 531             logger.log(Level.FINE, "DIGEST07:H({0}) = {1}",
 532                 new Object[]{beginA1.toString(), new String(binaryToHex(digest))});
 533         }
 534 
 535         // A1
 536         // --
 537         // A1 = { H ( {user-name : realm-value : passwd } ),
 538         // : nonce-value, : cnonce-value : authzid-value
 539         //
 540         A1 = new ByteArrayOutputStream();
 541         A1.write(digest);
 542         A1.write(':');
 543         A1.write(nonceValue);
 544         A1.write(':');
 545         A1.write(cNonceValue);
 546 
 547         if (authzidValue != null) {
 548             A1.write(':');
 549             A1.write(authzidValue);
 550         }
 551         md5.update(A1.toByteArray());
 552         digest = md5.digest();
 553         H_A1 = digest; // Record H(A1). Use for integrity & privacy.
 554         hexA1 = binaryToHex(digest);
 555 
 556         if (logger.isLoggable(Level.FINE)) {
 557             logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1));
 558         }
 559 
 560         //
 561         // H(k, : , s);
 562         //
 563         KD = new ByteArrayOutputStream();
 564         KD.write(hexA1);
 565         KD.write(':');
 566         KD.write(nonceValue);
 567         KD.write(':');
 568         KD.write(nonceCountToHex(nonceCount).getBytes(encoding));
 569         KD.write(':');
 570         KD.write(cNonceValue);
 571         KD.write(':');
 572         KD.write(qopValue.getBytes(encoding));
 573         KD.write(':');
 574         KD.write(hexA2);
 575 
 576         if (logger.isLoggable(Level.FINE)) {
 577             logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString());
 578         }
 579 
 580         md5.update(KD.toByteArray());
 581         digest = md5.digest();
 582 
 583         byte[] answer = binaryToHex(digest);
 584 
 585         if (logger.isLoggable(Level.FINE)) {
 586             logger.log(Level.FINE, "DIGEST10:response-value: {0}",
 587                 new String(answer));
 588         }
 589         return (answer);
 590     }
 591 
 592     /**
 593      * Takes 'nonceCount' value and returns HEX value of the value.
 594      *
 595      * @return A non-null String representing the current NONCE-COUNT
 596      */
 597     protected static String nonceCountToHex(int count) {
 598 
 599         String str = Integer.toHexString(count);
 600         StringBuilder pad = new StringBuilder();
 601 
 602         if (str.length() < 8) {
 603             for (int i = 0; i < 8-str.length(); i ++) {
 604                 pad.append("0");
 605             }
 606         }
 607 
 608         return pad.toString() + str;
 609     }
 610 
 611     /**
 612      * Parses digest-challenge string, extracting each token
 613      * and value(s)
 614      *
 615      * @param buf A non-null digest-challenge string.
 616      * @param multipleAllowed true if multiple qop or realm or QOP directives
 617      *  are allowed.
 618      * @throws SaslException if the buf cannot be parsed according to RFC 2831
 619      */
 620     protected static byte[][] parseDirectives(byte[] buf,
 621         String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException {
 622 
 623         byte[][] valueTable = new byte[keyTable.length][];
 624 
 625         ByteArrayOutputStream key = new ByteArrayOutputStream(10);
 626         ByteArrayOutputStream value = new ByteArrayOutputStream(10);
 627         boolean gettingKey = true;
 628         boolean gettingQuotedValue = false;
 629         boolean expectSeparator = false;
 630         byte bch;
 631 
 632         int i = skipLws(buf, 0);
 633         while (i < buf.length) {
 634             bch = buf[i];
 635 
 636             if (gettingKey) {
 637                 if (bch == ',') {
 638                     if (key.size() != 0) {
 639                         throw new SaslException("Directive key contains a ',':" +
 640                             key);
 641                     }
 642                     // Empty element, skip separator and lws
 643                     i = skipLws(buf, i+1);
 644 
 645                 } else if (bch == '=') {
 646                     if (key.size() == 0) {
 647                         throw new SaslException("Empty directive key");
 648                     }
 649                     gettingKey = false;      // Termination of key
 650                     i = skipLws(buf, i+1);   // Skip to next nonwhitespace
 651 
 652                     // Check whether value is quoted
 653                     if (i < buf.length) {
 654                         if (buf[i] == '"') {
 655                             gettingQuotedValue = true;
 656                             ++i; // Skip quote
 657                         }
 658                     } else {
 659                         throw new SaslException(
 660                             "Valueless directive found: " + key.toString());
 661                     }
 662                 } else if (isLws(bch)) {
 663                     // LWS that occurs after key
 664                     i = skipLws(buf, i+1);
 665 
 666                     // Expecting '='
 667                     if (i < buf.length) {
 668                         if (buf[i] != '=') {
 669                             throw new SaslException("'=' expected after key: " +
 670                                 key.toString());
 671                         }
 672                     } else {
 673                         throw new SaslException(
 674                             "'=' expected after key: " + key.toString());
 675                     }
 676                 } else {
 677                     key.write(bch);    // Append to key
 678                     ++i;               // Advance
 679                 }
 680             } else if (gettingQuotedValue) {
 681                 // Getting a quoted value
 682                 if (bch == '\\') {
 683                     // quoted-pair = "\" CHAR  ==> CHAR
 684                     ++i;       // Skip escape
 685                     if (i < buf.length) {
 686                         value.write(buf[i]);
 687                         ++i;   // Advance
 688                     } else {
 689                         // Trailing escape in a quoted value
 690                         throw new SaslException(
 691                             "Unmatched quote found for directive: "
 692                             + key.toString() + " with value: " + value.toString());
 693                     }
 694                 } else if (bch == '"') {
 695                     // closing quote
 696                     ++i;  // Skip closing quote
 697                     gettingQuotedValue = false;
 698                     expectSeparator = true;
 699                 } else {
 700                     value.write(bch);
 701                     ++i;  // Advance
 702                 }
 703 
 704             } else if (isLws(bch) || bch == ',') {
 705                 //  Value terminated
 706 
 707                 extractDirective(key.toString(), value.toByteArray(),
 708                     keyTable, valueTable, realmChoices, realmIndex);
 709                 key.reset();
 710                 value.reset();
 711                 gettingKey = true;
 712                 gettingQuotedValue = expectSeparator = false;
 713                 i = skipLws(buf, i+1);   // Skip separator and LWS
 714 
 715             } else if (expectSeparator) {
 716                 throw new SaslException(
 717                     "Expecting comma or linear whitespace after quoted string: \""
 718                         + value.toString() + "\"");
 719             } else {
 720                 value.write(bch);   // Unquoted value
 721                 ++i;                // Advance
 722             }
 723         }
 724 
 725         if (gettingQuotedValue) {
 726             throw new SaslException(
 727                 "Unmatched quote found for directive: " + key.toString() +
 728                 " with value: " + value.toString());
 729         }
 730 
 731         // Get last pair
 732         if (key.size() > 0) {
 733             extractDirective(key.toString(), value.toByteArray(),
 734                 keyTable, valueTable, realmChoices, realmIndex);
 735         }
 736 
 737         return valueTable;
 738     }
 739 
 740     // Is character a linear white space?
 741     // LWS            = [CRLF] 1*( SP | HT )
 742     // %%% Note that we're checking individual bytes instead of CRLF
 743     private static boolean isLws(byte b) {
 744         switch (b) {
 745         case 13:   // US-ASCII CR, carriage return
 746         case 10:   // US-ASCII LF, linefeed
 747         case 32:   // US-ASCII SP, space
 748         case 9:    // US-ASCII HT, horizontal-tab
 749             return true;
 750         }
 751         return false;
 752     }
 753 
 754     // Skip all linear white spaces
 755     private static int skipLws(byte[] buf, int start) {
 756         int i;
 757         for (i = start; i < buf.length; i++) {
 758             if (!isLws(buf[i])) {
 759                 return i;
 760             }
 761         }
 762         return i;
 763     }
 764 
 765     /**
 766      * Processes directive/value pairs from the digest-challenge and
 767      * fill out the challengeVal array.
 768      *
 769      * @param key A non-null String challenge token name.
 770      * @param value A non-null String token value.
 771      * @throws SaslException if a either the key or the value is null
 772      */
 773     private static void  extractDirective(String key, byte[] value,
 774         String[] keyTable, byte[][] valueTable,
 775         List<byte[]> realmChoices, int realmIndex) throws SaslException {
 776 
 777         for (int i = 0; i < keyTable.length; i++) {
 778             if (key.equalsIgnoreCase(keyTable[i])) {
 779                 if (valueTable[i] == null) {
 780                     valueTable[i] = value;
 781                     if (logger.isLoggable(Level.FINE)) {
 782                         logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}",
 783                             new Object[]{
 784                                 keyTable[i],
 785                                 new String(valueTable[i])});
 786                     }
 787                 } else if (realmChoices != null && i == realmIndex) {
 788                     // > 1 realm specified
 789                     if (realmChoices.isEmpty()) {
 790                         realmChoices.add(valueTable[i]); // add existing one
 791                     }
 792                     realmChoices.add(value);  // add new one
 793                 } else {
 794                     throw new SaslException(
 795                         "DIGEST-MD5: peer sent more than one " +
 796                         key + " directive: " + new String(value));
 797                 }
 798 
 799                 break; // end search
 800             }
 801         }
 802      }
 803 
 804 
 805     /**
 806      * Implementation of the SecurityCtx interface allowing for messages
 807      * between the client and server to be integrity checked. After a
 808      * successful DIGEST-MD5 authentication, integtrity checking is invoked
 809      * if the SASL QOP (quality-of-protection) is set to 'auth-int'.
 810      * <p>
 811      * Further details on the integrity-protection mechanism can be found
 812      * at section 2.3 - Integrity protection in the
 813      * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
 814      *
 815      * @author Jonathan Bruce
 816      */
 817     class DigestIntegrity implements SecurityCtx {
 818         /* Used for generating integrity keys - specified in RFC 2831*/
 819         static final private String CLIENT_INT_MAGIC = "Digest session key to " +
 820             "client-to-server signing key magic constant";
 821         static final private String SVR_INT_MAGIC = "Digest session key to " +
 822             "server-to-client signing key magic constant";
 823 
 824         /* Key pairs for integrity checking */
 825         protected byte[] myKi;     // == Kic for client; == Kis for server
 826         protected byte[] peerKi;   // == Kis for client; == Kic for server
 827 
 828         protected int mySeqNum = 0;
 829         protected int peerSeqNum = 0;
 830 
 831         // outgoing messageType and sequenceNum
 832         protected final byte[] messageType = new byte[2];
 833         protected final byte[] sequenceNum = new byte[4];
 834 
 835         /**
 836          * Initializes DigestIntegrity implementation of SecurityCtx to
 837          * enable DIGEST-MD5 integrity checking.
 838          *
 839          * @throws SaslException if an error is encountered generating the
 840          * key-pairs for integrity checking.
 841          */
 842         DigestIntegrity(boolean clientMode) throws SaslException {
 843             /* Initialize magic strings */
 844 
 845             try {
 846                 generateIntegrityKeyPair(clientMode);
 847 
 848             } catch (UnsupportedEncodingException e) {
 849                 throw new SaslException(
 850                     "DIGEST-MD5: Error encoding strings into UTF-8", e);
 851 
 852             } catch (IOException e) {
 853                 throw new SaslException("DIGEST-MD5: Error accessing buffers " +
 854                     "required to create integrity key pairs", e);
 855 
 856             } catch (NoSuchAlgorithmException e) {
 857                 throw new SaslException("DIGEST-MD5: Unsupported digest " +
 858                     "algorithm used to create integrity key pairs", e);
 859             }
 860 
 861             /* Message type is a fixed value */
 862             intToNetworkByteOrder(1, messageType, 0, 2);
 863         }
 864 
 865         /**
 866          * Generate client-server, server-client key pairs for DIGEST-MD5
 867          * integrity checking.
 868          *
 869          * @throws UnsupportedEncodingException if the UTF-8 encoding is not
 870          * supported on the platform.
 871          * @throws IOException if an error occurs when writing to or from the
 872          * byte array output buffers.
 873          * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
 874          * cannot loaded.
 875          */
 876         private void generateIntegrityKeyPair(boolean clientMode)
 877             throws UnsupportedEncodingException, IOException,
 878                 NoSuchAlgorithmException {
 879 
 880             byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding);
 881             byte[] simagic = SVR_INT_MAGIC.getBytes(encoding);
 882 
 883             MessageDigest md5 = MessageDigest.getInstance("MD5");
 884 
 885             // Both client-magic-keys and server-magic-keys are the same length
 886             byte[] keyBuffer = new byte[H_A1.length + cimagic.length];
 887 
 888             // Kic: Key for protecting msgs from client to server.
 889             System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length);
 890             System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length);
 891             md5.update(keyBuffer);
 892             byte[] Kic = md5.digest();
 893 
 894             // Kis: Key for protecting msgs from server to client
 895             // No need to recopy H_A1
 896             System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length);
 897 
 898             md5.update(keyBuffer);
 899             byte[] Kis = md5.digest();
 900 
 901             if (logger.isLoggable(Level.FINER)) {
 902                 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
 903                     "DIGEST12:Kic: ", Kic);
 904                 traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
 905                     "DIGEST13:Kis: ", Kis);
 906             }
 907 
 908             if (clientMode) {
 909                 myKi = Kic;
 910                 peerKi = Kis;
 911             } else {
 912                 myKi = Kis;
 913                 peerKi = Kic;
 914             }
 915         }
 916 
 917         /**
 918          * Append MAC onto outgoing message.
 919          *
 920          * @param outgoing A non-null byte array containing the outgoing message.
 921          * @param start The offset from which to read the byte array.
 922          * @param len The non-zero number of bytes for be read from the offset.
 923          * @return The message including the integrity MAC
 924          * @throws SaslException if an error is encountered converting a string
 925          * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
 926          * cannot be found or if there is an error writing to the byte array
 927          * output buffers.
 928          */
 929         public byte[] wrap(byte[] outgoing, int start, int len)
 930             throws SaslException {
 931 
 932             if (len == 0) {
 933                 return EMPTY_BYTE_ARRAY;
 934             }
 935 
 936             /* wrapped = message, MAC, message type, sequence number */
 937             byte[] wrapped = new byte[len+10+2+4];
 938 
 939             /* Start with message itself */
 940             System.arraycopy(outgoing, start, wrapped, 0, len);
 941 
 942             incrementSeqNum();
 943 
 944             /* Calculate MAC */
 945             byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
 946 
 947             if (logger.isLoggable(Level.FINEST)) {
 948                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ",
 949                     outgoing, start, len);
 950                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ",
 951                     sequenceNum);
 952                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac);
 953             }
 954 
 955             /* Add MAC[0..9] to message */
 956             System.arraycopy(mac, 0, wrapped, len, 10);
 957 
 958             /* Add message type [0..1] */
 959             System.arraycopy(messageType, 0, wrapped, len+10, 2);
 960 
 961             /* Add sequence number [0..3] */
 962             System.arraycopy(sequenceNum, 0, wrapped, len+12, 4);
 963             if (logger.isLoggable(Level.FINEST)) {
 964                 traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped);
 965             }
 966             return wrapped;
 967         }
 968 
 969         /**
 970          * Return verified message without MAC - only if the received MAC
 971          * and re-generated MAC are the same.
 972          *
 973          * @param incoming A non-null byte array containing the incoming
 974          * message.
 975          * @param start The offset from which to read the byte array.
 976          * @param len The non-zero number of bytes to read from the offset
 977          * position.
 978          * @return The verified message or null if integrity checking fails.
 979          * @throws SaslException if an error is encountered converting a string
 980          * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
 981          * cannot be found or if there is an error writing to the byte array
 982          * output buffers
 983          */
 984         public byte[] unwrap(byte[] incoming, int start, int len)
 985             throws SaslException {
 986 
 987             if (len == 0) {
 988                 return EMPTY_BYTE_ARRAY;
 989             }
 990 
 991             // shave off last 16 bytes of message
 992             byte[] mac = new byte[10];
 993             byte[] msg = new byte[len - 16];
 994             byte[] msgType = new byte[2];
 995             byte[] seqNum = new byte[4];
 996 
 997             /* Get Msg, MAC, msgType, sequenceNum */
 998             System.arraycopy(incoming, start, msg, 0, msg.length);
 999             System.arraycopy(incoming, start+msg.length, mac, 0, 10);
1000             System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2);
1001             System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4);
1002 
1003             /* Calculate MAC to ensure integrity */
1004             byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length);
1005 
1006             if (logger.isLoggable(Level.FINEST)) {
1007                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ",
1008                     msg);
1009                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ",
1010                     mac);
1011                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ",
1012                     msgType);
1013                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ",
1014                     seqNum);
1015                 traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ",
1016                     expectedMac);
1017             }
1018 
1019             /* First, compare MAC's before updating any of our state */
1020             if (!Arrays.equals(mac, expectedMac)) {
1021                 //  Discard message and do not increment sequence number
1022                 logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
1023                 return EMPTY_BYTE_ARRAY;
1024             }
1025 
1026             /* Ensure server-sequence numbers are correct */
1027             if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1028                 throw new SaslException("DIGEST-MD5: Out of order " +
1029                     "sequencing of messages from server. Got: " +
1030                     networkByteOrderToInt(seqNum, 0, 4) +
1031                     " Expected: " +     peerSeqNum);
1032             }
1033 
1034             if (!Arrays.equals(messageType, msgType)) {
1035                 throw new SaslException("DIGEST-MD5: invalid message type: " +
1036                     networkByteOrderToInt(msgType, 0, 2));
1037             }
1038 
1039             // Increment sequence number and return message
1040             peerSeqNum++;
1041             return msg;
1042         }
1043 
1044         /**
1045          * Generates MAC to be appended onto out-going messages.
1046          *
1047          * @param Ki A non-null byte array containing the key for the digest
1048          * @param SeqNum A non-null byte array contain the sequence number
1049          * @param msg  The message to be digested
1050          * @param start The offset from which to read the msg byte array
1051          * @param len The non-zero number of bytes to be read from the offset
1052          * @return The MAC of a message.
1053          *
1054          * @throws SaslException if an error occurs when generating MAC.
1055          */
1056         protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg,
1057             int start, int len) throws SaslException {
1058 
1059             byte[] seqAndMsg = new byte[4+len];
1060             System.arraycopy(seqnum, 0, seqAndMsg, 0, 4);
1061             System.arraycopy(msg, start, seqAndMsg, 4, len);
1062 
1063             try {
1064                 SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5");
1065                 Mac m = Mac.getInstance("HmacMD5");
1066                 m.init(keyKi);
1067                 m.update(seqAndMsg);
1068                 byte[] hMAC_MD5 = m.doFinal();
1069 
1070                 /* First 10 bytes of HMAC_MD5 digest */
1071                 byte macBuffer[] = new byte[10];
1072                 System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);
1073 
1074                 return macBuffer;
1075             } catch (InvalidKeyException e) {
1076                 throw new SaslException("DIGEST-MD5: Invalid bytes used for " +
1077                     "key of HMAC-MD5 hash.", e);
1078             } catch (NoSuchAlgorithmException e) {
1079                 throw new SaslException("DIGEST-MD5: Error creating " +
1080                     "instance of MD5 digest algorithm", e);
1081             }
1082         }
1083 
1084         /**
1085          * Increment own sequence number and set answer in NBO sequenceNum field.
1086          */
1087         protected void incrementSeqNum() {
1088             intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4);
1089         }
1090     }
1091 
1092     /**
1093      * Implementation of the SecurityCtx interface allowing for messages
1094      * between the client and server to be integrity checked and encrypted.
1095      * After a successful DIGEST-MD5 authentication, privacy is invoked if the
1096      * SASL QOP (quality-of-protection) is set to 'auth-conf'.
1097      * <p>
1098      * Further details on the integrity-protection mechanism can be found
1099      * at section 2.4 - Confidentiality protection in
1100      * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
1101      *
1102      * @author Jonathan Bruce
1103      */
1104     final class DigestPrivacy extends DigestIntegrity implements SecurityCtx {
1105         /* Used for generating privacy keys - specified in RFC 2831 */
1106         static final private String CLIENT_CONF_MAGIC =
1107             "Digest H(A1) to client-to-server sealing key magic constant";
1108         static final private String SVR_CONF_MAGIC =
1109             "Digest H(A1) to server-to-client sealing key magic constant";
1110 
1111         private Cipher encCipher;
1112         private Cipher decCipher;
1113 
1114         /**
1115          * Initializes the cipher object instances for encryption and decryption.
1116          *
1117          * @throws SaslException if an error occurs with the Key
1118          * initialization, or a string cannot be encoded into a byte array
1119          * using the UTF-8 encoding, or an error occurs when writing to a
1120          * byte array output buffers or the mechanism cannot load the MD5
1121          * message digest algorithm or invalid initialization parameters are
1122          * passed to the cipher object instances.
1123          */
1124         DigestPrivacy(boolean clientMode) throws SaslException {
1125 
1126             super(clientMode); // generate Kic, Kis keys for integrity-checking.
1127 
1128             try {
1129                 generatePrivacyKeyPair(clientMode);
1130 
1131             } catch (SaslException e) {
1132                 throw e;
1133 
1134             } catch (UnsupportedEncodingException e) {
1135                 throw new SaslException(
1136                     "DIGEST-MD5: Error encoding string value into UTF-8", e);
1137 
1138             } catch (IOException e) {
1139                 throw new SaslException("DIGEST-MD5: Error accessing " +
1140                     "buffers required to generate cipher keys", e);
1141             } catch (NoSuchAlgorithmException e) {
1142                 throw new SaslException("DIGEST-MD5: Error creating " +
1143                     "instance of required cipher or digest", e);
1144             }
1145         }
1146 
1147         /**
1148          * Generates client-server and server-client keys to encrypt and
1149          * decrypt messages. Also generates IVs for DES ciphers.
1150          *
1151          * @throws IOException if an error occurs when writing to or from the
1152          * byte array output buffers.
1153          * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
1154          * cannot loaded.
1155          * @throws UnsupportedEncodingException if an UTF-8 encoding is not
1156          * supported on the platform.
1157          * @throw SaslException if an error occurs initializing the keys and
1158          * IVs for the chosen cipher.
1159          */
1160         private void generatePrivacyKeyPair(boolean clientMode)
1161             throws IOException, UnsupportedEncodingException,
1162             NoSuchAlgorithmException, SaslException {
1163 
1164             byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding);
1165             byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding);
1166 
1167             /* Kcc = MD5{H(A1)[0..n], "Digest ... client-to-server"} */
1168             MessageDigest md5 = MessageDigest.getInstance("MD5");
1169 
1170             int n;
1171             if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) {
1172                 n = 5;          /* H(A1)[0..5] */
1173             } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) {
1174                 n = 7;          /* H(A1)[0..7] */
1175             } else { // des and 3des and rc4
1176                 n = 16;         /* H(A1)[0..16] */
1177             }
1178 
1179             /* {H(A1)[0..n], "Digest ... client-to-server..."} */
1180             // Both client-magic-keys and server-magic-keys are the same length
1181             byte[] keyBuffer =  new byte[n + ccmagic.length];
1182             System.arraycopy(H_A1, 0, keyBuffer, 0, n);   // H(A1)[0..n]
1183 
1184             /* Kcc: Key for encrypting messages from client->server */
1185             System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length);
1186             md5.update(keyBuffer);
1187             byte[] Kcc = md5.digest();
1188 
1189             /* Kcs: Key for decrypting messages from server->client */
1190             // No need to copy H_A1 again since it hasn't changed
1191             System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length);
1192             md5.update(keyBuffer);
1193             byte[] Kcs = md5.digest();
1194 
1195             if (logger.isLoggable(Level.FINER)) {
1196                 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1197                     "DIGEST24:Kcc: ", Kcc);
1198                 traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1199                     "DIGEST25:Kcs: ", Kcs);
1200             }
1201 
1202             byte[] myKc;
1203             byte[] peerKc;
1204 
1205             if (clientMode) {
1206                 myKc = Kcc;
1207                 peerKc = Kcs;
1208             } else {
1209                 myKc = Kcs;
1210                 peerKc = Kcc;
1211             }
1212 
1213             try {
1214                 SecretKey encKey;
1215                 SecretKey decKey;
1216 
1217                 /* Initialize cipher objects */
1218                 if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) {
1219                     encCipher = Cipher.getInstance("RC4");
1220                     decCipher = Cipher.getInstance("RC4");
1221 
1222                     encKey = new SecretKeySpec(myKc, "RC4");
1223                     decKey = new SecretKeySpec(peerKc, "RC4");
1224 
1225                     encCipher.init(Cipher.ENCRYPT_MODE, encKey);
1226                     decCipher.init(Cipher.DECRYPT_MODE, decKey);
1227 
1228                 } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) ||
1229                     (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) {
1230 
1231                     // DES or 3DES
1232                     String cipherFullname, cipherShortname;
1233 
1234                         // Use "NoPadding" when specifying cipher names
1235                         // RFC 2831 already defines padding rules for producing
1236                         // 8-byte aligned blocks
1237                     if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) {
1238                         cipherFullname = "DES/CBC/NoPadding";
1239                         cipherShortname = "des";
1240                     } else {
1241                         /* 3DES */
1242                         cipherFullname = "DESede/CBC/NoPadding";
1243                         cipherShortname = "desede";
1244                     }
1245 
1246                     encCipher = Cipher.getInstance(cipherFullname);
1247                     decCipher = Cipher.getInstance(cipherFullname);
1248 
1249                     encKey = makeDesKeys(myKc, cipherShortname);
1250                     decKey = makeDesKeys(peerKc, cipherShortname);
1251 
1252                     // Set up the DES IV, which is the last 8 bytes of Kcc/Kcs
1253                     IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8);
1254                     IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8);
1255 
1256                     // Initialize cipher objects
1257                     encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv);
1258                     decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv);
1259 
1260                     if (logger.isLoggable(Level.FINER)) {
1261                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1262                             "DIGEST26:" + negotiatedCipher + " IVcc: ",
1263                             encIv.getIV());
1264                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1265                             "DIGEST27:" + negotiatedCipher + " IVcs: ",
1266                             decIv.getIV());
1267                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1268                             "DIGEST28:" + negotiatedCipher + " encryption key: ",
1269                             encKey.getEncoded());
1270                         traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
1271                             "DIGEST29:" + negotiatedCipher + " decryption key: ",
1272                             decKey.getEncoded());
1273                     }
1274                 }
1275             } catch (InvalidKeySpecException e) {
1276                 throw new SaslException("DIGEST-MD5: Unsupported key " +
1277                     "specification used.", e);
1278             } catch (InvalidAlgorithmParameterException e) {
1279                 throw new SaslException("DIGEST-MD5: Invalid cipher " +
1280                     "algorithem parameter used to create cipher instance", e);
1281             } catch (NoSuchPaddingException e) {
1282                 throw new SaslException("DIGEST-MD5: Unsupported " +
1283                     "padding used for chosen cipher", e);
1284             } catch (InvalidKeyException e) {
1285                 throw new SaslException("DIGEST-MD5: Invalid data " +
1286                     "used to initialize keys", e);
1287             }
1288         }
1289 
1290         // -------------------------------------------------------------------
1291 
1292         /**
1293          * Encrypt out-going message.
1294          *
1295          * @param outgoing A non-null byte array containing the outgoing message.
1296          * @param start The offset from which to read the byte array.
1297          * @param len The non-zero number of bytes to be read from the offset.
1298          * @return The encrypted message.
1299          *
1300          * @throws SaslException if an error occurs when writing to or from the
1301          * byte array output buffers or if the MD5 message digest algorithm
1302          * cannot loaded or if an UTF-8 encoding is not supported on the
1303          * platform.
1304          */
1305         public byte[] wrap(byte[] outgoing, int start, int len)
1306             throws SaslException {
1307 
1308             if (len == 0) {
1309                 return EMPTY_BYTE_ARRAY;
1310             }
1311 
1312             /* HMAC(Ki, {SeqNum, msg})[0..9] */
1313             incrementSeqNum();
1314             byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
1315 
1316             if (logger.isLoggable(Level.FINEST)) {
1317                 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ",
1318                     outgoing, start, len);
1319                 traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ",
1320                     sequenceNum);
1321                 traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac);
1322             }
1323 
1324             // Calculate padding
1325             int bs = encCipher.getBlockSize();
1326             byte[] padding;
1327             if (bs > 1 ) {
1328                 int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9]
1329                 padding = new byte[pad];
1330                 for (int i=0; i < pad; i++) {
1331                     padding[i] = (byte)pad;
1332                 }
1333             } else {
1334                 padding = EMPTY_BYTE_ARRAY;
1335             }
1336 
1337             byte[] toBeEncrypted = new byte[len+padding.length+10];
1338 
1339             /* {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])} */
1340             System.arraycopy(outgoing, start, toBeEncrypted, 0, len);
1341             System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
1342             System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10);
1343 
1344             if (logger.isLoggable(Level.FINEST)) {
1345                 traceOutput(DP_CLASS_NAME, "wrap",
1346                     "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted);
1347             }
1348 
1349             /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
1350             byte[] cipherBlock;
1351             try {
1352                 // Do CBC (chaining) across packets
1353                 cipherBlock = encCipher.update(toBeEncrypted);
1354 
1355                 if (cipherBlock == null) {
1356                     // update() can return null
1357                     throw new IllegalBlockSizeException(""+toBeEncrypted.length);
1358                 }
1359             } catch (IllegalBlockSizeException e) {
1360                 throw new SaslException(
1361                     "DIGEST-MD5: Invalid block size for cipher", e);
1362             }
1363 
1364             byte[] wrapped = new byte[cipherBlock.length+2+4];
1365             System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length);
1366             System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2);
1367             System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4);
1368 
1369             if (logger.isLoggable(Level.FINEST)) {
1370                 traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped);
1371             }
1372 
1373             return wrapped;
1374         }
1375 
1376         /*
1377          * Decrypt incoming messages and verify their integrity.
1378          *
1379          * @param incoming A non-null byte array containing the incoming
1380          * encrypted message.
1381          * @param start The offset from which to read the byte array.
1382          * @param len The non-zero number of bytes to read from the offset
1383          * position.
1384          * @return The decrypted, verified message or null if integrity
1385          * checking
1386          * fails.
1387          * @throws SaslException if there are the SASL buffer is empty or if
1388          * if an error occurs reading the SASL buffer.
1389          */
1390         public byte[] unwrap(byte[] incoming, int start, int len)
1391             throws SaslException {
1392 
1393             if (len == 0) {
1394                 return EMPTY_BYTE_ARRAY;
1395             }
1396 
1397             byte[] encryptedMsg = new byte[len - 6];
1398             byte[] msgType = new byte[2];
1399             byte[] seqNum = new byte[4];
1400 
1401             /* Get cipherMsg; msgType; sequenceNum */
1402             System.arraycopy(incoming, start,
1403                 encryptedMsg, 0, encryptedMsg.length);
1404             System.arraycopy(incoming, start+encryptedMsg.length,
1405                 msgType, 0, 2);
1406             System.arraycopy(incoming, start+encryptedMsg.length+2,
1407                 seqNum, 0, 4);
1408 
1409             if (logger.isLoggable(Level.FINEST)) {
1410                 logger.log(Level.FINEST,
1411                     "DIGEST33:Expecting sequence num: {0}",
1412                     peerSeqNum);
1413                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ",
1414                     encryptedMsg);
1415             }
1416 
1417             // Decrypt message
1418             /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
1419             byte[] decryptedMsg;
1420 
1421             try {
1422                 // Do CBC (chaining) across packets
1423                 decryptedMsg = decCipher.update(encryptedMsg);
1424 
1425                 if (decryptedMsg == null) {
1426                     // update() can return null
1427                     throw new IllegalBlockSizeException(""+encryptedMsg.length);
1428                 }
1429             } catch (IllegalBlockSizeException e) {
1430                 throw new SaslException("DIGEST-MD5: Illegal block " +
1431                     "sizes used with chosen cipher", e);
1432             }
1433 
1434             byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
1435             byte[] mac = new byte[10];
1436 
1437             System.arraycopy(decryptedMsg, 0,
1438                 msgWithPadding, 0, msgWithPadding.length);
1439             System.arraycopy(decryptedMsg, msgWithPadding.length,
1440                 mac, 0, 10);
1441 
1442             if (logger.isLoggable(Level.FINEST)) {
1443                 traceOutput(DP_CLASS_NAME, "unwrap",
1444                     "DIGEST35:Unwrapped (w/padding): ", msgWithPadding);
1445                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac);
1446                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ",
1447                     msgType);
1448                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ",
1449                     seqNum);
1450             }
1451 
1452             int msgLength = msgWithPadding.length;
1453             int blockSize = decCipher.getBlockSize();
1454             if (blockSize > 1) {
1455                 // get value of last octet of the byte array
1456                 msgLength -= (int)msgWithPadding[msgWithPadding.length - 1];
1457                 if (msgLength < 0) {
1458                     //  Discard message and do not increment sequence number
1459                     if (logger.isLoggable(Level.INFO)) {
1460                         logger.log(Level.INFO,
1461                             "DIGEST39:Incorrect padding: {0}",
1462                             msgWithPadding[msgWithPadding.length - 1]);
1463                     }
1464                     return EMPTY_BYTE_ARRAY;
1465                 }
1466             }
1467 
1468             /* Re-calculate MAC to ensure integrity */
1469             byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding,
1470                 0, msgLength);
1471 
1472             if (logger.isLoggable(Level.FINEST)) {
1473                 traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ",
1474                     expectedMac);
1475             }
1476 
1477             // First, compare MACs before updating state
1478             if (!Arrays.equals(mac, expectedMac)) {
1479                 //  Discard message and do not increment sequence number
1480                 logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
1481                 return EMPTY_BYTE_ARRAY;
1482             }
1483 
1484             /* Ensure sequence number is correct */
1485             if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
1486                 throw new SaslException("DIGEST-MD5: Out of order " +
1487                     "sequencing of messages from server. Got: " +
1488                     networkByteOrderToInt(seqNum, 0, 4) + " Expected: " +
1489                     peerSeqNum);
1490             }
1491 
1492             /* Check message type */
1493             if (!Arrays.equals(messageType, msgType)) {
1494                 throw new SaslException("DIGEST-MD5: invalid message type: " +
1495                     networkByteOrderToInt(msgType, 0, 2));
1496             }
1497 
1498             // Increment sequence number and return message
1499             peerSeqNum++;
1500 
1501             if (msgLength == msgWithPadding.length) {
1502                 return msgWithPadding; // no padding
1503             } else {
1504                 // Get a copy of the message without padding
1505                 byte[] clearMsg = new byte[msgLength];
1506                 System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength);
1507                 return clearMsg;
1508             }
1509         }
1510     }
1511 
1512     // ---------------- DES and 3 DES key manipulation routines
1513 
1514     private static final BigInteger MASK = new BigInteger("7f", 16);
1515 
1516     /**
1517      * Sets the parity bit (0th bit) in each byte so that each byte
1518      * contains an odd number of 1's.
1519      */
1520     private static void setParityBit(byte[] key) {
1521         for (int i = 0; i < key.length; i++) {
1522             int b = key[i] & 0xfe;
1523             b |= (Integer.bitCount(b) & 1) ^ 1;
1524             key[i] = (byte) b;
1525         }
1526     }
1527 
1528     /**
1529      * Expands a 7-byte array into an 8-byte array that contains parity bits
1530      * The binary format of a cryptographic key is:
1531      *     (B1,B2,...,B7,P1,B8,...B14,P2,B15,...,B49,P7,B50,...,B56,P8)
1532      * where (B1,B2,...,B56) are the independent bits of a DES key and
1533      * (PI,P2,...,P8) are reserved for parity bits computed on the preceding
1534      * seven independent bits and set so that the parity of the octet is odd,
1535      * i.e., there is an odd number of "1" bits in the octet.
1536      */
1537     private static byte[] addDesParity(byte[] input, int offset, int len) {
1538         if (len != 7)
1539             throw new IllegalArgumentException(
1540                 "Invalid length of DES Key Value:" + len);
1541 
1542         byte[] raw = new byte[7];
1543         System.arraycopy(input, offset, raw, 0, len);
1544 
1545         byte[] result = new byte[8];
1546         BigInteger in = new BigInteger(raw);
1547 
1548         // Shift 7 bits each time into a byte
1549         for (int i=result.length-1; i>=0; i--) {
1550             result[i] = in.and(MASK).toByteArray()[0];
1551             result[i] <<= 1;         // make room for parity bit
1552             in = in.shiftRight(7);
1553         }
1554         setParityBit(result);
1555         return result;
1556     }
1557 
1558     /**
1559      * Create parity-adjusted keys suitable for DES / DESede encryption.
1560      *
1561      * @param input A non-null byte array containing key material for
1562      * DES / DESede.
1563      * @param desStrength A string specifying eithe a DES or a DESede key.
1564      * @return SecretKey An instance of either DESKeySpec or DESedeKeySpec.
1565      *
1566      * @throws NoSuchAlgorithmException if the either the DES or DESede
1567      * algorithms cannote be lodaed by JCE.
1568      * @throws InvalidKeyException if an invalid array of bytes is used
1569      * as a key for DES or DESede.
1570      * @throws InvalidKeySpecException in an invalid parameter is passed
1571      * to either te DESKeySpec of the DESedeKeySpec constructors.
1572      */
1573     private static SecretKey makeDesKeys(byte[] input, String desStrength)
1574         throws NoSuchAlgorithmException, InvalidKeyException,
1575             InvalidKeySpecException {
1576 
1577         // Generate first subkey using first 7 bytes
1578         byte[] subkey1 = addDesParity(input, 0, 7);
1579 
1580         KeySpec spec = null;
1581         SecretKeyFactory desFactory =
1582             SecretKeyFactory.getInstance(desStrength);
1583         switch (desStrength) {
1584             case "des":
1585                 spec = new DESKeySpec(subkey1, 0);
1586                 if (logger.isLoggable(Level.FINEST)) {
1587                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1588                         "DIGEST42:DES key input: ", input);
1589                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1590                         "DIGEST43:DES key parity-adjusted: ", subkey1);
1591                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1592                         "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey());
1593                     logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}",
1594                         Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0)));
1595                 }
1596                 break;
1597             case "desede":
1598                 // Generate second subkey using second 7 bytes
1599                 byte[] subkey2 = addDesParity(input, 7, 7);
1600                 // Construct 24-byte encryption-decryption-encryption sequence
1601                 byte[] ede = new byte[subkey1.length*2+subkey2.length];
1602                 System.arraycopy(subkey1, 0, ede, 0, subkey1.length);
1603                 System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length);
1604                 System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length,
1605                     subkey1.length);
1606                 spec = new DESedeKeySpec(ede, 0);
1607                 if (logger.isLoggable(Level.FINEST)) {
1608                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1609                         "DIGEST46:3DES key input: ", input);
1610                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1611                         "DIGEST47:3DES key ede: ", ede);
1612                     traceOutput(DP_CLASS_NAME, "makeDesKeys",
1613                         "DIGEST48:3DES key material: ",
1614                         ((DESedeKeySpec)spec).getKey());
1615                     logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ",
1616                         Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0)));
1617                 }
1618                 break;
1619             default:
1620                 throw new IllegalArgumentException("Invalid DES strength:" +
1621                     desStrength);
1622         }
1623         return desFactory.generateSecret(spec);
1624     }
1625 }