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