blob: 9130be50d8470379dcacccf3fd9324494c381d73 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.net.www.protocol.http;
27
28import java.util.Arrays;
29import java.util.StringTokenizer;
30import java.util.Random;
31
32import sun.net.www.HeaderParser;
33
34import java.io.*;
35import javax.crypto.*;
36import javax.crypto.spec.*;
37import java.security.*;
38import java.net.*;
39
40/**
41 * NTLMAuthentication:
42 *
43 * @author Michael McMahon
44 */
45
46/*
47 * NTLM authentication is nominally based on the framework defined in RFC2617,
48 * but differs from the standard (Basic & Digest) schemes as follows:
49 *
50 * 1. A complete authentication requires three request/response transactions
51 * as shown below:
52 * REQ ------------------------------->
53 * <---- 401 (signalling NTLM) --------
54 *
55 * REQ (with type1 NTLM msg) --------->
56 * <---- 401 (with type 2 NTLM msg) ---
57 *
58 * REQ (with type3 NTLM msg) --------->
59 * <---- OK ---------------------------
60 *
61 * 2. The scope of the authentication is the TCP connection (which must be kept-alive)
62 * after the type2 response is received. This means that NTLM does not work end-to-end
63 * through a proxy, rather between client and proxy, or between client and server (with no proxy)
64 */
65
66class NTLMAuthentication extends AuthenticationInfo {
67
68 static char NTLM_AUTH = 'N';
69
70 private byte[] type1;
71 private byte[] type3;
72
73 private SecretKeyFactory fac;
74 private Cipher cipher;
75 private MessageDigest md4;
76 private String hostname;
77 private static String defaultDomain; /* Domain to use if not specified by user */
78
79 static {
80 defaultDomain = java.security.AccessController.doPrivileged(
81 new sun.security.action.GetPropertyAction("http.auth.ntlm.domain",
82 "domain"));
83 };
84
85 static boolean supportsTransparentAuth () {
86 return false;
87 }
88
89 private void init0() {
90 type1 = new byte[256];
91 type3 = new byte[256];
92 System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,1}, 0, type1, 0, 9);
93 type1[12] = (byte) 3;
94 type1[13] = (byte) 0xb2;
95 type1[28] = (byte) 0x20;
96 System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,3}, 0, type3, 0, 9);
97 type3[12] = (byte) 0x18;
98 type3[14] = (byte) 0x18;
99 type3[20] = (byte) 0x18;
100 type3[22] = (byte) 0x18;
101 type3[32] = (byte) 0x40;
102 type3[60] = (byte) 1;
103 type3[61] = (byte) 0x82;
104
105 try {
106 hostname = java.security.AccessController.doPrivileged(
107 new java.security.PrivilegedAction<String>() {
108 public String run() {
109 String localhost;
110 try {
111 localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
112 } catch (UnknownHostException e) {
113 localhost = "localhost";
114 }
115 return localhost;
116 }
117 });
118 int x = hostname.indexOf ('.');
119 if (x != -1) {
120 hostname = hostname.substring (0, x);
121 }
122 fac = SecretKeyFactory.getInstance ("DES");
123 cipher = Cipher.getInstance ("DES/ECB/NoPadding");
124 md4 = sun.security.provider.MD4.getInstance();
125 } catch (NoSuchPaddingException e) {
126 assert false;
127 } catch (NoSuchAlgorithmException e) {
128 assert false;
129 }
130 };
131
132 PasswordAuthentication pw;
133 String username;
134 String ntdomain;
135 String password;
136
137 /**
138 * Create a NTLMAuthentication:
139 * Username may be specified as domain<BACKSLASH>username in the application Authenticator.
140 * If this notation is not used, then the domain will be taken
141 * from a system property: "http.auth.ntlm.domain".
142 */
143 public NTLMAuthentication(boolean isProxy, URL url, PasswordAuthentication pw) {
144 super(isProxy?PROXY_AUTHENTICATION:SERVER_AUTHENTICATION, NTLM_AUTH, url, "");
145 init (pw);
146 }
147
148 private void init (PasswordAuthentication pw) {
149 this.pw = pw;
150 String s = pw.getUserName();
151 int i = s.indexOf ('\\');
152 if (i == -1) {
153 username = s;
154 ntdomain = defaultDomain;
155 } else {
156 ntdomain = s.substring (0, i).toUpperCase();
157 username = s.substring (i+1);
158 }
159 password = new String (pw.getPassword());
160 init0();
161 }
162
163 /**
164 * Constructor used for proxy entries
165 */
166 public NTLMAuthentication(boolean isProxy, String host, int port,
167 PasswordAuthentication pw) {
168 super(isProxy?PROXY_AUTHENTICATION:SERVER_AUTHENTICATION, NTLM_AUTH,host, port, "");
169 init (pw);
170 }
171
172 /**
173 * @return true if this authentication supports preemptive authorization
174 */
175 boolean supportsPreemptiveAuthorization() {
176 return false;
177 }
178
179 /**
180 * @return the name of the HTTP header this authentication wants set
181 */
182 String getHeaderName() {
183 if (type == SERVER_AUTHENTICATION) {
184 return "Authorization";
185 } else {
186 return "Proxy-authorization";
187 }
188 }
189
190 /**
191 * Not supported. Must use the setHeaders() method
192 */
193 String getHeaderValue(URL url, String method) {
194 throw new RuntimeException ("getHeaderValue not supported");
195 }
196
197 /**
198 * Check if the header indicates that the current auth. parameters are stale.
199 * If so, then replace the relevant field with the new value
200 * and return true. Otherwise return false.
201 * returning true means the request can be retried with the same userid/password
202 * returning false means we have to go back to the user to ask for a new
203 * username password.
204 */
205 boolean isAuthorizationStale (String header) {
206 return false; /* should not be called for ntlm */
207 }
208
209 /**
210 * Set header(s) on the given connection.
211 * @param conn The connection to apply the header(s) to
212 * @param p A source of header values for this connection, not used because
213 * HeaderParser converts the fields to lower case, use raw instead
214 * @param raw The raw header field.
215 * @return true if all goes well, false if no headers were set.
216 */
217 synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
218
219 try {
220 String response;
221 if (raw.length() < 6) { /* NTLM<sp> */
222 response = buildType1Msg ();
223 } else {
224 String msg = raw.substring (5); /* skip NTLM<sp> */
225 response = buildType3Msg (msg);
226 }
227 conn.setAuthenticationProperty(getHeaderName(), response);
228 return true;
229 } catch (IOException e) {
230 return false;
231 } catch (GeneralSecurityException e) {
232 return false;
233 }
234 }
235
236 /* This is a no-op for NTLM, because there is no authentication information
237 * provided by the server to the client
238 */
239 public void checkResponse (String header, String method, URL url) throws IOException {
240 }
241
242
243 private void copybytes (byte[] dest, int destpos, String src, String enc) {
244 try {
245 byte[] x = src.getBytes(enc);
246 System.arraycopy (x, 0, dest, destpos, x.length);
247 } catch (UnsupportedEncodingException e) {
248 assert false;
249 }
250 }
251
252 private String buildType1Msg () {
253 int dlen = ntdomain.length();
254 type1[16]= (byte) (dlen % 256);
255 type1[17]= (byte) (dlen / 256);
256 type1[18] = type1[16];
257 type1[19] = type1[17];
258
259 int hlen = hostname.length();
260 type1[24]= (byte) (hlen % 256);
261 type1[25]= (byte) (hlen / 256);
262 type1[26] = type1[24];
263 type1[27] = type1[25];
264
265 copybytes (type1, 32, hostname, "ISO8859_1");
266 copybytes (type1, hlen+32, ntdomain, "ISO8859_1");
267 type1[20] = (byte) ((hlen+32) % 256);
268 type1[21] = (byte) ((hlen+32) / 256);
269
270 byte[] msg = new byte [32 + hlen + dlen];
271 System.arraycopy (type1, 0, msg, 0, 32 + hlen + dlen);
272 String result = "NTLM " + (new B64Encoder()).encode (msg);
273 return result;
274 }
275
276
277 /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
278 * input starts at offset off
279 */
280 private byte[] makeDesKey (byte[] input, int off) {
281 int[] in = new int [input.length];
282 for (int i=0; i<in.length; i++ ) {
283 in[i] = input[i]<0 ? input[i]+256: input[i];
284 }
285 byte[] out = new byte[8];
286 out[0] = (byte)in[off+0];
287 out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
288 out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
289 out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
290 out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
291 out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
292 out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
293 out[7] = (byte)((in[off+6] << 1) & 0xFF);
294 return out;
295 }
296
297 private byte[] calcLMHash () throws GeneralSecurityException {
298 byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
299 byte[] pwb = password.toUpperCase ().getBytes();
300 byte[] pwb1 = new byte [14];
301 int len = password.length();
302 if (len > 14)
303 len = 14;
304 System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
305
306 DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
307 DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
308
309 SecretKey key1 = fac.generateSecret (dks1);
310 SecretKey key2 = fac.generateSecret (dks2);
311 cipher.init (Cipher.ENCRYPT_MODE, key1);
312 byte[] out1 = cipher.doFinal (magic, 0, 8);
313 cipher.init (Cipher.ENCRYPT_MODE, key2);
314 byte[] out2 = cipher.doFinal (magic, 0, 8);
315
316 byte[] result = new byte [21];
317 System.arraycopy (out1, 0, result, 0, 8);
318 System.arraycopy (out2, 0, result, 8, 8);
319 return result;
320 }
321
322 private byte[] calcNTHash () throws GeneralSecurityException {
323 byte[] pw = null;
324 try {
325 pw = password.getBytes ("UnicodeLittleUnmarked");
326 } catch (UnsupportedEncodingException e) {
327 assert false;
328 }
329 byte[] out = md4.digest (pw);
330 byte[] result = new byte [21];
331 System.arraycopy (out, 0, result, 0, 16);
332 return result;
333 }
334
335 /* key is a 21 byte array. Split it into 3 7 byte chunks,
336 * Convert each to 8 byte DES keys, encrypt the text arg with
337 * each key and return the three results in a sequential []
338 */
339 private byte[] calcResponse (byte[] key, byte[] text)
340 throws GeneralSecurityException {
341 assert key.length == 21;
342 DESKeySpec dks1 = new DESKeySpec (makeDesKey (key, 0));
343 DESKeySpec dks2 = new DESKeySpec (makeDesKey (key, 7));
344 DESKeySpec dks3 = new DESKeySpec (makeDesKey (key, 14));
345 SecretKey key1 = fac.generateSecret (dks1);
346 SecretKey key2 = fac.generateSecret (dks2);
347 SecretKey key3 = fac.generateSecret (dks3);
348 cipher.init (Cipher.ENCRYPT_MODE, key1);
349 byte[] out1 = cipher.doFinal (text, 0, 8);
350 cipher.init (Cipher.ENCRYPT_MODE, key2);
351 byte[] out2 = cipher.doFinal (text, 0, 8);
352 cipher.init (Cipher.ENCRYPT_MODE, key3);
353 byte[] out3 = cipher.doFinal (text, 0, 8);
354 byte[] result = new byte [24];
355 System.arraycopy (out1, 0, result, 0, 8);
356 System.arraycopy (out2, 0, result, 8, 8);
357 System.arraycopy (out3, 0, result, 16, 8);
358 return result;
359 }
360
361 private String buildType3Msg (String challenge) throws GeneralSecurityException,
362 IOException {
363 /* First decode the type2 message to get the server nonce */
364 /* nonce is located at type2[24] for 8 bytes */
365
366 byte[] type2 = (new sun.misc.BASE64Decoder()).decodeBuffer (challenge);
367 byte[] nonce = new byte [8];
368 System.arraycopy (type2, 24, nonce, 0, 8);
369
370 int ulen = username.length()*2;
371 type3[36] = type3[38] = (byte) (ulen % 256);
372 type3[37] = type3[39] = (byte) (ulen / 256);
373 int dlen = ntdomain.length()*2;
374 type3[28] = type3[30] = (byte) (dlen % 256);
375 type3[29] = type3[31] = (byte) (dlen / 256);
376 int hlen = hostname.length()*2;
377 type3[44] = type3[46] = (byte) (hlen % 256);
378 type3[45] = type3[47] = (byte) (hlen / 256);
379
380 int l = 64;
381 copybytes (type3, l, ntdomain, "UnicodeLittleUnmarked");
382 type3[32] = (byte) (l % 256);
383 type3[33] = (byte) (l / 256);
384 l += dlen;
385 copybytes (type3, l, username, "UnicodeLittleUnmarked");
386 type3[40] = (byte) (l % 256);
387 type3[41] = (byte) (l / 256);
388 l += ulen;
389 copybytes (type3, l, hostname, "UnicodeLittleUnmarked");
390 type3[48] = (byte) (l % 256);
391 type3[49] = (byte) (l / 256);
392 l += hlen;
393
394 byte[] lmhash = calcLMHash();
395 byte[] lmresponse = calcResponse (lmhash, nonce);
396 byte[] nthash = calcNTHash();
397 byte[] ntresponse = calcResponse (nthash, nonce);
398 System.arraycopy (lmresponse, 0, type3, l, 24);
399 type3[16] = (byte) (l % 256);
400 type3[17] = (byte) (l / 256);
401 l += 24;
402 System.arraycopy (ntresponse, 0, type3, l, 24);
403 type3[24] = (byte) (l % 256);
404 type3[25] = (byte) (l / 256);
405 l += 24;
406 type3[56] = (byte) (l % 256);
407 type3[57] = (byte) (l / 256);
408
409 byte[] msg = new byte [l];
410 System.arraycopy (type3, 0, msg, 0, l);
411 String result = "NTLM " + (new B64Encoder()).encode (msg);
412 return result;
413 }
414
415}
416
417
418class B64Encoder extends sun.misc.BASE64Encoder {
419 /* to force it to to the entire encoding in one line */
420 protected int bytesPerLine () {
421 return 1024;
422 }
423}