1/*
2 * Copyright (c) 2010, 2013, 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 java.math.BigInteger;
29import java.util.Arrays;
30import java.util.Date;
31import java.util.Locale;
32
33/**
34 * The NTLM client. Not multi-thread enabled.<p>
35 * Example:
36 * <pre>
37 * Client client = new Client(null, "host", "dummy",
38 *       "REALM", "t0pSeCr3t".toCharArray());
39 * byte[] type1 = client.type1();
40 * // Send type1 to server and receive response as type2
41 * byte[] type3 = client.type3(type2, nonce);
42 * // Send type3 to server
43 * </pre>
44 */
45public final class Client extends NTLM {
46    private final String hostname;
47    private final String username;
48
49    private String domain;
50    private byte[] pw1, pw2;
51
52    /**
53     * Creates an NTLM Client instance.
54     * @param version the NTLM version to use, which can be:
55     * <ul>
56     * <li>LM/NTLM: Original NTLM v1
57     * <li>LM: Original NTLM v1, LM only
58     * <li>NTLM: Original NTLM v1, NTLM only
59     * <li>NTLM2: NTLM v1 with Client Challenge
60     * <li>LMv2/NTLMv2: NTLM v2
61     * <li>LMv2: NTLM v2, LM only
62     * <li>NTLMv2: NTLM v2, NTLM only
63     * </ul>
64     * If null, "LMv2/NTLMv2" will be used.
65     * @param hostname hostname of the client, can be null
66     * @param username username to be authenticated, must not be null
67     * @param domain domain of {@code username}, can be null
68     * @param password password for {@code username}, must not be not null.
69     * This method does not make any modification to this parameter, it neither
70     * needs to access the content of this parameter after this method call,
71     * so you are free to modify or nullify this parameter after this call.
72     * @throws NTLMException if {@code username} or {@code password} is null,
73     * or {@code version} is illegal.
74     *
75     */
76    public Client(String version, String hostname, String username,
77            String domain, char[] password) throws NTLMException {
78        super(version);
79        if ((username == null || password == null)) {
80            throw new NTLMException(NTLMException.PROTOCOL,
81                    "username/password cannot be null");
82        }
83        this.hostname = hostname;
84        this.username = username;
85        this.domain = domain == null ? "" : domain;
86        this.pw1 = getP1(password);
87        this.pw2 = getP2(password);
88        debug("NTLM Client: (h,u,t,version(v)) = (%s,%s,%s,%s(%s))\n",
89                    hostname, username, domain, version, v.toString());
90    }
91
92    /**
93     * Generates the Type 1 message
94     * @return the message generated
95     */
96    public byte[] type1() {
97        Writer p = new Writer(1, 32);
98        // Negotiate always sign, Negotiate NTLM,
99        // Request Target, Negotiate OEM, Negotiate unicode
100        int flags = 0x8207;
101        if (v != Version.NTLM) {
102            flags |= 0x80000;
103        }
104        p.writeInt(12, flags);
105        debug("NTLM Client: Type 1 created\n");
106        debug(p.getBytes());
107        return p.getBytes();
108    }
109
110    /**
111     * Generates the Type 3 message
112     * @param type2 the responding Type 2 message from server, must not be null
113     * @param nonce random 8-byte array to be used in message generation,
114     * must not be null except for original NTLM v1
115     * @return the message generated
116     * @throws NTLMException if the incoming message is invalid, or
117     * {@code nonce} is null for NTLM v1.
118     */
119    public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException {
120        if (type2 == null || (v != Version.NTLM && nonce == null)) {
121            throw new NTLMException(NTLMException.PROTOCOL,
122                    "type2 and nonce cannot be null");
123        }
124        debug("NTLM Client: Type 2 received\n");
125        debug(type2);
126        Reader r = new Reader(type2);
127        byte[] challenge = r.readBytes(24, 8);
128        int inputFlags = r.readInt(20);
129        boolean unicode = (inputFlags & 1) == 1;
130
131        // IE uses domainFromServer to generate an alist if server has not
132        // provided one. Firefox/WebKit do not. Neither do we.
133        //String domainFromServer = r.readSecurityBuffer(12, unicode);
134
135        int flags = 0x88200 | (inputFlags & 3);
136        Writer p = new Writer(3, 64);
137        byte[] lm = null, ntlm = null;
138
139        p.writeSecurityBuffer(28, domain, unicode);
140        p.writeSecurityBuffer(36, username, unicode);
141        p.writeSecurityBuffer(44, hostname, unicode);
142
143        if (v == Version.NTLM) {
144            byte[] lmhash = calcLMHash(pw1);
145            byte[] nthash = calcNTHash(pw2);
146            if (writeLM) lm = calcResponse (lmhash, challenge);
147            if (writeNTLM) ntlm = calcResponse (nthash, challenge);
148        } else if (v == Version.NTLM2) {
149            byte[] nthash = calcNTHash(pw2);
150            lm = ntlm2LM(nonce);
151            ntlm = ntlm2NTLM(nthash, nonce, challenge);
152        } else {
153            byte[] nthash = calcNTHash(pw2);
154            if (writeLM) lm = calcV2(nthash,
155                    username.toUpperCase(Locale.US)+domain, nonce, challenge);
156            if (writeNTLM) {
157                // Some client create a alist even if server does not send
158                // one: (i16)2 (i16)len target_in_unicode (i16)0 (i16) 0
159                byte[] alist = ((inputFlags & 0x800000) != 0) ?
160                    r.readSecurityBuffer(40) : new byte[0];
161                byte[] blob = new byte[32+alist.length];
162                System.arraycopy(new byte[]{1,1,0,0,0,0,0,0}, 0, blob, 0, 8);
163                // TS
164                byte[] time = BigInteger.valueOf(new Date().getTime())
165                        .add(new BigInteger("11644473600000"))
166                        .multiply(BigInteger.valueOf(10000))
167                        .toByteArray();
168                for (int i=0; i<time.length; i++) {
169                    blob[8+time.length-i-1] = time[i];
170                }
171                System.arraycopy(nonce, 0, blob, 16, 8);
172                System.arraycopy(new byte[]{0,0,0,0}, 0, blob, 24, 4);
173                System.arraycopy(alist, 0, blob, 28, alist.length);
174                System.arraycopy(new byte[]{0,0,0,0}, 0,
175                        blob, 28+alist.length, 4);
176                ntlm = calcV2(nthash, username.toUpperCase(Locale.US)+domain,
177                        blob, challenge);
178            }
179        }
180        p.writeSecurityBuffer(12, lm);
181        p.writeSecurityBuffer(20, ntlm);
182        p.writeSecurityBuffer(52, new byte[0]);
183
184        p.writeInt(60, flags);
185        debug("NTLM Client: Type 3 created\n");
186        debug(p.getBytes());
187        return p.getBytes();
188    }
189
190    /**
191     * Returns the domain value provided by server after the authentication
192     * is complete, or the domain value provided by the client before it.
193     * @return the domain
194     */
195    public String getDomain() {
196        return domain;
197    }
198
199    /**
200     * Disposes any password-derived information.
201     */
202    public void dispose() {
203        Arrays.fill(pw1, (byte)0);
204        Arrays.fill(pw2, (byte)0);
205    }
206}
207