View Javadoc

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;
35  import java.security.InvalidKeyException;
36  import java.security.NoSuchAlgorithmException;
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 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 getResponseFor(String message,
98              String username, String password, String host, String domain)
99              throws HttpException {
100                 
101         final String 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 e) {
124             throw new HttpException("DES encryption is not available.");
125         } catch (InvalidKeyException 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 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 getType1Message(String host, String 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 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 getType3Message(String user, String password,
338             String host, String 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 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 hex = Integer.toString(num, 16);
546         while (hex.length() < 4) {
547             hex = "0" + hex;
548         }
549         String low = hex.substring(2, 4);
550         String 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 s) {
563         if (s == null) {
564             throw new IllegalArgumentException("Parameter may not be null"); 
565         }
566         try {
567             return s.getBytes(DEFAULT_CHARSET); 
568         } catch (UnsupportedEncodingException unexpectedEncodingException) {
569             throw new RuntimeException("NTLM requires ASCII support"); 
570         }
571     }
572 }