You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2012/12/05 13:31:20 UTC

svn commit: r1417384 - in /httpcomponents/httpclient/trunk: RELEASE_NOTES.txt httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java

Author: olegk
Date: Wed Dec  5 12:31:20 2012
New Revision: 1417384

URL: http://svn.apache.org/viewvc?rev=1417384&view=rev
Log:
HTTPCLIENT-1266: NTLM engine refactoring and compatibility improvements.
Contributed by Karl Wright <DaddyWri at gmail.com>

Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java

Modified: httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/RELEASE_NOTES.txt?rev=1417384&r1=1417383&r2=1417384&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Wed Dec  5 12:31:20 2012
@@ -1,6 +1,9 @@
 Changes in trunk
 -------------------
 
+* [HTTPCLIENT-1266] NTLM engine refactoring and compatibility improvements.
+  Contributed by Karl Wright <DaddyWri at gmail.com>  
+
 * [HTTPCLIENT-1260]
   Javadoc incorrectly states StringBody(String)'s encoding uses the system default. 
   Contributed by Tim <tdhutt at gmail.com> and Gary Gregory <ggregory at apache.org> 

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java?rev=1417384&r1=1417383&r2=1417384&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java Wed Dec  5 12:31:20 2012
@@ -32,6 +32,7 @@ import java.util.Arrays;
 
 import javax.crypto.Cipher;
 import javax.crypto.spec.SecretKeySpec;
+import javax.crypto.Mac;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.util.EncodingUtils;
@@ -44,16 +45,26 @@ import org.apache.http.util.EncodingUtil
  */
 final class NTLMEngineImpl implements NTLMEngine {
 
-    // Flags we use
-    protected final static int FLAG_UNICODE_ENCODING = 0x00000001;
-    protected final static int FLAG_TARGET_DESIRED = 0x00000004;
-    protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010;
-    protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020;
-    protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200;
-    protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
-    protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000;
-    protected final static int FLAG_NEGOTIATE_128 = 0x20000000;
-    protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000;
+    // Flags we use; descriptions according to:
+    // http://davenport.sourceforge.net/ntlm.html
+    // and
+    // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx
+    protected final static int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001;      // Unicode string encoding requested
+    protected final static int FLAG_REQUEST_TARGET = 0x00000004;                      // Requests target field
+    protected final static int FLAG_REQUEST_SIGN = 0x00000010;  // Requests all messages have a signature attached, in NEGOTIATE message.
+    protected final static int FLAG_REQUEST_SEAL = 0x00000020;  // Request key exchange for message confidentiality in NEGOTIATE message.  MUST be used in conjunction with 56BIT.
+    protected final static int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080;    // Request Lan Manager key instead of user session key
+    protected final static int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security.  MUST be set in NEGOTIATE and CHALLENGE both
+    protected final static int FLAG_DOMAIN_PRESENT = 0x00001000;        // Domain is present in message
+    protected final static int FLAG_WORKSTATION_PRESENT = 0x00002000;   // Workstation is present in message
+    protected final static int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000;   // Requests a signature block on all messages.  Overridden by REQUEST_SIGN and REQUEST_SEAL.
+    protected final static int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security
+    protected final static int FLAG_REQUEST_VERSION = 0x02000000;       // Request protocol version
+    protected final static int FLAG_TARGETINFO_PRESENT = 0x00800000;    // From server in challenge message, indicating targetinfo is present
+    protected final static int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange
+    protected final static int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000;     // Request explicit key exchange
+    protected final static int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000;      // Must be used in conjunction with SEAL
+
 
     /** Secure random generator */
     private static final java.security.SecureRandom RND_GEN;
@@ -224,103 +235,285 @@ final class NTLMEngineImpl implements NT
         return rval;
     }
 
