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