--- 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. + * + *