View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/auth/NTLM.java $
3    * $Revision$
4    * $Date$
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;
34  import java.security.NoSuchAlgorithmException;
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$ $Date$
65   * @since 3.0
66   */
67  final class NTLM {
68  
69      /*** Character encoding */
70      public static final String 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 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 getResponseFor(String message,
93              String username, String password, String host, String domain)
94              throws AuthenticationException {
95                  
96          final String 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 e) {
119             throw new AuthenticationException("DES encryption is not available.", e);
120         } catch (InvalidKeyException 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 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 getType1Message(String host, String 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 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 getType3Message(String user, String password,
332             String host, String 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 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 hex = Integer.toString(num, 16);
540         while (hex.length() < 4) {
541             hex = "0" + hex;
542         }
543         String low = hex.substring(2, 4);
544         String 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 getCredentialCharset() {
555         return credentialCharset;
556     }
557 
558     /***
559      * @param credentialCharset The credentialCharset to set.
560      */
561     public void setCredentialCharset(String credentialCharset) {
562         this.credentialCharset = credentialCharset;
563     }
564 
565 }