-    /** Calculate an NTLM2 challenge block */
-    private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException {
+    /** Calculate a 16-byte secondary key */
+    private static byte[] makeSecondaryKey() throws NTLMEngineException {
         if (RND_GEN == null) {
             throw new NTLMEngineException("Random generator not available");
         }
-        byte[] rval = new byte[24];
+        byte[] rval = new byte[16];
         synchronized (RND_GEN) {
             RND_GEN.nextBytes(rval);
         }
-        // 8-byte challenge, padded with zeros to 24 bytes.
-        Arrays.fill(rval, 8, 24, (byte) 0x00);
         return rval;
     }
 
-    /**
-     * Calculates the LM Response for the given challenge, using the specified
-     * password.
-     *
-     * @param password
-     *            The user's password.
-     * @param challenge
-     *            The Type 2 challenge from the server.
-     *
-     * @return The LM Response.
-     */
-    static byte[] getLMResponse(String password, byte[] challenge)
+    protected static class CipherGen {
+
+        protected final String target;
+        protected final String user;
+        protected final String password;
+        protected final byte[] challenge;
+        protected final byte[] targetInformation;
+
+        // Information we can generate but may be passed in (for testing)
+        protected byte[] clientChallenge;
+        protected byte[] secondaryKey;
+        protected byte[] timestamp;
+
+        // Stuff we always generate
+        protected byte[] lmHash = null;
+        protected byte[] lmResponse = null;
+        protected byte[] ntlmHash = null;
+        protected byte[] ntlmResponse = null;
+        protected byte[] ntlmv2Hash = null;
+        protected byte[] lmv2Response = null;
+        protected byte[] ntlmv2Blob = null;
+        protected byte[] ntlmv2Response = null;
+        protected byte[] ntlm2SessionResponse = null;
+        protected byte[] lm2SessionResponse = null;
+        protected byte[] lmUserSessionKey = null;
+        protected byte[] ntlmUserSessionKey = null;
+        protected byte[] ntlmv2UserSessionKey = null;
+        protected byte[] ntlm2SessionResponseUserSessionKey = null;
+        protected byte[] lanManagerSessionKey = null;
+
+        public CipherGen(String target, String user, String password,
+            byte[] challenge, byte[] targetInformation,
+            byte[] clientChallenge, byte[] secondaryKey, byte[] timestamp) {
+            this.target = target;
+            this.user = user;
+            this.password = password;
+            this.challenge = challenge;
+            this.targetInformation = targetInformation;
+            this.clientChallenge = clientChallenge;
+            this.secondaryKey = secondaryKey;
+            this.timestamp = timestamp;
+        }
+
+        public CipherGen(String target, String user, String password,
+            byte[] challenge, byte[] targetInformation) {
+            this(target, user, password, challenge, targetInformation, null, null, null);
+        }
+
+        /** Calculate and return client challenge */
+        public byte[] getClientChallenge()
             throws NTLMEngineException {
-        byte[] lmHash = lmHash(password);
-        return lmResponse(lmHash, challenge);
-    }
+            if (clientChallenge == null)
+                clientChallenge = makeRandomChallenge();
+            return clientChallenge;
+        }
 
-    /**
-     * Calculates the NTLM Response for the given challenge, using the specified
-     * password.
-     *
-     * @param password
-     *            The user's password.
-     * @param challenge
-     *            The Type 2 challenge from the server.
-     *
-     * @return The NTLM Response.
-     */
-    static byte[] getNTLMResponse(String password, byte[] challenge)
+        /** Calculate and return random secondary key */
+        public byte[] getSecondaryKey()
             throws NTLMEngineException {
-        byte[] ntlmHash = ntlmHash(password);
-        return lmResponse(ntlmHash, challenge);
-    }
+            if (secondaryKey == null)
+                secondaryKey = makeSecondaryKey();
+            return secondaryKey;
+        }
 
