001: /*
002: * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.net.www.protocol.http;
027:
028: import java.util.Arrays;
029: import java.util.StringTokenizer;
030: import java.util.Random;
031:
032: import sun.net.www.HeaderParser;
033:
034: import java.io.*;
035: import javax.crypto.*;
036: import javax.crypto.spec.*;
037: import java.security.*;
038: import java.net.*;
039:
040: /**
041: * NTLMAuthentication:
042: *
043: * @author Michael McMahon
044: */
045:
046: /*
047: * NTLM authentication is nominally based on the framework defined in RFC2617,
048: * but differs from the standard (Basic & Digest) schemes as follows:
049: *
050: * 1. A complete authentication requires three request/response transactions
051: * as shown below:
052: * REQ ------------------------------->
053: * <---- 401 (signalling NTLM) --------
054: *
055: * REQ (with type1 NTLM msg) --------->
056: * <---- 401 (with type 2 NTLM msg) ---
057: *
058: * REQ (with type3 NTLM msg) --------->
059: * <---- OK ---------------------------
060: *
061: * 2. The scope of the authentication is the TCP connection (which must be kept-alive)
062: * after the type2 response is received. This means that NTLM does not work end-to-end
063: * through a proxy, rather between client and proxy, or between client and server (with no proxy)
064: */
065:
066: class NTLMAuthentication extends AuthenticationInfo {
067:
068: static char NTLM_AUTH = 'N';
069:
070: private byte[] type1;
071: private byte[] type3;
072:
073: private SecretKeyFactory fac;
074: private Cipher cipher;
075: private MessageDigest md4;
076: private String hostname;
077: private static String defaultDomain; /* Domain to use if not specified by user */
078:
079: static {
080: defaultDomain = (String) java.security.AccessController
081: .doPrivileged(new java.security.PrivilegedAction() {
082: public Object run() {
083: String s = System
084: .getProperty("http.auth.ntlm.domain");
085: if (s == null)
086: return "domain";
087: return s;
088: }
089: });
090: };
091:
092: static boolean supportsTransparentAuth() {
093: return false;
094: }
095:
096: private void init0() {
097: type1 = new byte[256];
098: type3 = new byte[256];
099: System.arraycopy(new byte[] { 'N', 'T', 'L', 'M', 'S', 'S',
100: 'P', 0, 1 }, 0, type1, 0, 9);
101: type1[12] = (byte) 3;
102: type1[13] = (byte) 0xb2;
103: type1[28] = (byte) 0x20;
104: System.arraycopy(new byte[] { 'N', 'T', 'L', 'M', 'S', 'S',
105: 'P', 0, 3 }, 0, type3, 0, 9);
106: type3[12] = (byte) 0x18;
107: type3[14] = (byte) 0x18;
108: type3[20] = (byte) 0x18;
109: type3[22] = (byte) 0x18;
110: type3[32] = (byte) 0x40;
111: type3[60] = (byte) 1;
112: type3[61] = (byte) 0x82;
113:
114: try {
115: hostname = (String) java.security.AccessController
116: .doPrivileged(new java.security.PrivilegedAction() {
117: public Object run() {
118: String localhost;
119: try {
120: localhost = InetAddress.getLocalHost()
121: .getHostName().toUpperCase();
122: } catch (UnknownHostException e) {
123: localhost = "localhost";
124: }
125: return localhost;
126: }
127: });
128: int x = hostname.indexOf('.');
129: if (x != -1) {
130: hostname = hostname.substring(0, x);
131: }
132: fac = SecretKeyFactory.getInstance("DES");
133: cipher = Cipher.getInstance("DES/ECB/NoPadding");
134: md4 = sun.security.provider.MD4.getInstance();
135: } catch (NoSuchPaddingException e) {
136: assert false;
137: } catch (NoSuchAlgorithmException e) {
138: assert false;
139: }
140: };
141:
142: PasswordAuthentication pw;
143: String username;
144: String ntdomain;
145: String password;
146:
147: /**
148: * Create a NTLMAuthentication:
149: * Username may be specified as domain<BACKSLASH>username in the application Authenticator.
150: * If this notation is not used, then the domain will be taken
151: * from a system property: "http.auth.ntlm.domain".
152: */
153: public NTLMAuthentication(boolean isProxy, URL url,
154: PasswordAuthentication pw) {
155: super (isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
156: NTLM_AUTH, url, "");
157: init(pw);
158: }
159:
160: private void init(PasswordAuthentication pw) {
161: this .pw = pw;
162: String s = pw.getUserName();
163: int i = s.indexOf('\\');
164: if (i == -1) {
165: username = s;
166: ntdomain = defaultDomain;
167: } else {
168: ntdomain = s.substring(0, i).toUpperCase();
169: username = s.substring(i + 1);
170: }
171: password = new String(pw.getPassword());
172: init0();
173: }
174:
175: /**
176: * Constructor used for proxy entries
177: */
178: public NTLMAuthentication(boolean isProxy, String host, int port,
179: PasswordAuthentication pw) {
180: super (isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
181: NTLM_AUTH, host, port, "");
182: init(pw);
183: }
184:
185: /**
186: * @return true if this authentication supports preemptive authorization
187: */
188: boolean supportsPreemptiveAuthorization() {
189: return false;
190: }
191:
192: /**
193: * @return the name of the HTTP header this authentication wants set
194: */
195: String getHeaderName() {
196: if (type == SERVER_AUTHENTICATION) {
197: return "Authorization";
198: } else {
199: return "Proxy-authorization";
200: }
201: }
202:
203: /**
204: * Not supported. Must use the setHeaders() method
205: */
206: String getHeaderValue(URL url, String method) {
207: throw new RuntimeException("getHeaderValue not supported");
208: }
209:
210: /**
211: * Check if the header indicates that the current auth. parameters are stale.
212: * If so, then replace the relevant field with the new value
213: * and return true. Otherwise return false.
214: * returning true means the request can be retried with the same userid/password
215: * returning false means we have to go back to the user to ask for a new
216: * username password.
217: */
218: boolean isAuthorizationStale(String header) {
219: return false; /* should not be called for ntlm */
220: }
221:
222: /**
223: * Set header(s) on the given connection.
224: * @param conn The connection to apply the header(s) to
225: * @param p A source of header values for this connection, not used because
226: * HeaderParser converts the fields to lower case, use raw instead
227: * @param raw The raw header field.
228: * @return true if all goes well, false if no headers were set.
229: */
230: synchronized boolean setHeaders(HttpURLConnection conn,
231: HeaderParser p, String raw) {
232:
233: try {
234: String response;
235: if (raw.length() < 6) { /* NTLM<sp> */
236: response = buildType1Msg();
237: } else {
238: String msg = raw.substring(5); /* skip NTLM<sp> */
239: response = buildType3Msg(msg);
240: }
241: conn.setAuthenticationProperty(getHeaderName(), response);
242: return true;
243: } catch (IOException e) {
244: return false;
245: } catch (GeneralSecurityException e) {
246: return false;
247: }
248: }
249:
250: /* This is a no-op for NTLM, because there is no authentication information
251: * provided by the server to the client
252: */
253: public void checkResponse(String header, String method, URL url)
254: throws IOException {
255: }
256:
257: private void copybytes(byte[] dest, int destpos, String src,
258: String enc) {
259: try {
260: byte[] x = src.getBytes(enc);
261: System.arraycopy(x, 0, dest, destpos, x.length);
262: } catch (UnsupportedEncodingException e) {
263: assert false;
264: }
265: }
266:
267: private String buildType1Msg() {
268: int dlen = ntdomain.length();
269: type1[16] = (byte) (dlen % 256);
270: type1[17] = (byte) (dlen / 256);
271: type1[18] = type1[16];
272: type1[19] = type1[17];
273:
274: int hlen = hostname.length();
275: type1[24] = (byte) (hlen % 256);
276: type1[25] = (byte) (hlen / 256);
277: type1[26] = type1[24];
278: type1[27] = type1[25];
279:
280: copybytes(type1, 32, hostname, "ISO8859_1");
281: copybytes(type1, hlen + 32, ntdomain, "ISO8859_1");
282: type1[20] = (byte) ((hlen + 32) % 256);
283: type1[21] = (byte) ((hlen + 32) / 256);
284:
285: byte[] msg = new byte[32 + hlen + dlen];
286: System.arraycopy(type1, 0, msg, 0, 32 + hlen + dlen);
287: String result = "NTLM " + (new B64Encoder()).encode(msg);
288: return result;
289: }
290:
291: /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
292: * input starts at offset off
293: */
294: private byte[] makeDesKey(byte[] input, int off) {
295: int[] in = new int[input.length];
296: for (int i = 0; i < in.length; i++) {
297: in[i] = input[i] < 0 ? input[i] + 256 : input[i];
298: }
299: byte[] out = new byte[8];
300: out[0] = (byte) in[off + 0];
301: out[1] = (byte) (((in[off + 0] << 7) & 0xFF) | (in[off + 1] >> 1));
302: out[2] = (byte) (((in[off + 1] << 6) & 0xFF) | (in[off + 2] >> 2));
303: out[3] = (byte) (((in[off + 2] << 5) & 0xFF) | (in[off + 3] >> 3));
304: out[4] = (byte) (((in[off + 3] << 4) & 0xFF) | (in[off + 4] >> 4));
305: out[5] = (byte) (((in[off + 4] << 3) & 0xFF) | (in[off + 5] >> 5));
306: out[6] = (byte) (((in[off + 5] << 2) & 0xFF) | (in[off + 6] >> 6));
307: out[7] = (byte) ((in[off + 6] << 1) & 0xFF);
308: return out;
309: }
310:
311: private byte[] calcLMHash() throws GeneralSecurityException {
312: byte[] magic = { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
313: byte[] pwb = password.toUpperCase().getBytes();
314: byte[] pwb1 = new byte[14];
315: int len = password.length();
316: if (len > 14)
317: len = 14;
318: System.arraycopy(pwb, 0, pwb1, 0, len); /* Zero padded */
319:
320: DESKeySpec dks1 = new DESKeySpec(makeDesKey(pwb1, 0));
321: DESKeySpec dks2 = new DESKeySpec(makeDesKey(pwb1, 7));
322:
323: SecretKey key1 = fac.generateSecret(dks1);
324: SecretKey key2 = fac.generateSecret(dks2);
325: cipher.init(Cipher.ENCRYPT_MODE, key1);
326: byte[] out1 = cipher.doFinal(magic, 0, 8);
327: cipher.init(Cipher.ENCRYPT_MODE, key2);
328: byte[] out2 = cipher.doFinal(magic, 0, 8);
329:
330: byte[] result = new byte[21];
331: System.arraycopy(out1, 0, result, 0, 8);
332: System.arraycopy(out2, 0, result, 8, 8);
333: return result;
334: }
335:
336: private byte[] calcNTHash() throws GeneralSecurityException {
337: byte[] pw = null;
338: try {
339: pw = password.getBytes("UnicodeLittleUnmarked");
340: } catch (UnsupportedEncodingException e) {
341: assert false;
342: }
343: byte[] out = md4.digest(pw);
344: byte[] result = new byte[21];
345: System.arraycopy(out, 0, result, 0, 16);
346: return result;
347: }
348:
349: /* key is a 21 byte array. Split it into 3 7 byte chunks,
350: * Convert each to 8 byte DES keys, encrypt the text arg with
351: * each key and return the three results in a sequential []
352: */
353: private byte[] calcResponse(byte[] key, byte[] text)
354: throws GeneralSecurityException {
355: assert key.length == 21;
356: DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
357: DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
358: DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
359: SecretKey key1 = fac.generateSecret(dks1);
360: SecretKey key2 = fac.generateSecret(dks2);
361: SecretKey key3 = fac.generateSecret(dks3);
362: cipher.init(Cipher.ENCRYPT_MODE, key1);
363: byte[] out1 = cipher.doFinal(text, 0, 8);
364: cipher.init(Cipher.ENCRYPT_MODE, key2);
365: byte[] out2 = cipher.doFinal(text, 0, 8);
366: cipher.init(Cipher.ENCRYPT_MODE, key3);
367: byte[] out3 = cipher.doFinal(text, 0, 8);
368: byte[] result = new byte[24];
369: System.arraycopy(out1, 0, result, 0, 8);
370: System.arraycopy(out2, 0, result, 8, 8);
371: System.arraycopy(out3, 0, result, 16, 8);
372: return result;
373: }
374:
375: private String buildType3Msg(String challenge)
376: throws GeneralSecurityException, IOException {
377: /* First decode the type2 message to get the server nonce */
378: /* nonce is located at type2[24] for 8 bytes */
379:
380: byte[] type2 = (new sun.misc.BASE64Decoder())
381: .decodeBuffer(challenge);
382: byte[] nonce = new byte[8];
383: System.arraycopy(type2, 24, nonce, 0, 8);
384:
385: int ulen = username.length() * 2;
386: type3[36] = type3[38] = (byte) (ulen % 256);
387: type3[37] = type3[39] = (byte) (ulen / 256);
388: int dlen = ntdomain.length() * 2;
389: type3[28] = type3[30] = (byte) (dlen % 256);
390: type3[29] = type3[31] = (byte) (dlen / 256);
391: int hlen = hostname.length() * 2;
392: type3[44] = type3[46] = (byte) (hlen % 256);
393: type3[45] = type3[47] = (byte) (hlen / 256);
394:
395: int l = 64;
396: copybytes(type3, l, ntdomain, "UnicodeLittleUnmarked");
397: type3[32] = (byte) (l % 256);
398: type3[33] = (byte) (l / 256);
399: l += dlen;
400: copybytes(type3, l, username, "UnicodeLittleUnmarked");
401: type3[40] = (byte) (l % 256);
402: type3[41] = (byte) (l / 256);
403: l += ulen;
404: copybytes(type3, l, hostname, "UnicodeLittleUnmarked");
405: type3[48] = (byte) (l % 256);
406: type3[49] = (byte) (l / 256);
407: l += hlen;
408:
409: byte[] lmhash = calcLMHash();
410: byte[] lmresponse = calcResponse(lmhash, nonce);
411: byte[] nthash = calcNTHash();
412: byte[] ntresponse = calcResponse(nthash, nonce);
413: System.arraycopy(lmresponse, 0, type3, l, 24);
414: type3[16] = (byte) (l % 256);
415: type3[17] = (byte) (l / 256);
416: l += 24;
417: System.arraycopy(ntresponse, 0, type3, l, 24);
418: type3[24] = (byte) (l % 256);
419: type3[25] = (byte) (l / 256);
420: l += 24;
421: type3[56] = (byte) (l % 256);
422: type3[57] = (byte) (l / 256);
423:
424: byte[] msg = new byte[l];
425: System.arraycopy(type3, 0, msg, 0, l);
426: String result = "NTLM " + (new B64Encoder()).encode(msg);
427: return result;
428: }
429:
430: }
431:
432: class B64Encoder extends sun.misc.BASE64Encoder {
433: /* to force it to to the entire encoding in one line */
434: protected int bytesPerLine() {
435: return 1024;
436: }
437: }
|