1 /*
2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/NTLM.java,v 1.12 2003/02/11 03:41:14 jsdever Exp $
3 * $Revision: 1.12 $
4 * $Date: 2003/02/11 03:41:14 $
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 import java.security.Security;
70
71 import javax.crypto.BadPaddingException;
72 import javax.crypto.Cipher;
73 import javax.crypto.IllegalBlockSizeException;
74 import javax.crypto.NoSuchPaddingException;
75 import javax.crypto.spec.SecretKeySpec;
76
77 import org.apache.commons.httpclient.util.Base64;
78 import org.apache.commons.logging.Log;
79 import org.apache.commons.logging.LogFactory;
80
81 /***
82 * Provides an implementation of the NTLM authentication protocol.
83 * <p>
84 * This class provides methods for generating authentication
85 * challenge responses for the NTLM authentication protocol. The NTLM
86 * protocol is a proprietary Microsoft protocol and as such no RFC
87 * exists for it. This class is based upon the reverse engineering
88 * efforts of a wide range of people.</p>
89 *
90 * @deprecated this class will be made package access for 2.0beta2
91 *
92 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
93 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
94 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
95 *
96 * @version $Revision: 1.12 $ $Date: 2003/02/11 03:41:14 $
97 * @since 2.0alpha2
98 */
99 public final class NTLM {
100
101 /*** The current response */
102 private byte[] currentResponse;
103
104 /*** The current position */
105 private int currentPosition = 0;
106
107 /*** Log object for this class. */
108 private static final Log LOG = LogFactory.getLog(NTLM.class);
109
110 /*** Character encoding */
111 public static final String DEFAULT_CHARSET = "ASCII";
112
113 //Initialize the security provider
114 static {
115 //TODO: do not use System properties
116 final String secProviderName
117 = System.getProperty("httpclient.security.provider",
118 "com.sun.crypto.provider.SunJCE");
119 try {
120 java.security.Provider secProvider = (java.security.Provider)
121 Class.forName(secProviderName).newInstance();
122 Security.addProvider(secProvider);
123 } catch (ClassNotFoundException e) {
124 LOG.error("Specified security provider " + secProviderName
125 + " could not be found by the class loader", e);
126 } catch (ClassCastException e) {
127 LOG.error("Specified security provider " + secProviderName
128 + " is not of type java.security.Provider", e);
129 } catch (InstantiationException e) {
130 LOG.error("Specified security provider " + secProviderName
131 + " could not be instantiated", e);
132 } catch (IllegalAccessException e) {
133 LOG.error("Specified security provider " + secProviderName
134 + " does not allow access to the constructor", e);
135 }
136 }
137
138 /***
139 * Returns the response for the given message.
140 *
141 * @param message the message that was received from the server.
142 * @param username the username to authenticate with.
143 * @param password the password to authenticate with.
144 * @param host The host.
145 * @param domain the NT domain to authenticate in.
146 * @return The response.
147 * @throws HttpException If the messages cannot be retrieved.
148 */
149 public final String getResponseFor(String message,
150 String username, String password, String host, String domain)
151 throws HttpException {
152
153 final String response;
154 if (message == null || message.trim().equals("")) {
155 response = getType1Message(host, domain);
156 } else {
157 response = getType3Message(username, password, host, domain,
158 parseType2Message(message));
159 }
160 return response;
161 }
162
163 /***
164 * Return the cipher for the specified key.
165 * @param key The key.
166 * @return Cipher The cipher.
167 * @throws HttpException If the cipher cannot be retrieved.
168 */
169 private Cipher getCipher(byte[] key) throws HttpException {
170 try {
171 final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
172 key = setupKey(key);
173 ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
174 return ecipher;
175 } catch (NoSuchAlgorithmException e) {
176 throw new HttpException("DES encryption is not available.");
177 } catch (InvalidKeyException e) {
178 throw new HttpException("Invalid key for DES encryption.");
179 } catch (NoSuchPaddingException e) {
180 throw new HttpException(
181 "NoPadding option for DES is not available.");
182 }
183 }
184
185 /***
186 * Adds parity bits to the key.
187 * @param key56 The key
188 * @return The modified key.
189 */
190 private byte[] setupKey(byte[] key56) {
191 byte[] key = new byte[8];
192 key[0] = (byte) ((key56[0] >> 1) & 0xff);
193 key[1] = (byte) ((((key56[0] & 0x01) << 6)
194 | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
195 key[2] = (byte) ((((key56[1] & 0x03) << 5)
196 | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
197 key[3] = (byte) ((((key56[2] & 0x07) << 4)
198 | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
199 key[4] = (byte) ((((key56[3] & 0x0f) << 3)
200 | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
201 key[5] = (byte) ((((key56[4] & 0x1f) << 2)
202 | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
203 key[6] = (byte) ((((key56[5] & 0x3f) << 1)
204 | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
205 key[7] = (byte) (key56[6] & 0x7f);
206
207 for (int i = 0; i < key.length; i++) {
208 key[i] = (byte) (key[i] << 1);
209 }
210 return key;
211 }
212
213 /***
214 * Encrypt the data.
215 * @param key The key.
216 * @param bytes The data
217 * @return byte[] The encrypted data
218 * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
219 */
220 private byte[] encrypt(byte[] key, byte[] bytes)
221 throws HttpException {
222 Cipher ecipher = getCipher(key);
223 try {
224 byte[] enc = ecipher.doFinal(bytes);
225 return enc;
226 } catch (IllegalBlockSizeException e) {
227 throw new HttpException("Invalid block size for DES encryption.");
228 } catch (BadPaddingException e) {
229 throw new HttpException(
230 "Data not padded correctly for DES encryption.");
231 }
232 }
233
234 /***
235 * Prepares the object to create a response of the given length.
236 * @param length the length of the response to prepare.
237 */
238 private void prepareResponse(int length) {
239 currentResponse = new byte[length];
240 currentPosition = 0;
241 }
242
243 /***
244 * Adds the given byte to the response.
245 * @param b the byte to add.
246 */
247 private void addByte(byte b) {
248 currentResponse[currentPosition] = b;
249 currentPosition++;
250 }
251
252 /***
253 * Adds the given bytes to the response.
254 * @param bytes the bytes to add.
255 */
256 private void addBytes(byte[] bytes) {
257 for (int i = 0; i < bytes.length; i++) {
258 currentResponse[currentPosition] = bytes[i];
259 currentPosition++;
260 }
261 }
262
263 /***
264 * Returns the response that has been generated after shrinking the array if
265 * required and base64 encodes the response.
266 * @return The response as above.
267 */
268 private String getResponse() {
269 byte[] resp;
270 if (currentResponse.length > currentPosition) {
271 byte[] tmp = new byte[currentPosition];
272 for (int i = 0; i < currentPosition; i++) {
273 tmp[i] = currentResponse[i];
274 }
275 resp = tmp;
276 } else {
277 resp = currentResponse;
278 }
279 return HttpConstants.getString(Base64.encode(resp));
280 }
281
282 /***
283 * TODO: Figure out what this method really does.
284 * @param host The host
285 * @param domain The domain
286 * @return String
287 */
288 private String getType1Message(String host, String domain) {
289 host = host.toUpperCase();
290 domain = domain.toUpperCase();
291 byte[] hostBytes = getBytes(host);
292 byte[] domainBytes = getBytes(domain);
293
294 int finalLength = 32 + hostBytes.length + domainBytes.length;
295 prepareResponse(finalLength);
296
297 // The initial id string.
298 byte[] protocol = getBytes("NTLMSSP");
299 addBytes(protocol);
300 addByte((byte) 0);
301
302 // Type
303 addByte((byte) 1);
304 addByte((byte) 0);
305 addByte((byte) 0);
306 addByte((byte) 0);
307
308 // Flags
309 addByte((byte) 6);
310 addByte((byte) 82);
311 addByte((byte) 0);
312 addByte((byte) 0);
313
314 // Domain length (first time).
315 int iDomLen = domainBytes.length;
316 byte[] domLen = convertShort(iDomLen);
317 addByte(domLen[0]);
318 addByte(domLen[1]);
319
320 // Domain length (second time).
321 addByte(domLen[0]);
322 addByte(domLen[1]);
323
324 // Domain offset.
325 byte[] domOff = convertShort(hostBytes.length + 32);
326 addByte(domOff[0]);
327 addByte(domOff[1]);
328 addByte((byte) 0);
329 addByte((byte) 0);
330
331 // Host length (first time).
332 byte[] hostLen = convertShort(hostBytes.length);
333 addByte(hostLen[0]);
334 addByte(hostLen[1]);
335
336 // Host length (second time).
337 addByte(hostLen[0]);
338 addByte(hostLen[1]);
339
340 // Host offset (always 32).
341 byte[] hostOff = convertShort(32);
342 addByte(hostOff[0]);
343 addByte(hostOff[1]);
344 addByte((byte) 0);
345 addByte((byte) 0);
346
347 // Host String.
348 addBytes(hostBytes);
349
350 // Domain String.
351 addBytes(domainBytes);
352
353 return getResponse();
354 }
355
356 /***
357 * Extracts the server nonce out of the given message type 2.
358 *
359 * @param message the String containing the base64 encoded message.
360 * @return an array of 8 bytes that the server sent to be used when
361 * hashing the password.
362 */
363 private byte[] parseType2Message(String message) {
364 // Decode the message first.
365 byte[] msg = Base64.decode(getBytes(message));
366 byte[] nonce = new byte[8];
367 // The nonce is the 8 bytes starting from the byte in position 24.
368 for (int i = 0; i < 8; i++) {
369 nonce[i] = msg[i + 24];
370 }
371 return nonce;
372 }
373
374 /***
375 * Creates the type 3 message using the given server nonce.
376 * @param user The user.
377 * @param password The password.
378 * @param host The host.
379 * @param domain The domain.
380 * @param nonce the 8 byte array the server sent.
381 * @return The type 3 message.
382 * @throws HttpException If {@encrypt(byte[],byte[])} fails.
383 */
384 private String getType3Message(String user, String password,
385 String host, String domain, byte[] nonce)
386 throws HttpException {
387
388 int ntRespLen = 0;
389 int lmRespLen = 24;
390 domain = domain.toUpperCase();
391 host = host.toUpperCase();
392 user = user.toUpperCase();
393 byte[] domainBytes = getBytes(domain);
394 byte[] hostBytes = getBytes(host);
395 byte[] userBytes = getBytes(user);
396 int domainLen = domainBytes.length;
397 int hostLen = hostBytes.length;
398 int userLen = userBytes.length;
399 int finalLength = 64 + ntRespLen + lmRespLen + domainLen
400 + userLen + hostLen;
401 prepareResponse(finalLength);
402 byte[] ntlmssp = getBytes("NTLMSSP");
403 addBytes(ntlmssp);
404 addByte((byte) 0);
405 addByte((byte) 3);
406 addByte((byte) 0);
407 addByte((byte) 0);
408 addByte((byte) 0);
409
410 // LM Resp Length (twice)
411 addBytes(convertShort(24));
412 addBytes(convertShort(24));
413
414 // LM Resp Offset
415 addBytes(convertShort(finalLength - 24));
416 addByte((byte) 0);
417 addByte((byte) 0);
418
419 // NT Resp Length (twice)
420 addBytes(convertShort(0));
421 addBytes(convertShort(0));
422
423 // NT Resp Offset
424 addBytes(convertShort(finalLength));
425 addByte((byte) 0);
426 addByte((byte) 0);
427
428 // Domain length (twice)
429 addBytes(convertShort(domainLen));
430 addBytes(convertShort(domainLen));
431
432 // Domain offset.
433 addBytes(convertShort(64));
434 addByte((byte) 0);
435 addByte((byte) 0);
436
437 // User Length (twice)
438 addBytes(convertShort(userLen));
439 addBytes(convertShort(userLen));
440
441 // User offset
442 addBytes(convertShort(64 + domainLen));
443 addByte((byte) 0);
444 addByte((byte) 0);
445
446 // Host length (twice)
447 addBytes(convertShort(hostLen));
448 addBytes(convertShort(hostLen));
449
450 // Host offset
451 addBytes(convertShort(64 + domainLen + userLen));
452
453 for (int i = 0; i < 6; i++) {
454 addByte((byte) 0);
455 }
456
457 // Message length
458 addBytes(convertShort(finalLength));
459 addByte((byte) 0);
460 addByte((byte) 0);
461
462 // Flags
463 addByte((byte) 6);
464 addByte((byte) 82);
465 addByte((byte) 0);
466 addByte((byte) 0);
467
468 addBytes(domainBytes);
469 addBytes(userBytes);
470 addBytes(hostBytes);
471 addBytes(hashPassword(password, nonce));
472 return getResponse();
473 }
474
475 /***
476 * Creates the LANManager and NT response for the given password using the
477 * given nonce.
478 * @param password the password to create a hash for.
479 * @param nonce the nonce sent by the server.
480 * @return The response.
481 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
482 */
483 private byte[] hashPassword(String password, byte[] nonce)
484 throws HttpException {
485 byte[] passw = getBytes(password.toUpperCase());
486 byte[] lmPw1 = new byte[7];
487 byte[] lmPw2 = new byte[7];
488
489 int len = passw.length;
490 if (len > 7) {
491 len = 7;
492 }
493
494 int idx;
495 for (idx = 0; idx < len; idx++) {
496 lmPw1[idx] = passw[idx];
497 }
498 for (; idx < 7; idx++) {
499 lmPw1[idx] = (byte) 0;
500 }
501
502 len = passw.length;
503 if (len > 14) {
504 len = 14;
505 }
506 for (idx = 7; idx < len; idx++) {
507 lmPw2[idx - 7] = passw[idx];
508 }
509 for (; idx < 14; idx++) {
510 lmPw2[idx - 7] = (byte) 0;
511 }
512
513 // Create LanManager hashed Password
514 byte[] magic = {
515 (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21,
516 (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
517 };
518
519 byte[] lmHpw1;
520 lmHpw1 = encrypt(lmPw1, magic);
521
522 byte[] lmHpw2 = encrypt(lmPw2, magic);
523
524 byte[] lmHpw = new byte[21];
525 for (int i = 0; i < lmHpw1.length; i++) {
526 lmHpw[i] = lmHpw1[i];
527 }
528 for (int i = 0; i < lmHpw2.length; i++) {
529 lmHpw[i + 8] = lmHpw2[i];
530 }
531 for (int i = 0; i < 5; i++) {
532 lmHpw[i + 16] = (byte) 0;
533 }
534
535 // Create the responses.
536 byte[] lmResp = new byte[24];
537 calcResp(lmHpw, nonce, lmResp);
538
539 return lmResp;
540 }
541
542 /***
543 * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte
544 * plaintext is encrypted with each key and the resulting 24 bytes are
545 * stored in the results array.
546 *
547 * @param keys The keys.
548 * @param plaintext The plain text to encrypt.
549 * @param results Where the results are stored.
550 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
551 */
552 private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
553 throws HttpException {
554 byte[] keys1 = new byte[7];
555 byte[] keys2 = new byte[7];
556 byte[] keys3 = new byte[7];
557 for (int i = 0; i < 7; i++) {
558 keys1[i] = keys[i];
559 }
560
561 for (int i = 0; i < 7; i++) {
562 keys2[i] = keys[i + 7];
563 }
564
565 for (int i = 0; i < 7; i++) {
566 keys3[i] = keys[i + 14];
567 }
568 byte[] results1 = encrypt(keys1, plaintext);
569
570 byte[] results2 = encrypt(keys2, plaintext);
571
572 byte[] results3 = encrypt(keys3, plaintext);
573
574 for (int i = 0; i < 8; i++) {
575 results[i] = results1[i];
576 }
577 for (int i = 0; i < 8; i++) {
578 results[i + 8] = results2[i];
579 }
580 for (int i = 0; i < 8; i++) {
581 results[i + 16] = results3[i];
582 }
583 }
584
585 /***
586 * Converts a given number to a two byte array in little endian order.
587 * @param num the number to convert.
588 * @return The new array.
589 */
590 private byte[] convertShort(int num) {
591 byte[] val = new byte[2];
592 String hex = Integer.toString(num, 16);
593 while (hex.length() < 4) {
594 hex = "0" + hex;
595 }
596 String low = hex.substring(2, 4);
597 String high = hex.substring(0, 2);
598
599 val[0] = (byte) Integer.parseInt(low, 16);
600 val[1] = (byte) Integer.parseInt(high, 16);
601 return val;
602 }
603
604 /***
605 * Convert a string to a byte array.
606 * @param s The string
607 * @return byte[] The resulting byte array.
608 */
609 private static byte[] getBytes(final String s) {
610 if (s == null) {
611 throw new IllegalArgumentException("Parameter may not be null");
612 }
613 try {
614 return s.getBytes(DEFAULT_CHARSET);
615 } catch (UnsupportedEncodingException unexpectedEncodingException) {
616 throw new RuntimeException("NTLM requires ASCII support");
617 }
618 }
619 }
This page was automatically generated by Maven