-    /**
-     * Calculates the NTLMv2 Response for the given challenge, using the
-     * specified authentication target, username, password, target information
-     * block, and client challenge.
-     *
-     * @param target
-     *            The authentication target (i.e., domain).
-     * @param user
-     *            The username.
-     * @param password
-     *            The user's password.
-     * @param targetInformation
-     *            The target information block from the Type 2 message.
-     * @param challenge
-     *            The Type 2 challenge from the server.
-     * @param clientChallenge
-     *            The random 8-byte client challenge.
-     *
-     * @return The NTLMv2 Response.
-     */
-    static byte[] getNTLMv2Response(String target, String user, String password,
-            byte[] challenge, byte[] clientChallenge, byte[] targetInformation)
+        /** Calculate and return the LMHash */
+        public byte[] getLMHash()
+            throws NTLMEngineException {
+            if (lmHash == null)
+                lmHash = lmHash(password);
+            return lmHash;
+        }
+
+        /** Calculate and return the LMResponse */
+        public byte[] getLMResponse()
+            throws NTLMEngineException {
+            if (lmResponse == null)
+                lmResponse = lmResponse(getLMHash(),challenge);
+            return lmResponse;
+        }
+
+        /** Calculate and return the NTLMHash */
+        public byte[] getNTLMHash()
+            throws NTLMEngineException {
+            if (ntlmHash == null)
+                ntlmHash = ntlmHash(password);
+            return ntlmHash;
+        }
+
+        /** Calculate and return the NTLMResponse */
+        public byte[] getNTLMResponse()
+            throws NTLMEngineException {
+            if (ntlmResponse == null)
+                ntlmResponse = lmResponse(getNTLMHash(),challenge);
+            return ntlmResponse;
+        }
+
+        /** Calculate the NTLMv2 hash */
+        public byte[] getNTLMv2Hash()
+            throws NTLMEngineException {
+            if (ntlmv2Hash == null)
+                ntlmv2Hash = ntlmv2Hash(target, user, password);
+            return ntlmv2Hash;
+        }
+
+        /** Calculate a timestamp */
+        public byte[] getTimestamp() {
+            if (timestamp == null) {
+                long time = System.currentTimeMillis();
+                time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
+                time *= 10000; // tenths of a microsecond.
+                // convert to little-endian byte array.
+                timestamp = new byte[8];
+                for (int i = 0; i < 8; i++) {
+                    timestamp[i] = (byte) time;
+                    time >>>= 8;
+                }
+            }
+            return timestamp;
+        }
+
+        /** Calculate the NTLMv2Blob */
+        public byte[] getNTLMv2Blob()
+            throws NTLMEngineException {
+            if (ntlmv2Blob == null)
+                ntlmv2Blob = createBlob(getClientChallenge(), targetInformation, getTimestamp());
+            return ntlmv2Blob;
+        }
+
+        /** Calculate the NTLMv2Response */
+        public byte[] getNTLMv2Response()
+            throws NTLMEngineException {
+            if (ntlmv2Response == null)
+                ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob());
+            return ntlmv2Response;
+        }
+
+        /** Calculate the LMv2Response */
+        public byte[] getLMv2Response()
             throws NTLMEngineException {
-        byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
-        byte[] blob = createBlob(clientChallenge, targetInformation);
-        return lmv2Response(ntlmv2Hash, challenge, blob);
+            if (lmv2Response == null)
+                lmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getClientChallenge());
+            return lmv2Response;
+        }
+
+        /** Get NTLM2SessionResponse */
+        public byte[] getNTLM2SessionResponse()
+            throws NTLMEngineException {
+            if (ntlm2SessionResponse == null)
+                ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge());
+            return ntlm2SessionResponse;
+        }
+
+        /** Calculate and return LM2 session response */
+        public byte[] getLM2SessionResponse()
+            throws NTLMEngineException {
+            if (lm2SessionResponse == null) {
+                byte[] clientChallenge = getClientChallenge();
+                lm2SessionResponse = new byte[24];
+                System.arraycopy(clientChallenge, 0, lm2SessionResponse, 0, clientChallenge.length);
+                Arrays.fill(lm2SessionResponse, clientChallenge.length, lm2SessionResponse.length, (byte) 0x00);
+            }
+            return lm2SessionResponse;
+        }
+
+        /** Get LMUserSessionKey */
+        public byte[] getLMUserSessionKey()
+            throws NTLMEngineException {
+            if (lmUserSessionKey == null) {
+                byte[] lmHash = getLMHash();
+                lmUserSessionKey = new byte[16];
+                System.arraycopy(lmHash, 0, lmUserSessionKey, 0, 8);
+                Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00);
+            }
+            return lmUserSessionKey;
+        }
+
+        /** Get NTLMUserSessionKey */
+        public byte[] getNTLMUserSessionKey()
+            throws NTLMEngineException {
+            if (ntlmUserSessionKey == null) {
+                byte[] ntlmHash = getNTLMHash();
+                MD4 md4 = new MD4();
+                md4.update(ntlmHash);
+                ntlmUserSessionKey = md4.getOutput();
+            }
+            return ntlmUserSessionKey;
+        }
+
+        /** GetNTLMv2UserSessionKey */
+        public byte[] getNTLMv2UserSessionKey()
+            throws NTLMEngineException {
+            if (ntlmv2UserSessionKey == null) {
+                byte[] ntlmv2Hash = getNTLMv2Hash();
+                byte[] ntlmv2Blob = getNTLMv2Blob();
+                byte[] temp = new byte[ntlmv2Blob.length + challenge.length];
+                // "The challenge is concatenated with the blob" - check this (MHL)
+                System.arraycopy(challenge, 0, temp, 0, challenge.length);
+                System.arraycopy(ntlmv2Blob, 0, temp, challenge.length, ntlmv2Blob.length);
+                byte[] partial = hmacMD5(temp,ntlmv2Hash);
+                ntlmv2UserSessionKey = hmacMD5(partial,ntlmv2Hash);
+            }
+            return ntlmv2UserSessionKey;
+        }
+
+        /** Get NTLM2SessionResponseUserSessionKey */
+        public byte[] getNTLM2SessionResponseUserSessionKey()
+            throws NTLMEngineException {
+            if (ntlm2SessionResponseUserSessionKey == null) {
+                byte[] ntlmUserSessionKey = getNTLMUserSessionKey();
+                byte[] ntlm2SessionResponseNonce = getLM2SessionResponse();
+                byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length];
+                System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length);
+                System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length);
+                ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce,ntlmUserSessionKey);
+            }
+            return ntlm2SessionResponseUserSessionKey;
+        }
+
+        /** Get LAN Manager session key */
+        public byte[] getLanManagerSessionKey()
+            throws NTLMEngineException {
+            if (lanManagerSessionKey == null) {
+                byte[] lmHash = getLMHash();
+                byte[] lmResponse = getLMResponse();
+                try {
+                    byte[] keyBytes = new byte[14];
+                    System.arraycopy(lmHash, 0, keyBytes, 0, lmHash.length);
+                    Arrays.fill(keyBytes, lmHash.length, keyBytes.length, (byte)0xbd);
+                    Key lowKey = createDESKey(keyBytes, 0);
+                    Key highKey = createDESKey(keyBytes, 7);
+                    byte[] truncatedResponse = new byte[8];
+                    System.arraycopy(lmResponse, 0, truncatedResponse, 0, truncatedResponse.length);
+                    Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
+                    des.init(Cipher.ENCRYPT_MODE, lowKey);
+                    byte[] lowPart = des.doFinal(truncatedResponse);
+                    des = Cipher.getInstance("DES/ECB/NoPadding");
+                    des.init(Cipher.ENCRYPT_MODE, highKey);
+                    byte[] highPart = des.doFinal(truncatedResponse);
+                    lanManagerSessionKey = new byte[16];
+                    System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length);
+                    System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length);
+                } catch (Exception e) {
+                    throw new NTLMEngineException(e.getMessage(), e);
+                }
+            }
+            return lanManagerSessionKey;
+        }
     }
 
