View Javadoc

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: 155418 $
4    * $Date: 2005-02-26 08:01:52 -0500 (Sat, 26 Feb 2005) $
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   */
29  
30  package org.apache.commons.httpclient.auth;
31  
32  import java.security.InvalidKeyException;
33  import java.security.NoSuchAlgorithmException;
34  
35  import javax.crypto.BadPaddingException;
36  import javax.crypto.Cipher;
37  import javax.crypto.IllegalBlockSizeException;
38  import javax.crypto.NoSuchPaddingException;
39  import javax.crypto.spec.SecretKeySpec;
40  
41  import org.apache.commons.codec.binary.Base64;
42  import org.apache.commons.httpclient.util.EncodingUtil;
43  
44  /***
45   * Provides an implementation of the NTLM authentication protocol.
46   * <p>
47   * This class provides methods for generating authentication
48   * challenge responses for the NTLM authentication protocol.  The NTLM
49   * protocol is a proprietary Microsoft protocol and as such no RFC
50   * exists for it.  This class is based upon the reverse engineering
51   * efforts of a wide range of people.</p>
52   *
53   * <p>Please note that an implementation of JCE must be correctly installed and configured when
54   * using NTLM support.</p>
55   *
56   * <p>This class should not be used externally to HttpClient as it's API is specifically
57   * designed to work with HttpClient's use case, in particular it's connection management.</p>
58   *
59   * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
60   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
61   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
62   *
63   * @version $Revision: 155418 $ $Date: 2005-02-26 08:01:52 -0500 (Sat, 26 Feb 2005) $
64   * @since 3.0
65   */
66  final class NTLM {
67  
68      /*** Character encoding */
69      public static final String DEFAULT_CHARSET = "ASCII";
70  
71      /*** The current response */
72      private byte[] currentResponse;
73  
74      /*** The current position */
75      private int currentPosition = 0;
76  
77      /*** The character set to use for encoding the credentials */
78      private String credentialCharset = DEFAULT_CHARSET;
79      
80      /***
81       * Returns the response for the given message.
82       *
83       * @param message the message that was received from the server.
84       * @param username the username to authenticate with.
85       * @param password the password to authenticate with.
86       * @param host The host.
87       * @param domain the NT domain to authenticate in.
88       * @return The response.
89       * @throws HttpException If the messages cannot be retrieved.
90       */
91      public final String getResponseFor(String message,
92              String username, String password, String host, String domain)
93              throws AuthenticationException {
94                  
95          final String response;
96          if (message == null || message.trim().equals("")) {
97              response = getType1Message(host, domain);
98          } else {
99              response = getType3Message(username, password, host, domain,
100                     parseType2Message(message));
101         }
102         return response;
103     }
104 
105     /***
106      * Return the cipher for the specified key.
107      * @param key The key.
108      * @return Cipher The cipher.
109      * @throws AuthenticationException If the cipher cannot be retrieved.
110      */
111     private Cipher getCipher(byte[] key) throws AuthenticationException {
112         try {
113             final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
114             key = setupKey(key);
115             ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
116             return ecipher;
117         } catch (NoSuchAlgorithmException e) {
118             throw new AuthenticationException("DES encryption is not available.", e);
119         } catch (InvalidKeyException e) {
120             throw new AuthenticationException("Invalid key for DES encryption.", e);
121         } catch (NoSuchPaddingException e) {
122             throw new AuthenticationException(
123                 "NoPadding option for DES is not available.", e);
124         }
125     }
126 
127     /*** 
128      * Adds parity bits to the key.
129      * @param key56 The key
130      * @return The modified key.
131      */
132     private byte[] setupKey(byte[] key56) {
133         byte[] key = new byte[8];
134         key[0] = (byte) ((key56[0] >> 1) & 0xff);
135         key[1] = (byte) ((((key56[0] & 0x01) << 6) 
136             | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
137         key[2] = (byte) ((((key56[1] & 0x03) << 5) 
138             | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
139         key[3] = (byte) ((((key56[2] & 0x07) << 4) 
140             | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
141         key[4] = (byte) ((((key56[3] & 0x0f) << 3) 
142             | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
143         key[5] = (byte) ((((key56[4] & 0x1f) << 2) 
144             | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
145         key[6] = (byte) ((((key56[5] & 0x3f) << 1) 
146             | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
147         key[7] = (byte) (key56[6] & 0x7f);
148         
149         for (int i = 0; i < key.length; i++) {
150             key[i] = (byte) (key[i] << 1);
151         }
152         return key;
153     }
154 
155     /***
156      * Encrypt the data.
157      * @param key The key.
158      * @param bytes The data
159      * @return byte[] The encrypted data
160      * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
161      */
162     private byte[] encrypt(byte[] key, byte[] bytes)
163         throws AuthenticationException {
164         Cipher ecipher = getCipher(key);
165         try {
166             byte[] enc = ecipher.doFinal(bytes);
167             return enc;
168         } catch (IllegalBlockSizeException e) {
169             throw new AuthenticationException("Invalid block size for DES encryption.", e);
170         } catch (BadPaddingException e) {
171             throw new AuthenticationException("Data not padded correctly for DES encryption.", e);
172         }
173     }
174 
175     /*** 
176      * Prepares the object to create a response of the given length.
177      * @param length the length of the response to prepare.
178      */
179     private void prepareResponse(int length) {
180         currentResponse = new byte[length];
181         currentPosition = 0;
182     }
183 
184     /*** 
185      * Adds the given byte to the response.
186      * @param b the byte to add.
187      */
188     private void addByte(byte b) {
189         currentResponse[currentPosition] = b;
190         currentPosition++;
191     }
192 
193     /*** 
194      * Adds the given bytes to the response.
195      * @param bytes the bytes to add.
196      */
197     private void addBytes(byte[] bytes) {
198         for (int i = 0; i < bytes.length; i++) {
199             currentResponse[currentPosition] = bytes[i];
200             currentPosition++;
201         }
202     }
203 
204     /*** 
205      * Returns the response that has been generated after shrinking the array if
206      * required and base64 encodes the response.
207      * @return The response as above.
208      */
209     private String getResponse() {
210         byte[] resp;
211         if (currentResponse.length > currentPosition) {
212             byte[] tmp = new byte[currentPosition];
213             for (int i = 0; i < currentPosition; i++) {
214                 tmp[i] = currentResponse[i];
215             }
216             resp = tmp;
217         } else {
218             resp = currentResponse;
219         }
220         return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
221     }
222     
223     /***
224      * Creates the first message (type 1 message) in the NTLM authentication sequence.
225      * This message includes the user name, domain and host for the authentication session.
226      *
227      * @param host the computer name of the host requesting authentication.
228      * @param domain The domain to authenticate with.
229      * @return String the message to add to the HTTP request header.
230      */
231     public String getType1Message(String host, String domain) {
232         host = host.toUpperCase();
233         domain = domain.toUpperCase();
234         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
235         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
236 
237         int finalLength = 32 + hostBytes.length + domainBytes.length;
238         prepareResponse(finalLength);
239         
240         // The initial id string.
241         byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
242         addBytes(protocol);
243         addByte((byte) 0);
244 
245         // Type
246         addByte((byte) 1);
247         addByte((byte) 0);
248         addByte((byte) 0);
249         addByte((byte) 0);
250 
251         // Flags
252         addByte((byte) 6);
253         addByte((byte) 82);
254         addByte((byte) 0);
255         addByte((byte) 0);
256 
257         // Domain length (first time).
258         int iDomLen = domainBytes.length;
259         byte[] domLen = convertShort(iDomLen);
260         addByte(domLen[0]);
261         addByte(domLen[1]);
262 
263         // Domain length (second time).
264         addByte(domLen[0]);
265         addByte(domLen[1]);
266 
267         // Domain offset.
268         byte[] domOff = convertShort(hostBytes.length + 32);
269         addByte(domOff[0]);
270         addByte(domOff[1]);
271         addByte((byte) 0);
272         addByte((byte) 0);
273 
274         // Host length (first time).
275         byte[] hostLen = convertShort(hostBytes.length);
276         addByte(hostLen[0]);
277         addByte(hostLen[1]);
278 
279         // Host length (second time).
280         addByte(hostLen[0]);
281         addByte(hostLen[1]);
282 
283         // Host offset (always 32).
284         byte[] hostOff = convertShort(32);
285         addByte(hostOff[0]);
286         addByte(hostOff[1]);
287         addByte((byte) 0);
288         addByte((byte) 0);
289 
290         // Host String.
291         addBytes(hostBytes);
292 
293         // Domain String.
294         addBytes(domainBytes);
295 
296         return getResponse();
297     }
298 
299     /*** 
300      * Extracts the server nonce out of the given message type 2.
301      * 
302      * @param message the String containing the base64 encoded message.
303      * @return an array of 8 bytes that the server sent to be used when
304      * hashing the password.
305      */
306     public byte[] parseType2Message(String message) {
307         // Decode the message first.
308         byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET));
309         byte[] nonce = new byte[8];
310         // The nonce is the 8 bytes starting from the byte in position 24.
311         for (int i = 0; i < 8; i++) {
312             nonce[i] = msg[i + 24];
313         }
314         return nonce;
315     }
316 
317     /*** 
318      * Creates the type 3 message using the given server nonce.  The type 3 message includes all the
319      * information for authentication, host, domain, username and the result of encrypting the
320      * nonce sent by the server using the user's password as the key.
321      *
322      * @param user The user name.  This should not include the domain name.
323      * @param password The password.
324      * @param host The host that is originating the authentication request.
325      * @param domain The domain to authenticate within.
326      * @param nonce the 8 byte array the server sent.
327      * @return The type 3 message.
328      * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
329      */
330     public String getType3Message(String user, String password,
331             String host, String domain, byte[] nonce)
332     throws AuthenticationException {
333 
334         int ntRespLen = 0;
335         int lmRespLen = 24;
336         domain = domain.toUpperCase();
337         host = host.toUpperCase();
338         user = user.toUpperCase();
339         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
340         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
341         byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset);
342         int domainLen = domainBytes.length;
343         int hostLen = hostBytes.length;
344         int userLen = userBytes.length;
345         int finalLength = 64 + ntRespLen + lmRespLen + domainLen 
346             + userLen + hostLen;
347         prepareResponse(finalLength);
348         byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
349         addBytes(ntlmssp);
350         addByte((byte) 0);
351         addByte((byte) 3);
352         addByte((byte) 0);
353         addByte((byte) 0);
354         addByte((byte) 0);
355 
356         // LM Resp Length (twice)
357         addBytes(convertShort(24));
358         addBytes(convertShort(24));
359 
360         // LM Resp Offset
361         addBytes(convertShort(finalLength - 24));
362         addByte((byte) 0);
363         addByte((byte) 0);
364 
365         // NT Resp Length (twice)
366         addBytes(convertShort(0));
367         addBytes(convertShort(0));
368 
369         // NT Resp Offset
370         addBytes(convertShort(finalLength));
371         addByte((byte) 0);
372         addByte((byte) 0);
373 
374         // Domain length (twice)
375         addBytes(convertShort(domainLen));
376         addBytes(convertShort(domainLen));
377         
378         // Domain offset.
379         addBytes(convertShort(64));
380         addByte((byte) 0);
381         addByte((byte) 0);
382 
383         // User Length (twice)
384         addBytes(convertShort(userLen));
385         addBytes(convertShort(userLen));
386 
387         // User offset
388         addBytes(convertShort(64 + domainLen));
389         addByte((byte) 0);
390         addByte((byte) 0);
391 
392         // Host length (twice)
393         addBytes(convertShort(hostLen));
394         addBytes(convertShort(hostLen));
395 
396         // Host offset
397         addBytes(convertShort(64 + domainLen + userLen));
398 
399         for (int i = 0; i < 6; i++) {
400             addByte((byte) 0);
401         }
402 
403         // Message length
404         addBytes(convertShort(finalLength));
405         addByte((byte) 0);
406         addByte((byte) 0);
407 
408         // Flags
409         addByte((byte) 6);
410         addByte((byte) 82);
411         addByte((byte) 0);
412         addByte((byte) 0);
413 
414         addBytes(domainBytes);
415         addBytes(userBytes);
416         addBytes(hostBytes);
417         addBytes(hashPassword(password, nonce));
418         return getResponse();
419     }
420 
421     /*** 
422      * Creates the LANManager and NT response for the given password using the
423      * given nonce.
424      * @param password the password to create a hash for.
425      * @param nonce the nonce sent by the server.
426      * @return The response.
427      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
428      */
429     private byte[] hashPassword(String password, byte[] nonce)
430         throws AuthenticationException {
431         byte[] passw = EncodingUtil.getBytes(password.toUpperCase(), credentialCharset);
432         byte[] lmPw1 = new byte[7];
433         byte[] lmPw2 = new byte[7];
434 
435         int len = passw.length;
436         if (len > 7) {
437             len = 7;
438         }
439 
440         int idx;
441         for (idx = 0; idx < len; idx++) {
442             lmPw1[idx] = passw[idx];
443         }
444         for (; idx < 7; idx++) {
445             lmPw1[idx] = (byte) 0;
446         }
447 
448         len = passw.length;
449         if (len > 14) {
450             len = 14;
451         }
452         for (idx = 7; idx < len; idx++) {
453             lmPw2[idx - 7] = passw[idx];
454         }
455         for (; idx < 14; idx++) {
456             lmPw2[idx - 7] = (byte) 0;
457         }
458 
459         // Create LanManager hashed Password
460         byte[] magic = {
461             (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, 
462             (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
463         };
464 
465         byte[] lmHpw1;
466         lmHpw1 = encrypt(lmPw1, magic);
467 
468         byte[] lmHpw2 = encrypt(lmPw2, magic);
469 
470         byte[] lmHpw = new byte[21];
471         for (int i = 0; i < lmHpw1.length; i++) {
472             lmHpw[i] = lmHpw1[i];
473         }
474         for (int i = 0; i < lmHpw2.length; i++) {
475             lmHpw[i + 8] = lmHpw2[i];
476         }
477         for (int i = 0; i < 5; i++) {
478             lmHpw[i + 16] = (byte) 0;
479         }
480 
481         // Create the responses.
482         byte[] lmResp = new byte[24];
483         calcResp(lmHpw, nonce, lmResp);
484 
485         return lmResp;
486     }
487 
488     /*** 
489      * Takes a 21 byte array and treats it as 3 56-bit DES keys.  The 8 byte
490      * plaintext is encrypted with each key and the resulting 24 bytes are
491      * stored in the results array.
492      * 
493      * @param keys The keys.
494      * @param plaintext The plain text to encrypt.
495      * @param results Where the results are stored.
496      * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
497      */
498     private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
499         throws AuthenticationException {
500         byte[] keys1 = new byte[7];
501         byte[] keys2 = new byte[7];
502         byte[] keys3 = new byte[7];
503         for (int i = 0; i < 7; i++) {
504             keys1[i] = keys[i];
505         }
506 
507         for (int i = 0; i < 7; i++) {
508             keys2[i] = keys[i + 7];
509         }
510 
511         for (int i = 0; i < 7; i++) {
512             keys3[i] = keys[i + 14];
513         }
514         byte[] results1 = encrypt(keys1, plaintext);
515 
516         byte[] results2 = encrypt(keys2, plaintext);
517 
518         byte[] results3 = encrypt(keys3, plaintext);
519 
520         for (int i = 0; i < 8; i++) {
521             results[i] = results1[i];
522         }
523         for (int i = 0; i < 8; i++) {
524             results[i + 8] = results2[i];
525         }
526         for (int i = 0; i < 8; i++) {
527             results[i + 16] = results3[i];
528         }
529     }
530 
531     /*** 
532      * Converts a given number to a two byte array in little endian order.
533      * @param num the number to convert.
534      * @return The byte representation of <i>num</i> in little endian order.
535      */
536     private byte[] convertShort(int num) {
537         byte[] val = new byte[2];
538         String hex = Integer.toString(num, 16);
539         while (hex.length() < 4) {
540             hex = "0" + hex;
541         }
542         String low = hex.substring(2, 4);
543         String high = hex.substring(0, 2);
544 
545         val[0] = (byte) Integer.parseInt(low, 16);
546         val[1] = (byte) Integer.parseInt(high, 16);
547         return val;
548     }
549     
550     /***
551      * @return Returns the credentialCharset.
552      */
553     public String getCredentialCharset() {
554         return credentialCharset;
555     }
556 
557     /***
558      * @param credentialCharset The credentialCharset to set.
559      */
560     public void setCredentialCharset(String credentialCharset) {
561         this.credentialCharset = credentialCharset;
562     }
563 
564 }