001: /*
002: * This program is free software; you can redistribute it and/or modify
003: * it under the terms of the GNU General Public License as published by
004: * the Free Software Foundation; either version 2 of the License, or
005: * (at your option) any later version.
006: *
007: * This program is distributed in the hope that it will be useful,
008: * but WITHOUT ANY WARRANTY; without even the implied warranty of
009: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: * GNU General Public License for more details.
011: *
012: * You should have received a copy of the GNU General Public License
013: * along with this program; if not, write to the Free Software
014: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
015: */
016:
017: /*
018: * NTLM.java
019: * Copyright (C) 2002 Luigi Dragone
020: *
021: */
022:
023: package com.luigidragone.net.ntlm;
024:
025: import java.security.*;
026: import java.security.spec.*;
027: import javax.crypto.*;
028: import javax.crypto.spec.*;
029: import java.io.*;
030:
031: /**
032: * <p>
033: * This class implements the Microsoft NTLM authentication protocol.
034: * </p>
035: * <p>
036: * NTLM is a Microsoft proprietary network authentication protocol used in many
037: * situations (e.g. by the Microsoft Proxy Server to authenticate a browser).
038: * </p>
039: * <p>
040: * It requires a JCE compatible MD4 hash algorithm implementation and
041: * a DES with no-padding ECB cipher to compute the requested value. <br>
042: * An open source JCE compatible library is Cryptix JCE and it is available
043: * <a href="http://www.cryptix.org/">here</a>. We are assuming that the JCE provider
044: * is correctly installed and configured. Notice that the Sun JCE implementation
045: * proviedes the DES cipher but doesn't provide the MD4 hashing.
046: * </p>
047: * To perform an authentication the following information are needed:
048: * <ul>
049: * <li>the host name (with its own domain);</li>
050: * <li>the user name (with its own domain);</li>
051: * <li>the user password.</li>
052: * </ul>
053: * Alternatively, the user password can be replaced with its Lan Manager and
054: * NT hashed versions. On a Windows system these data can be collected in the
055: * registry, otherwise they can be extracted from a SAMBA password file.<br>
056: * Notice that the host and user domain could not be the same.
057: * </p>
058: * <p>
059: * To start an NTLM authentication procedure (e.g. with a proxy server) build a
060: * request message calling {@link #formatRequest(String, String) formatRequest}
061: * and send it to the server.<br>
062: * Once the challenge packet has been correctly received extract from it the nonce
063: * with {@link #getNonce(byte[]) getNonce} function and use it to compute the
064: * reply and build the response message with
065: * {@link #formatResponse(String, String, String, byte[], byte[], byte[]) formatResponse}
066: * method and send it back to the server.<br>
067: * Repeat the previous steps until the server authenticates the client's identity
068: * or a large number of retries has been made. The check of a successful authentication
069: * is protocol specific (e.g. code 200 in HTTP), thus it is not performed by this component.
070: * </p>
071: * <p>
072: * We want to access to the page <code>http://www.server.com/page.html</code>
073: * through an NTLM proxy <code>proxy.domain.com</code> that accepts connection on
074: * port 80.<br>
075: * We access to proxy from host <code>HOSTDOMAIN\\HOST<code> with the user
076: * <code>USERDOMAIN\\user</code> (password <code>"1234567890"</code>).<br>
077: * As first step we open a socket connection to proxy server and set up the
078: * required object. Notice that we use a keep-alive connection, because NTLM
079: * authentication is connection based and the connection must be alive through the
080: * whole process.<br>
081: * <pre>
082: * Socket s = new Socket("proxy.domain.com", 80);
083: * s.setKeepAlive(true);
084: * InputStream is = s.getInputStream();
085: * OutputStream os = s.getOutputStream();
086: * BufferedReader r = new BufferedReader(new InputStreamReader(is));
087: * BufferedWriter w = new BufferedWriter(new OutputStreamWriter(os));
088: *
089: * String host = "HOST";
090: * String hostDomain = "HOSTDOMAIN";
091: * String user = "user";
092: * String userDomain = "USERDOMAIN";
093: * String password = "1234567890";
094: * </pre>
095: * Then, we format a request message and send it in a HTTP compliant GET message.<br>
096: * <pre>
097: * byte[] fstMsg = NTLM.formatRequest(host, hostDomain);
098: * byte[] fstMsg64 = Codecs.base64Encode(fstMsg);
099: * System.out.println("NTLM Request Packet: " + new String(fstMsg64));
100: *
101: * w.write("GET http://www.server.com/page.html HTTP/1.0\n");
102: * w.write("Host: www.server.com\n");
103: * w.write("Proxy-Connection: Keep-Alive\n");
104: * w.write("Proxy-Authorization: NTLM " + new String(fstMsg64) + "\n\n");
105: * w.flush();
106: * System.out.println("First Message Sent");
107: * </pre>
108: * We wait for the server response and we parse it to extract the nonce.<br>
109: * <pre>
110: * String resp = "";
111: * int contentLength = 0;
112: * while((line = r.readLine()) != null)
113: * if(line.length() == 0)
114: * break;
115: * if(line.startsWith("Content-Length"))
116: * contentLength = Integer.parseInt(line.substring(line.indexOf(":") + 1).trim());
117: * else if(line.startsWith("Proxy-Authenticate"))
118: * resp = line.substring(line.indexOf(":") + 1).trim();
119: * r.skip(contentLength);
120: * System.out.println("Second Message Received");
121: * System.out.println("Content Length: " + contentLength);
122: * System.out.println("Proxy-Authenticate: " + resp);
123: * resp = resp.substring(resp.indexOf(" ")).trim();
124: * System.out.println("NTLM Chellange Packet: " + resp);
125: * resp = Codecs.base64Decode(resp);
126: * byte[] sndMsg = resp.getBytes();
127: * byte[] nonce = NTLM.getNonce(sndMsg);
128: * </pre>
129: * With the nonce collected in the previous step we create a response message.<br>
130: * <pre>
131: * byte[] trdMsg = NTLM.formatResponse(host, user, userDomain,
132: * NTLM.computeLMPassword(password), NTLM.computeNTPassword(password),
133: * nonce);
134: * System.out.println(trdMsg.length);
135: * byte[] trdMsg64 = Codecs.base64Encode(trdMsg);
136: * System.out.println("NTLM Response Packet: " + new String(trdMsg64));
137: * </pre>
138: * We sent the message to the server.<br>
139: * <pre>
140: * w.write("GET http://www.server.com/page.html HTTP/1.0\n");
141: * w.write("Proxy-Connection: Keep-Alive\n");
142: * w.write("Host: www.server.com\n");
143: * w.write("Proxy-Authorization: NTLM " + new String(trdMsg64) + "\n\n");
144: * w.flush();
145: * System.out.println("Third Message Sent");
146: * </pre>
147: * Finally we wait the server reply.<br>
148: * <pre>
149: * System.out.println("Server response: " + r.readLine());
150: * </pre>
151: * If the reply is like <code>"HTTP/1.0 200 OK"</code> it has worked fine, else
152: * the server response is containing a new nonce.
153: * </p>
154: * <p>
155: * Notice that despite the computing of hashed passwords and of nonce response is
156: * exactly the same of the SMB authentication protocol, the message format is slightly
157: * different. <br>
158: * Therefore, the methods {@link #computeLMPassword(String) computeLMPassword},
159: * {@link #computeNTPassword(String) computeNTPassword} and
160: * {@link #computeNTLMResponse(byte[], byte[], byte[], byte[], byte[]) computeNTLMResponse}
161: * can be used to perform a SMB authentication too.
162: * </p>
163: * <p>
164: * This implementation is based on:
165: * <ul>
166: * <li>the reverse engineering of the NTLM protocol made by Ronald Tschalär
167: * and available <a href="http://www.innovation.ch/java/ntlm.html">here</a>;</li>
168: * <li>the documentation about NTLM provided with <a href="http://www.atstake.com/research/lc3/">
169: * L0phtCrack 1.5</a>;</li>
170: * <li>the "Handbook of Applied Cryptography", freely available
171: * <a href="http://www.cacr.math.uwaterloo.ca/hac/">here</a>;</li>
172: * <li>the C source code of NTLM library in the <a href="http://www.samba.org/">
173: * SAMBA Project</a>.</li>
174: * </ul>
175: * Nevertheless, because there isn't any official protocol specification publicly
176: * available there is any warranty that code works correctly and that it is
177: * conforming to Microsoft NTLM protocol.
178: * </p>
179: * <p>
180: * For implementation reasons only the public members perform argument consistency
181: * checks. The public members also catch and hide every exceptions that can be
182: * throwed (even though interfaces specify otherwise).
183: * </p>
184: *
185: * @author Luigi Dragone (<a href="mailto:luigi@luigidragone.com">luigi@luigidragone.com</a>)
186: *
187: * @version 1.0.1
188: *
189: * @see <a href="http://www.innovation.ch/java/ntlm.html">NTLM Authentication Scheme for HTTP</a>
190: * @see <a href="ftp://ftp.samba.org/pub/samba/docs/textdocs/ENCRYPTION.txt">LanMan and NT Password Encryption in Samba 2.x</a>
191: * @see <a href="http://www.cacr.math.uwaterloo.ca/hac/">"Handbook of Applied Cryptography"</a>
192: * @see <a href="http://java.sun.com/products/jce/">JCE</a>
193: * @see <a href="http://www.cryptix.org/">Cryptix</a>
194: */
195: public class NTLM {
196: protected NTLM() {
197: }
198:
199: /**
200: * The magic number used to compute the Lan Manager hashed password.
201: */
202: protected static final byte[] MAGIC = new byte[] { 0x4B, 0x47,
203: 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
204:
205: /**
206: * <p>
207: * Converts an unsigned byte to an unsigned integer.
208: * </p>
209: * <p>
210: * Notice that Java bytes are always signed, but the cryptographic algorithms
211: * rely on unsigned ones, that can be simulated in this way.<br>
212: * A bit mask is employed to prevent that the signum bit is extended to MSBs.
213: * </p>
214: */
215: protected static int unsignedByteToInt(byte b) {
216: return (int) b & 0xFF;
217: }
218:
219: protected static byte getLoByte(char c) {
220: return (byte) c;
221: }
222:
223: protected static byte getHiByte(char c) {
224: return (byte) ((c >>> 8) & 0xFF);
225: }
226:
227: protected static short swapBytes(short s) {
228: return (short) (((s << 8) & 0xFF00) | ((s >>> 8) & 0x00FF));
229: }
230:
231: /**
232: * <p>
233: * Computes an odd DES key from 56 bits represented as a 7-bytes array.
234: * </p>
235: * <p>
236: * Keeps elements from index <code>offset</code> to index <code>offset + 7</code> of
237: * supplied array.
238: * </p>
239: *
240: * @param keyData a byte array containing the 56 bits used to compute the DES key
241: * @param offset the offset of the first element of the 56-bits key data
242: *
243: * @return the odd DES key generated
244: *
245: * @exception InvalidKeyException
246: * @exception NoSuchAlgorithmException
247: * @exception InvalidKeySpecException
248: */
249: protected static Key computeDESKey(byte[] keyData, int offset)
250: throws InvalidKeyException, NoSuchAlgorithmException,
251: InvalidKeySpecException {
252: byte[] desKeyData = new byte[8];
253: int[] k = new int[7];
254:
255: for (int i = 0; i < 7; i++)
256: k[i] = unsignedByteToInt(keyData[offset + i]);
257:
258: desKeyData[0] = (byte) (k[0] >>> 1);
259: desKeyData[1] = (byte) (((k[0] & 0x01) << 6) | (k[1] >>> 2));
260: desKeyData[2] = (byte) (((k[1] & 0x03) << 5) | (k[2] >>> 3));
261: desKeyData[3] = (byte) (((k[2] & 0x07) << 4) | (k[3] >>> 4));
262: desKeyData[4] = (byte) (((k[3] & 0x0F) << 3) | (k[4] >>> 5));
263: desKeyData[5] = (byte) (((k[4] & 0x1F) << 2) | (k[5] >>> 6));
264: desKeyData[6] = (byte) (((k[5] & 0x3F) << 1) | (k[6] >>> 7));
265: desKeyData[7] = (byte) (k[6] & 0x7F);
266:
267: for (int i = 0; i < 8; i++)
268: desKeyData[i] = (byte) (unsignedByteToInt(desKeyData[i]) << 1);
269:
270: KeySpec desKeySpec = new DESKeySpec(desKeyData);
271: SecretKeyFactory keyFactory = SecretKeyFactory
272: .getInstance("DES");
273: SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
274: return secretKey;
275: }
276:
277: /**
278: * Encrypts the 8-bytes plain text three times with the 3 56-bits DES keys and
279: * puts the result in a 24-bytes array.
280: *
281: * @param keys a 21-bytes array containing 3 56-bits DES keys
282: * @param plaintext a 8-bytes array to be encrypted
283: *
284: * @return a 24-bytes array containing the plaintext DES encrypted with the supplied keys
285: *
286: * @exception InvalidKeyException
287: * @exception NoSuchAlgorithmException
288: * @exception javax.crypto.NoSuchPaddingException
289: * @exception InvalidKeySpecException
290: * @exception BadPaddingException
291: * @exception IllegalBlockSizeException
292: * @exception ShortBufferException
293: */
294: protected static byte[] encrypt(byte[] keys, byte[] plaintext)
295: throws InvalidKeyException, NoSuchAlgorithmException,
296: javax.crypto.NoSuchPaddingException,
297: InvalidKeySpecException, BadPaddingException,
298: IllegalBlockSizeException, ShortBufferException {
299: byte[] ciphertext = new byte[24];
300: Cipher c = Cipher.getInstance("DES/ECB/NoPadding");
301: Key k = computeDESKey(keys, 0);
302: c.init(Cipher.ENCRYPT_MODE, k);
303: c.doFinal(plaintext, 0, 8, ciphertext, 0);
304: k = computeDESKey(keys, 7);
305: c.init(Cipher.ENCRYPT_MODE, k);
306: c.doFinal(plaintext, 0, 8, ciphertext, 8);
307: k = computeDESKey(keys, 14);
308: c.init(Cipher.ENCRYPT_MODE, k);
309: c.doFinal(plaintext, 0, 8, ciphertext, 16);
310: return ciphertext;
311: }
312:
313: /**
314: * Computes the Lan Manager hashed version of a password.
315: *
316: * @param password the user password
317: *
318: * @return the Lan Manager hashed version of the password in a 16-bytes array
319: *
320: * @exception IllegalArgumentException if the supplied password is null
321: * @exception javax.crypto.NoSuchPaddingException if there isn't any suitable padding method
322: * @exception NoSuchAlgorithmException if there isn't any suitable cipher algorithm
323: */
324: public static byte[] computeLMPassword(String password)
325: throws IllegalArgumentException, NoSuchPaddingException,
326: NoSuchAlgorithmException {
327: if (password == null)
328: throw new IllegalArgumentException(
329: "password : null value not allowed");
330: try {
331: //Gets the first 14-bytes of the ASCII upper cased password
332: int len = password.length();
333: if (len > 14)
334: len = 14;
335: Cipher c = Cipher.getInstance("DES/ECB/NoPadding");
336:
337: byte[] lm_pw = new byte[14];
338: byte[] bytes = password.toUpperCase().getBytes();
339: int i;
340: for (i = 0; i < len; i++)
341: lm_pw[i] = bytes[i];
342: for (; i < 14; i++)
343: lm_pw[i] = 0;
344:
345: byte[] lm_hpw = new byte[16];
346: //Builds a first DES key with its first 7 bytes
347: Key k = computeDESKey(lm_pw, 0);
348: c.init(Cipher.ENCRYPT_MODE, k);
349: //Hashes the MAGIC number with this key into the first 8 bytes of the result
350: c.doFinal(MAGIC, 0, 8, lm_hpw, 0);
351:
352: //Repeats the work with the last 7 bytes to gets the last 8 bytes of the result
353: k = computeDESKey(lm_pw, 7);
354: c.init(Cipher.ENCRYPT_MODE, k);
355: c.doFinal(MAGIC, 0, 8, lm_hpw, 8);
356:
357: return lm_hpw;
358: } catch (InvalidKeySpecException ex) {
359: return null;
360: } catch (InvalidKeyException ex) {
361: return null;
362: } catch (BadPaddingException ex) {
363: return null;
364: } catch (IllegalBlockSizeException ex) {
365: return null;
366: } catch (ShortBufferException ex) {
367: return null;
368: }
369: }
370:
371: /**
372: * Computes the NT hashed version of a password.
373: *
374: * @param password the user password
375: *
376: * @return the NT hashed version of the password in a 16-bytes array
377: *
378: * @exception IllegalArgumentException if the supplied password is null
379: * @exception NoSuchAlgorithmException if there isn't any suitable cipher algorithm
380: */
381: public static byte[] computeNTPassword(String password)
382: throws IllegalArgumentException, NoSuchAlgorithmException {
383: if (password == null)
384: throw new IllegalArgumentException(
385: "password : null value not allowed");
386: //Gets the first 14-bytes of the UNICODE password
387: int len = password.length();
388: if (len > 14)
389: len = 14;
390: byte[] nt_pw = new byte[2 * len];
391: for (int i = 0; i < len; i++) {
392: char ch = password.charAt(i);
393: nt_pw[2 * i] = getLoByte(ch);
394: nt_pw[2 * i + 1] = getHiByte(ch);
395: }
396:
397: //Return its MD4 digest as the hashed version
398: MessageDigest md = MessageDigest.getInstance("MD4");
399: return md.digest(nt_pw);
400: }
401:
402: /**
403: * <p>
404: * Computes the NTLM response to the nonce based on the supplied hashed passwords.
405: * </p>
406: * <p>
407: * If the hashed password are not available they can be computed from the cleartext password
408: * by the means of {@link #computeLMPassword(String) computeLMPassword} and
409: * {@link #computeNTPassword(String) computeNTPassword} methods.
410: * </p>
411: *
412: * @param lmPassword a 16-bytes array containing the Lan Manager hashed password
413: * @param ntPassword a 16-bytes array containing the Lan Manager hashed password
414: * @param nonce a 8-bytes array representing the server's nonce
415: * @param lmResponse a 24-bytes array that will contain the Lan Manager response after the method invocation
416: * @param ntResponse a 24-bytes array that will contain the NT response after the method invocation
417: *
418: * @exception IllegalArgumentException if a parameter has an illegal size
419: * @exception javax.crypto.NoSuchPaddingException if there isn't any suitable padding method
420: * @exception NoSuchAlgorithmException if there isn't any suitable cipher algorithm
421: */
422: public static void computeNTLMResponse(byte[] lmPassword,
423: byte[] ntPassword, byte[] nonce, byte[] lmResponse,
424: byte[] ntResponse) throws IllegalArgumentException,
425: NoSuchPaddingException, NoSuchAlgorithmException {
426: if (lmPassword.length != 16)
427: throw new IllegalArgumentException(
428: "lmPassword : illegal size");
429: if (ntPassword.length != 16)
430: throw new IllegalArgumentException(
431: "ntPassword : illegal size");
432: if (nonce.length != 8)
433: throw new IllegalArgumentException("nonce : illegal size");
434: if (lmResponse.length != 24)
435: throw new IllegalArgumentException(
436: "lmResponse : illegal size");
437: if (ntResponse.length != 24)
438: throw new IllegalArgumentException(
439: "ntResponse : illegal size");
440: try {
441: //Puts the hashed passwords into 21-bytes arrays with trailing 0s
442: byte[] lmHPw = new byte[21];
443: byte[] ntHPw = new byte[21];
444: System.arraycopy(lmPassword, 0, lmHPw, 0, 16);
445: System.arraycopy(ntPassword, 0, ntHPw, 0, 16);
446: for (int i = 16; i < 21; i++) {
447: lmHPw[i] = 0;
448: ntHPw[i] = 0;
449: }
450: //Encrypts the nonce with the padded hashed passwords to compute the responses
451: System.arraycopy(encrypt(lmHPw, nonce), 0, lmResponse, 0,
452: 24);
453: System.arraycopy(encrypt(ntHPw, nonce), 0, ntResponse, 0,
454: 24);
455: } catch (ShortBufferException ex) {
456: } catch (IllegalBlockSizeException ex) {
457: } catch (BadPaddingException ex) {
458: } catch (InvalidKeySpecException ex) {
459: } catch (InvalidKeyException ex) {
460: }
461: }
462:
463: /**
464: * <p>
465: * Builds a request message for the host of the specified domain that can be send
466: * to the server to start the NTLM protocol.
467: * </p>
468: * <p>
469: * The returned message should be encoded according to protocol specific rules
470: * (e.g. base 64 encoding).<br>
471: * The message format is discussed <a href="http://www.innovation.ch/java/ntlm.html">here</a>.
472: * </p>
473: *
474: * @param host the name of the host that is authenticating
475: * @param hostDomain the name of the domain to which the host belongs
476: *
477: * @return the request message to send to server to open an authentication procedure
478: *
479: * @exception IOException if an error occurs during the message formatting
480: *
481: * @see <a href="http://www.innovation.ch/java/ntlm.html">NTLM Authentication Scheme for HTTP</a>
482: *
483: */
484: public static byte[] formatRequest(String host, String hostDomain)
485: throws IOException {
486: hostDomain = hostDomain.toUpperCase();
487: host = host.toUpperCase();
488: short domainLen = (short) hostDomain.length();
489: short hostLen = (short) host.length();
490: short hostOff = 0x20;
491: short domainOff = (short) (hostOff + hostLen);
492: ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
493: DataOutputStream dataOut = new DataOutputStream(os);
494: dataOut.writeBytes("NTLMSSP\0");
495: dataOut.writeByte(0x01);
496: dataOut.writeByte(0x00);
497: dataOut.writeByte(0x00);
498: dataOut.writeByte(0x00);
499: dataOut.writeShort(swapBytes((short) 0xb203));
500: dataOut.writeShort(0x0000);
501: dataOut.writeShort(swapBytes(domainLen));
502: dataOut.writeShort(swapBytes(domainLen));
503: dataOut.writeShort(swapBytes(domainOff));
504: dataOut.writeShort(0x0000);
505: dataOut.writeShort(swapBytes(hostLen));
506: dataOut.writeShort(swapBytes(hostLen));
507: dataOut.writeShort(swapBytes(hostOff));
508: dataOut.writeShort(0x0000);
509: dataOut.write(host.getBytes());
510: dataOut.write(hostDomain.getBytes());
511: dataOut.flush();
512: return os.toByteArray();
513: }
514:
515: /**
516: * <p>
517: * Extracts from the server challenge response the nonce required to perform
518: * the authentication.
519: * </p>
520: * <p>
521: * The received message should be decoded according to protocol specific rules
522: * (e.g. base 64 encoding).<br>
523: * The message format is discussed <a href="http://www.innovation.ch/java/ntlm.html">here</a>.
524: * </p>
525: *
526: * @param msg a byte array containing the server challenge message
527: *
528: * @exception IllegalArgumentException if a parameter has an illegal size
529: *
530: * @see <a href="http://www.innovation.ch/java/ntlm.html">NTLM Authentication Scheme for HTTP</a>
531: */
532: public static byte[] getNonce(byte[] msg)
533: throws IllegalArgumentException {
534: if (msg.length < 32)
535: throw new IllegalArgumentException("msg : illegal size");
536: byte[] nonce = new byte[8];
537: System.arraycopy(msg, 24, nonce, 0, 8);
538: return nonce;
539: }
540:
541: /**
542: * <p>
543: * Builds the nonce response message.
544: * </p>
545: * <p>It requires the Lan Manager and NT hashed
546: * version of user password, that can be computed from the cleartext version by
547: * {@link #computeNTPassword(String) computeNTPassword} and
548: * {@link #computeNTLMResponse(byte[], byte[], byte[], byte[], byte[]) computeNTLMResponse},
549: * and the nonce obtained from the server by {@link #getNonce(byte[]) getNonce}.<br>
550: * The returned message should be encoded according to protocol specific rules
551: * (e.g. base 64 encoding).<br>
552: * The message format is discussed <a href="http://www.innovation.ch/java/ntlm.html">here</a>.
553: * </p>
554: *
555: * @param host the name of the host that is authenticating
556: * @param user the name of the user
557: * @param userDomain the name of the domain to which the user belongs
558: * @param lmPassword a 16-bytes array containing the Lan Manager hashed password
559: * @param ntPassword a 16-bytes array containing the NT hashed password
560: * @param nonce a 8-byte array containing the nonce sent by server to reply to the request message
561: *
562: * @return the challenge response message to send to server to complete the authentication procedure
563: *
564: * @exception IOException if an error occurs during the message formatting
565: * @exception IllegalArgumentException if a parameter has an illegal size
566: * @exception javax.crypto.NoSuchPaddingException if there isn't any suitable padding method
567: * @exception NoSuchAlgorithmException if there isn't any suitable cipher algorithm
568: *
569: * @see <a href="http://www.innovation.ch/java/ntlm.html">NTLM Authentication Scheme for HTTP</a>
570: */
571: public static byte[] formatResponse(String host, String user,
572: String userDomain, byte[] lmPassword, byte[] ntPassword,
573: byte[] nonce) throws IllegalArgumentException, IOException,
574: NoSuchAlgorithmException, NoSuchPaddingException {
575: if (host == null)
576: throw new IllegalArgumentException(
577: "host : null value not allowed");
578: if (user == null)
579: throw new IllegalArgumentException(
580: "user : null value not allowed");
581: if (userDomain == null)
582: throw new IllegalArgumentException(
583: "userDomain : null value not allowed");
584: if (lmPassword == null)
585: throw new IllegalArgumentException(
586: "lmPassword : null value not allowed");
587: if (ntPassword == null)
588: throw new IllegalArgumentException(
589: "ntPassword : null value not allowed");
590: if (nonce == null)
591: throw new IllegalArgumentException(
592: "nonce : null value not allowed");
593: if (lmPassword.length != 16)
594: throw new IllegalArgumentException(
595: "lmPassword : illegal size");
596: if (ntPassword.length != 16)
597: throw new IllegalArgumentException(
598: "ntPassword : illegal size");
599: if (nonce.length != 8)
600: throw new IllegalArgumentException("nonce : illegal size");
601:
602: byte[] lmResponse = new byte[24];
603: byte[] ntResponse = new byte[24];
604:
605: computeNTLMResponse(lmPassword, ntPassword, nonce, lmResponse,
606: ntResponse);
607:
608: userDomain = userDomain.toUpperCase();
609: host = host.toUpperCase();
610: short lmRespLen = (short) 0x18;
611: short ntRespLen = (short) 0x18;
612: short domainLen = (short) (2 * userDomain.length());
613: short hostLen = (short) (2 * host.length());
614: short userLen = (short) (2 * user.length());
615: short domainOff = (short) 0x40;
616: short userOff = (short) (domainOff + domainLen);
617: short hostOff = (short) (userOff + userLen);
618: short lmRespOff = (short) (hostOff + hostLen);
619: short ntRespOff = (short) (lmRespOff + lmRespLen);
620: short msgLen = (short) (ntRespOff + ntRespLen);
621: ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
622: DataOutputStream dataOut = new DataOutputStream(os);
623: dataOut.writeBytes("NTLMSSP\0");
624: dataOut.writeByte(0x03);
625: dataOut.writeByte(0x00);
626: dataOut.writeByte(0x00);
627: dataOut.writeByte(0x00);
628: dataOut.writeShort(swapBytes(lmRespLen));
629: dataOut.writeShort(swapBytes(lmRespLen));
630: dataOut.writeShort(swapBytes(lmRespOff));
631: dataOut.writeShort(0x0000);
632: dataOut.writeShort(swapBytes(ntRespLen));
633: dataOut.writeShort(swapBytes(ntRespLen));
634: dataOut.writeShort(swapBytes(ntRespOff));
635: dataOut.writeShort(0x0000);
636: dataOut.writeShort(swapBytes(domainLen));
637: dataOut.writeShort(swapBytes(domainLen));
638: dataOut.writeShort(swapBytes(domainOff));
639: dataOut.writeShort(0x0000);
640: dataOut.writeShort(swapBytes(userLen));
641: dataOut.writeShort(swapBytes(userLen));
642: dataOut.writeShort(swapBytes(userOff));
643: dataOut.writeShort(0x0000);
644: dataOut.writeShort(swapBytes(hostLen));
645: dataOut.writeShort(swapBytes(hostLen));
646: dataOut.writeShort(swapBytes(hostOff));
647: dataOut.writeShort(0x0000);
648: dataOut.writeInt(0x00000000);
649: dataOut.writeShort(swapBytes(msgLen));
650: dataOut.writeShort(0x0000);
651: dataOut.writeShort(0x0000); // dataOut.writeShort(swapBytes((short)0x8201));
652: dataOut.writeShort(0x0000);
653:
654: for (int i = 0; i < userDomain.length(); i++)
655: dataOut.writeShort(swapBytes((short) userDomain.charAt(i)));
656: for (int i = 0; i < user.length(); i++)
657: dataOut.writeShort(swapBytes((short) user.charAt(i)));
658: for (int i = 0; i < host.length(); i++)
659: dataOut.writeShort(swapBytes((short) host.charAt(i)));
660: dataOut.write(lmResponse);
661: dataOut.write(ntResponse);
662: dataOut.flush();
663: return os.toByteArray();
664: }
665: }
|