-    /**
-     * Calculates the LMv2 Response for the given challenge, using the specified
-     * authentication target, username, password, and client challenge.
-     *
-     * @param target
-     *            The authentication target (i.e., domain).
-     * @param user
-     *            The username.
-     * @param password
-     *            The user's password.
-     * @param challenge
-     *            The Type 2 challenge from the server.
-     * @param clientChallenge
-     *            The random 8-byte client challenge.
-     *
-     * @return The LMv2 Response.
-     */
-    static byte[] getLMv2Response(String target, String user, String password,
-            byte[] challenge, byte[] clientChallenge) throws NTLMEngineException {
-        byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
-        return lmv2Response(ntlmv2Hash, challenge, clientChallenge);
+    /** Calculates HMAC-MD5 */
+    static byte[] hmacMD5(byte[] value, byte[] key)
+        throws NTLMEngineException {
+        HMACMD5 hmacMD5 = new HMACMD5(key);
+        hmacMD5.update(value);
+        return hmacMD5.getOutput();
+    }
+
+    /** Calculates RC4 */
+    static byte[] RC4(byte[] value, byte[] key)
+        throws NTLMEngineException {
+        try {
+            Cipher rc4 = Cipher.getInstance("RC4");
+            rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4"));
+            return rc4.doFinal(value);
+        } catch (Exception e) {
+            throw new NTLMEngineException(e.getMessage(), e);
+        }
     }
 
     /**
@@ -338,11 +531,9 @@ final class NTLMEngineImpl implements NT
      *         field of the Type 3 message; the LM response field contains the
      *         client challenge, null-padded to 24 bytes.
      */
-    static byte[] getNTLM2SessionResponse(String password, byte[] challenge,
+    static byte[] ntlm2SessionResponse(byte[] ntlmHash, byte[] challenge,
             byte[] clientChallenge) throws NTLMEngineException {
         try {
-            byte[] ntlmHash = ntlmHash(password);
-
             // Look up MD5 algorithm (was necessary on jdk 1.4.2)
             // This used to be needed, but java 1.5.0_07 includes the MD5
             // algorithm (finally)
@@ -521,21 +712,13 @@ final class NTLMEngineImpl implements NT
      *
      * @return The blob, used in the calculation of the NTLMv2 Response.
      */
-    private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) {
+    private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation, byte[] timestamp) {
         byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
         byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
         byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
-        long time = System.currentTimeMillis();
-        time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
-        time *= 10000; // tenths of a microsecond.
-        // convert to little-endian byte array.
-        byte[] timestamp = new byte[8];
-        for (int i = 0; i < 8; i++) {
-            timestamp[i] = (byte) time;
-            time >>>= 8;
-        }
+        byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
         byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8
-                + unknown1.length + targetInformation.length];
+                + unknown1.length + targetInformation.length + unknown2.length];
         int offset = 0;
         System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
         offset += blobSignature.length;
@@ -548,6 +731,9 @@ final class NTLMEngineImpl implements NT
         System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
         offset += unknown1.length;
         System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length);
+        offset += targetInformation.length;
+        System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
+        offset += unknown2.length;
         return blob;
     }
 
