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      */
DigestMD5Base(Map<String, ?> props, String className, int firstStep, String digestUri, CallbackHandler cbh)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      */
getMechanismName()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      */
unwrap(byte[] incoming, int start, int len)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      */
wrap(byte[] outgoing, int start, int len)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 
dispose()242     public void dispose() throws SaslException {
243         if (secCtx != null) {
244             secCtx = null;
245         }
246     }
247 
getNegotiatedProperty(String propName)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 
generateNonce()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      */
writeQuotedStringValue(ByteArrayOutputStream out, byte[] buf)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
needEscape(String str)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
needEscape(char ch)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 
quotedStringValue(String str)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      */
binaryToHex(byte[] digest)387     protected byte[] binaryToHex(byte[] digest) throws
388     UnsupportedEncodingException {
389 
390         StringBuffer digestString = new StringBuffer();
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      */
stringToByte_8859_1(String str)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 
getPlatformCiphers()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      */
generateResponseValue( String authMethod, String digestUriValue, String qopValue, String usernameValue, String realmValue, char[] passwdValue, byte[] nonceValue, byte[] cNonceValue, int nonceCount, byte[] authzidValue )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      */
nonceCountToHex(int count)598     protected static String nonceCountToHex(int count) {
599 
600         String str = Integer.toHexString(count);
601         StringBuffer pad = new StringBuffer();
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      */
parseDirectives(byte[] buf, String[]keyTable, List<byte[]> realmChoices, int realmIndex)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
isLws(byte b)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
skipLws(byte[] buf, int start)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      */
extractDirective(String key, byte[] value, String[] keyTable, byte[][] valueTable, List<byte[]> realmChoices, int realmIndex)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          */
DigestIntegrity(boolean clientMode)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          */
generateIntegrityKeyPair(boolean clientMode)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          */
wrap(byte[] outgoing, int start, int len)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          */
unwrap(byte[] incoming, int start, int len)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          */
getHMAC(byte[] Ki, byte[] seqnum, byte[] msg, int start, int len)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          */
incrementSeqNum()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          */
DigestPrivacy(boolean clientMode)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          */
generatePrivacyKeyPair(boolean clientMode)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          */
wrap(byte[] outgoing, int start, int len)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          */
unwrap(byte[] incoming, int start, int len)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                     new Integer(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                             new Byte(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      */
setParityBit(byte[] key)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      */
addDesParity(byte[] input, int offset, int len)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      */
makeDesKeys(byte[] input, String desStrength)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 }
1627