1632 lines · 62.4 KB · Java
RawDownload
/* * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.security.sasl.digest; import java.util.Map;import java.util.Arrays;import java.util.List;import java.util.Set;import java.util.logging.Logger;import java.util.logging.Level;import java.math.BigInteger;import java.util.Random;import java.security.Provider; import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.UnsupportedEncodingException;import java.io.IOException; import java.security.MessageDigest;import java.security.AccessController;import java.security.PrivilegedAction;import java.security.NoSuchAlgorithmException;import java.security.InvalidKeyException;import java.security.spec.KeySpec;import java.security.spec.InvalidKeySpecException;import java.security.InvalidAlgorithmParameterException; import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.Mac;import javax.crypto.SecretKeyFactory;import javax.crypto.BadPaddingException;import javax.crypto.NoSuchPaddingException;import javax.crypto.IllegalBlockSizeException;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import javax.crypto.spec.DESKeySpec;import javax.crypto.spec.DESedeKeySpec; import javax.security.sasl.*;import com.sun.security.sasl.util.AbstractSaslImpl; import javax.security.auth.callback.CallbackHandler; /** * Utility class for DIGEST-MD5 mechanism. Provides utility methods * and contains two inner classes which implement the SecurityCtx * interface. The inner classes provide the funtionality to allow * for quality-of-protection (QOP) with integrity checking and * privacy. * * @author Jonathan Bruce * @author Rosanna Lee */abstract class DigestMD5Base extends AbstractSaslImpl {    /* ------------------------- Constants ------------------------ */     // Used for logging    private static final String DI_CLASS_NAME = DigestIntegrity.class.getName();    private static final String DP_CLASS_NAME = DigestPrivacy.class.getName();     /* Constants - defined in RFC2831 */    protected static final int MAX_CHALLENGE_LENGTH = 2048;    protected static final int MAX_RESPONSE_LENGTH = 4096;    protected static final int DEFAULT_MAXBUF = 65536;     /* Supported ciphers for 'auth-conf' */    protected static final int DES3 = 0;    protected static final int RC4 = 1;    protected static final int DES = 2;    protected static final int RC4_56 = 3;    protected static final int RC4_40 = 4;    protected static final String[] CIPHER_TOKENS = { "3des",                                                      "rc4",                                                      "des",                                                      "rc4-56",                                                      "rc4-40" };    private static final String[] JCE_CIPHER_NAME = {        "DESede/CBC/NoPadding",        "RC4",        "DES/CBC/NoPadding",    };     /*     * If QOP is set to 'auth-conf', a DIGEST-MD5 mechanism must have     * support for the DES and Triple DES cipher algorithms (optionally,     * support for RC4 [128/56/40 bit keys] ciphers) to provide for     * confidentiality. See RFC 2831 for details. This implementation     * provides support for DES, Triple DES and RC4 ciphers.     *     * The value of strength effects the strength of cipher used. The mappings     * of 'high', 'medium', and 'low' give the following behaviour.     *     *  HIGH_STRENGTH   - Triple DES     *                  - RC4 (128bit)     *  MEDIUM_STRENGTH - DES     *                  - RC4 (56bit)     *  LOW_SRENGTH     - RC4 (40bit)     */    protected static final byte DES_3_STRENGTH = HIGH_STRENGTH;    protected static final byte RC4_STRENGTH = HIGH_STRENGTH;    protected static final byte DES_STRENGTH = MEDIUM_STRENGTH;    protected static final byte RC4_56_STRENGTH = MEDIUM_STRENGTH;    protected static final byte RC4_40_STRENGTH = LOW_STRENGTH;    protected static final byte UNSET = (byte)0;    protected static final byte[] CIPHER_MASKS = { DES_3_STRENGTH,                                                   RC4_STRENGTH,                                                   DES_STRENGTH,                                                   RC4_56_STRENGTH,                                                   RC4_40_STRENGTH };     private static final String SECURITY_LAYER_MARKER =        ":00000000000000000000000000000000";     protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];     /* ------------------- Variable Fields ----------------------- */     /* Used to track progress of authentication; step numbers from RFC 2831 */    protected int step;     /* Used to get username/password, choose realm for client */    /* Used to obtain authorization, pw info, canonicalized authzid for server */    protected CallbackHandler cbh;     protected SecurityCtx secCtx;    protected byte[] H_A1; // component of response-value     protected byte[] nonce;         // server generated nonce     /* Variables set when parsing directives in digest challenge/response. */    protected String negotiatedStrength;    protected String negotiatedCipher;    protected String negotiatedQop;    protected String negotiatedRealm;    protected boolean useUTF8 = false;    protected String encoding = "8859_1";  // default unless server specifies utf-8     protected String digestUri;    protected String authzid;       // authzid or canonicalized authzid     /**     * Constucts an instance of DigestMD5Base. Calls super constructor     * to parse properties for mechanism.     *     * @param props A map of property/value pairs     * @param className name of class to use for logging     * @param firstStep number of first step in authentication state machine     * @param digestUri digestUri used in authentication     * @param cbh callback handler used to get info required for auth     *     * @throws SaslException If invalid value found in props.     */    protected DigestMD5Base(Map props, String className, int firstStep,        String digestUri, CallbackHandler cbh) throws SaslException {        super(props, className); // sets QOP, STENGTH and BUFFER_SIZE         step = firstStep;        this.digestUri = digestUri;        this.cbh = cbh;    }     /**     * Retrieves the SASL mechanism IANA name.     *     * @return The String "DIGEST-MD5"     */    public String getMechanismName() {        return "DIGEST-MD5";    }     /**     * Unwrap the incoming message using the wrap method of the secCtx object     * instance.     *     * @param incoming The byte array containing the incoming bytes.     * @param start The offset from which to read the byte array.     * @param len The number of bytes to read from the offset.     * @return The unwrapped message according to either the integrity or     * privacy quality-of-protection specifications.     * @throws SaslException if an error occurs when unwrapping the incoming     * message     */    public byte[] unwrap(byte[] incoming, int start, int len) throws SaslException {        if (!completed) {            throw new IllegalStateException(                "DIGEST-MD5 authentication not completed");        }         if (secCtx == null) {            throw new IllegalStateException(                "Neither integrity nor privacy was negotiated");        }         return (secCtx.unwrap(incoming, start, len));    }     /**     * Wrap outgoing bytes using the wrap method of the secCtx object     * instance.     *     * @param outgoing The byte array containing the outgoing bytes.     * @param start The offset from which to read the byte array.     * @param len The number of bytes to read from the offset.     * @return The wrapped message according to either the integrity or     * privacy quality-of-protection specifications.     * @throws SaslException if an error occurs when wrapping the outgoing     * message     */    public byte[] wrap(byte[] outgoing, int start, int len) throws SaslException {        if (!completed) {            throw new IllegalStateException(                "DIGEST-MD5 authentication not completed");        }         if (secCtx == null) {            throw new IllegalStateException(                "Neither integrity nor privacy was negotiated");        }         return (secCtx.wrap(outgoing, start, len));    }     public void dispose() throws SaslException {        if (secCtx != null) {            secCtx = null;        }    }     public Object getNegotiatedProperty(String propName) {        if (completed) {            if (propName.equals(Sasl.STRENGTH)) {                return negotiatedStrength;            } else {                return super.getNegotiatedProperty(propName);            }        } else {            throw new IllegalStateException(                "DIGEST-MD5 authentication not completed");        }    }     /* ----------------- Digest-MD5 utilities ---------------- */    /**     * Generate random-string used for digest-response.     * This method uses Random to get random bytes and then     * base64 encodes the bytes. Could also use binaryToHex() but this     * is slightly faster and a more compact representation of the same info.     * @return A non-null byte array containing the nonce value for the     * digest challenge or response.     * Could use SecureRandom to be more secure but it is very slow.     */     /** This array maps the characters to their 6 bit values */    private final static char pem_array[] = {        //       0   1   2   3   4   5   6   7                'A','B','C','D','E','F','G','H', // 0                'I','J','K','L','M','N','O','P', // 1                'Q','R','S','T','U','V','W','X', // 2                'Y','Z','a','b','c','d','e','f', // 3                'g','h','i','j','k','l','m','n', // 4                'o','p','q','r','s','t','u','v', // 5                'w','x','y','z','0','1','2','3', // 6                '4','5','6','7','8','9','+','/'  // 7    };     // Make sure that this is a multiple of 3    private static final int RAW_NONCE_SIZE = 30;     // Base 64 encoding turns each 3 bytes into 4    private static final int ENCODED_NONCE_SIZE = RAW_NONCE_SIZE*4/3;     protected static final byte[] generateNonce() {         // SecureRandom random = new SecureRandom();        Random random = new Random();        byte[] randomData = new byte[RAW_NONCE_SIZE];        random.nextBytes(randomData);         byte[] nonce = new byte[ENCODED_NONCE_SIZE];         // Base64-encode bytes        byte a, b, c;        int j = 0;        for (int i = 0; i < randomData.length; i += 3) {            a = randomData[i];            b = randomData[i+1];            c = randomData[i+2];            nonce[j++] = (byte)(pem_array[(a >>> 2) & 0x3F]);            nonce[j++] = (byte)(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);            nonce[j++] = (byte)(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);            nonce[j++] = (byte)(pem_array[c & 0x3F]);        }         return nonce;         // %%% For testing using RFC 2831 example, uncomment the following 2 lines        // System.out.println("!!!Using RFC 2831's cnonce for testing!!!");        // return "OA6MHXh6VqTrRk".getBytes();    }     /**     * Checks if a byte[] contains characters that must be quoted     * and write the resulting, possibly escaped, characters to out.     */    protected static void writeQuotedStringValue(ByteArrayOutputStream out,        byte[] buf) {         int len = buf.length;        byte ch;        for (int i = 0; i < len; i++) {            ch = buf[i];            if (needEscape((char)ch)) {                out.write('\\');            }            out.write(ch);        }    }     // See Section 7.2 of RFC 2831; double-quote character is not allowed    // unless escaped; also escape the escape character and CTL chars except LWS    private static boolean needEscape(String str) {        int len = str.length();        for (int i = 0; i < len; i++) {            if (needEscape(str.charAt(i))) {                return true;            }        }        return false;    }     // Determines whether a character needs to be escaped in a quoted string    private static boolean needEscape(char ch) {        return ch == '"' ||  // escape char            ch == '\\' ||    // quote            ch == 127 ||     // DEL             // 0 <= ch <= 31 except CR, HT and LF            (ch >= 0 && ch <= 31 && ch != 13 && ch != 9 && ch != 10);    }     protected static String quotedStringValue(String str) {        if (needEscape(str)) {            int len = str.length();            char[] buf = new char[len+len];            int j = 0;            char ch;            for (int i = 0; i < len; i++) {                ch = str.charAt(i);                if (needEscape(ch)) {                    buf[j++] =  '\\';                }                buf[j++] = ch;            }            return new String(buf, 0, j);        } else {            return str;        }    }     /**     * Convert a byte array to hexadecimal string.     *     * @param a non-null byte array     * @return a non-null String contain the HEX value     */    protected byte[] binaryToHex(byte[] digest) throws    UnsupportedEncodingException {         StringBuffer digestString = new StringBuffer();         for (int i = 0; i < digest.length; i ++) {            if ((digest[i] & 0x000000ff) < 0x10) {                digestString.append("0"+                    Integer.toHexString(digest[i] & 0x000000ff));            } else {                digestString.append(                    Integer.toHexString(digest[i] & 0x000000ff));            }        }        return digestString.toString().getBytes(encoding);    }     /**     * Used to convert username-value, passwd or realm to 8859_1 encoding     * if all chars in string are within the 8859_1 (Latin 1) encoding range.     *     * @param a non-null String     * @return a non-nuill byte array containing the correct character encoding     * for username, paswd or realm.     */    protected byte[] stringToByte_8859_1(String str) throws SaslException {         char[] buffer = str.toCharArray();         try {            if (useUTF8) {                for( int i = 0; i< buffer.length; i++ ) {                    if( buffer[i] > '\u00FF' ) {                        return str.getBytes("UTF8");                    }                }            }            return str.getBytes("8859_1");        } catch (UnsupportedEncodingException e) {            throw new SaslException(                "cannot encode string in UTF8 or 8859-1 (Latin-1)", e);        }    }     protected static byte[] getPlatformCiphers() {        byte[] ciphers = new byte[CIPHER_TOKENS.length];         for (int i = 0; i < JCE_CIPHER_NAME.length; i++) {            try {                // Checking whether the transformation is available from the                // current installed providers.                Cipher.getInstance(JCE_CIPHER_NAME[i]);                 logger.log(Level.FINE, "DIGEST01:Platform supports {0}", JCE_CIPHER_NAME[i]);                ciphers[i] |= CIPHER_MASKS[i];            } catch (NoSuchAlgorithmException e) {                // no implementation found for requested algorithm.            } catch (NoSuchPaddingException e) {                // no implementation found for requested algorithm.            }        }         if (ciphers[RC4] != UNSET) {            ciphers[RC4_56] |= CIPHER_MASKS[RC4_56];            ciphers[RC4_40] |= CIPHER_MASKS[RC4_40];        }         return ciphers;    }     /**     * Assembles response-value for digest-response.     *     * @param authMethod "AUTHENTICATE" for client-generated response;     *        "" for server-generated response     * @return A non-null byte array containing the repsonse-value.     * @throws NoSuchAlgorithmException if the platform does not have MD5     * digest support.     * @throws UnsupportedEncodingException if a an error occurs     * encoding a string into either Latin-1 or UTF-8.     * @throws IOException if an error occurs writing to the output     * byte array buffer.     */    protected byte[] generateResponseValue(        String authMethod,        String digestUriValue,        String qopValue,        String usernameValue,        String realmValue,        char[] passwdValue,        byte[] nonceValue,        byte[] cNonceValue,        int nonceCount,        byte[] authzidValue        ) throws NoSuchAlgorithmException,            UnsupportedEncodingException,            IOException {         MessageDigest md5 = MessageDigest.getInstance("MD5");        byte[] hexA1, hexA2;        ByteArrayOutputStream A2, beginA1, A1, KD;         // A2        // --        // A2 = { "AUTHENTICATE:", digest-uri-value,        // [:00000000000000000000000000000000] }  // if auth-int or auth-conf        //        A2 = new ByteArrayOutputStream();        A2.write((authMethod + ":" + digestUriValue).getBytes(encoding));        if (qopValue.equals("auth-conf") ||

Lines 1–500 of 1632.