0N/A/*
2362N/A * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.security.sasl.digest;
0N/A
0N/Aimport java.util.Map;
0N/Aimport java.util.Arrays;
0N/Aimport java.util.List;
0N/Aimport java.util.Set;
0N/Aimport java.util.logging.Logger;
0N/Aimport java.util.logging.Level;
0N/Aimport java.math.BigInteger;
0N/Aimport java.util.Random;
0N/Aimport java.security.Provider;
0N/A
0N/Aimport java.io.ByteArrayInputStream;
0N/Aimport java.io.ByteArrayOutputStream;
0N/Aimport java.io.UnsupportedEncodingException;
0N/Aimport java.io.IOException;
0N/A
0N/Aimport java.security.MessageDigest;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.security.NoSuchAlgorithmException;
0N/Aimport java.security.InvalidKeyException;
0N/Aimport java.security.spec.KeySpec;
0N/Aimport java.security.spec.InvalidKeySpecException;
0N/Aimport java.security.InvalidAlgorithmParameterException;
0N/A
0N/Aimport javax.crypto.Cipher;
0N/Aimport javax.crypto.SecretKey;
0N/Aimport javax.crypto.Mac;
0N/Aimport javax.crypto.SecretKeyFactory;
0N/Aimport javax.crypto.BadPaddingException;
0N/Aimport javax.crypto.NoSuchPaddingException;
0N/Aimport javax.crypto.IllegalBlockSizeException;
0N/Aimport javax.crypto.spec.IvParameterSpec;
0N/Aimport javax.crypto.spec.SecretKeySpec;
0N/Aimport javax.crypto.spec.DESKeySpec;
0N/Aimport javax.crypto.spec.DESedeKeySpec;
0N/A
0N/Aimport javax.security.sasl.*;
0N/Aimport com.sun.security.sasl.util.AbstractSaslImpl;
0N/A
0N/Aimport javax.security.auth.callback.CallbackHandler;
0N/A
0N/A/**
0N/A * Utility class for DIGEST-MD5 mechanism. Provides utility methods
0N/A * and contains two inner classes which implement the SecurityCtx
0N/A * interface. The inner classes provide the funtionality to allow
0N/A * for quality-of-protection (QOP) with integrity checking and
0N/A * privacy.
0N/A *
0N/A * @author Jonathan Bruce
0N/A * @author Rosanna Lee
0N/A */
0N/Aabstract class DigestMD5Base extends AbstractSaslImpl {
0N/A /* ------------------------- Constants ------------------------ */
0N/A
0N/A // Used for logging
0N/A private static final String DI_CLASS_NAME = DigestIntegrity.class.getName();
0N/A private static final String DP_CLASS_NAME = DigestPrivacy.class.getName();
0N/A
0N/A /* Constants - defined in RFC2831 */
0N/A protected static final int MAX_CHALLENGE_LENGTH = 2048;
0N/A protected static final int MAX_RESPONSE_LENGTH = 4096;
0N/A protected static final int DEFAULT_MAXBUF = 65536;
0N/A
0N/A /* Supported ciphers for 'auth-conf' */
0N/A protected static final int DES3 = 0;
0N/A protected static final int RC4 = 1;
0N/A protected static final int DES = 2;
0N/A protected static final int RC4_56 = 3;
0N/A protected static final int RC4_40 = 4;
0N/A protected static final String[] CIPHER_TOKENS = { "3des",
0N/A "rc4",
0N/A "des",
0N/A "rc4-56",
0N/A "rc4-40" };
0N/A private static final String[] JCE_CIPHER_NAME = {
0N/A "DESede/CBC/NoPadding",
0N/A "RC4",
0N/A "DES/CBC/NoPadding",
0N/A };
0N/A
0N/A /*
0N/A * If QOP is set to 'auth-conf', a DIGEST-MD5 mechanism must have
0N/A * support for the DES and Triple DES cipher algorithms (optionally,
0N/A * support for RC4 [128/56/40 bit keys] ciphers) to provide for
0N/A * confidentiality. See RFC 2831 for details. This implementation
0N/A * provides support for DES, Triple DES and RC4 ciphers.
0N/A *
0N/A * The value of strength effects the strength of cipher used. The mappings
0N/A * of 'high', 'medium', and 'low' give the following behaviour.
0N/A *
0N/A * HIGH_STRENGTH - Triple DES
0N/A * - RC4 (128bit)
0N/A * MEDIUM_STRENGTH - DES
0N/A * - RC4 (56bit)
0N/A * LOW_SRENGTH - RC4 (40bit)
0N/A */
0N/A protected static final byte DES_3_STRENGTH = HIGH_STRENGTH;
0N/A protected static final byte RC4_STRENGTH = HIGH_STRENGTH;
0N/A protected static final byte DES_STRENGTH = MEDIUM_STRENGTH;
0N/A protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH;
0N/A protected static final byte RC4_40_STRENGTH = LOW_STRENGTH;
0N/A protected static final byte UNSET = (byte)0;
0N/A protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH,
0N/A RC4_STRENGTH,
0N/A DES_STRENGTH,
0N/A RC4_56_STRENGTH,
0N/A RC4_40_STRENGTH };
0N/A
0N/A private static final String SECURITY_LAYER_MARKER =
0N/A ":00000000000000000000000000000000";
0N/A
0N/A protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
0N/A
0N/A /* ------------------- Variable Fields ----------------------- */
0N/A
0N/A /* Used to track progress of authentication; step numbers from RFC 2831 */
0N/A protected int step;
0N/A
0N/A /* Used to get username/password, choose realm for client */
0N/A /* Used to obtain authorization, pw info, canonicalized authzid for server */
0N/A protected CallbackHandler cbh;
0N/A
0N/A protected SecurityCtx secCtx;
0N/A protected byte[] H_A1; // component of response-value
0N/A
0N/A protected byte[] nonce; // server generated nonce
0N/A
0N/A /* Variables set when parsing directives in digest challenge/response. */
0N/A protected String negotiatedStrength;
0N/A protected String negotiatedCipher;
0N/A protected String negotiatedQop;
0N/A protected String negotiatedRealm;
0N/A protected boolean useUTF8 = false;
0N/A protected String encoding = "8859_1"; // default unless server specifies utf-8
0N/A
0N/A protected String digestUri;
0N/A protected String authzid; // authzid or canonicalized authzid
0N/A
0N/A /**
0N/A * Constucts an instance of DigestMD5Base. Calls super constructor
0N/A * to parse properties for mechanism.
0N/A *
0N/A * @param props A map of property/value pairs
0N/A * @param className name of class to use for logging
0N/A * @param firstStep number of first step in authentication state machine
0N/A * @param digestUri digestUri used in authentication
0N/A * @param cbh callback handler used to get info required for auth
0N/A *
0N/A * @throws SaslException If invalid value found in props.
0N/A */
0N/A protected DigestMD5Base(Map props, String className, int firstStep,
0N/A String digestUri, CallbackHandler cbh) throws SaslException {
0N/A super(props, className); // sets QOP, STENGTH and BUFFER_SIZE
0N/A
0N/A step = firstStep;
0N/A this.digestUri = digestUri;
0N/A this.cbh = cbh;
0N/A }
0N/A
0N/A /**
0N/A * Retrieves the SASL mechanism IANA name.
0N/A *
0N/A * @return The String "DIGEST-MD5"
0N/A */
0N/A public String getMechanismName() {
0N/A return "DIGEST-MD5";
0N/A }
0N/A
0N/A /**
0N/A * Unwrap the incoming message using the wrap method of the secCtx object
0N/A * instance.
0N/A *
0N/A * @param incoming The byte array containing the incoming bytes.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The number of bytes to read from the offset.
0N/A * @return The unwrapped message according to either the integrity or
0N/A * privacy quality-of-protection specifications.
0N/A * @throws SaslException if an error occurs when unwrapping the incoming
0N/A * message
0N/A */
0N/A public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {
0N/A if (!completed) {
0N/A throw new IllegalStateException(
0N/A "DIGEST-MD5 authentication not completed");
0N/A }
0N/A
0N/A if (secCtx == null) {
0N/A throw new IllegalStateException(
0N/A "Neither integrity nor privacy was negotiated");
0N/A }
0N/A
0N/A return (secCtx.unwrap(incoming, start, len));
0N/A }
0N/A
0N/A /**
0N/A * Wrap outgoing bytes using the wrap method of the secCtx object
0N/A * instance.
0N/A *
0N/A * @param outgoing The byte array containing the outgoing bytes.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The number of bytes to read from the offset.
0N/A * @return The wrapped message according to either the integrity or
0N/A * privacy quality-of-protection specifications.
0N/A * @throws SaslException if an error occurs when wrapping the outgoing
0N/A * message
0N/A */
0N/A public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {
0N/A if (!completed) {
0N/A throw new IllegalStateException(
0N/A "DIGEST-MD5 authentication not completed");
0N/A }
0N/A
0N/A if (secCtx == null) {
0N/A throw new IllegalStateException(
0N/A "Neither integrity nor privacy was negotiated");
0N/A }
0N/A
0N/A return (secCtx.wrap(outgoing, start, len));
0N/A }
0N/A
0N/A public void dispose() throws SaslException {
0N/A if (secCtx != null) {
0N/A secCtx = null;
0N/A }
0N/A }
0N/A
0N/A public Object getNegotiatedProperty(String propName) {
0N/A if (completed) {
0N/A if (propName.equals(Sasl.STRENGTH)) {
0N/A return negotiatedStrength;
0N/A } else {
0N/A return super.getNegotiatedProperty(propName);
0N/A }
0N/A } else {
0N/A throw new IllegalStateException(
0N/A "DIGEST-MD5 authentication not completed");
0N/A }
0N/A }
0N/A
0N/A /* ----------------- Digest-MD5 utilities ---------------- */
0N/A /**
0N/A * Generate random-string used for digest-response.
0N/A * This method uses Random to get random bytes and then
0N/A * base64 encodes the bytes. Could also use binaryToHex() but this
0N/A * is slightly faster and a more compact representation of the same info.
0N/A * @return A non-null byte array containing the nonce value for the
0N/A * digest challenge or response.
0N/A * Could use SecureRandom to be more secure but it is very slow.
0N/A */
0N/A
0N/A /** This array maps the characters to their 6 bit values */
0N/A private final static char pem_array[] = {
0N/A // 0 1 2 3 4 5 6 7
0N/A 'A','B','C','D','E','F','G','H', // 0
0N/A 'I','J','K','L','M','N','O','P', // 1
0N/A 'Q','R','S','T','U','V','W','X', // 2
0N/A 'Y','Z','a','b','c','d','e','f', // 3
0N/A 'g','h','i','j','k','l','m','n', // 4
0N/A 'o','p','q','r','s','t','u','v', // 5
0N/A 'w','x','y','z','0','1','2','3', // 6
0N/A '4','5','6','7','8','9','+','/' // 7
0N/A };
0N/A
0N/A // Make sure that this is a multiple of 3
0N/A private static final int RAW_NONCE_SIZE = 30;
0N/A
0N/A // Base 64 encoding turns each 3 bytes into 4
0N/A private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3;
0N/A
0N/A protected static final byte[] generateNonce() {
0N/A
0N/A // SecureRandom random = new SecureRandom();
0N/A Random random = new Random();
0N/A byte[] randomData = new byte[RAW_NONCE_SIZE];
0N/A random.nextBytes(randomData);
0N/A
0N/A byte[] nonce = new byte[ENCODED_NONCE_SIZE];
0N/A
0N/A // Base64-encode bytes
0N/A byte a, b, c;
0N/A int j = 0;
0N/A for (int i = 0; i < randomData.length; i += 3) {
0N/A a = randomData[i];
0N/A b = randomData[i+1];
0N/A c = randomData[i+2];
0N/A nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]);
0N/A nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
0N/A nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
0N/A nonce[j++] = (byte)(pem_array[c & 0x3F]);
0N/A }
0N/A
0N/A return nonce;
0N/A
0N/A // %%% For testing using RFC 2831 example, uncomment the following 2 lines
0N/A // System.out.println("!!!Using RFC 2831's cnonce for testing!!!");
0N/A // return "OA6MHXh6VqTrRk".getBytes();
0N/A }
0N/A
0N/A /**
0N/A * Checks if a byte[] contains characters that must be quoted
0N/A * and write the resulting, possibly escaped, characters to out.
0N/A */
0N/A protected static void writeQuotedStringValue(ByteArrayOutputStream out,
0N/A byte[] buf) {
0N/A
0N/A int len = buf.length;
0N/A byte ch;
0N/A for (int i = 0; i < len; i++) {
0N/A ch = buf[i];
0N/A if (needEscape((char)ch)) {
0N/A out.write('\\');
0N/A }
0N/A out.write(ch);
0N/A }
0N/A }
0N/A
0N/A // See Section 7.2 of RFC 2831; double-quote character is not allowed
0N/A // unless escaped; also escape the escape character and CTL chars except LWS
0N/A private static boolean needEscape(String str) {
0N/A int len = str.length();
0N/A for (int i = 0; i < len; i++) {
0N/A if (needEscape(str.charAt(i))) {
0N/A return true;
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A // Determines whether a character needs to be escaped in a quoted string
0N/A private static boolean needEscape(char ch) {
0N/A return ch == '"' || // escape char
0N/A ch == '\\' || // quote
0N/A ch == 127 || // DEL
0N/A
0N/A // 0 <= ch <= 31 except CR, HT and LF
0N/A (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10);
0N/A }
0N/A
0N/A protected static String quotedStringValue(String str) {
0N/A if (needEscape(str)) {
0N/A int len = str.length();
0N/A char[] buf = new char[len+len];
0N/A int j = 0;
0N/A char ch;
0N/A for (int i = 0; i < len; i++) {
0N/A ch = str.charAt(i);
0N/A if (needEscape(ch)) {
0N/A buf[j++] = '\\';
0N/A }
0N/A buf[j++] = ch;
0N/A }
0N/A return new String(buf, 0, j);
0N/A } else {
0N/A return str;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Convert a byte array to hexadecimal string.
0N/A *
0N/A * @param a non-null byte array
0N/A * @return a non-null String contain the HEX value
0N/A */
0N/A protected byte[] binaryToHex(byte[] digest) throws
0N/A UnsupportedEncodingException {
0N/A
0N/A StringBuffer digestString = new StringBuffer();
0N/A
0N/A for (int i = 0; i < digest.length; i ++) {
0N/A if ((digest[i] & 0x000000ff) < 0x10) {
0N/A digestString.append("0"+
0N/A Integer.toHexString(digest[i] & 0x000000ff));
0N/A } else {
0N/A digestString.append(
0N/A Integer.toHexString(digest[i] & 0x000000ff));
0N/A }
0N/A }
0N/A return digestString.toString().getBytes(encoding);
0N/A }
0N/A
0N/A /**
0N/A * Used to convert username-value, passwd or realm to 8859_1 encoding
0N/A * if all chars in string are within the 8859_1 (Latin 1) encoding range.
0N/A *
0N/A * @param a non-null String
0N/A * @return a non-nuill byte array containing the correct character encoding
0N/A * for username, paswd or realm.
0N/A */
0N/A protected byte[] stringToByte_8859_1(String str) throws SaslException {
0N/A
0N/A char[] buffer = str.toCharArray();
0N/A
0N/A try {
0N/A if (useUTF8) {
0N/A for( int i = 0; i< buffer.length; i++ ) {
0N/A if( buffer[i] > '\u00FF' ) {
0N/A return str.getBytes("UTF8");
0N/A }
0N/A }
0N/A }
0N/A return str.getBytes("8859_1");
0N/A } catch (UnsupportedEncodingException e) {
0N/A throw new SaslException(
0N/A "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);
0N/A }
0N/A }
0N/A
0N/A protected static byte[] getPlatformCiphers() {
0N/A byte[] ciphers = new byte[CIPHER_TOKENS.length];
0N/A
0N/A for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {
0N/A try {
0N/A // Checking whether the transformation is available from the
0N/A // current installed providers.
0N/A Cipher.getInstance(JCE_CIPHER_NAME[i]);
0N/A
0N/A logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);
0N/A ciphers[i] |= CIPHER_MASKS[i];
0N/A } catch (NoSuchAlgorithmException e) {
0N/A // no implementation found for requested algorithm.
0N/A } catch (NoSuchPaddingException e) {
0N/A // no implementation found for requested algorithm.
0N/A }
0N/A }
0N/A
0N/A if (ciphers[RC4] != UNSET) {
0N/A ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];
0N/A ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];
0N/A }
0N/A
0N/A return ciphers;
0N/A }
0N/A
0N/A /**
0N/A * Assembles response-value for digest-response.
0N/A *
0N/A * @param authMethod "AUTHENTICATE" for client-generated response;
0N/A * "" for server-generated response
0N/A * @return A non-null byte array containing the repsonse-value.
0N/A * @throws NoSuchAlgorithmException if the platform does not have MD5
0N/A * digest support.
0N/A * @throws UnsupportedEncodingException if a an error occurs
0N/A * encoding a string into either Latin-1 or UTF-8.
0N/A * @throws IOException if an error occurs writing to the output
0N/A * byte array buffer.
0N/A */
0N/A protected byte[] generateResponseValue(
0N/A String authMethod,
0N/A String digestUriValue,
0N/A String qopValue,
0N/A String usernameValue,
0N/A String realmValue,
0N/A char[] passwdValue,
0N/A byte[] nonceValue,
0N/A byte[] cNonceValue,
0N/A int nonceCount,
0N/A byte[] authzidValue
0N/A ) throws NoSuchAlgorithmException,
0N/A UnsupportedEncodingException,
0N/A IOException {
0N/A
0N/A MessageDigest md5 = MessageDigest.getInstance("MD5");
0N/A byte[] hexA1, hexA2;
0N/A ByteArrayOutputStream A2, beginA1, A1, KD;
0N/A
0N/A // A2
0N/A // --
0N/A // A2 = { "AUTHENTICATE:", digest-uri-value,
0N/A // [:00000000000000000000000000000000] } // if auth-int or auth-conf
0N/A //
0N/A A2 = new ByteArrayOutputStream();
0N/A A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));
0N/A if (qopValue.equals("auth-conf") ||
0N/A qopValue.equals("auth-int")) {
0N/A
0N/A logger.log(Level.FINE, "DIGEST04:QOP: {0}", qopValue);
0N/A
0N/A A2.write(SECURITY_LAYER_MARKER.getBytes(encoding));
0N/A }
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST05:A2: {0}", A2.toString());
0N/A }
0N/A
0N/A md5.update(A2.toByteArray());
0N/A byte[] digest = md5.digest();
0N/A hexA2 = binaryToHex(digest);
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST06:HEX(H(A2)): {0}", new String(hexA2));
0N/A }
0N/A
0N/A // A1
0N/A // --
0N/A // H(user-name : realm-value : passwd)
0N/A //
0N/A beginA1 = new ByteArrayOutputStream();
0N/A beginA1.write(stringToByte_8859_1(usernameValue));
0N/A beginA1.write(':');
0N/A // if no realm, realm will be an empty string
0N/A beginA1.write(stringToByte_8859_1(realmValue));
0N/A beginA1.write(':');
0N/A beginA1.write(stringToByte_8859_1(new String(passwdValue)));
0N/A
0N/A md5.update(beginA1.toByteArray());
0N/A digest = md5.digest();
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST07:H({0}) = {1}",
0N/A new Object[]{beginA1.toString(), new String(binaryToHex(digest))});
0N/A }
0N/A
0N/A // A1
0N/A // --
0N/A // A1 = { H ( {user-name : realm-value : passwd } ),
0N/A // : nonce-value, : cnonce-value : authzid-value
0N/A //
0N/A A1 = new ByteArrayOutputStream();
0N/A A1.write(digest);
0N/A A1.write(':');
0N/A A1.write(nonceValue);
0N/A A1.write(':');
0N/A A1.write(cNonceValue);
0N/A
0N/A if (authzidValue != null) {
0N/A A1.write(':');
0N/A A1.write(authzidValue);
0N/A }
0N/A md5.update(A1.toByteArray());
0N/A digest = md5.digest();
0N/A H_A1 = digest; // Record H(A1). Use for integrity & privacy.
0N/A hexA1 = binaryToHex(digest);
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST08:H(A1) = {0}", new String(hexA1));
0N/A }
0N/A
0N/A //
0N/A // H(k, : , s);
0N/A //
0N/A KD = new ByteArrayOutputStream();
0N/A KD.write(hexA1);
0N/A KD.write(':');
0N/A KD.write(nonceValue);
0N/A KD.write(':');
0N/A KD.write(nonceCountToHex(nonceCount).getBytes(encoding));
0N/A KD.write(':');
0N/A KD.write(cNonceValue);
0N/A KD.write(':');
0N/A KD.write(qopValue.getBytes(encoding));
0N/A KD.write(':');
0N/A KD.write(hexA2);
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST09:KD: {0}", KD.toString());
0N/A }
0N/A
0N/A md5.update(KD.toByteArray());
0N/A digest = md5.digest();
0N/A
0N/A byte[] answer = binaryToHex(digest);
0N/A
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST10:response-value: {0}",
0N/A new String(answer));
0N/A }
0N/A return (answer);
0N/A }
0N/A
0N/A /**
0N/A * Takes 'nonceCount' value and returns HEX value of the value.
0N/A *
0N/A * @return A non-null String representing the current NONCE-COUNT
0N/A */
0N/A protected static String nonceCountToHex(int count) {
0N/A
0N/A String str = Integer.toHexString(count);
0N/A StringBuffer pad = new StringBuffer();
0N/A
0N/A if (str.length() < 8) {
0N/A for (int i = 0; i < 8-str.length(); i ++) {
0N/A pad.append("0");
0N/A }
0N/A }
0N/A
0N/A return pad.toString() + str;
0N/A }
0N/A
0N/A /**
0N/A * Parses digest-challenge string, extracting each token
0N/A * and value(s)
0N/A *
0N/A * @param buf A non-null digest-challenge string.
0N/A * @param multipleAllowed true if multiple qop or realm or QOP directives
0N/A * are allowed.
0N/A * @throws SaslException if the buf cannot be parsed according to RFC 2831
0N/A */
0N/A protected static byte[][] parseDirectives(byte[] buf,
0N/A String[]keyTable, List<byte[]> realmChoices, int realmIndex) throws SaslException {
0N/A
0N/A byte[][] valueTable = new byte[keyTable.length][];
0N/A
0N/A ByteArrayOutputStream key = new ByteArrayOutputStream(10);
0N/A ByteArrayOutputStream value = new ByteArrayOutputStream(10);
0N/A boolean gettingKey = true;
0N/A boolean gettingQuotedValue = false;
0N/A boolean expectSeparator = false;
0N/A byte bch;
0N/A
0N/A int i = skipLws(buf, 0);
0N/A while (i < buf.length) {
0N/A bch = buf[i];
0N/A
0N/A if (gettingKey) {
0N/A if (bch == ',') {
0N/A if (key.size() != 0) {
0N/A throw new SaslException("Directive key contains a ',':" +
0N/A key);
0N/A }
0N/A // Empty element, skip separator and lws
0N/A i = skipLws(buf, i+1);
0N/A
0N/A } else if (bch == '=') {
0N/A if (key.size() == 0) {
0N/A throw new SaslException("Empty directive key");
0N/A }
0N/A gettingKey = false; // Termination of key
0N/A i = skipLws(buf, i+1); // Skip to next nonwhitespace
0N/A
0N/A // Check whether value is quoted
0N/A if (i < buf.length) {
0N/A if (buf[i] == '"') {
0N/A gettingQuotedValue = true;
0N/A ++i; // Skip quote
0N/A }
0N/A } else {
0N/A throw new SaslException(
0N/A "Valueless directive found: " + key.toString());
0N/A }
0N/A } else if (isLws(bch)) {
0N/A // LWS that occurs after key
0N/A i = skipLws(buf, i+1);
0N/A
0N/A // Expecting '='
0N/A if (i < buf.length) {
0N/A if (buf[i] != '=') {
0N/A throw new SaslException("'=' expected after key: " +
0N/A key.toString());
0N/A }
0N/A } else {
0N/A throw new SaslException(
0N/A "'=' expected after key: " + key.toString());
0N/A }
0N/A } else {
0N/A key.write(bch); // Append to key
0N/A ++i; // Advance
0N/A }
0N/A } else if (gettingQuotedValue) {
0N/A // Getting a quoted value
0N/A if (bch == '\\') {
0N/A // quoted-pair = "\" CHAR ==> CHAR
0N/A ++i; // Skip escape
0N/A if (i < buf.length) {
0N/A value.write(buf[i]);
0N/A ++i; // Advance
0N/A } else {
0N/A // Trailing escape in a quoted value
0N/A throw new SaslException(
0N/A "Unmatched quote found for directive: "
0N/A + key.toString() + " with value: " + value.toString());
0N/A }
0N/A } else if (bch == '"') {
0N/A // closing quote
0N/A ++i; // Skip closing quote
0N/A gettingQuotedValue = false;
0N/A expectSeparator = true;
0N/A } else {
0N/A value.write(bch);
0N/A ++i; // Advance
0N/A }
0N/A
0N/A } else if (isLws(bch) || bch == ',') {
0N/A // Value terminated
0N/A
0N/A extractDirective(key.toString(), value.toByteArray(),
0N/A keyTable, valueTable, realmChoices, realmIndex);
0N/A key.reset();
0N/A value.reset();
0N/A gettingKey = true;
0N/A gettingQuotedValue = expectSeparator = false;
0N/A i = skipLws(buf, i+1); // Skip separator and LWS
0N/A
0N/A } else if (expectSeparator) {
0N/A throw new SaslException(
0N/A "Expecting comma or linear whitespace after quoted string: \""
0N/A + value.toString() + "\"");
0N/A } else {
0N/A value.write(bch); // Unquoted value
0N/A ++i; // Advance
0N/A }
0N/A }
0N/A
0N/A if (gettingQuotedValue) {
0N/A throw new SaslException(
0N/A "Unmatched quote found for directive: " + key.toString() +
0N/A " with value: " + value.toString());
0N/A }
0N/A
0N/A // Get last pair
0N/A if (key.size() > 0) {
0N/A extractDirective(key.toString(), value.toByteArray(),
0N/A keyTable, valueTable, realmChoices, realmIndex);
0N/A }
0N/A
0N/A return valueTable;
0N/A }
0N/A
0N/A // Is character a linear white space?
0N/A // LWS = [CRLF] 1*( SP | HT )
0N/A // %%% Note that we're checking individual bytes instead of CRLF
0N/A private static boolean isLws(byte b) {
0N/A switch (b) {
0N/A case 13: // US-ASCII CR, carriage return
0N/A case 10: // US-ASCII LF, linefeed
0N/A case 32: // US-ASCII SP, space
0N/A case 9: // US-ASCII HT, horizontal-tab
0N/A return true;
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A // Skip all linear white spaces
0N/A private static int skipLws(byte[] buf, int start) {
0N/A int i;
0N/A for (i = start; i < buf.length; i++) {
0N/A if (!isLws(buf[i])) {
0N/A return i;
0N/A }
0N/A }
0N/A return i;
0N/A }
0N/A
0N/A /**
0N/A * Processes directive/value pairs from the digest-challenge and
0N/A * fill out the challengeVal array.
0N/A *
0N/A * @param key A non-null String challenge token name.
0N/A * @param value A non-null String token value.
0N/A * @throws SaslException if a either the key or the value is null
0N/A */
0N/A private static void extractDirective(String key, byte[] value,
0N/A String[] keyTable, byte[][] valueTable,
0N/A List<byte[]> realmChoices, int realmIndex) throws SaslException {
0N/A
0N/A for (int i = 0; i < keyTable.length; i++) {
0N/A if (key.equalsIgnoreCase(keyTable[i])) {
0N/A if (valueTable[i] == null) {
0N/A valueTable[i] = value;
0N/A if (logger.isLoggable(Level.FINE)) {
0N/A logger.log(Level.FINE, "DIGEST11:Directive {0} = {1}",
0N/A new Object[]{
0N/A keyTable[i],
0N/A new String(valueTable[i])});
0N/A }
0N/A } else if (realmChoices != null && i == realmIndex) {
0N/A // > 1 realm specified
0N/A if (realmChoices.size() == 0) {
0N/A realmChoices.add(valueTable[i]); // add existing one
0N/A }
0N/A realmChoices.add(value); // add new one
0N/A } else {
0N/A throw new SaslException(
0N/A "DIGEST-MD5: peer sent more than one " +
0N/A key + " directive: " + new String(value));
0N/A }
0N/A
0N/A break; // end search
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Implementation of the SecurityCtx interface allowing for messages
0N/A * between the client and server to be integrity checked. After a
0N/A * successful DIGEST-MD5 authentication, integtrity checking is invoked
0N/A * if the SASL QOP (quality-of-protection) is set to 'auth-int'.
0N/A * <p>
0N/A * Further details on the integrity-protection mechanism can be found
0N/A * at section 2.3 - Integrity protection in the
0N/A * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
0N/A *
0N/A * @author Jonathan Bruce
0N/A */
0N/A class DigestIntegrity implements SecurityCtx {
0N/A /* Used for generating integrity keys - specified in RFC 2831*/
0N/A static final private String CLIENT_INT_MAGIC = "Digest session key to " +
0N/A "client-to-server signing key magic constant";
0N/A static final private String SVR_INT_MAGIC = "Digest session key to " +
0N/A "server-to-client signing key magic constant";
0N/A
0N/A /* Key pairs for integrity checking */
0N/A protected byte[] myKi; // == Kic for client; == Kis for server
0N/A protected byte[] peerKi; // == Kis for client; == Kic for server
0N/A
0N/A protected int mySeqNum = 0;
0N/A protected int peerSeqNum = 0;
0N/A
0N/A // outgoing messageType and sequenceNum
0N/A protected final byte[] messageType = new byte[2];
0N/A protected final byte[] sequenceNum = new byte[4];
0N/A
0N/A /**
0N/A * Initializes DigestIntegrity implementation of SecurityCtx to
0N/A * enable DIGEST-MD5 integrity checking.
0N/A *
0N/A * @throws SaslException if an error is encountered generating the
0N/A * key-pairs for integrity checking.
0N/A */
0N/A DigestIntegrity(boolean clientMode) throws SaslException {
0N/A /* Initialize magic strings */
0N/A
0N/A try {
0N/A generateIntegrityKeyPair(clientMode);
0N/A
0N/A } catch (UnsupportedEncodingException e) {
0N/A throw new SaslException(
0N/A "DIGEST-MD5: Error encoding strings into UTF-8", e);
0N/A
0N/A } catch (IOException e) {
0N/A throw new SaslException("DIGEST-MD5: Error accessing buffers " +
0N/A "required to create integrity key pairs", e);
0N/A
0N/A } catch (NoSuchAlgorithmException e) {
0N/A throw new SaslException("DIGEST-MD5: Unsupported digest " +
0N/A "algorithm used to create integrity key pairs", e);
0N/A }
0N/A
0N/A /* Message type is a fixed value */
0N/A intToNetworkByteOrder(1, messageType, 0, 2);
0N/A }
0N/A
0N/A /**
0N/A * Generate client-server, server-client key pairs for DIGEST-MD5
0N/A * integrity checking.
0N/A *
0N/A * @throws UnsupportedEncodingException if the UTF-8 encoding is not
0N/A * supported on the platform.
0N/A * @throws IOException if an error occurs when writing to or from the
0N/A * byte array output buffers.
0N/A * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
0N/A * cannot loaded.
0N/A */
0N/A private void generateIntegrityKeyPair(boolean clientMode)
0N/A throws UnsupportedEncodingException, IOException,
0N/A NoSuchAlgorithmException {
0N/A
0N/A byte[] cimagic = CLIENT_INT_MAGIC.getBytes(encoding);
0N/A byte[] simagic = SVR_INT_MAGIC.getBytes(encoding);
0N/A
0N/A MessageDigest md5 = MessageDigest.getInstance("MD5");
0N/A
0N/A // Both client-magic-keys and server-magic-keys are the same length
0N/A byte[] keyBuffer = new byte[H_A1.length + cimagic.length];
0N/A
0N/A // Kic: Key for protecting msgs from client to server.
0N/A System.arraycopy(H_A1, 0, keyBuffer, 0, H_A1.length);
0N/A System.arraycopy(cimagic, 0, keyBuffer, H_A1.length, cimagic.length);
0N/A md5.update(keyBuffer);
0N/A byte[] Kic = md5.digest();
0N/A
0N/A // Kis: Key for protecting msgs from server to client
0N/A // No need to recopy H_A1
0N/A System.arraycopy(simagic, 0, keyBuffer, H_A1.length, simagic.length);
0N/A
0N/A md5.update(keyBuffer);
0N/A byte[] Kis = md5.digest();
0N/A
0N/A if (logger.isLoggable(Level.FINER)) {
0N/A traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
0N/A "DIGEST12:Kic: ", Kic);
0N/A traceOutput(DI_CLASS_NAME, "generateIntegrityKeyPair",
0N/A "DIGEST13:Kis: ", Kis);
0N/A }
0N/A
0N/A if (clientMode) {
0N/A myKi = Kic;
0N/A peerKi = Kis;
0N/A } else {
0N/A myKi = Kis;
0N/A peerKi = Kic;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Append MAC onto outgoing message.
0N/A *
0N/A * @param outgoing A non-null byte array containing the outgoing message.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The non-zero number of bytes for be read from the offset.
0N/A * @return The message including the integrity MAC
0N/A * @throws SaslException if an error is encountered converting a string
0N/A * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
0N/A * cannot be found or if there is an error writing to the byte array
0N/A * output buffers.
0N/A */
0N/A public byte[] wrap(byte[] outgoing, int start, int len)
0N/A throws SaslException {
0N/A
0N/A if (len == 0) {
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A /* wrapped = message, MAC, message type, sequence number */
0N/A byte[] wrapped = new byte[len+10+2+4];
0N/A
0N/A /* Start with message itself */
0N/A System.arraycopy(outgoing, start, wrapped, 0, len);
0N/A
0N/A incrementSeqNum();
0N/A
0N/A /* Calculate MAC */
0N/A byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DI_CLASS_NAME, "wrap", "DIGEST14:outgoing: ",
0N/A outgoing, start, len);
0N/A traceOutput(DI_CLASS_NAME, "wrap", "DIGEST15:seqNum: ",
0N/A sequenceNum);
0N/A traceOutput(DI_CLASS_NAME, "wrap", "DIGEST16:MAC: ", mac);
0N/A }
0N/A
0N/A /* Add MAC[0..9] to message */
0N/A System.arraycopy(mac, 0, wrapped, len, 10);
0N/A
0N/A /* Add message type [0..1] */
0N/A System.arraycopy(messageType, 0, wrapped, len+10, 2);
0N/A
0N/A /* Add sequence number [0..3] */
0N/A System.arraycopy(sequenceNum, 0, wrapped, len+12, 4);
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DI_CLASS_NAME, "wrap", "DIGEST17:wrapped: ", wrapped);
0N/A }
0N/A return wrapped;
0N/A }
0N/A
0N/A /**
0N/A * Return verified message without MAC - only if the received MAC
0N/A * and re-generated MAC are the same.
0N/A *
0N/A * @param incoming A non-null byte array containing the incoming
0N/A * message.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The non-zero number of bytes to read from the offset
0N/A * position.
0N/A * @return The verified message or null if integrity checking fails.
0N/A * @throws SaslException if an error is encountered converting a string
0N/A * into a UTF-8 byte encoding, or if the MD5 message digest algorithm
0N/A * cannot be found or if there is an error writing to the byte array
0N/A * output buffers
0N/A */
0N/A public byte[] unwrap(byte[] incoming, int start, int len)
0N/A throws SaslException {
0N/A
0N/A if (len == 0) {
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A // shave off last 16 bytes of message
0N/A byte[] mac = new byte[10];
0N/A byte[] msg = new byte[len - 16];
0N/A byte[] msgType = new byte[2];
0N/A byte[] seqNum = new byte[4];
0N/A
0N/A /* Get Msg, MAC, msgType, sequenceNum */
0N/A System.arraycopy(incoming, start, msg, 0, msg.length);
0N/A System.arraycopy(incoming, start+msg.length, mac, 0, 10);
0N/A System.arraycopy(incoming, start+msg.length+10, msgType, 0, 2);
0N/A System.arraycopy(incoming, start+msg.length+12, seqNum, 0, 4);
0N/A
0N/A /* Calculate MAC to ensure integrity */
0N/A byte[] expectedMac = getHMAC(peerKi, seqNum, msg, 0, msg.length);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST18:incoming: ",
0N/A msg);
0N/A traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST19:MAC: ",
0N/A mac);
0N/A traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST20:messageType: ",
0N/A msgType);
0N/A traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST21:sequenceNum: ",
0N/A seqNum);
0N/A traceOutput(DI_CLASS_NAME, "unwrap", "DIGEST22:expectedMAC: ",
0N/A expectedMac);
0N/A }
0N/A
0N/A /* First, compare MAC's before updating any of our state */
0N/A if (!Arrays.equals(mac, expectedMac)) {
0N/A // Discard message and do not increment sequence number
0N/A logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A /* Ensure server-sequence numbers are correct */
0N/A if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
0N/A throw new SaslException("DIGEST-MD5: Out of order " +
0N/A "sequencing of messages from server. Got: " +
0N/A networkByteOrderToInt(seqNum, 0, 4) +
0N/A " Expected: " + peerSeqNum);
0N/A }
0N/A
0N/A if (!Arrays.equals(messageType, msgType)) {
0N/A throw new SaslException("DIGEST-MD5: invalid message type: " +
0N/A networkByteOrderToInt(msgType, 0, 2));
0N/A }
0N/A
0N/A // Increment sequence number and return message
0N/A peerSeqNum++;
0N/A return msg;
0N/A }
0N/A
0N/A /**
0N/A * Generates MAC to be appended onto out-going messages.
0N/A *
0N/A * @param Ki A non-null byte array containing the key for the digest
0N/A * @param SeqNum A non-null byte array contain the sequence number
0N/A * @param msg The message to be digested
0N/A * @param start The offset from which to read the msg byte array
0N/A * @param len The non-zero number of bytes to be read from the offset
0N/A * @return The MAC of a message.
0N/A *
0N/A * @throws SaslException if an error occurs when generating MAC.
0N/A */
0N/A protected byte[] getHMAC(byte[] Ki, byte[] seqnum, byte[] msg,
0N/A int start, int len) throws SaslException {
0N/A
0N/A byte[] seqAndMsg = new byte[4+len];
0N/A System.arraycopy(seqnum, 0, seqAndMsg, 0, 4);
0N/A System.arraycopy(msg, start, seqAndMsg, 4, len);
0N/A
0N/A try {
0N/A SecretKey keyKi = new SecretKeySpec(Ki, "HmacMD5");
0N/A Mac m = Mac.getInstance("HmacMD5");
0N/A m.init(keyKi);
0N/A m.update(seqAndMsg);
0N/A byte[] hMAC_MD5 = m.doFinal();
0N/A
0N/A /* First 10 bytes of HMAC_MD5 digest */
0N/A byte macBuffer[] = new byte[10];
0N/A System.arraycopy(hMAC_MD5, 0, macBuffer, 0, 10);
0N/A
0N/A return macBuffer;
0N/A } catch (InvalidKeyException e) {
0N/A throw new SaslException("DIGEST-MD5: Invalid bytes used for " +
0N/A "key of HMAC-MD5 hash.", e);
0N/A } catch (NoSuchAlgorithmException e) {
0N/A throw new SaslException("DIGEST-MD5: Error creating " +
0N/A "instance of MD5 digest algorithm", e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Increment own sequence number and set answer in NBO sequenceNum field.
0N/A */
0N/A protected void incrementSeqNum() {
0N/A intToNetworkByteOrder(mySeqNum++, sequenceNum, 0, 4);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Implementation of the SecurityCtx interface allowing for messages
0N/A * between the client and server to be integrity checked and encrypted.
0N/A * After a successful DIGEST-MD5 authentication, privacy is invoked if the
0N/A * SASL QOP (quality-of-protection) is set to 'auth-conf'.
0N/A * <p>
0N/A * Further details on the integrity-protection mechanism can be found
0N/A * at section 2.4 - Confidentiality protection in
0N/A * <a href="http://www.ietf.org/rfc/rfc2831.txt">RFC2831</a> definition.
0N/A *
0N/A * @author Jonathan Bruce
0N/A */
0N/A final class DigestPrivacy extends DigestIntegrity implements SecurityCtx {
0N/A /* Used for generating privacy keys - specified in RFC 2831 */
0N/A static final private String CLIENT_CONF_MAGIC =
0N/A "Digest H(A1) to client-to-server sealing key magic constant";
0N/A static final private String SVR_CONF_MAGIC =
0N/A "Digest H(A1) to server-to-client sealing key magic constant";
0N/A
0N/A private Cipher encCipher;
0N/A private Cipher decCipher;
0N/A
0N/A /**
0N/A * Initializes the cipher object instances for encryption and decryption.
0N/A *
0N/A * @throws SaslException if an error occurs with the Key
0N/A * initialization, or a string cannot be encoded into a byte array
0N/A * using the UTF-8 encoding, or an error occurs when writing to a
0N/A * byte array output buffers or the mechanism cannot load the MD5
0N/A * message digest algorithm or invalid initialization parameters are
0N/A * passed to the cipher object instances.
0N/A */
0N/A DigestPrivacy(boolean clientMode) throws SaslException {
0N/A
0N/A super(clientMode); // generate Kic, Kis keys for integrity-checking.
0N/A
0N/A try {
0N/A generatePrivacyKeyPair(clientMode);
0N/A
0N/A } catch (SaslException e) {
0N/A throw e;
0N/A
0N/A } catch (UnsupportedEncodingException e) {
0N/A throw new SaslException(
0N/A "DIGEST-MD5: Error encoding string value into UTF-8", e);
0N/A
0N/A } catch (IOException e) {
0N/A throw new SaslException("DIGEST-MD5: Error accessing " +
0N/A "buffers required to generate cipher keys", e);
0N/A } catch (NoSuchAlgorithmException e) {
0N/A throw new SaslException("DIGEST-MD5: Error creating " +
0N/A "instance of required cipher or digest", e);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Generates client-server and server-client keys to encrypt and
0N/A * decrypt messages. Also generates IVs for DES ciphers.
0N/A *
0N/A * @throws IOException if an error occurs when writing to or from the
0N/A * byte array output buffers.
0N/A * @throws NoSuchAlgorithmException if the MD5 message digest algorithm
0N/A * cannot loaded.
0N/A * @throws UnsupportedEncodingException if an UTF-8 encoding is not
0N/A * supported on the platform.
0N/A * @throw SaslException if an error occurs initializing the keys and
0N/A * IVs for the chosen cipher.
0N/A */
0N/A private void generatePrivacyKeyPair(boolean clientMode)
0N/A throws IOException, UnsupportedEncodingException,
0N/A NoSuchAlgorithmException, SaslException {
0N/A
0N/A byte[] ccmagic = CLIENT_CONF_MAGIC.getBytes(encoding);
0N/A byte[] scmagic = SVR_CONF_MAGIC.getBytes(encoding);
0N/A
0N/A /* Kcc = MD5{H(A1)[0..n], "Digest ... client-to-server"} */
0N/A MessageDigest md5 = MessageDigest.getInstance("MD5");
0N/A
0N/A int n;
0N/A if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_40])) {
0N/A n = 5; /* H(A1)[0..5] */
0N/A } else if (negotiatedCipher.equals(CIPHER_TOKENS[RC4_56])) {
0N/A n = 7; /* H(A1)[0..7] */
0N/A } else { // des and 3des and rc4
0N/A n = 16; /* H(A1)[0..16] */
0N/A }
0N/A
0N/A /* {H(A1)[0..n], "Digest ... client-to-server..."} */
0N/A // Both client-magic-keys and server-magic-keys are the same length
0N/A byte[] keyBuffer = new byte[n + ccmagic.length];
0N/A System.arraycopy(H_A1, 0, keyBuffer, 0, n); // H(A1)[0..n]
0N/A
0N/A /* Kcc: Key for encrypting messages from client->server */
0N/A System.arraycopy(ccmagic, 0, keyBuffer, n, ccmagic.length);
0N/A md5.update(keyBuffer);
0N/A byte[] Kcc = md5.digest();
0N/A
0N/A /* Kcs: Key for decrypting messages from server->client */
0N/A // No need to copy H_A1 again since it hasn't changed
0N/A System.arraycopy(scmagic, 0, keyBuffer, n, scmagic.length);
0N/A md5.update(keyBuffer);
0N/A byte[] Kcs = md5.digest();
0N/A
0N/A if (logger.isLoggable(Level.FINER)) {
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST24:Kcc: ", Kcc);
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST25:Kcs: ", Kcs);
0N/A }
0N/A
0N/A byte[] myKc;
0N/A byte[] peerKc;
0N/A
0N/A if (clientMode) {
0N/A myKc = Kcc;
0N/A peerKc = Kcs;
0N/A } else {
0N/A myKc = Kcs;
0N/A peerKc = Kcc;
0N/A }
0N/A
0N/A try {
0N/A SecretKey encKey;
0N/A SecretKey decKey;
0N/A
0N/A /* Initialize cipher objects */
0N/A if (negotiatedCipher.indexOf(CIPHER_TOKENS[RC4]) > -1) {
0N/A encCipher = Cipher.getInstance("RC4");
0N/A decCipher = Cipher.getInstance("RC4");
0N/A
0N/A encKey = new SecretKeySpec(myKc, "RC4");
0N/A decKey = new SecretKeySpec(peerKc, "RC4");
0N/A
0N/A encCipher.init(Cipher.ENCRYPT_MODE, encKey);
0N/A decCipher.init(Cipher.DECRYPT_MODE, decKey);
0N/A
0N/A } else if ((negotiatedCipher.equals(CIPHER_TOKENS[DES])) ||
0N/A (negotiatedCipher.equals(CIPHER_TOKENS[DES3]))) {
0N/A
0N/A // DES or 3DES
0N/A String cipherFullname, cipherShortname;
0N/A
0N/A // Use "NoPadding" when specifying cipher names
0N/A // RFC 2831 already defines padding rules for producing
0N/A // 8-byte aligned blocks
0N/A if (negotiatedCipher.equals(CIPHER_TOKENS[DES])) {
0N/A cipherFullname = "DES/CBC/NoPadding";
0N/A cipherShortname = "des";
0N/A } else {
0N/A /* 3DES */
0N/A cipherFullname = "DESede/CBC/NoPadding";
0N/A cipherShortname = "desede";
0N/A }
0N/A
0N/A encCipher = Cipher.getInstance(cipherFullname);
0N/A decCipher = Cipher.getInstance(cipherFullname);
0N/A
0N/A encKey = makeDesKeys(myKc, cipherShortname);
0N/A decKey = makeDesKeys(peerKc, cipherShortname);
0N/A
0N/A // Set up the DES IV, which is the last 8 bytes of Kcc/Kcs
0N/A IvParameterSpec encIv = new IvParameterSpec(myKc, 8, 8);
0N/A IvParameterSpec decIv = new IvParameterSpec(peerKc, 8, 8);
0N/A
0N/A // Initialize cipher objects
0N/A encCipher.init(Cipher.ENCRYPT_MODE, encKey, encIv);
0N/A decCipher.init(Cipher.DECRYPT_MODE, decKey, decIv);
0N/A
0N/A if (logger.isLoggable(Level.FINER)) {
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST26:" + negotiatedCipher + " IVcc: ",
0N/A encIv.getIV());
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST27:" + negotiatedCipher + " IVcs: ",
0N/A decIv.getIV());
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST28:" + negotiatedCipher + " encryption key: ",
0N/A encKey.getEncoded());
0N/A traceOutput(DP_CLASS_NAME, "generatePrivacyKeyPair",
0N/A "DIGEST29:" + negotiatedCipher + " decryption key: ",
0N/A decKey.getEncoded());
0N/A }
0N/A }
0N/A } catch (InvalidKeySpecException e) {
0N/A throw new SaslException("DIGEST-MD5: Unsupported key " +
0N/A "specification used.", e);
0N/A } catch (InvalidAlgorithmParameterException e) {
0N/A throw new SaslException("DIGEST-MD5: Invalid cipher " +
0N/A "algorithem parameter used to create cipher instance", e);
0N/A } catch (NoSuchPaddingException e) {
0N/A throw new SaslException("DIGEST-MD5: Unsupported " +
0N/A "padding used for chosen cipher", e);
0N/A } catch (InvalidKeyException e) {
0N/A throw new SaslException("DIGEST-MD5: Invalid data " +
0N/A "used to initialize keys", e);
0N/A }
0N/A }
0N/A
0N/A // -------------------------------------------------------------------
0N/A
0N/A /**
0N/A * Encrypt out-going message.
0N/A *
0N/A * @param outgoing A non-null byte array containing the outgoing message.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The non-zero number of bytes to be read from the offset.
0N/A * @return The encrypted message.
0N/A *
0N/A * @throws SaslException if an error occurs when writing to or from the
0N/A * byte array output buffers or if the MD5 message digest algorithm
0N/A * cannot loaded or if an UTF-8 encoding is not supported on the
0N/A * platform.
0N/A */
0N/A public byte[] wrap(byte[] outgoing, int start, int len)
0N/A throws SaslException {
0N/A
0N/A if (len == 0) {
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A /* HMAC(Ki, {SeqNum, msg})[0..9] */
0N/A incrementSeqNum();
0N/A byte[] mac = getHMAC(myKi, sequenceNum, outgoing, start, len);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "wrap", "DIGEST30:Outgoing: ",
0N/A outgoing, start, len);
0N/A traceOutput(DP_CLASS_NAME, "wrap", "seqNum: ",
0N/A sequenceNum);
0N/A traceOutput(DP_CLASS_NAME, "wrap", "MAC: ", mac);
0N/A }
0N/A
0N/A // Calculate padding
0N/A int bs = encCipher.getBlockSize();
0N/A byte[] padding;
0N/A if (bs > 1 ) {
0N/A int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9]
0N/A padding = new byte[pad];
0N/A for (int i=0; i < pad; i++) {
0N/A padding[i] = (byte)pad;
0N/A }
0N/A } else {
0N/A padding = EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A byte[] toBeEncrypted = new byte[len+padding.length+10];
0N/A
0N/A /* {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])} */
0N/A System.arraycopy(outgoing, start, toBeEncrypted, 0, len);
0N/A System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
0N/A System.arraycopy(mac, 0, toBeEncrypted, len+padding.length, 10);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "wrap",
0N/A "DIGEST31:{msg, pad, KicMAC}: ", toBeEncrypted);
0N/A }
0N/A
0N/A /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
0N/A byte[] cipherBlock;
0N/A try {
0N/A // Do CBC (chaining) across packets
0N/A cipherBlock = encCipher.update(toBeEncrypted);
0N/A
0N/A if (cipherBlock == null) {
0N/A // update() can return null
0N/A throw new IllegalBlockSizeException(""+toBeEncrypted.length);
0N/A }
0N/A } catch (IllegalBlockSizeException e) {
0N/A throw new SaslException(
0N/A "DIGEST-MD5: Invalid block size for cipher", e);
0N/A }
0N/A
0N/A byte[] wrapped = new byte[cipherBlock.length+2+4];
0N/A System.arraycopy(cipherBlock, 0, wrapped, 0, cipherBlock.length);
0N/A System.arraycopy(messageType, 0, wrapped, cipherBlock.length, 2);
0N/A System.arraycopy(sequenceNum, 0, wrapped, cipherBlock.length+2, 4);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "wrap", "DIGEST32:Wrapped: ", wrapped);
0N/A }
0N/A
0N/A return wrapped;
0N/A }
0N/A
0N/A /*
0N/A * Decrypt incoming messages and verify their integrity.
0N/A *
0N/A * @param incoming A non-null byte array containing the incoming
0N/A * encrypted message.
0N/A * @param start The offset from which to read the byte array.
0N/A * @param len The non-zero number of bytes to read from the offset
0N/A * position.
0N/A * @return The decrypted, verified message or null if integrity
0N/A * checking
0N/A * fails.
0N/A * @throws SaslException if there are the SASL buffer is empty or if
0N/A * if an error occurs reading the SASL buffer.
0N/A */
0N/A public byte[] unwrap(byte[] incoming, int start, int len)
0N/A throws SaslException {
0N/A
0N/A if (len == 0) {
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A byte[] encryptedMsg = new byte[len - 6];
0N/A byte[] msgType = new byte[2];
0N/A byte[] seqNum = new byte[4];
0N/A
0N/A /* Get cipherMsg; msgType; sequenceNum */
0N/A System.arraycopy(incoming, start,
0N/A encryptedMsg, 0, encryptedMsg.length);
0N/A System.arraycopy(incoming, start+encryptedMsg.length,
0N/A msgType, 0, 2);
0N/A System.arraycopy(incoming, start+encryptedMsg.length+2,
0N/A seqNum, 0, 4);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A logger.log(Level.FINEST,
0N/A "DIGEST33:Expecting sequence num: {0}",
0N/A new Integer(peerSeqNum));
0N/A traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST34:incoming: ",
0N/A encryptedMsg);
0N/A }
0N/A
0N/A // Decrypt message
0N/A /* CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}) */
0N/A byte[] decryptedMsg;
0N/A
0N/A try {
0N/A // Do CBC (chaining) across packets
0N/A decryptedMsg = decCipher.update(encryptedMsg);
0N/A
0N/A if (decryptedMsg == null) {
0N/A // update() can return null
0N/A throw new IllegalBlockSizeException(""+encryptedMsg.length);
0N/A }
0N/A } catch (IllegalBlockSizeException e) {
0N/A throw new SaslException("DIGEST-MD5: Illegal block " +
0N/A "sizes used with chosen cipher", e);
0N/A }
0N/A
0N/A byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
0N/A byte[] mac = new byte[10];
0N/A
0N/A System.arraycopy(decryptedMsg, 0,
0N/A msgWithPadding, 0, msgWithPadding.length);
0N/A System.arraycopy(decryptedMsg, msgWithPadding.length,
0N/A mac, 0, 10);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "unwrap",
0N/A "DIGEST35:Unwrapped (w/padding): ", msgWithPadding);
0N/A traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST36:MAC: ", mac);
0N/A traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST37:messageType: ",
0N/A msgType);
0N/A traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST38:sequenceNum: ",
0N/A seqNum);
0N/A }
0N/A
0N/A int msgLength = msgWithPadding.length;
0N/A int blockSize = decCipher.getBlockSize();
0N/A if (blockSize > 1) {
0N/A // get value of last octet of the byte array
0N/A msgLength -= (int)msgWithPadding[msgWithPadding.length - 1];
0N/A if (msgLength < 0) {
0N/A // Discard message and do not increment sequence number
0N/A if (logger.isLoggable(Level.INFO)) {
0N/A logger.log(Level.INFO,
0N/A "DIGEST39:Incorrect padding: {0}",
0N/A new Byte(msgWithPadding[msgWithPadding.length - 1]));
0N/A }
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A }
0N/A
0N/A /* Re-calculate MAC to ensure integrity */
0N/A byte[] expectedMac = getHMAC(peerKi, seqNum, msgWithPadding,
0N/A 0, msgLength);
0N/A
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "unwrap", "DIGEST40:KisMAC: ",
0N/A expectedMac);
0N/A }
0N/A
0N/A // First, compare MACs before updating state
0N/A if (!Arrays.equals(mac, expectedMac)) {
0N/A // Discard message and do not increment sequence number
0N/A logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
0N/A return EMPTY_BYTE_ARRAY;
0N/A }
0N/A
0N/A /* Ensure sequence number is correct */
0N/A if (peerSeqNum != networkByteOrderToInt(seqNum, 0, 4)) {
0N/A throw new SaslException("DIGEST-MD5: Out of order " +
0N/A "sequencing of messages from server. Got: " +
0N/A networkByteOrderToInt(seqNum, 0, 4) + " Expected: " +
0N/A peerSeqNum);
0N/A }
0N/A
0N/A /* Check message type */
0N/A if (!Arrays.equals(messageType, msgType)) {
0N/A throw new SaslException("DIGEST-MD5: invalid message type: " +
0N/A networkByteOrderToInt(msgType, 0, 2));
0N/A }
0N/A
0N/A // Increment sequence number and return message
0N/A peerSeqNum++;
0N/A
0N/A if (msgLength == msgWithPadding.length) {
0N/A return msgWithPadding; // no padding
0N/A } else {
0N/A // Get a copy of the message without padding
0N/A byte[] clearMsg = new byte[msgLength];
0N/A System.arraycopy(msgWithPadding, 0, clearMsg, 0, msgLength);
0N/A return clearMsg;
0N/A }
0N/A }
0N/A }
0N/A
0N/A // ---------------- DES and 3 DES key manipulation routines
0N/A
0N/A private static final BigInteger MASK = new BigInteger("7f", 16);
0N/A
0N/A /**
0N/A * Sets the parity bit (0th bit) in each byte so that each byte
0N/A * contains an odd number of 1's.
0N/A */
0N/A private static void setParityBit(byte[] key) {
0N/A for (int i = 0; i < key.length; i++) {
1241N/A int b = key[i] & 0xfe;
1241N/A b |= (Integer.bitCount(b) & 1) ^ 1;
1241N/A key[i] = (byte) b;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Expands a 7-byte array into an 8-byte array that contains parity bits
0N/A * The binary format of a cryptographic key is:
0N/A * (B1,B2,...,B7,P1,B8,...B14,P2,B15,...,B49,P7,B50,...,B56,P8)
0N/A * where (B1,B2,...,B56) are the independent bits of a DES key and
0N/A * (PI,P2,...,P8) are reserved for parity bits computed on the preceding
0N/A * seven independent bits and set so that the parity of the octet is odd,
0N/A * i.e., there is an odd number of "1" bits in the octet.
0N/A */
0N/A private static byte[] addDesParity(byte[] input, int offset, int len) {
0N/A if (len != 7)
0N/A throw new IllegalArgumentException(
0N/A "Invalid length of DES Key Value:" + len);
0N/A
0N/A byte[] raw = new byte[7];
0N/A System.arraycopy(input, offset, raw, 0, len);
0N/A
0N/A byte[] result = new byte[8];
0N/A BigInteger in = new BigInteger(raw);
0N/A
0N/A // Shift 7 bits each time into a byte
0N/A for (int i=result.length-1; i>=0; i--) {
0N/A result[i] = in.and(MASK).toByteArray()[0];
0N/A result[i] <<= 1; // make room for parity bit
0N/A in = in.shiftRight(7);
0N/A }
0N/A setParityBit(result);
0N/A return result;
0N/A }
0N/A
0N/A /**
0N/A * Create parity-adjusted keys suitable for DES / DESede encryption.
0N/A *
0N/A * @param input A non-null byte array containing key material for
0N/A * DES / DESede.
0N/A * @param desStrength A string specifying eithe a DES or a DESede key.
0N/A * @return SecretKey An instance of either DESKeySpec or DESedeKeySpec.
0N/A *
0N/A * @throws NoSuchAlgorithmException if the either the DES or DESede
0N/A * algorithms cannote be lodaed by JCE.
0N/A * @throws InvalidKeyException if an invalid array of bytes is used
0N/A * as a key for DES or DESede.
0N/A * @throws InvalidKeySpecException in an invalid parameter is passed
0N/A * to either te DESKeySpec of the DESedeKeySpec constructors.
0N/A */
0N/A private static SecretKey makeDesKeys(byte[] input, String desStrength)
0N/A throws NoSuchAlgorithmException, InvalidKeyException,
0N/A InvalidKeySpecException {
0N/A
0N/A // Generate first subkey using first 7 bytes
0N/A byte[] subkey1 = addDesParity(input, 0, 7);
0N/A
0N/A KeySpec spec = null;
0N/A SecretKeyFactory desFactory =
0N/A SecretKeyFactory.getInstance(desStrength);
0N/A
0N/A if (desStrength.equals("des")) {
0N/A spec = new DESKeySpec(subkey1, 0);
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST42:DES key input: ", input);
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST43:DES key parity-adjusted: ", subkey1);
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST44:DES key material: ", ((DESKeySpec)spec).getKey());
0N/A logger.log(Level.FINEST, "DIGEST45: is parity-adjusted? {0}",
0N/A Boolean.valueOf(DESKeySpec.isParityAdjusted(subkey1, 0)));
0N/A }
0N/A
0N/A } else if (desStrength.equals("desede")) {
0N/A
0N/A // Generate second subkey using second 7 bytes
0N/A byte[] subkey2 = addDesParity(input, 7, 7);
0N/A
0N/A // Construct 24-byte encryption-decryption-encryption sequence
0N/A byte[] ede = new byte[subkey1.length*2+subkey2.length];
0N/A System.arraycopy(subkey1, 0, ede, 0, subkey1.length);
0N/A System.arraycopy(subkey2, 0, ede, subkey1.length, subkey2.length);
0N/A System.arraycopy(subkey1, 0, ede, subkey1.length+subkey2.length,
0N/A subkey1.length);
0N/A
0N/A spec = new DESedeKeySpec(ede, 0);
0N/A if (logger.isLoggable(Level.FINEST)) {
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST46:3DES key input: ", input);
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST47:3DES key ede: ", ede);
0N/A traceOutput(DP_CLASS_NAME, "makeDesKeys",
0N/A "DIGEST48:3DES key material: ",
0N/A ((DESedeKeySpec)spec).getKey());
0N/A logger.log(Level.FINEST, "DIGEST49: is parity-adjusted? ",
0N/A Boolean.valueOf(DESedeKeySpec.isParityAdjusted(ede, 0)));
0N/A }
0N/A } else {
0N/A throw new IllegalArgumentException("Invalid DES strength:" +
0N/A desStrength);
0N/A }
0N/A return desFactory.generateSecret(spec);
0N/A }
0N/A}