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