1/*
2 * Copyright (c) 2010, 2015, 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
26package com.sun.security.ntlm;
27
28import static com.sun.security.ntlm.Version.*;
29import java.io.IOException;
30import java.io.UnsupportedEncodingException;
31import java.security.InvalidKeyException;
32import java.security.MessageDigest;
33import java.security.NoSuchAlgorithmException;
34import java.security.spec.InvalidKeySpecException;
35import java.util.Arrays;
36import java.util.Locale;
37import javax.crypto.BadPaddingException;
38import javax.crypto.Cipher;
39import javax.crypto.IllegalBlockSizeException;
40import javax.crypto.Mac;
41import javax.crypto.NoSuchPaddingException;
42import javax.crypto.SecretKey;
43import javax.crypto.SecretKeyFactory;
44import javax.crypto.spec.DESKeySpec;
45import javax.crypto.spec.SecretKeySpec;
46
47/**
48 * NTLM authentication implemented according to MS-NLMP, version 12.1
49 * @since 1.7
50 */
51class NTLM {
52
53    private final SecretKeyFactory fac;
54    private final Cipher cipher;
55    private final MessageDigest md4;
56    private final Mac hmac;
57    private final MessageDigest md5;
58    private static final boolean DEBUG =
59            java.security.AccessController.doPrivileged(
60                    new sun.security.action.GetBooleanAction("ntlm.debug"))
61                        .booleanValue();
62
63    final Version v;
64
65    final boolean writeLM;
66    final boolean writeNTLM;
67
68    protected NTLM(String version) throws NTLMException {
69        if (version == null) version = "LMv2/NTLMv2";
70        switch (version) {
71            case "LM": v = NTLM; writeLM = true; writeNTLM = false; break;
72            case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break;
73            case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break;
74            case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break;
75            case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break;
76            case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break;
77            case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break;
78            default: throw new NTLMException(NTLMException.BAD_VERSION,
79                    "Unknown version " + version);
80        }
81        try {
82            fac = SecretKeyFactory.getInstance ("DES");
83            cipher = Cipher.getInstance ("DES/ECB/NoPadding");
84            md4 = sun.security.provider.MD4.getInstance();
85            hmac = Mac.getInstance("HmacMD5");
86            md5 = MessageDigest.getInstance("MD5");
87        } catch (NoSuchPaddingException e) {
88            throw new AssertionError();
89        } catch (NoSuchAlgorithmException e) {
90            throw new AssertionError();
91        }
92    }
93
94    /**
95     * Prints out a formatted string, called in various places inside then NTLM
96     * implementation for debugging/logging purposes. When the system property
97     * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is
98     * called. This method is designed to be overridden by child classes to
99     * match their own debugging/logging mechanisms.
100     * @param format a format string
101     * @param args the arguments referenced by <code>format</code>
102     * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[])
103     */
104    public void debug(String format, Object... args) {
105        if (DEBUG) {
106            System.out.printf(format, args);
107        }
108    }
109
110    /**
111     * Prints out the content of a byte array, called in various places inside
112     * the NTLM implementation for debugging/logging purposes. When the system
113     * property "ntlm.debug" is set, the hexdump of the array is printed into
114     * System.out. This method is designed to be overridden by child classes to
115     * match their own debugging/logging mechanisms.
116     * @param bytes the byte array to print out
117     */
118    public void debug(byte[] bytes) {
119        if (DEBUG) {
120            try {
121                new sun.security.util.HexDumpEncoder().encodeBuffer(bytes, System.out);
122            } catch (IOException ioe) {
123                // Impossible
124            }
125        }
126    }
127
128    /**
129     * Reading an NTLM packet
130     */
131    static class Reader {
132
133        private final byte[] internal;
134
135        Reader(byte[] data) {
136            internal = data;
137        }
138
139        int readInt(int offset) throws NTLMException {
140            try {
141                return (internal[offset] & 0xff) +
142                        ((internal[offset+1] & 0xff) << 8) +
143                        ((internal[offset+2] & 0xff) << 16) +
144                        ((internal[offset+3] & 0xff) << 24);
145            } catch (ArrayIndexOutOfBoundsException ex) {
146                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
147                        "Input message incorrect size");
148            }
149        }
150
151        int readShort(int offset) throws NTLMException {
152            try {
153                return (internal[offset] & 0xff) +
154                        ((internal[offset+1] & 0xff << 8));
155            } catch (ArrayIndexOutOfBoundsException ex) {
156                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
157                        "Input message incorrect size");
158            }
159        }
160
161        byte[] readBytes(int offset, int len) throws NTLMException {
162            try {
163                return Arrays.copyOfRange(internal, offset, offset + len);
164            } catch (ArrayIndexOutOfBoundsException ex) {
165                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
166                        "Input message incorrect size");
167            }
168        }
169
170        byte[] readSecurityBuffer(int offset) throws NTLMException {
171            int pos = readInt(offset+4);
172            if (pos == 0) return new byte[0];
173            try {
174                return Arrays.copyOfRange(
175                        internal, pos, pos + readShort(offset));
176            } catch (ArrayIndexOutOfBoundsException ex) {
177                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
178                        "Input message incorrect size");
179            }
180        }
181
182        String readSecurityBuffer(int offset, boolean unicode)
183                throws NTLMException {
184            byte[] raw = readSecurityBuffer(offset);
185            try {
186                return raw == null ? null : new String(
187                        raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1");
188            } catch (UnsupportedEncodingException ex) {
189                throw new NTLMException(NTLMException.PACKET_READ_ERROR,
190                        "Invalid input encoding");
191            }
192        }
193    }
194
195    /**
196     * Writing an NTLM packet
197     */
198    static class Writer {
199
200        private byte[] internal;    // buffer
201        private int current;        // current written content interface buffer
202
203        /**
204         * Starts writing a NTLM packet
205         * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE
206         * @param len the base length, without security buffers
207         */
208        Writer(int type, int len) {
209            assert len < 256;
210            internal = new byte[256];
211            current = len;
212            System.arraycopy (
213                    new byte[] {'N','T','L','M','S','S','P',0,(byte)type},
214                    0, internal, 0, 9);
215        }
216
217        void writeShort(int offset, int number) {
218            internal[offset] = (byte)(number);
219            internal[offset+1] = (byte)(number >> 8);
220        }
221
222        void writeInt(int offset, int number) {
223            internal[offset] = (byte)(number);
224            internal[offset+1] = (byte)(number >> 8);
225            internal[offset+2] = (byte)(number >> 16);
226            internal[offset+3] = (byte)(number >> 24);
227        }
228
229        void writeBytes(int offset, byte[] data) {
230            System.arraycopy(data, 0, internal, offset, data.length);
231        }
232
233        void writeSecurityBuffer(int offset, byte[] data) {
234            if (data == null) {
235                writeShort(offset+4, current);
236            } else {
237                int len = data.length;
238                if (current + len > internal.length) {
239                    internal = Arrays.copyOf(internal, current + len + 256);
240                }
241                writeShort(offset, len);
242                writeShort(offset+2, len);
243                writeShort(offset+4, current);
244                System.arraycopy(data, 0, internal, current, len);
245                current += len;
246            }
247        }
248
249        void writeSecurityBuffer(int offset, String str, boolean unicode) {
250            try {
251                writeSecurityBuffer(offset, str == null ? null : str.getBytes(
252                        unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"));
253            } catch (UnsupportedEncodingException ex) {
254                assert false;
255            }
256        }
257
258        byte[] getBytes() {
259            return Arrays.copyOf(internal, current);
260        }
261    }
262
263    // LM/NTLM
264
265    /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
266     * input starts at offset off
267     */
268    byte[] makeDesKey (byte[] input, int off) {
269        int[] in = new int [input.length];
270        for (int i=0; i<in.length; i++ ) {
271            in[i] = input[i]<0 ? input[i]+256: input[i];
272        }
273        byte[] out = new byte[8];
274        out[0] = (byte)in[off+0];
275        out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
276        out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
277        out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
278        out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
279        out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
280        out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
281        out[7] = (byte)((in[off+6] << 1) & 0xFF);
282        return out;
283    }
284
285    byte[] calcLMHash (byte[] pwb) {
286        byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
287        byte[] pwb1 = new byte [14];
288        int len = pwb.length;
289        if (len > 14)
290            len = 14;
291        System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
292
293        try {
294            DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
295            DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
296
297            SecretKey key1 = fac.generateSecret (dks1);
298            SecretKey key2 = fac.generateSecret (dks2);
299            cipher.init (Cipher.ENCRYPT_MODE, key1);
300            byte[] out1 = cipher.doFinal (magic, 0, 8);
301            cipher.init (Cipher.ENCRYPT_MODE, key2);
302            byte[] out2 = cipher.doFinal (magic, 0, 8);
303            byte[] result = new byte [21];
304            System.arraycopy (out1, 0, result, 0, 8);
305            System.arraycopy (out2, 0, result, 8, 8);
306            return result;
307        } catch (InvalidKeyException ive) {
308            // Will not happen, all key material are 8 bytes
309            assert false;
310        } catch (InvalidKeySpecException ikse) {
311            // Will not happen, we only feed DESKeySpec to DES factory
312            assert false;
313        } catch (IllegalBlockSizeException ibse) {
314            // Will not happen, we encrypt 8 bytes
315            assert false;
316        } catch (BadPaddingException bpe) {
317            // Will not happen, this is encryption
318            assert false;
319        }
320        return null;    // will not happen, we returned already
321    }
322
323    byte[] calcNTHash (byte[] pw) {
324        byte[] out = md4.digest (pw);
325        byte[] result = new byte [21];
326        System.arraycopy (out, 0, result, 0, 16);
327        return result;
328    }
329
330    /* key is a 21 byte array. Split it into 3 7 byte chunks,
331     * Convert each to 8 byte DES keys, encrypt the text arg with
332     * each key and return the three results in a sequential []
333     */
334    byte[] calcResponse (byte[] key, byte[] text) {
335        try {
336            assert key.length == 21;
337            DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
338            DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
339            DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
340            SecretKey key1 = fac.generateSecret(dks1);
341            SecretKey key2 = fac.generateSecret(dks2);
342            SecretKey key3 = fac.generateSecret(dks3);
343            cipher.init(Cipher.ENCRYPT_MODE, key1);
344            byte[] out1 = cipher.doFinal(text, 0, 8);
345            cipher.init(Cipher.ENCRYPT_MODE, key2);
346            byte[] out2 = cipher.doFinal(text, 0, 8);
347            cipher.init(Cipher.ENCRYPT_MODE, key3);
348            byte[] out3 = cipher.doFinal(text, 0, 8);
349            byte[] result = new byte[24];
350            System.arraycopy(out1, 0, result, 0, 8);
351            System.arraycopy(out2, 0, result, 8, 8);
352            System.arraycopy(out3, 0, result, 16, 8);
353            return result;
354        } catch (IllegalBlockSizeException ex) {    // None will happen
355            assert false;
356        } catch (BadPaddingException ex) {
357            assert false;
358        } catch (InvalidKeySpecException ex) {
359            assert false;
360        } catch (InvalidKeyException ex) {
361            assert false;
362        }
363        return null;
364    }
365
366    // LMv2/NTLMv2
367
368    byte[] hmacMD5(byte[] key, byte[] text) {
369        try {
370            SecretKeySpec skey =
371                    new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5");
372            hmac.init(skey);
373            return hmac.doFinal(text);
374        } catch (InvalidKeyException ex) {
375            assert false;
376        } catch (RuntimeException e) {
377            assert false;
378        }
379        return null;
380    }
381
382    byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) {
383        try {
384            byte[] ntlmv2hash = hmacMD5(nthash,
385                    text.getBytes("UnicodeLittleUnmarked"));
386            byte[] cn = new byte[blob.length+8];
387            System.arraycopy(challenge, 0, cn, 0, 8);
388            System.arraycopy(blob, 0, cn, 8, blob.length);
389            byte[] result = new byte[16+blob.length];
390            System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16);
391            System.arraycopy(blob, 0, result, 16, blob.length);
392            return result;
393        } catch (UnsupportedEncodingException ex) {
394            assert false;
395        }
396        return null;
397    }
398
399    // NTLM2 LM/NTLM
400
401    static byte[] ntlm2LM(byte[] nonce) {
402        return Arrays.copyOf(nonce, 24);
403    }
404
405    byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) {
406        byte[] b = Arrays.copyOf(challenge, 16);
407        System.arraycopy(nonce, 0, b, 8, 8);
408        byte[] sesshash = Arrays.copyOf(md5.digest(b), 8);
409        return calcResponse(ntlmHash, sesshash);
410    }
411
412    // Password in ASCII and UNICODE
413
414    static byte[] getP1(char[] password) {
415        try {
416            return new String(password).toUpperCase(
417                                    Locale.ENGLISH).getBytes("ISO8859_1");
418        } catch (UnsupportedEncodingException ex) {
419            return null;
420        }
421    }
422
423    static byte[] getP2(char[] password) {
424        try {
425            return new String(password).getBytes("UnicodeLittleUnmarked");
426        } catch (UnsupportedEncodingException ex) {
427            return null;
428        }
429    }
430}
431