KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > maverick > http > NTLM


1 /*
2  * SSL-Explorer
3  *
4  * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */

19             
20 package com.maverick.http;
21
22 import java.io.IOException JavaDoc;
23
24 import com.maverick.crypto.encoders.Base64;
25 import com.maverick.crypto.engines.DESEngine;
26
27 /**
28  *
29  * @author Lee David Painter <a HREF="mailto:lee@3sp.com">&lt;lee@3sp.com&gt;</a>
30  */

31 class NTLM {
32
33     /** Character encoding */
34     public static final String JavaDoc DEFAULT_CHARSET = "ASCII"; //$NON-NLS-1$
35

36     /** The current response */
37     private byte[] currentResponse;
38
39     /** The current position */
40     private int currentPosition = 0;
41
42     /** The character set to use for encoding the credentials */
43     private String JavaDoc credentialCharset = DEFAULT_CHARSET;
44
45     /**
46      * Returns the response for the given message.
47      *
48      * @param message the message that was received from the server.
49      * @param username the username to authenticate with.
50      * @param password the password to authenticate with.
51      * @param host The host.
52      * @param domain the NT domain to authenticate in.
53      * @return The response.
54      * @throws ProxyException If the messages cannot be retrieved.
55      */

56     public String JavaDoc getResponseFor(String JavaDoc message, String JavaDoc username, String JavaDoc password, String JavaDoc host, String JavaDoc domain) throws IOException JavaDoc {
57
58         final String JavaDoc response;
59         if (message == null || message.trim().equals("")) { //$NON-NLS-1$
60
response = getType1Message(host, domain);
61         } else {
62             response = getType3Message(username, password, host, domain, parseType2Message(message));
63         }
64         return response;
65     }
66
67     /**
68      * Return the cipher for the specified key.
69      *
70      * @param key The key.
71      * @return Cipher The cipher.
72      * @throws IOException If the cipher cannot be retrieved.
73      */

74     private DESEngine getCipher(byte[] key) throws IOException JavaDoc {
75
76         DESEngine cipher = new DESEngine();
77         key = setupKey(key);
78         cipher.init(true, key);
79         return cipher;
80
81     }
82
83     /**
84      * Adds parity bits to the key.
85      *
86      * @param key56 The key
87      * @return The modified key.
88      */

89     private byte[] setupKey(byte[] key56) {
90         byte[] key = new byte[8];
91         key[0] = (byte) ((key56[0] >> 1) & 0xff);
92         key[1] = (byte) ((((key56[0] & 0x01) << 6) | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
93         key[2] = (byte) ((((key56[1] & 0x03) << 5) | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
94         key[3] = (byte) ((((key56[2] & 0x07) << 4) | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
95         key[4] = (byte) ((((key56[3] & 0x0f) << 3) | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
96         key[5] = (byte) ((((key56[4] & 0x1f) << 2) | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
97         key[6] = (byte) ((((key56[5] & 0x3f) << 1) | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
98         key[7] = (byte) (key56[6] & 0x7f);
99
100         for (int i = 0; i < key.length; i++) {
101             key[i] = (byte) (key[i] << 1);
102         }
103         return key;
104     }
105
106     /**
107      * Encrypt the data.
108      *
109      * @param key The key.
110      * @param bytes The data
111      * @return byte[] The encrypted data
112      * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
113      */

114     private byte[] encrypt(byte[] key, byte[] bytes) throws IOException JavaDoc {
115
116         DESEngine cipher = getCipher(key);
117         byte[] enc = cipher.doFinal(bytes);
118         return enc;
119
120     }
121
122     /**
123      * Prepares the object to create a response of the given length.
124      *
125      * @param length the length of the response to prepare.
126      */

127     private void prepareResponse(int length) {
128         currentResponse = new byte[length];
129         currentPosition = 0;
130     }
131
132     /**
133      * Adds the given byte to the response.
134      *
135      * @param b the byte to add.
136      */

137     private void addByte(byte b) {
138         currentResponse[currentPosition] = b;
139         currentPosition++;
140     }
141
142     /**
143      * Adds the given bytes to the response.
144      *
145      * @param bytes the bytes to add.
146      */

147     private void addBytes(byte[] bytes) {
148         for (int i = 0; i < bytes.length; i++) {
149             currentResponse[currentPosition] = bytes[i];
150             currentPosition++;
151         }
152     }
153
154     /**
155      * Returns the response that has been generated after shrinking the array if
156      * required and base64 encodes the response.
157      *
158      * @return The response as above.
159      */

160     private String JavaDoc getResponse() {
161         byte[] resp;
162         if (currentResponse.length > currentPosition) {
163             byte[] tmp = new byte[currentPosition];
164             for (int i = 0; i < currentPosition; i++) {
165                 tmp[i] = currentResponse[i];
166             }
167             resp = tmp;
168         } else {
169             resp = currentResponse;
170         }
171         return new String JavaDoc(Base64.encode(resp));
172     }
173
174     /**
175      * Creates the first message (type 1 message) in the NTLM authentication
176      * sequence. This message includes the user name, domain and host for the
177      * authentication session.
178      *
179      * @param host the computer name of the host requesting authentication.
180      * @param domain The domain to authenticate with.
181      * @return String the message to add to the HTTP request header.
182      */

183     public String JavaDoc getType1Message(String JavaDoc host, String JavaDoc domain) {
184         host = host.toUpperCase();
185         domain = domain.toUpperCase();
186         byte[] hostBytes = host.getBytes();
187         byte[] domainBytes = domain.getBytes();
188
189         int finalLength = 32 + hostBytes.length + domainBytes.length;
190         prepareResponse(finalLength);
191
192         // The initial id string.
193
byte[] protocol = "NTLMSSP".getBytes(); //$NON-NLS-1$
194
addBytes(protocol);
195         addByte((byte) 0);
196
197         // Type
198
addByte((byte) 1);
199         addByte((byte) 0);
200         addByte((byte) 0);
201         addByte((byte) 0);
202
203         // Flags
204
addByte((byte) 6);
205         addByte((byte) 82);
206         addByte((byte) 0);
207         addByte((byte) 0);
208
209         // Domain length (first time).
210
int iDomLen = domainBytes.length;
211         byte[] domLen = convertShort(iDomLen);
212         addByte(domLen[0]);
213         addByte(domLen[1]);
214
215         // Domain length (second time).
216
addByte(domLen[0]);
217         addByte(domLen[1]);
218
219         // Domain offset.
220
byte[] domOff = convertShort(hostBytes.length + 32);
221         addByte(domOff[0]);
222         addByte(domOff[1]);
223         addByte((byte) 0);
224         addByte((byte) 0);
225
226         // Host length (first time).
227
byte[] hostLen = convertShort(hostBytes.length);
228         addByte(hostLen[0]);
229         addByte(hostLen[1]);
230
231         // Host length (second time).
232
addByte(hostLen[0]);
233         addByte(hostLen[1]);
234
235         // Host offset (always 32).
236
byte[] hostOff = convertShort(32);
237         addByte(hostOff[0]);
238         addByte(hostOff[1]);
239         addByte((byte) 0);
240         addByte((byte) 0);
241
242         // Host String.
243
addBytes(hostBytes);
244
245         // Domain String.
246
addBytes(domainBytes);
247
248         return getResponse();
249     }
250
251     /**
252      * Extracts the server nonce out of the given message type 2.
253      *
254      * @param message the String containing the base64 encoded message.
255      * @return an array of 8 bytes that the server sent to be used when hashing
256      * the password.
257      */

258     public byte[] parseType2Message(String JavaDoc message) {
259         // Decode the message first.
260
byte[] msg = Base64.decode(message);
261         byte[] nonce = new byte[8];
262         // The nonce is the 8 bytes starting from the byte in position 24.
263
for (int i = 0; i < 8; i++) {
264             nonce[i] = msg[i + 24];
265         }
266         return nonce;
267     }
268
269     /**
270      * Creates the type 3 message using the given server nonce. The type 3
271      * message includes all the information for authentication, host, domain,
272      * username and the result of encrypting the nonce sent by the server using
273      * the user's password as the key.
274      *
275      * @param user The user name. This should not include the domain name.
276      * @param password The password.
277      * @param host The host that is originating the authentication request.
278      * @param domain The domain to authenticate within.
279      * @param nonce the 8 byte array the server sent.
280      * @return The type 3 message.
281      * @throws IOException If {@encrypt(byte[],byte[])} fails.
282      */

283     public String JavaDoc getType3Message(String JavaDoc user, String JavaDoc password, String JavaDoc host, String JavaDoc domain, byte[] nonce) throws IOException JavaDoc {
284
285         int ntRespLen = 0;
286         int lmRespLen = 24;
287         domain = domain.toUpperCase();
288         host = host.toUpperCase();
289         user = user.toUpperCase();
290         byte[] domainBytes = domain.getBytes();
291         byte[] hostBytes = host.getBytes();
292         byte[] userBytes = user.getBytes();
293         int domainLen = domainBytes.length;
294         int hostLen = hostBytes.length;
295         int userLen = userBytes.length;
296         int finalLength = 64 + ntRespLen + lmRespLen + domainLen + userLen + hostLen;
297         prepareResponse(finalLength);
298         byte[] ntlmssp = "NTLMSSP".getBytes(); //$NON-NLS-1$
299
addBytes(ntlmssp);
300         addByte((byte) 0);
301         addByte((byte) 3);
302         addByte((byte) 0);
303         addByte((byte) 0);
304         addByte((byte) 0);
305
306         // LM Resp Length (twice)
307
addBytes(convertShort(24));
308         addBytes(convertShort(24));
309
310         // LM Resp Offset
311
addBytes(convertShort(finalLength - 24));
312         addByte((byte) 0);
313         addByte((byte) 0);
314
315         // NT Resp Length (twice)
316
addBytes(convertShort(0));
317         addBytes(convertShort(0));
318
319         // NT Resp Offset
320
addBytes(convertShort(finalLength));
321         addByte((byte) 0);
322         addByte((byte) 0);
323
324         // Domain length (twice)
325
addBytes(convertShort(domainLen));
326         addBytes(convertShort(domainLen));
327
328         // Domain offset.
329
addBytes(convertShort(64));
330         addByte((byte) 0);
331         addByte((byte) 0);
332
333         // User Length (twice)
334
addBytes(convertShort(userLen));
335         addBytes(convertShort(userLen));
336
337         // User offset
338
addBytes(convertShort(64 + domainLen));
339         addByte((byte) 0);
340         addByte((byte) 0);
341
342         // Host length (twice)
343
addBytes(convertShort(hostLen));
344         addBytes(convertShort(hostLen));
345
346         // Host offset
347
addBytes(convertShort(64 + domainLen + userLen));
348
349         for (int i = 0; i < 6; i++) {
350             addByte((byte) 0);
351         }
352
353         // Message length
354
addBytes(convertShort(finalLength));
355         addByte((byte) 0);
356         addByte((byte) 0);
357
358         // Flags
359
addByte((byte) 6);
360         addByte((byte) 82);
361         addByte((byte) 0);
362         addByte((byte) 0);
363
364         addBytes(domainBytes);
365         addBytes(userBytes);
366         addBytes(hostBytes);
367         addBytes(hashPassword(password, nonce));
368         return getResponse();
369     }
370
371     /**
372      * Creates the LANManager and NT response for the given password using the
373      * given nonce.
374      *
375      * @param password the password to create a hash for.
376      * @param nonce the nonce sent by the server.
377      * @return The response.
378      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
379      */

380     private byte[] hashPassword(String JavaDoc password, byte[] nonce) throws IOException JavaDoc {
381         byte[] passw = password.toUpperCase().getBytes();
382         byte[] lmPw1 = new byte[7];
383         byte[] lmPw2 = new byte[7];
384
385         int len = passw.length;
386         if (len > 7) {
387             len = 7;
388         }
389
390         int idx;
391         for (idx = 0; idx < len; idx++) {
392             lmPw1[idx] = passw[idx];
393         }
394         for (; idx < 7; idx++) {
395             lmPw1[idx] = (byte) 0;
396         }
397
398         len = passw.length;
399         if (len > 14) {
400             len = 14;
401         }
402         for (idx = 7; idx < len; idx++) {
403             lmPw2[idx - 7] = passw[idx];
404         }
405         for (; idx < 14; idx++) {
406             lmPw2[idx - 7] = (byte) 0;
407         }
408
409         // Create LanManager hashed Password
410
byte[] magic = { (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25 };
411
412         byte[] lmHpw1;
413         lmHpw1 = encrypt(lmPw1, magic);
414
415         byte[] lmHpw2 = encrypt(lmPw2, magic);
416
417         byte[] lmHpw = new byte[21];
418         for (int i = 0; i < lmHpw1.length; i++) {
419             lmHpw[i] = lmHpw1[i];
420         }
421         for (int i = 0; i < lmHpw2.length; i++) {
422             lmHpw[i + 8] = lmHpw2[i];
423         }
424         for (int i = 0; i < 5; i++) {
425             lmHpw[i + 16] = (byte) 0;
426         }
427
428         // Create the responses.
429
byte[] lmResp = new byte[24];
430         calcResp(lmHpw, nonce, lmResp);
431
432         return lmResp;
433     }
434
435     /**
436      * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte
437      * plaintext is encrypted with each key and the resulting 24 bytes are
438      * stored in the results array.
439      *
440      * @param keys The keys.
441      * @param plaintext The plain text to encrypt.
442      * @param results Where the results are stored.
443      * @throws IOException If {@link #encrypt(byte[],byte[])} fails.
444      */

445     private void calcResp(byte[] keys, byte[] plaintext, byte[] results) throws IOException JavaDoc {
446         byte[] keys1 = new byte[7];
447         byte[] keys2 = new byte[7];
448         byte[] keys3 = new byte[7];
449         for (int i = 0; i < 7; i++) {
450             keys1[i] = keys[i];
451         }
452
453         for (int i = 0; i < 7; i++) {
454             keys2[i] = keys[i + 7];
455         }
456
457         for (int i = 0; i < 7; i++) {
458             keys3[i] = keys[i + 14];
459         }
460         byte[] results1 = encrypt(keys1, plaintext);
461
462         byte[] results2 = encrypt(keys2, plaintext);
463
464         byte[] results3 = encrypt(keys3, plaintext);
465
466         for (int i = 0; i < 8; i++) {
467             results[i] = results1[i];
468         }
469         for (int i = 0; i < 8; i++) {
470             results[i + 8] = results2[i];
471         }
472         for (int i = 0; i < 8; i++) {
473             results[i + 16] = results3[i];
474         }
475     }
476
477     /**
478      * Converts a given number to a two byte array in little endian order.
479      *
480      * @param num the number to convert.
481      * @return The byte representation of <i>num</i> in little endian order.
482      */

483     private byte[] convertShort(int num) {
484         byte[] val = new byte[2];
485         String JavaDoc hex = Integer.toString(num, 16);
486         while (hex.length() < 4) {
487             hex = "0" + hex; //$NON-NLS-1$
488
}
489         String JavaDoc low = hex.substring(2, 4);
490         String JavaDoc high = hex.substring(0, 2);
491
492         val[0] = (byte) Integer.parseInt(low, 16);
493         val[1] = (byte) Integer.parseInt(high, 16);
494         return val;
495     }
496
497     /**
498      * @return Returns the credentialCharset.
499      */

500     public String JavaDoc getCredentialCharset() {
501         return credentialCharset;
502     }
503
504     /**
505      * @param credentialCharset The credentialCharset to set.
506      */

507     public void setCredentialCharset(String JavaDoc credentialCharset) {
508         this.credentialCharset = credentialCharset;
509     }
510
511 }
512
Popular Tags