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]);
+ }
+ }
}