KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > auth > NTLM


1 /*
2  * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/auth/NTLM.java,v 1.11 2004/05/13 04:02:00 mbecke Exp $
3  * $Revision: 480424 $
4  * $Date: 2006-11-29 05:56:49 +0000 (Wed, 29 Nov 2006) $
5  *
6  * ====================================================================
7  *
8  * Licensed to the Apache Software Foundation (ASF) under one or more
9  * contributor license agreements. See the NOTICE file distributed with
10  * this work for additional information regarding copyright ownership.
11  * The ASF licenses this file to You under the Apache License, Version 2.0
12  * (the "License"); you may not use this file except in compliance with
13  * the License. You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  * ====================================================================
23  *
24  * This software consists of voluntary contributions made by many
25  * individuals on behalf of the Apache Software Foundation. For more
26  * information on the Apache Software Foundation, please see
27  * <http://www.apache.org/>.
28  *
29  */

30
31 package org.apache.commons.httpclient.auth;
32
33 import java.security.InvalidKeyException JavaDoc;
34 import java.security.NoSuchAlgorithmException JavaDoc;
35
36 import javax.crypto.BadPaddingException;
37 import javax.crypto.Cipher;
38 import javax.crypto.IllegalBlockSizeException;
39 import javax.crypto.NoSuchPaddingException;
40 import javax.crypto.spec.SecretKeySpec;
41
42 import org.apache.commons.codec.binary.Base64;
43 import org.apache.commons.httpclient.util.EncodingUtil;
44
45 /**
46  * Provides an implementation of the NTLM authentication protocol.
47  * <p>
48  * This class provides methods for generating authentication
49  * challenge responses for the NTLM authentication protocol. The NTLM
50  * protocol is a proprietary Microsoft protocol and as such no RFC
51  * exists for it. This class is based upon the reverse engineering
52  * efforts of a wide range of people.</p>
53  *
54  * <p>Please note that an implementation of JCE must be correctly installed and configured when
55  * using NTLM support.</p>
56  *
57  * <p>This class should not be used externally to HttpClient as it's API is specifically
58  * designed to work with HttpClient's use case, in particular it's connection management.</p>
59  *
60  * @author <a HREF="mailto:adrian@ephox.com">Adrian Sutton</a>
61  * @author <a HREF="mailto:jsdever@apache.org">Jeff Dever</a>
62  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
63  *
64  * @version $Revision: 480424 $ $Date: 2006-11-29 05:56:49 +0000 (Wed, 29 Nov 2006) $
65  * @since 3.0
66  */