@@ -780,40 +966,67 @@ final class NTLMEngineImpl implements NT
         String getResponse() {
             // Now, build the message. Calculate its length first, including
             // signature or type.
-            int finalLength = 32 + hostBytes.length + domainBytes.length;
+            int finalLength = 32 + 8 + hostBytes.length + domainBytes.length;
 
             // Set up the response. This will initialize the signature, message
             // type, and flags.
             prepareResponse(finalLength, 1);
 
             // Flags. These are the complete set of flags we support.
-            addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN
-                    | FLAG_NEGOTIATE_SEAL |
-                    /*
-                     * FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH |
-                     */
-                    FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128);
+            addULong(
+                    FLAG_WORKSTATION_PRESENT |
+                    FLAG_DOMAIN_PRESENT |
+
+                    // Required flags
+                    //FLAG_REQUEST_LAN_MANAGER_KEY |
+                    FLAG_REQUEST_NTLMv1 |
+                    FLAG_REQUEST_NTLM2_SESSION |
+
+                    // Protocol version request
+                    FLAG_REQUEST_VERSION |
+
+                    // Recommended privacy settings
+                    //FLAG_REQUEST_ALWAYS_SIGN |
+                    //FLAG_REQUEST_SEAL |
+                    //FLAG_REQUEST_SIGN |
+
+                    // These must be set according to documentation, based on use of SEAL above
+                    //FLAG_REQUEST_128BIT_KEY_EXCH |
+                    //FLAG_REQUEST_56BIT_ENCRYPTION |
+                    //FLAG_REQUEST_EXPLICIT_KEY_EXCH |
+
+                    FLAG_REQUEST_UNICODE_ENCODING |
+                    FLAG_REQUEST_TARGET);
 
             // Domain length (two times).
             addUShort(domainBytes.length);
             addUShort(domainBytes.length);
 
             // Domain offset.
-            addULong(hostBytes.length + 32);
+            addULong(hostBytes.length + 32 + 8);
 
             // Host length (two times).
             addUShort(hostBytes.length);
             addUShort(hostBytes.length);
 
-            // Host offset (always 32).
-            addULong(32);
+            // Host offset (always 32 + 8).
+            addULong(32 + 8);
 
-            // Host String.
+            // Version
+            addUShort(0x0105);
+            // Build
+            addULong(2600);
+            // NTLM revision
+            addUShort(15);
+
+
+            // Host (workstation) String.
             addBytes(hostBytes);
 
             // Domain String.
             addBytes(domainBytes);
 
+
             return super.getResponse();
         }
 
