KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Attic/NTLM.java,v 1.12.2.2 2004/02/22 18:21:13 olegk Exp $
3  * $Revision: 1.12.2.2 $
4  * $Date: 2004/02/22 18:21:13 $
5  *
6  * ====================================================================
7  *
8  * Copyright 2002-2004 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ====================================================================
22  *
23  * This software consists of voluntary contributions made by many
24  * individuals on behalf of the Apache Software Foundation. For more
25  * information on the Apache Software Foundation, please see
26  * <http://www.apache.org/>.
27  *
28  * [Additional notices, if required by prior licensing conditions]
29  *
30  */

31
32 package org.apache.commons.httpclient;
33
34 import java.io.UnsupportedEncodingException JavaDoc;
35 import java.security.InvalidKeyException JavaDoc;
36 import java.security.NoSuchAlgorithmException JavaDoc;
37
38 import javax.crypto.BadPaddingException;
39 import javax.crypto.Cipher;
40 import javax.crypto.IllegalBlockSizeException;
41 import javax.crypto.NoSuchPaddingException;
42 import javax.crypto.spec.SecretKeySpec;
43
44 import org.apache.commons.httpclient.util.Base64;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48 /**
49  * Provides an implementation of the NTLM authentication protocol.
50  * <p>
51  * This class provides methods for generating authentication
52  * challenge responses for the NTLM authentication protocol. The NTLM
53  * protocol is a proprietary Microsoft protocol and as such no RFC
54  * exists for it. This class is based upon the reverse engineering
55  * efforts of a wide range of people.</p>
56  *
57  * <p>Please note that an implementation of JCE must be correctly installed and configured when
58  * using NTLM support.</p>
59  *
60  * <p>This class should not be used externally to HttpClient as it's API is specifically
61  * designed to work with HttpClient's use case, in particular it's connection management.</p>
62  *
63  * @deprecated this class will be made package access for 2.0beta2
64  *
65  * @author <a HREF="mailto:adrian@ephox.com">Adrian Sutton</a>
66  * @author <a HREF="mailto:jsdever@apache.org">Jeff Dever</a>
67  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
68  *
69  * @version $Revision: 1.12.2.2 $ $Date: 2004/02/22 18:21:13 $
70  * @since 2.0alpha2
71  */

72 public final class NTLM {
73
74     /** The current response */
75     private byte[] currentResponse;
76
77     /** The current position */
78     private int currentPosition = 0;
79
80     /** Log object for this class. */
81     private static final Log LOG = LogFactory.getLog(NTLM.class);
82
83     /** Character encoding */
84     public static final String JavaDoc DEFAULT_CHARSET = "ASCII";
85
86     /**
87      * Returns the response for the given message.
88      *
89      * @param message the message that was received from the server.
90      * @param username the username to authenticate with.
91      * @param password the password to authenticate with.
92      * @param host The host.
93      * @param domain the NT domain to authenticate in.
94      * @return The response.
95      * @throws HttpException If the messages cannot be retrieved.
96      */

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

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

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

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

186     private void prepareResponse(int length) {
187         currentResponse = new byte[length];
188         currentPosition = 0;
189     }
190
191     /**
192      * Adds the given byte to the response.
193      * @param b the byte to add.
194      */

195     private void addByte(byte b) {
196         currentResponse[currentPosition] = b;
197         currentPosition++;
198     }
199
200     /**
201      * Adds the given bytes to the response.
202      * @param bytes the bytes to add.
203      */

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

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

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

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

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

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

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

543     private byte[] convertShort(int num) {
544         byte[] val = new byte[2];
545         String JavaDoc hex = Integer.toString(num, 16);
546         while (hex.length() < 4) {
547             hex = "0" + hex;
548         }
549         String JavaDoc low = hex.substring(2, 4);
550         String JavaDoc high = hex.substring(0, 2);
551
552         val[0] = (byte) Integer.parseInt(low, 16);
553         val[1] = (byte) Integer.parseInt(high, 16);
554         return val;
555     }
556     
557     /**
558      * Convert a string to a byte array.
559      * @param s The string
560      * @return byte[] The resulting byte array.
561      */

562     private static byte[] getBytes(final String JavaDoc s) {
563         if (s == null) {
564             throw new IllegalArgumentException JavaDoc("Parameter may not be null");
565         }
566         try {
567             return s.getBytes(DEFAULT_CHARSET);
568         } catch (UnsupportedEncodingException JavaDoc unexpectedEncodingException) {
569             throw new RuntimeException JavaDoc("NTLM requires ASCII support");
570         }
571     }
572 }
573
Popular Tags