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