@@ -829,16 +1042,31 @@ final class NTLMEngineImpl implements NT
         Type2Message(String message) throws NTLMEngineException {
             super(message, 2);
 
+            // Type 2 message is laid out as follows:
+            // First 8 bytes: NTLMSSP[0]
+            // Next 4 bytes: Ulong, value 2
+            // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset)
+            // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235
+            // Next 8 bytes, starting at offset 24: Challenge
+            // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros)
+            // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset)
+            // Next 2 bytes, major/minor version number (e.g. 0x05 0x02)
+            // Next 8 bytes, build number
+            // Next 2 bytes, protocol version number (e.g. 0x00 0x0f)
+            // Next, various text fields, and a ushort of value 0 at the end
+
             // Parse out the rest of the info we need from the message
             // The nonce is the 8 bytes starting from the byte in position 24.
             challenge = new byte[8];
             readBytes(challenge, 24);
 
             flags = readULong(20);
-            if ((flags & FLAG_UNICODE_ENCODING) == 0)
+
+            if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0)
                 throw new NTLMEngineException(
                         "NTLM type 2 message has flags that make no sense: "
                                 + Integer.toString(flags));
+
             // Do the target!
             target = null;
             // The TARGET_DESIRED flag is said to not have understood semantics
@@ -899,6 +1127,8 @@ final class NTLMEngineImpl implements NT
 
         protected byte[] lmResp;
         protected byte[] ntResp;
+        protected byte[] sessionKey;
+
 
         /** Constructor. Pass the arguments we will need */
         Type3Message(String domain, String host, String user, String password, byte[] nonce,
@@ -912,38 +1142,62 @@ final class NTLMEngineImpl implements NT
             // Use only the base domain name!
             domain = convertDomain(domain);
 
+            // Create a cipher generator class
+            CipherGen gen = new CipherGen(target, user, password, nonce, targetInformation);
+
             // Use the new code to calculate the responses, including v2 if that
             // seems warranted.
+            byte[] userSessionKey;
             try {
-                if (targetInformation != null && target != null) {
-                    byte[] clientChallenge = makeRandomChallenge();
-                    ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge,
-                            targetInformation);
-                    lmResp = getLMv2Response(target, user, password, nonce, clientChallenge);
+                if (((type2Flags & FLAG_REQUEST_NTLM2_SESSION) == 0) &&
+                    ((type2Flags & FLAG_REQUEST_NTLMv1) == 0) &&
+                    targetInformation != null && target != null) {
+                    // NTLMv2
+                    ntResp = gen.getNTLMv2Response();
+                    lmResp = gen.getLMv2Response();
+                    if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
+                        userSessionKey = gen.getLanManagerSessionKey();
+                    else
+                        userSessionKey = gen.getNTLMv2UserSessionKey();
                 } else {
-                    if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) {
+                    // NTLMv1
+                    if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
                         // NTLM2 session stuff is requested
-                        byte[] clientChallenge = makeNTLM2RandomChallenge();
-
-                        ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge);
-                        lmResp = clientChallenge;
-
+                        ntResp = gen.getNTLM2SessionResponse();
+                        lmResp = gen.getLM2SessionResponse();
+                        if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
+                            userSessionKey = gen.getLanManagerSessionKey();
+                        else
+                            userSessionKey = gen.getNTLM2SessionResponseUserSessionKey();
                         // All the other flags we send (signing, sealing, key
                         // exchange) are supported, but they don't do anything
                         // at all in an
                         // NTLM2 context! So we're done at this point.
                     } else {
-                        ntResp = getNTLMResponse(password, nonce);
-                        lmResp = getLMResponse(password, nonce);
+                        ntResp = gen.getNTLMResponse();
+                        lmResp = gen.getLMResponse();
+                        if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
+                            userSessionKey = gen.getLanManagerSessionKey();
+                        else
+                            userSessionKey = gen.getNTLMUserSessionKey();
                     }
                 }
             } catch (NTLMEngineException e) {
                 // This likely means we couldn't find the MD4 hash algorithm -
                 // fail back to just using LM
                 ntResp = new byte[0];
-                lmResp = getLMResponse(password, nonce);
+                lmResp = gen.getLMResponse();
+                if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
+                    userSessionKey = gen.getLanManagerSessionKey();
+                else
+                    userSessionKey = gen.getLMUserSessionKey();
             }
 
