001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/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 $
003: * $Revision: 480424 $
004: * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient.auth;
032:
033: import java.security.InvalidKeyException;
034: import java.security.NoSuchAlgorithmException;
035:
036: import javax.crypto.BadPaddingException;
037: import javax.crypto.Cipher;
038: import javax.crypto.IllegalBlockSizeException;
039: import javax.crypto.NoSuchPaddingException;
040: import javax.crypto.spec.SecretKeySpec;
041:
042: import org.apache.commons.codec.binary.Base64;
043: import org.apache.commons.httpclient.util.EncodingUtil;
044:
045: /**
046: * Provides an implementation of the NTLM authentication protocol.
047: * <p>
048: * This class provides methods for generating authentication
049: * challenge responses for the NTLM authentication protocol. The NTLM
050: * protocol is a proprietary Microsoft protocol and as such no RFC
051: * exists for it. This class is based upon the reverse engineering
052: * efforts of a wide range of people.</p>
053: *
054: * <p>Please note that an implementation of JCE must be correctly installed and configured when
055: * using NTLM support.</p>
056: *
057: * <p>This class should not be used externally to HttpClient as it's API is specifically
058: * designed to work with HttpClient's use case, in particular it's connection management.</p>
059: *
060: * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
061: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
062: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
063: *
064: * @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
065: * @since 3.0
066: */
067: final class NTLM {
068:
069: /** Character encoding */
070: public static final String DEFAULT_CHARSET = "ASCII";
071:
072: /** The current response */
073: private byte[] currentResponse;
074:
075: /** The current position */
076: private int currentPosition = 0;
077:
078: /** The character set to use for encoding the credentials */
079: private String credentialCharset = DEFAULT_CHARSET;
080:
081: /**
082: * Returns the response for the given message.
083: *
084: * @param message the message that was received from the server.
085: * @param username the username to authenticate with.
086: * @param password the password to authenticate with.
087: * @param host The host.
088: * @param domain the NT domain to authenticate in.
089: * @return The response.
090: * @throws HttpException If the messages cannot be retrieved.
091: */
092: public final String getResponseFor(String message, String username,
093: String password, String host, String domain)
094: throws AuthenticationException {
095:
096: final String response;
097: if (message == null || message.trim().equals("")) {
098: response = getType1Message(host, domain);
099: } else {
100: response = getType3Message(username, password, host,
101: domain, 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
115: .getInstance("DES/ECB/NoPadding");
116: key = setupKey(key);
117: ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key,
118: "DES"));
119: return ecipher;
120: } catch (NoSuchAlgorithmException e) {
121: throw new AuthenticationException(
122: "DES encryption is not available.", e);
123: } catch (InvalidKeyException e) {
124: throw new AuthenticationException(
125: "Invalid key for DES encryption.", e);
126: } catch (NoSuchPaddingException e) {
127: throw new AuthenticationException(
128: "NoPadding option for DES is not available.", e);
129: }
130: }
131:
132: /**
133: * Adds parity bits to the key.
134: * @param key56 The key
135: * @return The modified key.
136: */
137: private byte[] setupKey(byte[] key56) {
138: byte[] key = new byte[8];
139: key[0] = (byte) ((key56[0] >> 1) & 0xff);
140: key[1] = (byte) ((((key56[0] & 0x01) << 6) | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
141: key[2] = (byte) ((((key56[1] & 0x03) << 5) | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
142: key[3] = (byte) ((((key56[2] & 0x07) << 4) | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
143: key[4] = (byte) ((((key56[3] & 0x0f) << 3) | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
144: key[5] = (byte) ((((key56[4] & 0x1f) << 2) | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
145: key[6] = (byte) ((((key56[5] & 0x3f) << 1) | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
146: key[7] = (byte) (key56[6] & 0x7f);
147:
148: for (int i = 0; i < key.length; i++) {
149: key[i] = (byte) (key[i] << 1);
150: }
151: return key;
152: }
153:
154: /**
155: * Encrypt the data.
156: * @param key The key.
157: * @param bytes The data
158: * @return byte[] The encrypted data
159: * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
160: */
161: private byte[] encrypt(byte[] key, byte[] bytes)
162: throws AuthenticationException {
163: Cipher ecipher = getCipher(key);
164: try {
165: byte[] enc = ecipher.doFinal(bytes);
166: return enc;
167: } catch (IllegalBlockSizeException e) {
168: throw new AuthenticationException(
169: "Invalid block size for DES encryption.", e);
170: } catch (BadPaddingException e) {
171: throw new AuthenticationException(
172: "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,
237: DEFAULT_CHARSET);
238:
239: int finalLength = 32 + hostBytes.length + domainBytes.length;
240: prepareResponse(finalLength);
241:
242: // The initial id string.
243: byte[] protocol = EncodingUtil.getBytes("NTLMSSP",
244: DEFAULT_CHARSET);
245: addBytes(protocol);
246: addByte((byte) 0);
247:
248: // Type
249: addByte((byte) 1);
250: addByte((byte) 0);
251: addByte((byte) 0);
252: addByte((byte) 0);
253:
254: // Flags
255: addByte((byte) 6);
256: addByte((byte) 82);
257: addByte((byte) 0);
258: addByte((byte) 0);
259:
260: // Domain length (first time).
261: int iDomLen = domainBytes.length;
262: byte[] domLen = convertShort(iDomLen);
263: addByte(domLen[0]);
264: addByte(domLen[1]);
265:
266: // Domain length (second time).
267: addByte(domLen[0]);
268: addByte(domLen[1]);
269:
270: // Domain offset.
271: byte[] domOff = convertShort(hostBytes.length + 32);
272: addByte(domOff[0]);
273: addByte(domOff[1]);
274: addByte((byte) 0);
275: addByte((byte) 0);
276:
277: // Host length (first time).
278: byte[] hostLen = convertShort(hostBytes.length);
279: addByte(hostLen[0]);
280: addByte(hostLen[1]);
281:
282: // Host length (second time).
283: addByte(hostLen[0]);
284: addByte(hostLen[1]);
285:
286: // Host offset (always 32).
287: byte[] hostOff = convertShort(32);
288: addByte(hostOff[0]);
289: addByte(hostOff[1]);
290: addByte((byte) 0);
291: addByte((byte) 0);
292:
293: // Host String.
294: addBytes(hostBytes);
295:
296: // Domain String.
297: addBytes(domainBytes);
298:
299: return getResponse();
300: }
301:
302: /**
303: * Extracts the server nonce out of the given message type 2.
304: *
305: * @param message the String containing the base64 encoded message.
306: * @return an array of 8 bytes that the server sent to be used when
307: * hashing the password.
308: */
309: public byte[] parseType2Message(String message) {
310: // Decode the message first.
311: byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message,
312: DEFAULT_CHARSET));
313: byte[] nonce = new byte[8];
314: // The nonce is the 8 bytes starting from the byte in position 24.
315: for (int i = 0; i < 8; i++) {
316: nonce[i] = msg[i + 24];
317: }
318: return nonce;
319: }
320:
321: /**
322: * Creates the type 3 message using the given server nonce. The type 3 message includes all the
323: * information for authentication, host, domain, username and the result of encrypting the
324: * nonce sent by the server using the user's password as the key.
325: *
326: * @param user The user name. This should not include the domain name.
327: * @param password The password.
328: * @param host The host that is originating the authentication request.
329: * @param domain The domain to authenticate within.
330: * @param nonce the 8 byte array the server sent.
331: * @return The type 3 message.
332: * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
333: */
334: public String getType3Message(String user, String password,
335: String host, String domain, byte[] nonce)
336: throws AuthenticationException {
337:
338: int ntRespLen = 0;
339: int lmRespLen = 24;
340: domain = domain.toUpperCase();
341: host = host.toUpperCase();
342: user = user.toUpperCase();
343: byte[] domainBytes = EncodingUtil.getBytes(domain,
344: DEFAULT_CHARSET);
345: byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
346: byte[] userBytes = EncodingUtil.getBytes(user,
347: credentialCharset);
348: int domainLen = domainBytes.length;
349: int hostLen = hostBytes.length;
350: int userLen = userBytes.length;
351: int finalLength = 64 + ntRespLen + lmRespLen + domainLen
352: + userLen + hostLen;
353: prepareResponse(finalLength);
354: byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP",
355: DEFAULT_CHARSET);
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: // LM Resp Length (twice)
364: addBytes(convertShort(24));
365: addBytes(convertShort(24));
366:
367: // LM Resp Offset
368: addBytes(convertShort(finalLength - 24));
369: addByte((byte) 0);
370: addByte((byte) 0);
371:
372: // NT Resp Length (twice)
373: addBytes(convertShort(0));
374: addBytes(convertShort(0));
375:
376: // NT Resp Offset
377: addBytes(convertShort(finalLength));
378: addByte((byte) 0);
379: addByte((byte) 0);
380:
381: // Domain length (twice)
382: addBytes(convertShort(domainLen));
383: addBytes(convertShort(domainLen));
384:
385: // Domain offset.
386: addBytes(convertShort(64));
387: addByte((byte) 0);
388: addByte((byte) 0);
389:
390: // User Length (twice)
391: addBytes(convertShort(userLen));
392: addBytes(convertShort(userLen));
393:
394: // User offset
395: addBytes(convertShort(64 + domainLen));
396: addByte((byte) 0);
397: addByte((byte) 0);
398:
399: // Host length (twice)
400: addBytes(convertShort(hostLen));
401: addBytes(convertShort(hostLen));
402:
403: // Host offset
404: addBytes(convertShort(64 + domainLen + userLen));
405:
406: for (int i = 0; i < 6; i++) {
407: addByte((byte) 0);
408: }
409:
410: // Message length
411: addBytes(convertShort(finalLength));
412: addByte((byte) 0);
413: addByte((byte) 0);
414:
415: // Flags
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 AuthenticationException {
438: byte[] passw = EncodingUtil.getBytes(password.toUpperCase(),
439: credentialCharset);
440: byte[] lmPw1 = new byte[7];
441: byte[] lmPw2 = new byte[7];
442:
443: int len = passw.length;
444: if (len > 7) {
445: len = 7;
446: }
447:
448: int idx;
449: for (idx = 0; idx < len; idx++) {
450: lmPw1[idx] = passw[idx];
451: }
452: for (; idx < 7; idx++) {
453: lmPw1[idx] = (byte) 0;
454: }
455:
456: len = passw.length;
457: if (len > 14) {
458: len = 14;
459: }
460: for (idx = 7; idx < len; idx++) {
461: lmPw2[idx - 7] = passw[idx];
462: }
463: for (; idx < 14; idx++) {
464: lmPw2[idx - 7] = (byte) 0;
465: }
466:
467: // Create LanManager hashed Password
468: byte[] magic = { (byte) 0x4B, (byte) 0x47, (byte) 0x53,
469: (byte) 0x21, (byte) 0x40, (byte) 0x23, (byte) 0x24,
470: (byte) 0x25 };
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: // Create the responses.
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 AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
504: */
505: private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
506: throws AuthenticationException {
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: * @return Returns the credentialCharset.
559: */
560: public String getCredentialCharset() {
561: return credentialCharset;
562: }
563:
564: /**
565: * @param credentialCharset The credentialCharset to set.
566: */
567: public void setCredentialCharset(String credentialCharset) {
568: this.credentialCharset = credentialCharset;
569: }
570:
571: }
|