67 final class NTLM {
68
69     /** Character encoding */
70     public static final String JavaDoc DEFAULT_CHARSET = "ASCII";
71
72     /** The current response */
73     private byte[] currentResponse;
74
75     /** The current position */
76     private int currentPosition = 0;
77
78     /** The character set to use for encoding the credentials */
79     private String JavaDoc credentialCharset = DEFAULT_CHARSET;
80     
81     /**
82      * Returns the response for the given message.
83      *
84      * @param message the message that was received from the server.
85      * @param username the username to authenticate with.
86      * @param password the password to authenticate with.
87      * @param host The host.
88      * @param domain the NT domain to authenticate in.
89      * @return The response.
90      * @throws HttpException If the messages cannot be retrieved.
91      */

92     public final String JavaDoc getResponseFor(String JavaDoc message,
93             String JavaDoc username, String JavaDoc password, String JavaDoc host, String JavaDoc domain)
94             throws AuthenticationException {
95                 
96         final String JavaDoc response;
97         if (message == null || message.trim().equals("")) {
98             response = getType1Message(host, domain);
99         } else {
100             response = getType3Message(username, password, host, domain,
101                     parseType2Message(message));
102         }
103         return response;
104     }
105
106     /**
107      * Return the cipher for the specified key.
108      * @param key The key.
109      * @return Cipher The cipher.
110      * @throws AuthenticationException If the cipher cannot be retrieved.
111      */

112     private Cipher getCipher(byte[] key) throws AuthenticationException {
113         try {
114             final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
115             key = setupKey(key);
116             ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
117             return ecipher;
118         } catch (NoSuchAlgorithmException JavaDoc e) {
119             throw new AuthenticationException("DES encryption is not available.", e);
120         } catch (InvalidKeyException JavaDoc e) {
121             throw new AuthenticationException("Invalid key for DES encryption.", e);
122         } catch (NoSuchPaddingException e) {
123             throw new AuthenticationException(
124                 "NoPadding option for DES is not available.", e);
125         }
126     }
127
128     /**
129      * Adds parity bits to the key.
130      * @param key56 The key
131      * @return The modified key.
132      */

133     private byte[] setupKey(byte[] key56) {
134         byte[] key = new byte[8];
135         key[0] = (byte) ((key56[0] >> 1) & 0xff);
136         key[1] = (byte) ((((key56[0] & 0x01) << 6)
137             | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
138         key[2] = (byte) ((((key56[1] & 0x03) << 5)
139             | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
140         key[3] = (byte) ((((key56[2] & 0x07) << 4)
141             | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
142         key[4] = (byte) ((((key56[3] & 0x0f) << 3)
143             | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
144         key[5] = (byte) ((((key56[4] & 0x1f) << 2)
145             | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
146         key[6] = (byte) ((((key56[5] & 0x3f) << 1)
147             | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
148         key[7] = (byte) (key56[6] & 0x7f);
149         
150         for (int i = 0; i < key.length; i++) {
151             key[i] = (byte) (key[i] << 1);
152         }
153         return key;
154     }
155
156     /**
157      * Encrypt the data.
158      * @param key The key.
159      * @param bytes The data
160      * @return byte[] The encrypted data
161      * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
162      */

163     private byte[] encrypt(byte[] key, byte[] bytes)
164         throws AuthenticationException {
165         Cipher ecipher = getCipher(key);
166         try {
167             byte[] enc = ecipher.doFinal(bytes);
168             return enc;
169         } catch (IllegalBlockSizeException e) {
170             throw new AuthenticationException("Invalid block size for DES encryption.", e);
171         } catch (BadPaddingException e) {
172             throw new AuthenticationException("Data not padded correctly for DES encryption.", e);
173         }
174     }
175
176     /**
177      * Prepares the object to create a response of the given length.
178      * @param length the length of the response to prepare.
179      */

180     private void prepareResponse(int length) {
181         currentResponse = new byte[length];
182         currentPosition = 0;
183     }
184
185     /**
186      * Adds the given byte to the response.
187      * @param b the byte to add.
188      */

189     private void addByte(byte b) {
190         currentResponse[currentPosition] = b;
191         currentPosition++;
192     }
193
194     /**
195      * Adds the given bytes to the response.
196      * @param bytes the bytes to add.
197      */

198     private void addBytes(byte[] bytes) {
199         for (int i = 0; i < bytes.length; i++) {
200             currentResponse[currentPosition] = bytes[i];
201             currentPosition++;
202         }
203     }
204
205     /**
206      * Returns the response that has been generated after shrinking the array if
207      * required and base64 encodes the response.
208      * @return The response as above.
209      */

210     private String JavaDoc getResponse() {
211         byte[] resp;
212         if (currentResponse.length > currentPosition) {
213             byte[] tmp = new byte[currentPosition];
214             for (int i = 0; i < currentPosition; i++) {
215                 tmp[i] = currentResponse[i];
216             }
217             resp = tmp;
218         } else {
219             resp = currentResponse;
220         }
221         return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
222     }
223     
224     /**
225      * Creates the first message (type 1 message) in the NTLM authentication sequence.
226      * This message includes the user name, domain and host for the authentication session.
227      *
228      * @param host the computer name of the host requesting authentication.
229      * @param domain The domain to authenticate with.
230      * @return String the message to add to the HTTP request header.
231      */

232     public String JavaDoc getType1Message(String JavaDoc host, String JavaDoc domain) {
233         host = host.toUpperCase();
234         domain = domain.toUpperCase();
235         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
236         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
237
238         int finalLength = 32 + hostBytes.length + domainBytes.length;
239         prepareResponse(finalLength);
240         
241         // The initial id string.
242
byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
243         addBytes(protocol);
244         addByte((byte) 0);
245
246         // Type
247
addByte((byte) 1);
248         addByte((byte) 0);
249         addByte((byte) 0);
250         addByte((byte) 0);
251
252         // Flags
253
addByte((byte) 6);
254         addByte((byte) 82);
255         addByte((byte) 0);
256         addByte((byte) 0);
257
258         // Domain length (first time).
259
int iDomLen = domainBytes.length;
260         byte[] domLen = convertShort(iDomLen);
261         addByte(domLen[0]);
262         addByte(domLen[1]);
263
264         // Domain length (second time).
265
addByte(domLen[0]);
266         addByte(domLen[1]);
267
268         // Domain offset.
269
byte[] domOff = convertShort(hostBytes.length + 32);
270         addByte(domOff[0]);
271         addByte(domOff[1]);
272         addByte((byte) 0);
273         addByte((byte) 0);
274
275         // Host length (first time).
276
byte[] hostLen = convertShort(hostBytes.length);
277         addByte(hostLen[0]);
278         addByte(hostLen[1]);
279
280         // Host length (second time).
281
addByte(hostLen[0]);
282         addByte(hostLen[1]);
283
284         // Host offset (always 32).
285
byte[] hostOff = convertShort(32);
286         addByte(hostOff[0]);
287         addByte(hostOff[1]);
288         addByte((byte) 0);
289         addByte((byte) 0);
290
291         // Host String.
292
addBytes(hostBytes);
293
294         // Domain String.
295
addBytes(domainBytes);
296
297         return getResponse();
298     }
299
300     /**
301      * Extracts the server nonce out of the given message type 2.
302      *
303      * @param message the String containing the base64 encoded message.
304      * @return an array of 8 bytes that the server sent to be used when
305      * hashing the password.
306      */

307     public byte[] parseType2Message(String JavaDoc message) {
308         // Decode the message first.
309
byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET));
310         byte[] nonce = new byte[8];
311         // The nonce is the 8 bytes starting from the byte in position 24.
312
for (int i = 0; i < 8; i++) {
313             nonce[i] = msg[i + 24];
314         }
315         return nonce;
316     }
317
318     /**
319      * Creates the type 3 message using the given server nonce. The type 3 message includes all the
320      * information for authentication, host, domain, username and the result of encrypting the
321      * nonce sent by the server using the user's password as the key.
322      *
323      * @param user The user name. This should not include the domain name.
324      * @param password The password.
325      * @param host The host that is originating the authentication request.
326      * @param domain The domain to authenticate within.
327      * @param nonce the 8 byte array the server sent.
328      * @return The type 3 message.
329      * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
330      */

331     public String JavaDoc getType3Message(String JavaDoc user, String JavaDoc password,
332             String JavaDoc host, String JavaDoc domain, byte[] nonce)
333     throws AuthenticationException {
334
335         int ntRespLen = 0;
336         int lmRespLen = 24;
337         domain = domain.toUpperCase();
338         host = host.toUpperCase();
339         user = user.toUpperCase();
340         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
341         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
342         byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset);
343         int domainLen = domainBytes.length;
344         int hostLen = hostBytes.length;
345         int userLen = userBytes.length;
346         int finalLength = 64 + ntRespLen + lmRespLen + domainLen
347             + userLen + hostLen;
348         prepareResponse(finalLength);
349         byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
350         addBytes(ntlmssp);
351         addByte((byte) 0);
352         addByte((byte) 3);
353         addByte((byte) 0);
354         addByte((byte) 0);
355         addByte((byte) 0);
356
357         // LM Resp Length (twice)
358
addBytes(convertShort(24));
359         addBytes(convertShort(24));
360
361         // LM Resp Offset
362
addBytes(convertShort(finalLength - 24));
363         addByte((byte) 0);
364         addByte((byte) 0);
365
366         // NT Resp Length (twice)
367
addBytes(convertShort(0));
368         addBytes(convertShort(0));
369
370         // NT Resp Offset
371
addBytes(convertShort(finalLength));
372         addByte((byte) 0);
373         addByte((byte) 0);
374
375         // Domain length (twice)
376
addBytes(convertShort(domainLen));
377         addBytes(convertShort(domainLen));
378         
379         // Domain offset.
380
addBytes(convertShort(64));
381         addByte((byte) 0);
382         addByte((byte) 0);
383
384         // User Length (twice)
385
addBytes(convertShort(userLen));
386         addBytes(convertShort(userLen));
387
388         // User offset
389
addBytes(convertShort(64 + domainLen));
390         addByte((byte) 0);
391         addByte((byte) 0);
392
393         // Host length (twice)
394
addBytes(convertShort(hostLen));
395         addBytes(convertShort(hostLen));
396
397         // Host offset
398
addBytes(convertShort(64 + domainLen + userLen));
399
400         for (int i = 0; i < 6; i++) {
401             addByte((byte) 0);
402         }
403
404         // Message length
405
addBytes(convertShort(finalLength));
406         addByte((byte) 0);
407         addByte((byte) 0);
408
409         // Flags
410
addByte((byte) 6);
411         addByte((byte) 82);
412         addByte((byte) 0);
413         addByte((byte) 0);
414
415         addBytes(domainBytes);
416         addBytes(userBytes);
417         addBytes(hostBytes);
418         addBytes(hashPassword(password, nonce));
419         return getResponse();
420     }
421
422     /**
423      * Creates the LANManager and NT response for the given password using the
424      * given nonce.
425      * @param password the password to create a hash for.
426      * @param nonce the nonce sent by the server.
427      * @return The response.
428      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
429      */

430     private byte[] hashPassword(String JavaDoc password, byte[] nonce)
431         throws AuthenticationException {
432         byte[] passw = EncodingUtil.getBytes(password.toUpperCase(), credentialCharset);
433         byte[] lmPw1 = new byte[7];
434         byte[] lmPw2 = new byte[7];
435
436         int len = passw.length;
437         if (len > 7) {
438             len = 7;
439         }
440
441         int idx;
442         for (idx = 0; idx < len; idx++) {
443             lmPw1[idx] = passw[idx];
444         }
445         for (; idx < 7; idx++) {
446             lmPw1[idx] = (byte) 0;
447         }
448
449         len = passw.length;
450         if (len > 14) {
451             len = 14;
452         }
453         for (idx = 7; idx < len; idx++) {
454             lmPw2[idx - 7] = passw[idx];
455         }
456         for (; idx < 14; idx++) {
457             lmPw2[idx - 7] = (byte) 0;
458         }
459
460         // Create LanManager hashed Password
461
byte[] magic = {
462             (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21,
463             (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
464         };
465
466         byte[] lmHpw1;
467         lmHpw1 = encrypt(lmPw1, magic);
468
469         byte[] lmHpw2 = encrypt(lmPw2, magic);
470
471         byte[] lmHpw = new byte[21];
472         for (int i = 0; i < lmHpw1.length; i++) {
473             lmHpw[i] = lmHpw1[i];
474         }
475         for (int i = 0; i < lmHpw2.length; i++) {
476             lmHpw[i + 8] = lmHpw2[i];
477         }
478         for (int i = 0; i < 5; i++) {
479             lmHpw[i + 16] = (byte) 0;
480         }
481
482         // Create the responses.
483
byte[] lmResp = new byte[24];
484         calcResp(lmHpw, nonce, lmResp);
485
486         return lmResp;
487     }
488
489     /**
490      * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte
491      * plaintext is encrypted with each key and the resulting 24 bytes are
492      * stored in the results array.
493      *
494      * @param keys The keys.
495      * @param plaintext The plain text to encrypt.
496      * @param results Where the results are stored.
497      * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
498      */

499     private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
500         throws AuthenticationException {
501         byte[] keys1 = new byte[7];
502         byte[] keys2 = new byte[7];
503         byte[] keys3 = new byte[7];
504         for (int i = 0; i < 7; i++) {
505             keys1[i] = keys[i];
506         }
507
508         for (int i = 0; i < 7; i++) {
509             keys2[i] = keys[i + 7];
510         }
511
512         for (int i = 0; i < 7; i++) {
513             keys3[i] = keys[i + 14];
514         }
515         byte[] results1 = encrypt(keys1, plaintext);
516
517         byte[] results2 = encrypt(keys2, plaintext);
518
519         byte[] results3 = encrypt(keys3, plaintext);
520
521         for (int i = 0; i < 8; i++) {
522             results[i] = results1[i];
523         }
524         for (int i = 0; i < 8; i++) {
525             results[i + 8] = results2[i];
526         }
527         for (int i = 0; i < 8; i++) {
528             results[i + 16] = results3[i];
529         }
530     }
531
532     /**
533      * Converts a given number to a two byte array in little endian order.
534      * @param num the number to convert.
535      * @return The byte representation of <i>num</i> in little endian order.
536      */

537     private byte[] convertShort(int num) {
538         byte[] val = new byte[2];
539         String JavaDoc hex = Integer.toString(num, 16);
540         while (hex.length() < 4) {
541             hex = "0" + hex;
542         }
543         String JavaDoc low = hex.substring(2, 4);
544         String JavaDoc high = hex.substring(0, 2);
545
546         val[0] = (byte) Integer.parseInt(low, 16);
547         val[1] = (byte) Integer.parseInt(high, 16);
548         return val;
549     }
550     
551     /**
552      * @return Returns the credentialCharset.
553      */

554     public String JavaDoc getCredentialCharset() {
555         return credentialCharset;
556     }
557
558     /**
559      * @param credentialCharset The credentialCharset to set.
560      */

561     public void setCredentialCharset(String JavaDoc credentialCharset) {
562         this.credentialCharset = credentialCharset;
563     }
564
565 }
566
Popular Tags