+            if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0)
+                sessionKey = RC4(gen.getSecondaryKey(), userSessionKey);
+            else
+                sessionKey = null;
+
             try {
                 domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
                 hostBytes = host.getBytes("UnicodeLittleUnmarked");
@@ -962,15 +1216,20 @@ final class NTLMEngineImpl implements NT
             int domainLen = domainBytes.length;
             int hostLen = hostBytes.length;
             int userLen = userBytes.length;
+            int sessionKeyLen;
+            if (sessionKey != null)
+                sessionKeyLen = sessionKey.length;
+            else
+                sessionKeyLen = 0;
 
             // Calculate the layout within the packet
-            int lmRespOffset = 64;
+            int lmRespOffset = 64 + 8;  // allocate space for the version
             int ntRespOffset = lmRespOffset + lmRespLen;
             int domainOffset = ntRespOffset + ntRespLen;
             int userOffset = domainOffset + domainLen;
             int hostOffset = userOffset + userLen;
             int sessionKeyOffset = hostOffset + hostLen;
-            int finalLength = sessionKeyOffset + 0;
+            int finalLength = sessionKeyOffset + sessionKeyLen;
 
             // Start the response. Length includes signature and type
             prepareResponse(finalLength, 3);
@@ -1010,19 +1269,50 @@ final class NTLMEngineImpl implements NT
             // Host offset
             addULong(hostOffset);
 
-            // 4 bytes of zeros - not sure what this is
-            addULong(0);
+            // Session key length (twice)
+            addUShort(sessionKeyLen);
+            addUShort(sessionKeyLen);
+
+            // Session key offset
+            addULong(sessionKeyOffset);
 
             // Message length
             addULong(finalLength);
 
-            // Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING +
+            // Flags. Currently: WORKSTATION_PRESENT + DOMAIN_PRESENT + UNICODE_ENCODING +
             // TARGET_DESIRED + NEGOTIATE_128
-            addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED
-                    | FLAG_NEGOTIATE_128 | (type2Flags & FLAG_NEGOTIATE_NTLM2)
-                    | (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL)
-                    | (type2Flags & FLAG_NEGOTIATE_KEY_EXCH)
-                    | (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN));
+            addULong(
+                    FLAG_WORKSTATION_PRESENT |
+                    FLAG_DOMAIN_PRESENT |
+
+                    // Required flags
+                    (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) |
+                    (type2Flags & FLAG_REQUEST_NTLMv1) |
+                    (type2Flags & FLAG_REQUEST_NTLM2_SESSION) |
+
+                    // Protocol version request
+                    FLAG_REQUEST_VERSION |
+
+                    // Recommended privacy settings
+                    (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) |
+                    (type2Flags & FLAG_REQUEST_SEAL) |
+                    (type2Flags & FLAG_REQUEST_SIGN) |
+
+                    // These must be set according to documentation, based on use of SEAL above
+                    (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) |
+                    (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) |
+                    (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) |
+
+                    FLAG_REQUEST_UNICODE_ENCODING |
+                    FLAG_REQUEST_TARGET
+            );
+
+            // Version
+            addUShort(0x0105);
+            // Build
+            addULong(2600);
+            // NTLM revision
+            addUShort(15);
 
             // Add the actual data
             addBytes(lmResp);
@@ -1030,6 +1320,8 @@ final class NTLMEngineImpl implements NT
             addBytes(domainBytes);
             addBytes(userBytes);
             addBytes(hostBytes);
+            if (sessionKey != null)
+                addBytes(sessionKey);
 
             return super.getResponse();
         }

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java?rev=1417384&r1=1417383&r2=1417384&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java Wed Dec  5 12:31:20 2012
@@ -27,6 +27,7 @@
 package org.apache.http.impl.auth;
 
 import org.junit.Test;
