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