1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Attic/NTLM.java,v 1.12.2.1 2003/08/16 00:09:37 adrian Exp $
3    * $Revision: 1.12.2.1 $
4    * $Date: 2003/08/16 00:09:37 $
5    *
6    * ====================================================================
7    *
8    * The Apache Software License, Version 1.1
9    *
10   * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
11   * reserved.
12   *
13   * Redistribution and use in source and binary forms, with or without
14   * modification, are permitted provided that the following conditions
15   * are met:
16   *
17   * 1. Redistributions of source code must retain the above copyright
18   *    notice, this list of conditions and the following disclaimer.
19   *
20   * 2. Redistributions in binary form must reproduce the above copyright
21   *    notice, this list of conditions and the following disclaimer in
22   *    the documentation and/or other materials provided with the
23   *    distribution.
24   *
25   * 3. The end-user documentation included with the redistribution, if
26   *    any, must include the following acknowlegement:
27   *       "This product includes software developed by the
28   *        Apache Software Foundation (http://www.apache.org/)."
29   *    Alternately, this acknowlegement may appear in the software itself,
30   *    if and wherever such third-party acknowlegements normally appear.
31   *
32   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33   *    Foundation" must not be used to endorse or promote products derived
34   *    from this software without prior written permission. For written
35   *    permission, please contact apache@apache.org.
36   *
37   * 5. Products derived from this software may not be called "Apache"
38   *    nor may "Apache" appear in their names without prior written
39   *    permission of the Apache Group.
40   *
41   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52   * SUCH DAMAGE.
53   * ====================================================================
54   *
55   * This software consists of voluntary contributions made by many
56   * individuals on behalf of the Apache Software Foundation.  For more
57   * information on the Apache Software Foundation, please see
58   * <http://www.apache.org/>.
59   *
60   * [Additional notices, if required by prior licensing conditions]
61   *
62   */
63  
64  package org.apache.commons.httpclient;
65  
66  import java.io.UnsupportedEncodingException;
67  import java.security.InvalidKeyException;
68  import java.security.NoSuchAlgorithmException;
69  
70  import javax.crypto.BadPaddingException;
71  import javax.crypto.Cipher;
72  import javax.crypto.IllegalBlockSizeException;
73  import javax.crypto.NoSuchPaddingException;
74  import javax.crypto.spec.SecretKeySpec;
75  
76  import org.apache.commons.httpclient.util.Base64;
77  import org.apache.commons.logging.Log;
78  import org.apache.commons.logging.LogFactory;
79  
80  /***
81   * Provides an implementation of the NTLM authentication protocol.
82   * <p>
83   * This class provides methods for generating authentication
84   * challenge responses for the NTLM authentication protocol.  The NTLM
85   * protocol is a proprietary Microsoft protocol and as such no RFC
86   * exists for it.  This class is based upon the reverse engineering
87   * efforts of a wide range of people.</p>
88   * 
89   * <p>Please note that an implementation of JCE must be correctly installed and configured when
90   * using NTLM support.</p>
91   * 
92   * <p>This class should not be used externally to HttpClient as it's API is specifically
93   * designed to work with HttpClient's use case, in particular it's connection management.</p>
94   *
95   * @deprecated this class will be made package access for 2.0beta2
96   *
97   * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
98   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
99   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
100  *
101  * @version $Revision: 1.12.2.1 $ $Date: 2003/08/16 00:09:37 $
102  * @since 2.0alpha2
103  */
104 public final class NTLM {
105 
106     /*** The current response */
107     private byte[] currentResponse;
108 
109     /*** The current position */
110     private int currentPosition = 0;
111 
112     /*** Log object for this class. */
113     private static final Log LOG = LogFactory.getLog(NTLM.class);
114 
115     /*** Character encoding */
116     public static final String DEFAULT_CHARSET = "ASCII";
117 
118     /***
119      * Returns the response for the given message.
120      *
121      * @param message the message that was received from the server.
122      * @param username the username to authenticate with.
123      * @param password the password to authenticate with.
124      * @param host The host.
125      * @param domain the NT domain to authenticate in.
126      * @return The response.
127      * @throws HttpException If the messages cannot be retrieved.
128      */
129     public final String getResponseFor(String message,
130             String username, String password, String host, String domain)
131             throws HttpException {
132                 
133         final String response;
134         if (message == null || message.trim().equals("")) {
135             response = getType1Message(host, domain);
136         } else {
137             response = getType3Message(username, password, host, domain,
138                     parseType2Message(message));
139         }
140         return response;
141     }
142 
143     /***
144      * Return the cipher for the specified key.
145      * @param key The key.
146      * @return Cipher The cipher.
147      * @throws HttpException If the cipher cannot be retrieved.
148      */
149     private Cipher getCipher(byte[] key) throws HttpException {
150         try {
151             final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
152             key = setupKey(key);
153             ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
154             return ecipher;
155         } catch (NoSuchAlgorithmException e) {
156             throw new HttpException("DES encryption is not available.");
157         } catch (InvalidKeyException e) {
158             throw new HttpException("Invalid key for DES encryption.");
159         } catch (NoSuchPaddingException e) {
160             throw new HttpException(
161                 "NoPadding option for DES is not available.");
162         }
163     }
164 
165     /*** 
166      * Adds parity bits to the key.
167      * @param key56 The key
168      * @return The modified key.
169      */
170     private byte[] setupKey(byte[] key56) {
171         byte[] key = new byte[8];
172         key[0] = (byte) ((key56[0] >> 1) & 0xff);
173         key[1] = (byte) ((((key56[0] & 0x01) << 6) 
174             | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
175         key[2] = (byte) ((((key56[1] & 0x03) << 5) 
176             | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
177         key[3] = (byte) ((((key56[2] & 0x07) << 4) 
178             | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
179         key[4] = (byte) ((((key56[3] & 0x0f) << 3) 
180             | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
181         key[5] = (byte) ((((key56[4] & 0x1f) << 2) 
182             | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
183         key[6] = (byte) ((((key56[5] & 0x3f) << 1) 
184             | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
185         key[7] = (byte) (key56[6] & 0x7f);
186         
187         for (int i = 0; i < key.length; i++) {
188             key[i] = (byte) (key[i] << 1);
189         }
190         return key;
191     }
192 
193     /***
194      * Encrypt the data.
195      * @param key The key.
196      * @param bytes The data
197      * @return byte[] The encrypted data
198      * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
199      */
200     private byte[] encrypt(byte[] key, byte[] bytes)
201         throws HttpException {
202         Cipher ecipher = getCipher(key);
203         try {
204             byte[] enc = ecipher.doFinal(bytes);
205             return enc;
206         } catch (IllegalBlockSizeException e) {
207             throw new HttpException("Invalid block size for DES encryption.");
208         } catch (BadPaddingException e) {
209             throw new HttpException(
210                     "Data not padded correctly for DES encryption.");
211         }
212     }
213 
214     /*** 
215      * Prepares the object to create a response of the given length.
216      * @param length the length of the response to prepare.
217      */
218     private void prepareResponse(int length) {
219         currentResponse = new byte[length];
220         currentPosition = 0;
221     }
222 
223     /*** 
224      * Adds the given byte to the response.
225      * @param b the byte to add.
226      */
227     private void addByte(byte b) {
228         currentResponse[currentPosition] = b;
229         currentPosition++;
230     }
231 
232     /*** 
233      * Adds the given bytes to the response.
234      * @param bytes the bytes to add.
235      */
236     private void addBytes(byte[] bytes) {
237         for (int i = 0; i < bytes.length; i++) {
238             currentResponse[currentPosition] = bytes[i];
239             currentPosition++;
240         }
241     }
242 
243     /*** 
244      * Returns the response that has been generated after shrinking the array if
245      * required and base64 encodes the response.
246      * @return The response as above.
247      */
248     private String getResponse() {
249         byte[] resp;
250         if (currentResponse.length > currentPosition) {
251             byte[] tmp = new byte[currentPosition];
252             for (int i = 0; i < currentPosition; i++) {
253                 tmp[i] = currentResponse[i];
254             }
255             resp = tmp;
256         } else {
257             resp = currentResponse;
258         }
259         return HttpConstants.getString(Base64.encode(resp));
260     }
261     
262     /***
263      * Creates the first message (type 1 message) in the NTLM authentication sequence.
264      * This message includes the user name, domain and host for the authentication session.
265      * 
266      * @param host the computer name of the host requesting authentication.
267      * @param domain The domain to authenticate with.
268      * @return String the message to add to the HTTP request header.
269      */
270     private String getType1Message(String host, String domain) {
271         host = host.toUpperCase();
272         domain = domain.toUpperCase();
273         byte[] hostBytes = getBytes(host);
274         byte[] domainBytes = getBytes(domain);
275 
276         int finalLength = 32 + hostBytes.length + domainBytes.length;
277         prepareResponse(finalLength);
278         
279         // The initial id string.
280         byte[] protocol = getBytes("NTLMSSP");
281         addBytes(protocol);
282         addByte((byte) 0);
283 
284         // Type
285         addByte((byte) 1);
286         addByte((byte) 0);
287         addByte((byte) 0);
288         addByte((byte) 0);
289 
290         // Flags
291         addByte((byte) 6);
292         addByte((byte) 82);
293         addByte((byte) 0);
294         addByte((byte) 0);
295 
296         // Domain length (first time).
297         int iDomLen = domainBytes.length;
298         byte[] domLen = convertShort(iDomLen);
299         addByte(domLen[0]);
300         addByte(domLen[1]);
301 
302         // Domain length (second time).
303         addByte(domLen[0]);
304         addByte(domLen[1]);
305 
306         // Domain offset.
307         byte[] domOff = convertShort(hostBytes.length + 32);
308         addByte(domOff[0]);
309         addByte(domOff[1]);
310         addByte((byte) 0);
311         addByte((byte) 0);
312 
313         // Host length (first time).
314         byte[] hostLen = convertShort(hostBytes.length);
315         addByte(hostLen[0]);
316         addByte(hostLen[1]);
317 
318         // Host length (second time).
319         addByte(hostLen[0]);
320         addByte(hostLen[1]);
321 
322         // Host offset (always 32).
323         byte[] hostOff = convertShort(32);
324         addByte(hostOff[0]);
325         addByte(hostOff[1]);
326         addByte((byte) 0);
327         addByte((byte) 0);
328 
329         // Host String.
330         addBytes(hostBytes);
331 
332         // Domain String.
333         addBytes(domainBytes);
334 
335         return getResponse();
336     }
337 
338     /*** 
339      * Extracts the server nonce out of the given message type 2.
340      * 
341      * @param message the String containing the base64 encoded message.
342      * @return an array of 8 bytes that the server sent to be used when
343      * hashing the password.
344      */
345     private byte[] parseType2Message(String message) {
346         // Decode the message first.
347         byte[] msg = Base64.decode(getBytes(message));
348         byte[] nonce = new byte[8];
349         // The nonce is the 8 bytes starting from the byte in position 24.
350         for (int i = 0; i < 8; i++) {
351             nonce[i] = msg[i + 24];
352         }
353         return nonce;
354     }
355 
356     /*** 
357      * Creates the type 3 message using the given server nonce.  The type 3 message includes all the
358      * information for authentication, host, domain, username and the result of encrypting the
359      * nonce sent by the server using the user's password as the key.
360      * 
361      * @param user The user name.  This should not include the domain name.
362      * @param password The password.
363      * @param host The host that is originating the authentication request.
364      * @param domain The domain to authenticate within.
365      * @param nonce the 8 byte array the server sent.
366      * @return The type 3 message.
367      * @throws HttpException If {@encrypt(byte[],byte[])} fails.
368      */
369     private String getType3Message(String user, String password,
370             String host, String domain, byte[] nonce)
371     throws HttpException {
372 
373         int ntRespLen = 0;
374         int lmRespLen = 24;
375         domain = domain.toUpperCase();
376         host = host.toUpperCase();
377         user = user.toUpperCase();
378         byte[] domainBytes = getBytes(domain);
379         byte[] hostBytes = getBytes(host);
380         byte[] userBytes = getBytes(user);
381         int domainLen = domainBytes.length;
382         int hostLen = hostBytes.length;
383         int userLen = userBytes.length;
384         int finalLength = 64 + ntRespLen + lmRespLen + domainLen 
385             + userLen + hostLen;
386         prepareResponse(finalLength);
387         byte[] ntlmssp = getBytes("NTLMSSP");
388         addBytes(ntlmssp);
389         addByte((byte) 0);
390         addByte((byte) 3);
391         addByte((byte) 0);
392         addByte((byte) 0);
393         addByte((byte) 0);
394 
395         // LM Resp Length (twice)
396         addBytes(convertShort(24));
397         addBytes(convertShort(24));
398 
399         // LM Resp Offset
400         addBytes(convertShort(finalLength - 24));
401         addByte((byte) 0);
402         addByte((byte) 0);
403 
404         // NT Resp Length (twice)
405         addBytes(convertShort(0));
406         addBytes(convertShort(0));
407 
408         // NT Resp Offset
409         addBytes(convertShort(finalLength));
410         addByte((byte) 0);
411         addByte((byte) 0);
412 
413         // Domain length (twice)
414         addBytes(convertShort(domainLen));
415         addBytes(convertShort(domainLen));
416         
417         // Domain offset.
418         addBytes(convertShort(64));
419         addByte((byte) 0);
420         addByte((byte) 0);
421 
422         // User Length (twice)
423         addBytes(convertShort(userLen));
424         addBytes(convertShort(userLen));
425 
426         // User offset
427         addBytes(convertShort(64 + domainLen));
428         addByte((byte) 0);
429         addByte((byte) 0);
430 
431         // Host length (twice)
432         addBytes(convertShort(hostLen));
433         addBytes(convertShort(hostLen));
434 
435         // Host offset
436         addBytes(convertShort(64 + domainLen + userLen));
437 
438         for (int i = 0; i < 6; i++) {
439             addByte((byte) 0);
440         }
441 
442         // Message length
443         addBytes(convertShort(finalLength));
444         addByte((byte) 0);
445         addByte((byte) 0);
446 
447         // Flags
448         addByte((byte) 6);
449         addByte((byte) 82);
450         addByte((byte) 0);
451         addByte((byte) 0);
452 
453         addBytes(domainBytes);
454         addBytes(userBytes);
455         addBytes(hostBytes);
456         addBytes(hashPassword(password, nonce));
457         return getResponse();
458     }
459 
460     /*** 
461      * Creates the LANManager and NT response for the given password using the
462      * given nonce.
463      * @param password the password to create a hash for.
464      * @param nonce the nonce sent by the server.
465      * @return The response.
466      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
467      */
468     private byte[] hashPassword(String password, byte[] nonce)
469         throws HttpException {
470         byte[] passw = getBytes(password.toUpperCase());
471         byte[] lmPw1 = new byte[7];
472         byte[] lmPw2 = new byte[7];
473 
474         int len = passw.length;
475         if (len > 7) {
476             len = 7;
477         }
478 
479         int idx;
480         for (idx = 0; idx < len; idx++) {
481             lmPw1[idx] = passw[idx];
482         }
483         for (; idx < 7; idx++) {
484             lmPw1[idx] = (byte) 0;
485         }
486 
487         len = passw.length;
488         if (len > 14) {
489             len = 14;
490         }
491         for (idx = 7; idx < len; idx++) {
492             lmPw2[idx - 7] = passw[idx];
493         }
494         for (; idx < 14; idx++) {
495             lmPw2[idx - 7] = (byte) 0;
496         }
497 
498         // Create LanManager hashed Password
499         byte[] magic = {
500             (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, 
501             (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
502         };
503 
504         byte[] lmHpw1;
505         lmHpw1 = encrypt(lmPw1, magic);
506 
507         byte[] lmHpw2 = encrypt(lmPw2, magic);
508 
509         byte[] lmHpw = new byte[21];
510         for (int i = 0; i < lmHpw1.length; i++) {
511             lmHpw[i] = lmHpw1[i];
512         }
513         for (int i = 0; i < lmHpw2.length; i++) {
514             lmHpw[i + 8] = lmHpw2[i];
515         }
516         for (int i = 0; i < 5; i++) {
517             lmHpw[i + 16] = (byte) 0;
518         }
519 
520         // Create the responses.
521         byte[] lmResp = new byte[24];
522         calcResp(lmHpw, nonce, lmResp);
523 
524         return lmResp;
525     }
526 
527     /*** 
528      * Takes a 21 byte array and treats it as 3 56-bit DES keys.  The 8 byte
529      * plaintext is encrypted with each key and the resulting 24 bytes are
530      * stored in the results array.
531      * 
532      * @param keys The keys.
533      * @param plaintext The plain text to encrypt.
534      * @param results Where the results are stored.
535      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
536      */
537     private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
538         throws HttpException {
539         byte[] keys1 = new byte[7];
540         byte[] keys2 = new byte[7];
541         byte[] keys3 = new byte[7];
542         for (int i = 0; i < 7; i++) {
543             keys1[i] = keys[i];
544         }
545 
546         for (int i = 0; i < 7; i++) {
547             keys2[i] = keys[i + 7];
548         }
549 
550         for (int i = 0; i < 7; i++) {
551             keys3[i] = keys[i + 14];
552         }
553         byte[] results1 = encrypt(keys1, plaintext);
554 
555         byte[] results2 = encrypt(keys2, plaintext);
556 
557         byte[] results3 = encrypt(keys3, plaintext);
558 
559         for (int i = 0; i < 8; i++) {
560             results[i] = results1[i];
561         }
562         for (int i = 0; i < 8; i++) {
563             results[i + 8] = results2[i];
564         }
565         for (int i = 0; i < 8; i++) {
566             results[i + 16] = results3[i];
567         }
568     }
569 
570     /*** 
571      * Converts a given number to a two byte array in little endian order.
572      * @param num the number to convert.
573      * @return The byte representation of <i>num</i> in little endian order.
574      */
575     private byte[] convertShort(int num) {
576         byte[] val = new byte[2];
577         String hex = Integer.toString(num, 16);
578         while (hex.length() < 4) {
579             hex = "0" + hex;
580         }
581         String low = hex.substring(2, 4);
582         String high = hex.substring(0, 2);
583 
584         val[0] = (byte) Integer.parseInt(low, 16);
585         val[1] = (byte) Integer.parseInt(high, 16);
586         return val;
587     }
588     
589     /***
590      * Convert a string to a byte array.
591      * @param s The string
592      * @return byte[] The resulting byte array.
593      */
594     private static byte[] getBytes(final String s) {
595         if (s == null) {
596             throw new IllegalArgumentException("Parameter may not be null"); 
597         }
598         try {
599             return s.getBytes(DEFAULT_CHARSET); 
600         } catch (UnsupportedEncodingException unexpectedEncodingException) {
601             throw new RuntimeException("NTLM requires ASCII support"); 
602         }
603     }
604 }
This page was automatically generated by Maven