--- NTLM.java.orig Sun Feb 15 15:41:58 2004 +++ NTLM.java Wed Dec 29 12:42:34 2004 @@ -102,6 +102,16 @@ * @since 2.0alpha2 */ public final class NTLM { + private static final int F_NEGOTIATE_UNICODE = 0x00000001; + private static final int F_NEGOTIATE_OEM = 0x00000002; + private static final int F_REQUEST_TARGET = 0x00000004; + private static final int F_NEGOTIATE_NTLM = 0x00000200; + private static final int F_DOMAIN_SUPPLIED = 0x00001000; + private static final int F_WS_SUPPLIED = 0x00002000; + + private static final int HASH_LENGTH = 21; + private static final int RESPONSE_LENGTH = 24; + private static final int TYPE3_HEADER_LENGTH = 64; /** The current response */ private byte[] currentResponse; @@ -230,6 +240,32 @@ } /** + * Add a little-endian 2-byte value to the response. + * @param s short value to add + */ + private void addShort(int s) + { + addByte((byte) (s & 0xFF)); + s >>= 8; + addByte((byte) (s & 0xFF)); + } + + /** + * Add a little-endian 4-byte value to the response. + * @param l long value to add + */ + private void addLong(int l) + { + addByte((byte) (l & 0xFF)); + l >>= 8; + addByte((byte) (l & 0xFF)); + l >>= 8; + addByte((byte) (l & 0xFF)); + l >>= 8; + addByte((byte) (l & 0xFF)); + } + + /** * Adds the given bytes to the response. * @param bytes the bytes to add. */ @@ -268,10 +304,8 @@ * @return String the message to add to the HTTP request header. */ private String getType1Message(String host, String domain) { - host = host.toUpperCase(); - domain = domain.toUpperCase(); - byte[] hostBytes = getBytes(host); - byte[] domainBytes = getBytes(domain); + byte[] hostBytes = getBytes(host.toUpperCase()); + byte[] domainBytes = getBytes(domain.toUpperCase()); int finalLength = 32 + hostBytes.length + domainBytes.length; prepareResponse(finalLength); @@ -281,50 +315,31 @@ addBytes(protocol); addByte((byte) 0); - // Type - addByte((byte) 1); - addByte((byte) 0); - addByte((byte) 0); - addByte((byte) 0); + // Type 1 + addLong(1); // Flags - addByte((byte) 6); - addByte((byte) 82); - addByte((byte) 0); - addByte((byte) 0); + addLong(F_NEGOTIATE_UNICODE | F_NEGOTIATE_OEM + | F_REQUEST_TARGET | F_NEGOTIATE_NTLM | F_DOMAIN_SUPPLIED + | F_WS_SUPPLIED); // Domain length (first time). - int iDomLen = domainBytes.length; - byte[] domLen = convertShort(iDomLen); - addByte(domLen[0]); - addByte(domLen[1]); + addShort(domainBytes.length); // Domain length (second time). - addByte(domLen[0]); - addByte(domLen[1]); + addShort(domainBytes.length); // Domain offset. - byte[] domOff = convertShort(hostBytes.length + 32); - addByte(domOff[0]); - addByte(domOff[1]); - addByte((byte) 0); - addByte((byte) 0); + addLong(hostBytes.length + 32); // Host length (first time). - byte[] hostLen = convertShort(hostBytes.length); - addByte(hostLen[0]); - addByte(hostLen[1]); + addShort(hostBytes.length); // Host length (second time). - addByte(hostLen[0]); - addByte(hostLen[1]); + addShort(hostBytes.length); // Host offset (always 32). - byte[] hostOff = convertShort(32); - addByte(hostOff[0]); - addByte(hostOff[1]); - addByte((byte) 0); - addByte((byte) 0); + addLong(32); // Host String. addBytes(hostBytes); @@ -354,13 +369,15 @@ } /** - * Creates the type 3 message using the given server nonce. The type 3 message includes all the - * information for authentication, host, domain, username and the result of encrypting the - * nonce sent by the server using the user's password as the key. + * Creates the type 3 message using the given server nonce. The type 3 + * message includes all the information for authentication, host, + * domain, username and the result of encrypting the nonce sent by the + * server using the user's password as the key. * * @param user The user name. This should not include the domain name. * @param password The password. - * @param host The host that is originating the authentication request. + * @param host The host that is originating the authentication + * request. * @param domain The domain to authenticate within. * @param nonce the 8 byte array the server sent. * @return The type 3 message. @@ -369,103 +386,120 @@ private String getType3Message(String user, String password, String host, String domain, byte[] nonce) throws HttpException { + byte[] domainBytes; + byte[] hostBytes; + byte[] userBytes; + try { + domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); + hostBytes = host.toUpperCase().getBytes("UnicodeLittleUnmarked"); + userBytes = user.toUpperCase().getBytes("UnicodeLittleUnmarked"); + } catch (UnsupportedEncodingException ex) { + throw new HttpException( + "UnicodeLittleUnmarked is not supported"); + } - int ntRespLen = 0; - int lmRespLen = 24; - domain = domain.toUpperCase(); - host = host.toUpperCase(); - user = user.toUpperCase(); - byte[] domainBytes = getBytes(domain); - byte[] hostBytes = getBytes(host); - byte[] userBytes = getBytes(user); - int domainLen = domainBytes.length; - int hostLen = hostBytes.length; - int userLen = userBytes.length; - int finalLength = 64 + ntRespLen + lmRespLen + domainLen - + userLen + hostLen; - prepareResponse(finalLength); - byte[] ntlmssp = getBytes("NTLMSSP"); - addBytes(ntlmssp); - addByte((byte) 0); - addByte((byte) 3); - addByte((byte) 0); - addByte((byte) 0); + /* + * Total message length + */ + int messageLength = TYPE3_HEADER_LENGTH + domainBytes.length + + userBytes.length + hostBytes.length + RESPONSE_LENGTH + + RESPONSE_LENGTH; + prepareResponse(messageLength); + addBytes(getBytes("NTLMSSP")); addByte((byte) 0); - // LM Resp Length (twice) - addBytes(convertShort(24)); - addBytes(convertShort(24)); + /* + * Type 3 message + */ + addLong(3); - // LM Resp Offset - addBytes(convertShort(finalLength - 24)); - addByte((byte) 0); - addByte((byte) 0); + /* + * LM response length (twice) + */ + addShort(RESPONSE_LENGTH); + addShort(RESPONSE_LENGTH); - // NT Resp Length (twice) - addBytes(convertShort(0)); - addBytes(convertShort(0)); + /* + * LM response offset + */ + addLong(TYPE3_HEADER_LENGTH + domainBytes.length + + userBytes.length + hostBytes.length); - // NT Resp Offset - addBytes(convertShort(finalLength)); - addByte((byte) 0); - addByte((byte) 0); + /* + * NT response length (twice) + */ + addShort(RESPONSE_LENGTH); + addShort(RESPONSE_LENGTH); - // Domain length (twice) - addBytes(convertShort(domainLen)); - addBytes(convertShort(domainLen)); + /* + * NT response offset + */ + addLong(TYPE3_HEADER_LENGTH + domainBytes.length + + userBytes.length + hostBytes.length + RESPONSE_LENGTH); - // Domain offset. - addBytes(convertShort(64)); - addByte((byte) 0); - addByte((byte) 0); + /* + * Domain name length (twice) + */ + addShort(domainBytes.length); + addShort(domainBytes.length); - // User Length (twice) - addBytes(convertShort(userLen)); - addBytes(convertShort(userLen)); + /* + * Domain name offset + */ + addLong(TYPE3_HEADER_LENGTH); - // User offset - addBytes(convertShort(64 + domainLen)); - addByte((byte) 0); - addByte((byte) 0); + /* + * User name length (twice) + */ + addShort(userBytes.length); + addShort(userBytes.length); - // Host length (twice) - addBytes(convertShort(hostLen)); - addBytes(convertShort(hostLen)); + /* + * User name offset + */ + addLong(TYPE3_HEADER_LENGTH + domainBytes.length); - // Host offset - addBytes(convertShort(64 + domainLen + userLen)); + /* + * Host name length (twice) + */ + addShort(hostBytes.length); + addShort(hostBytes.length); - for (int i = 0; i < 6; i++) { - addByte((byte) 0); - } + /* + * Host name offset + */ + addLong(TYPE3_HEADER_LENGTH + domainBytes.length + userBytes.length); - // Message length - addBytes(convertShort(finalLength)); - addByte((byte) 0); - addByte((byte) 0); + /* + * Session key reference? + */ + addShort(0); + addShort(0); + addLong(messageLength); - // Flags - addByte((byte) 6); - addByte((byte) 82); - addByte((byte) 0); - addByte((byte) 0); + /* + * Flags + */ + addLong(F_NEGOTIATE_UNICODE | F_REQUEST_TARGET + | F_NEGOTIATE_NTLM | F_DOMAIN_SUPPLIED | F_WS_SUPPLIED); addBytes(domainBytes); addBytes(userBytes); addBytes(hostBytes); - addBytes(hashPassword(password, nonce)); + addBytes(lmHash(password, nonce)); + addBytes(ntHash(password, nonce)); return getResponse(); } /** - * Creates the LANManager and NT response for the given password using the + * Creates the LANManager response for the given password using the * given nonce. * @param password the password to create a hash for. * @param nonce the nonce sent by the server. * @return The response. * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. */ - private byte[] hashPassword(String password, byte[] nonce) + private byte[] lmHash(String password, byte[] nonce) throws HttpException { byte[] passw = getBytes(password.toUpperCase()); byte[] lmPw1 = new byte[7]; @@ -525,6 +559,47 @@ } /** + * Creates the NT response for the given password using the + * given nonce. + * @param password the password to create a hash for. + * @param nonce the nonce sent by the server. + * @return The response. + * @throws HttpException If digest calculation fails. + */ + private byte[] ntHash(String password, byte[] nonce) + throws HttpException { + byte[] passw = getBytes(password); + byte[] ntPwd = new byte[passw.length * 2]; + byte[] ntHpwd = new byte[HASH_LENGTH]; + + for (int i = 0, j = 0; i < ntPwd.length; i += 2, j++) { + ntPwd[i] = passw[j]; + ntPwd[i + 1] = 0; + } + + try { + MD4 md = new MD4(); + md.update(ntPwd); + md.digest(ntHpwd, 0, MD4.HASH_SIZE); + } catch (Exception ex) { + throw new HttpException("Cannot compute message digest: " + + ex.getMessage()); + } + + for (int i = MD4.HASH_SIZE; i < ntHpwd.length; i++) { + ntHpwd[i] = (byte) 0; + } + + /* + * Create the response + */ + byte[] ntResp = new byte[RESPONSE_LENGTH]; + calcResp(ntHpwd, nonce, ntResp); + + return ntResp; + } + + /** * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte * plaintext is encrypted with each key and the resulting 24 bytes are * stored in the results array. @@ -568,25 +643,6 @@ } /** - * Converts a given number to a two byte array in little endian order. - * @param num the number to convert. - * @return The byte representation of num in little endian order. - */ - private byte[] convertShort(int num) { - byte[] val = new byte[2]; - String hex = Integer.toString(num, 16); - while (hex.length() < 4) { - hex = "0" + hex; - } - String low = hex.substring(2, 4); - String high = hex.substring(0, 2); - - val[0] = (byte) Integer.parseInt(low, 16); - val[1] = (byte) Integer.parseInt(high, 16); - return val; - } - - /** * Convert a string to a byte array. * @param s The string * @return byte[] The resulting byte array. @@ -599,6 +655,268 @@ return s.getBytes(DEFAULT_CHARSET); } catch (UnsupportedEncodingException unexpectedEncodingException) { throw new RuntimeException("NTLM requires ASCII support"); + } + } + + /** + * MD4 message digest algorithm. Lifted from Cryptix project. + *

+ * Cryptix General License + *

+ * Copyright (c) 1995-2004 The Cryptix Foundation Limited. All rights + * reserved. + *

+ * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + *

+ * 1. Redistributions of source code must retain the copyright notice, + * this list of conditions and the following disclaimer. + *

+ * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + *

+ * THIS SOFTWARE IS PROVIDED BY THE CRYPTIX FOUNDATION LIMITED AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CRYPTIX FOUNDATION LIMITED OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + *

+ * + * @author Raif S. Naffah + * @author Jeroen C. van Gelderen + * @see Cryptix project + */ + public final static class MD4 { + /** Size (in bytes) of this hash */ + private static final int HASH_SIZE = 16; + + /** Size (in bytes) of the blocks. */ + private static final int BLOCK_SIZE = 64; + + /** A buffer */ + private final byte[] buffer = new byte[BLOCK_SIZE]; + + /** Current offset in the buffer */ + private int bufferOffset; + + /** Total count of processed bytes */ + private int byteCount; + + /** 4 32-bit words (interim result) */ + private final int[] context = new int[4]; + + /** 512 bits work buffer = 16 x 32-bit words */ + private final int[] X = new int[16]; + + /** + * Default constructor. + */ + public MD4() + { + reset(); + } + + public int digest(byte[] destination, int offset, int len) + { + buffer[bufferOffset++] = (byte) 0x80; + + int lenOfBitLen = 8; + int C = BLOCK_SIZE - lenOfBitLen; + + if (bufferOffset > C) { + while (bufferOffset < BLOCK_SIZE) + buffer[bufferOffset++] = (byte) 0x00; + + roundUpdate(buffer); + bufferOffset = 0; + } + + while (bufferOffset < C) + buffer[bufferOffset++] = (byte) 0x00; + + long bitCount = byteCount * 8; + + /* + * 64-bit length is appended in little endian order + */ + for (int i = 0; i < 64; i += 8) + buffer[bufferOffset++] = (byte) (bitCount >>> (i)); + + roundUpdate(buffer); + + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + destination[offset + (i * 4 + j)] = + (byte) (context[i] >>> (8 * j)); + + reset(); + return HASH_SIZE; + } + + public void reset() + { + /* + * zero out the counters + */ + bufferOffset = 0; + byteCount = 0; + + /* + * initial values of MD4 i.e. A, B, C, D + * as per rfc-1320; they are low-order byte first + */ + context[0] = 0x67452301; + context[1] = 0xEFCDAB89; + context[2] = 0x98BADCFE; + context[3] = 0x10325476; + } + + /** + * Update MD4 hash. + * + * @param block input data + */ + public void update(byte[] block) + { + update(block, 0, block.length); + } + + /** + * Update MD4 hash. + * + * @param input input buffer + * @param offset offset of data in the buffer + * @param length length of data block + */ + public void update(byte[] input, int offset, int length) + { + byteCount += length; + int todo; + while (length >= (todo = BLOCK_SIZE - bufferOffset)) { + System.arraycopy(input, offset, buffer, bufferOffset, todo); + roundUpdate(buffer); + length -= todo; + offset += todo; + bufferOffset = 0; + } + + System.arraycopy(input, offset, buffer, bufferOffset, length); + bufferOffset += length; + } + + /** + * Single round of update. Update with 64-byte block. + * + * @param block input data -- 64 bytes + */ + private void roundUpdate(byte[] block) + { + int offset = 0; + + /* + * encodes 64 bytes from input block into an + * array of 16 32-bit entities. + */ + for (int i = 0; i < BLOCK_SIZE / 4; i++) + X[i] = (block[offset++] & 0xFF) + | (block[offset++] & 0xFF) << 8 + | (block[offset++] & 0xFF) << 16 + | (block[offset++] & 0xFF) << 24; + + int A = context[0]; + int B = context[1]; + int C = context[2]; + int D = context[3]; + + A = FF(A, B, C, D, X[0], 3); + D = FF(D, A, B, C, X[1], 7); + C = FF(C, D, A, B, X[2], 11); + B = FF(B, C, D, A, X[3], 19); + A = FF(A, B, C, D, X[4], 3); + D = FF(D, A, B, C, X[5], 7); + C = FF(C, D, A, B, X[6], 11); + B = FF(B, C, D, A, X[7], 19); + A = FF(A, B, C, D, X[8], 3); + D = FF(D, A, B, C, X[9], 7); + C = FF(C, D, A, B, X[10], 11); + B = FF(B, C, D, A, X[11], 19); + A = FF(A, B, C, D, X[12], 3); + D = FF(D, A, B, C, X[13], 7); + C = FF(C, D, A, B, X[14], 11); + B = FF(B, C, D, A, X[15], 19); + + A = GG(A, B, C, D, X[0], 3); + D = GG(D, A, B, C, X[4], 5); + C = GG(C, D, A, B, X[8], 9); + B = GG(B, C, D, A, X[12], 13); + A = GG(A, B, C, D, X[1], 3); + D = GG(D, A, B, C, X[5], 5); + C = GG(C, D, A, B, X[9], 9); + B = GG(B, C, D, A, X[13], 13); + A = GG(A, B, C, D, X[2], 3); + D = GG(D, A, B, C, X[6], 5); + C = GG(C, D, A, B, X[10], 9); + B = GG(B, C, D, A, X[14], 13); + A = GG(A, B, C, D, X[3], 3); + D = GG(D, A, B, C, X[7], 5); + C = GG(C, D, A, B, X[11], 9); + B = GG(B, C, D, A, X[15], 13); + + A = HH(A, B, C, D, X[0], 3); + D = HH(D, A, B, C, X[8], 9); + C = HH(C, D, A, B, X[4], 11); + B = HH(B, C, D, A, X[12], 15); + A = HH(A, B, C, D, X[2], 3); + D = HH(D, A, B, C, X[10], 9); + C = HH(C, D, A, B, X[6], 11); + B = HH(B, C, D, A, X[14], 15); + A = HH(A, B, C, D, X[1], 3); + D = HH(D, A, B, C, X[9], 9); + C = HH(C, D, A, B, X[5], 11); + B = HH(B, C, D, A, X[13], 15); + A = HH(A, B, C, D, X[3], 3); + D = HH(D, A, B, C, X[11], 9); + C = HH(C, D, A, B, X[7], 11); + B = HH(B, C, D, A, X[15], 15); + + context[0] += A; + context[1] += B; + context[2] += C; + context[3] += D; + } + + private static int FF(int a, int b, int c, int d, int x, int s) + { + int t = a + ((b & c) | (~b & d)) + x; + return t << s | t >>> (32 - s); + } + + private static int GG(int a, int b, int c, int d, int x, int s) + { + int t = a + ((b & (c | d)) | (c & d)) + x + 0x5A827999; + return t << s | t >>> (32 - s); + } + + private static int HH(int a, int b, int c, int d, int x, int s) + { + int t = a + (b ^ c ^ d) + x + 0x6ED9EBA1; + return t << s | t >>> (32 - s); } } }