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