426 lines · 15.7 KB · Java
RawDownload
/* * Copyright (c) 2010, 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.ntlm; import static com.sun.security.ntlm.Version.*;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.security.spec.InvalidKeySpecException;import java.util.Arrays;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.Mac;import javax.crypto.NoSuchPaddingException;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.DESKeySpec;import javax.crypto.spec.SecretKeySpec; /** * NTLM authentication implemented according to MS-NLMP, version 12.1 * @since 1.7 */class NTLM {     private final SecretKeyFactory fac;    private final Cipher cipher;    private final MessageDigest md4;    private final Mac hmac;    private final MessageDigest md5;    private static final boolean DEBUG =            System.getProperty("ntlm.debug") != null;     final Version v;     final boolean writeLM;    final boolean writeNTLM;     protected NTLM(String version) throws NTLMException {        if (version == null) version = "LMv2/NTLMv2";        switch (version) {            case "LM": v = NTLM; writeLM = true; writeNTLM = false; break;            case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break;            case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break;            case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break;            case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break;            case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break;            case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break;            default: throw new NTLMException(NTLMException.BAD_VERSION,                    "Unknown version " + version);        }        try {            fac = SecretKeyFactory.getInstance ("DES");            cipher = Cipher.getInstance ("DES/ECB/NoPadding");            md4 = sun.security.provider.MD4.getInstance();            hmac = Mac.getInstance("HmacMD5");            md5 = MessageDigest.getInstance("MD5");        } catch (NoSuchPaddingException e) {            throw new AssertionError();        } catch (NoSuchAlgorithmException e) {            throw new AssertionError();        }    }     /**     * Prints out a formatted string, called in various places inside then NTLM     * implementation for debugging/logging purposes. When the system property     * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is     * called. This method is designed to be overridden by child classes to     * match their own debugging/logging mechanisms.     * @param format a format string     * @param args the arguments referenced by <code>format</code>     * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[])     */    public void debug(String format, Object... args) {        if (DEBUG) {            System.out.printf(format, args);        }    }     /**     * Prints out the content of a byte array, called in various places inside     * the NTLM implementation for debugging/logging purposes. When the system     * property "ntlm.debug" is set, the hexdump of the array is printed into     * System.out. This method is designed to be overridden by child classes to     * match their own debugging/logging mechanisms.     * @param bytes the byte array to print out     */    public void debug(byte[] bytes) {        if (DEBUG) {            try {                new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out);            } catch (IOException ioe) {                // Impossible            }        }    }     /**     * Reading an NTLM packet     */    static class Reader {         private final byte[] internal;         Reader(byte[] data) {            internal = data;        }         int readInt(int offset) throws NTLMException {            try {                return internal[offset] & 0xff +                        (internal[offset+1] & 0xff << 8) +                        (internal[offset+2] & 0xff << 16) +                        (internal[offset+3] & 0xff << 24);            } catch (ArrayIndexOutOfBoundsException ex) {                throw new NTLMException(NTLMException.PACKET_READ_ERROR,                        "Input message incorrect size");            }        }         int readShort(int offset) throws NTLMException {            try {                return internal[offset] & 0xff +                        (internal[offset+1] & 0xff << 8);            } catch (ArrayIndexOutOfBoundsException ex) {                throw new NTLMException(NTLMException.PACKET_READ_ERROR,                        "Input message incorrect size");            }        }         byte[] readBytes(int offset, int len) throws NTLMException {            try {                return Arrays.copyOfRange(internal, offset, offset + len);            } catch (ArrayIndexOutOfBoundsException ex) {                throw new NTLMException(NTLMException.PACKET_READ_ERROR,                        "Input message incorrect size");            }        }         byte[] readSecurityBuffer(int offset) throws NTLMException {            int pos = readInt(offset+4);            if (pos == 0) return null;            try {                return Arrays.copyOfRange(                        internal, pos, pos + readShort(offset));            } catch (ArrayIndexOutOfBoundsException ex) {                throw new NTLMException(NTLMException.PACKET_READ_ERROR,                        "Input message incorrect size");            }        }         String readSecurityBuffer(int offset, boolean unicode)                throws NTLMException {            byte[] raw = readSecurityBuffer(offset);            try {                return raw == null ? null : new String(                        raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1");            } catch (UnsupportedEncodingException ex) {                throw new NTLMException(NTLMException.PACKET_READ_ERROR,                        "Invalid input encoding");            }        }    }     /**     * Writing an NTLM packet     */    static class Writer {         private byte[] internal;    // buffer        private int current;        // current written content interface buffer         /**         * Starts writing a NTLM packet         * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE         * @param len the base length, without security buffers         */        Writer(int type, int len) {            assert len < 256;            internal = new byte[256];            current = len;            System.arraycopy (                    new byte[] {'N','T','L','M','S','S','P',0,(byte)type},                    0, internal, 0, 9);        }         void writeShort(int offset, int number) {            internal[offset] = (byte)(number);            internal[offset+1] = (byte)(number >> 8);        }         void writeInt(int offset, int number) {            internal[offset] = (byte)(number);            internal[offset+1] = (byte)(number >> 8);            internal[offset+2] = (byte)(number >> 16);            internal[offset+3] = (byte)(number >> 24);        }         void writeBytes(int offset, byte[] data) {            System.arraycopy(data, 0, internal, offset, data.length);        }         void writeSecurityBuffer(int offset, byte[] data) {            if (data == null) {                writeShort(offset+4, current);            } else {                int len = data.length;                if (current + len > internal.length) {                    internal = Arrays.copyOf(internal, current + len + 256);                }                writeShort(offset, len);                writeShort(offset+2, len);                writeShort(offset+4, current);                System.arraycopy(data, 0, internal, current, len);                current += len;            }        }         void writeSecurityBuffer(int offset, String str, boolean unicode) {            try {                writeSecurityBuffer(offset, str == null ? null : str.getBytes(                        unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"));            } catch (UnsupportedEncodingException ex) {                assert false;            }        }         byte[] getBytes() {            return Arrays.copyOf(internal, current);        }    }     // LM/NTLM     /* Convert a 7 byte array to an 8 byte array (for a des key with parity)     * input starts at offset off     */    byte[] makeDesKey (byte[] input, int off) {        int[] in = new int [input.length];        for (int i=0; i<in.length; i++ ) {            in[i] = input[i]<0 ? input[i]+256: input[i];        }        byte[] out = new byte[8];        out[0] = (byte)in[off+0];        out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));        out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));        out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));        out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));        out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));        out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));        out[7] = (byte)((in[off+6] << 1) & 0xFF);        return out;    }     byte[] calcLMHash (byte[] pwb) {        byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};        byte[] pwb1 = new byte [14];        int len = pwb.length;        if (len > 14)            len = 14;        System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */         try {            DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));            DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));             SecretKey key1 = fac.generateSecret (dks1);            SecretKey key2 = fac.generateSecret (dks2);            cipher.init (Cipher.ENCRYPT_MODE, key1);            byte[] out1 = cipher.doFinal (magic, 0, 8);            cipher.init (Cipher.ENCRYPT_MODE, key2);            byte[] out2 = cipher.doFinal (magic, 0, 8);            byte[] result = new byte [21];            System.arraycopy (out1, 0, result, 0, 8);            System.arraycopy (out2, 0, result, 8, 8);            return result;        } catch (InvalidKeyException ive) {            // Will not happen, all key material are 8 bytes            assert false;        } catch (InvalidKeySpecException ikse) {            // Will not happen, we only feed DESKeySpec to DES factory            assert false;        } catch (IllegalBlockSizeException ibse) {            // Will not happen, we encrypt 8 bytes            assert false;        } catch (BadPaddingException bpe) {            // Will not happen, this is encryption            assert false;        }        return null;    // will not happen, we returned already    }     byte[] calcNTHash (byte[] pw) {        byte[] out = md4.digest (pw);        byte[] result = new byte [21];        System.arraycopy (out, 0, result, 0, 16);        return result;    }     /* key is a 21 byte array. Split it into 3 7 byte chunks,     * Convert each to 8 byte DES keys, encrypt the text arg with     * each key and return the three results in a sequential []     */    byte[] calcResponse (byte[] key, byte[] text) {        try {            assert key.length == 21;            DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));            DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));            DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));            SecretKey key1 = fac.generateSecret(dks1);            SecretKey key2 = fac.generateSecret(dks2);            SecretKey key3 = fac.generateSecret(dks3);            cipher.init(Cipher.ENCRYPT_MODE, key1);            byte[] out1 = cipher.doFinal(text, 0, 8);            cipher.init(Cipher.ENCRYPT_MODE, key2);            byte[] out2 = cipher.doFinal(text, 0, 8);            cipher.init(Cipher.ENCRYPT_MODE, key3);            byte[] out3 = cipher.doFinal(text, 0, 8);            byte[] result = new byte[24];            System.arraycopy(out1, 0, result, 0, 8);            System.arraycopy(out2, 0, result, 8, 8);            System.arraycopy(out3, 0, result, 16, 8);            return result;        } catch (IllegalBlockSizeException ex) {    // None will happen            assert false;        } catch (BadPaddingException ex) {            assert false;        } catch (InvalidKeySpecException ex) {            assert false;        } catch (InvalidKeyException ex) {            assert false;        }        return null;    }     // LMv2/NTLMv2     byte[] hmacMD5(byte[] key, byte[] text) {        try {            SecretKeySpec skey =                    new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5");            hmac.init(skey);            return hmac.doFinal(text);        } catch (InvalidKeyException ex) {            assert false;        } catch (RuntimeException e) {            assert false;        }        return null;    }     byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) {        try {            byte[] ntlmv2hash = hmacMD5(nthash,                    text.getBytes("UnicodeLittleUnmarked"));            byte[] cn = new byte[blob.length+8];            System.arraycopy(challenge, 0, cn, 0, 8);            System.arraycopy(blob, 0, cn, 8, blob.length);            byte[] result = new byte[16+blob.length];            System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16);            System.arraycopy(blob, 0, result, 16, blob.length);            return result;        } catch (UnsupportedEncodingException ex) {            assert false;        }        return null;    }     // NTLM2 LM/NTLM     static byte[] ntlm2LM(byte[] nonce) {        return Arrays.copyOf(nonce, 24);    }     byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) {        byte[] b = Arrays.copyOf(challenge, 16);        System.arraycopy(nonce, 0, b, 8, 8);        byte[] sesshash = Arrays.copyOf(md5.digest(b), 8);        return calcResponse(ntlmHash, sesshash);    }     // Password in ASCII and UNICODE     static byte[] getP1(char[] password) {        try {            return new String(password).toUpperCase().getBytes("ISO8859_1");        } catch (UnsupportedEncodingException ex) {            return null;        }    }     static byte[] getP2(char[] password) {        try {            return new String(password).getBytes("UnicodeLittleUnmarked");        } catch (UnsupportedEncodingException ex) {            return null;        }    }}