+import org.junit.Assert;
 
 public class TestNTLMEngineImpl {
 
@@ -45,18 +46,18 @@ public class TestNTLMEngineImpl {
     }
 
     /* Test suite helper */
-    static byte checkToNibble(char c) {
+    static byte toNibble(char c) {
         if (c >= 'a' && c <= 'f')
             return (byte) (c - 'a' + 0x0a);
         return (byte) (c - '0');
     }
 
     /* Test suite helper */
-    static byte[] checkToBytes(String hex) {
+    static byte[] toBytes(String hex) {
         byte[] rval = new byte[hex.length() / 2];
         int i = 0;
         while (i < rval.length) {
-            rval[i] = (byte) ((checkToNibble(hex.charAt(i * 2)) << 4) | (checkToNibble(hex
+            rval[i] = (byte) ((toNibble(hex.charAt(i * 2)) << 4) | (toNibble(hex
                     .charAt(i * 2 + 1))));
             i++;
         }
@@ -69,7 +70,7 @@ public class TestNTLMEngineImpl {
         md4 = new NTLMEngineImpl.MD4();
         md4.update(input.getBytes("ASCII"));
         byte[] answer = md4.getOutput();
-        byte[] correctAnswer = checkToBytes(hexOutput);
+        byte[] correctAnswer = toBytes(hexOutput);
         if (answer.length != correctAnswer.length)
             throw new Exception("Answer length disagrees for MD4('" + input + "')");
         int i = 0;
@@ -81,4 +82,133 @@ public class TestNTLMEngineImpl {
         }
     }
 
+    @Test
+    public void testLMResponse() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            null,
+            null,
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            null,
+            null,
+            null,
+            null);
+
+        checkArraysMatch(toBytes("c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"),
+            gen.getLMResponse());
+    }
+
+    @Test
+    public void testNTLMResponse() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            null,
+            null,
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            null,
+            null,
+            null,
+            null);
+
+        checkArraysMatch(toBytes("25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"),
+            gen.getNTLMResponse());
+    }
+
+    @Test
+    public void testLMv2Response() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            "DOMAIN",
+            "user",
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            null,
+            toBytes("ffffff0011223344"),
+            null,
+            null);
+
+        checkArraysMatch(toBytes("d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"),
+            gen.getLMv2Response());
+    }
+
+    @Test
+    public void testNTLMv2Response() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            "DOMAIN",
+            "user",
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"),
+            toBytes("ffffff0011223344"),
+            null,
+            toBytes("0090d336b734c301"));
+
+        checkArraysMatch(toBytes("01010000000000000090d336b734c301ffffff00112233440000000002000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d000000000000000000"),
+            gen.getNTLMv2Blob());
+        checkArraysMatch(toBytes("cbabbca713eb795d04c97abc01ee498301010000000000000090d336b734c301ffffff00112233440000000002000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d000000000000000000"),
+            gen.getNTLMv2Response());
+    }
+
+    @Test
+    public void testLM2SessionResponse() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            "DOMAIN",
+            "user",
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"),
+            toBytes("ffffff0011223344"),
+            null,
+            toBytes("0090d336b734c301"));
+
+        checkArraysMatch(toBytes("ffffff001122334400000000000000000000000000000000"),
+            gen.getLM2SessionResponse());
+    }
+
+    @Test
+    public void testNTLM2SessionResponse() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            "DOMAIN",
+            "user",
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"),
+            toBytes("ffffff0011223344"),
+            null,
+            toBytes("0090d336b734c301"));
+
+        checkArraysMatch(toBytes("10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"),
+            gen.getNTLM2SessionResponse());
+    }
+
+    @Test
+    public void testNTLMUserSessionKey() throws Exception {
+        NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+            "DOMAIN",
+            "user",
+            "SecREt01",
+            toBytes("0123456789abcdef"),
+            toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"),
+            toBytes("ffffff0011223344"),
+            null,
+            toBytes("0090d336b734c301"));
+
+        checkArraysMatch(toBytes("3f373ea8e4af954f14faa506f8eebdc4"),
+            gen.getNTLMUserSessionKey());
+    }
+
+    @Test
+    public void testRC4() throws Exception {
+        checkArraysMatch(toBytes("e37f97f2544f4d7e"),
+            NTLMEngineImpl.RC4(toBytes("0a003602317a759a"),
+                toBytes("2785f595293f3e2813439d73a223810d")));
+    }
+
+    /* Byte array check helper */
+    static void checkArraysMatch(byte[] a1, byte[] a2)
+        throws Exception {
+        Assert.assertEquals(a1.length,a2.length);
+        for (int i = 0; i < a1.length; i++) {
+            Assert.assertEquals(a1[i],a2[i]);
+        }
+    }
 }