HTTPCLIENT-1266: NTLM engine refactoring and compatibility improvements.

Contributed by Karl Wright <DaddyWri at gmail.com>

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1417384 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2012-12-05 12:31:20 +00:00
parent 9b6aeffa80
commit 264d2038ea
3 changed files with 572 additions and 147 deletions

View File

@ -1,6 +1,9 @@
Changes in trunk Changes in trunk
------------------- -------------------
* [HTTPCLIENT-1266] NTLM engine refactoring and compatibility improvements.
Contributed by Karl Wright <DaddyWri at gmail.com>
* [HTTPCLIENT-1260] * [HTTPCLIENT-1260]
Javadoc incorrectly states StringBody(String)'s encoding uses the system default. 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> Contributed by Tim <tdhutt at gmail.com> and Gary Gregory <ggregory at apache.org>

View File

@ -32,6 +32,7 @@ import java.util.Arrays;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.http.util.EncodingUtils; import org.apache.http.util.EncodingUtils;
@ -44,16 +45,26 @@ import org.apache.http.util.EncodingUtils;
*/ */
final class NTLMEngineImpl implements NTLMEngine { final class NTLMEngineImpl implements NTLMEngine {
// Flags we use // Flags we use; descriptions according to:
protected final static int FLAG_UNICODE_ENCODING = 0x00000001; // http://davenport.sourceforge.net/ntlm.html
protected final static int FLAG_TARGET_DESIRED = 0x00000004; // and
protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010; // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx
protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020; protected final static int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested
protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200; protected final static int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field
protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; protected final static int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message.
protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000; 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_NEGOTIATE_128 = 0x20000000; protected final static int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key
protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000; 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 */ /** Secure random generator */
private static final java.security.SecureRandom RND_GEN; private static final java.security.SecureRandom RND_GEN;
@ -224,103 +235,285 @@ final class NTLMEngineImpl implements NTLMEngine {
return rval; return rval;
} }
/** Calculate an NTLM2 challenge block */ /** Calculate a 16-byte secondary key */
private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException { private static byte[] makeSecondaryKey() throws NTLMEngineException {
if (RND_GEN == null) { if (RND_GEN == null) {
throw new NTLMEngineException("Random generator not available"); throw new NTLMEngineException("Random generator not available");
} }
byte[] rval = new byte[24]; byte[] rval = new byte[16];
synchronized (RND_GEN) { synchronized (RND_GEN) {
RND_GEN.nextBytes(rval); RND_GEN.nextBytes(rval);
} }
// 8-byte challenge, padded with zeros to 24 bytes.
Arrays.fill(rval, 8, 24, (byte) 0x00);
return rval; return rval;
} }
/** protected static class CipherGen {
* Calculates the LM Response for the given challenge, using the specified
* password. protected final String target;
* protected final String user;
* @param password protected final String password;
* The user's password. protected final byte[] challenge;
* @param challenge protected final byte[] targetInformation;
* The Type 2 challenge from the server.
* // Information we can generate but may be passed in (for testing)
* @return The LM Response. protected byte[] clientChallenge;
*/ protected byte[] secondaryKey;
static byte[] getLMResponse(String password, byte[] challenge) 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 { throws NTLMEngineException {
byte[] lmHash = lmHash(password); if (clientChallenge == null)
return lmResponse(lmHash, challenge); clientChallenge = makeRandomChallenge();
return clientChallenge;
}
/** Calculate and return random secondary key */
public byte[] getSecondaryKey()
throws NTLMEngineException {
if (secondaryKey == null)
secondaryKey = makeSecondaryKey();
return secondaryKey;
}
/** 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 {
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 HMAC-MD5 */
* Calculates the NTLM Response for the given challenge, using the specified static byte[] hmacMD5(byte[] value, byte[] key)
* password. throws NTLMEngineException {
* HMACMD5 hmacMD5 = new HMACMD5(key);
* @param password hmacMD5.update(value);
* The user's password. return hmacMD5.getOutput();
* @param challenge
* The Type 2 challenge from the server.
*
* @return The NTLM Response.
*/
static byte[] getNTLMResponse(String password, byte[] challenge)
throws NTLMEngineException {
byte[] ntlmHash = ntlmHash(password);
return lmResponse(ntlmHash, challenge);
} }
/** /** Calculates RC4 */
* Calculates the NTLMv2 Response for the given challenge, using the static byte[] RC4(byte[] value, byte[] key)
* specified authentication target, username, password, target information throws NTLMEngineException {
* block, and client challenge. try {
* Cipher rc4 = Cipher.getInstance("RC4");
* @param target rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4"));
* The authentication target (i.e., domain). return rc4.doFinal(value);
* @param user } catch (Exception e) {
* The username. throw new NTLMEngineException(e.getMessage(), e);
* @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)
throws NTLMEngineException {
byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
byte[] blob = createBlob(clientChallenge, targetInformation);
return lmv2Response(ntlmv2Hash, challenge, blob);
}
/**
* 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);
} }
/** /**
@ -338,11 +531,9 @@ final class NTLMEngineImpl implements NTLMEngine {
* field of the Type 3 message; the LM response field contains the * field of the Type 3 message; the LM response field contains the
* client challenge, null-padded to 24 bytes. * 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 { byte[] clientChallenge) throws NTLMEngineException {
try { try {
byte[] ntlmHash = ntlmHash(password);
// Look up MD5 algorithm (was necessary on jdk 1.4.2) // 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 // This used to be needed, but java 1.5.0_07 includes the MD5
// algorithm (finally) // algorithm (finally)
@ -521,21 +712,13 @@ final class NTLMEngineImpl implements NTLMEngine {
* *
* @return The blob, used in the calculation of the NTLMv2 Response. * @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[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (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 }; byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
long time = System.currentTimeMillis(); byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
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[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8
+ unknown1.length + targetInformation.length]; + unknown1.length + targetInformation.length + unknown2.length];
int offset = 0; int offset = 0;
System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
offset += blobSignature.length; offset += blobSignature.length;
@ -548,6 +731,9 @@ final class NTLMEngineImpl implements NTLMEngine {
System.arraycopy(unknown1, 0, blob, offset, unknown1.length); System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
offset += unknown1.length; offset += unknown1.length;
System.arraycopy(targetInformation, 0, blob, offset, targetInformation.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; return blob;
} }
@ -780,40 +966,67 @@ final class NTLMEngineImpl implements NTLMEngine {
String getResponse() { String getResponse() {
// Now, build the message. Calculate its length first, including // Now, build the message. Calculate its length first, including
// signature or type. // 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 // Set up the response. This will initialize the signature, message
// type, and flags. // type, and flags.
prepareResponse(finalLength, 1); prepareResponse(finalLength, 1);
// Flags. These are the complete set of flags we support. // Flags. These are the complete set of flags we support.
addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN addULong(
| FLAG_NEGOTIATE_SEAL | FLAG_WORKSTATION_PRESENT |
/* FLAG_DOMAIN_PRESENT |
* FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH |
*/ // Required flags
FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128); //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). // Domain length (two times).
addUShort(domainBytes.length); addUShort(domainBytes.length);
addUShort(domainBytes.length); addUShort(domainBytes.length);
// Domain offset. // Domain offset.
addULong(hostBytes.length + 32); addULong(hostBytes.length + 32 + 8);
// Host length (two times). // Host length (two times).
addUShort(hostBytes.length); addUShort(hostBytes.length);
addUShort(hostBytes.length); addUShort(hostBytes.length);
// Host offset (always 32). // Host offset (always 32 + 8).
addULong(32); addULong(32 + 8);
// Host String. // Version
addUShort(0x0105);
// Build
addULong(2600);
// NTLM revision
addUShort(15);
// Host (workstation) String.
addBytes(hostBytes); addBytes(hostBytes);
// Domain String. // Domain String.
addBytes(domainBytes); addBytes(domainBytes);
return super.getResponse(); return super.getResponse();
} }
@ -829,16 +1042,31 @@ final class NTLMEngineImpl implements NTLMEngine {
Type2Message(String message) throws NTLMEngineException { Type2Message(String message) throws NTLMEngineException {
super(message, 2); 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 // 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. // The nonce is the 8 bytes starting from the byte in position 24.
challenge = new byte[8]; challenge = new byte[8];
readBytes(challenge, 24); readBytes(challenge, 24);
flags = readULong(20); flags = readULong(20);
if ((flags & FLAG_UNICODE_ENCODING) == 0)
if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0)
throw new NTLMEngineException( throw new NTLMEngineException(
"NTLM type 2 message has flags that make no sense: " "NTLM type 2 message has flags that make no sense: "
+ Integer.toString(flags)); + Integer.toString(flags));
// Do the target! // Do the target!
target = null; target = null;
// The TARGET_DESIRED flag is said to not have understood semantics // The TARGET_DESIRED flag is said to not have understood semantics
@ -899,6 +1127,8 @@ final class NTLMEngineImpl implements NTLMEngine {
protected byte[] lmResp; protected byte[] lmResp;
protected byte[] ntResp; protected byte[] ntResp;
protected byte[] sessionKey;
/** Constructor. Pass the arguments we will need */ /** Constructor. Pass the arguments we will need */
Type3Message(String domain, String host, String user, String password, byte[] nonce, Type3Message(String domain, String host, String user, String password, byte[] nonce,
@ -912,38 +1142,62 @@ final class NTLMEngineImpl implements NTLMEngine {
// Use only the base domain name! // Use only the base domain name!
domain = convertDomain(domain); 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 // Use the new code to calculate the responses, including v2 if that
// seems warranted. // seems warranted.
byte[] userSessionKey;
try { try {
if (targetInformation != null && target != null) { if (((type2Flags & FLAG_REQUEST_NTLM2_SESSION) == 0) &&
byte[] clientChallenge = makeRandomChallenge(); ((type2Flags & FLAG_REQUEST_NTLMv1) == 0) &&
ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge, targetInformation != null && target != null) {
targetInformation); // NTLMv2
lmResp = getLMv2Response(target, user, password, nonce, clientChallenge); ntResp = gen.getNTLMv2Response();
lmResp = gen.getLMv2Response();
if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
userSessionKey = gen.getLanManagerSessionKey();
else
userSessionKey = gen.getNTLMv2UserSessionKey();
} else { } else {
if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) { // NTLMv1
if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
// NTLM2 session stuff is requested // NTLM2 session stuff is requested
byte[] clientChallenge = makeNTLM2RandomChallenge(); ntResp = gen.getNTLM2SessionResponse();
lmResp = gen.getLM2SessionResponse();
ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge); if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
lmResp = clientChallenge; userSessionKey = gen.getLanManagerSessionKey();
else
userSessionKey = gen.getNTLM2SessionResponseUserSessionKey();
// All the other flags we send (signing, sealing, key // All the other flags we send (signing, sealing, key
// exchange) are supported, but they don't do anything // exchange) are supported, but they don't do anything
// at all in an // at all in an
// NTLM2 context! So we're done at this point. // NTLM2 context! So we're done at this point.
} else { } else {
ntResp = getNTLMResponse(password, nonce); ntResp = gen.getNTLMResponse();
lmResp = getLMResponse(password, nonce); lmResp = gen.getLMResponse();
if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0)
userSessionKey = gen.getLanManagerSessionKey();
else
userSessionKey = gen.getNTLMUserSessionKey();
} }
} }
} catch (NTLMEngineException e) { } catch (NTLMEngineException e) {
// This likely means we couldn't find the MD4 hash algorithm - // This likely means we couldn't find the MD4 hash algorithm -
// fail back to just using LM // fail back to just using LM
ntResp = new byte[0]; 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 { try {
domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
hostBytes = host.getBytes("UnicodeLittleUnmarked"); hostBytes = host.getBytes("UnicodeLittleUnmarked");
@ -962,15 +1216,20 @@ final class NTLMEngineImpl implements NTLMEngine {
int domainLen = domainBytes.length; int domainLen = domainBytes.length;
int hostLen = hostBytes.length; int hostLen = hostBytes.length;
int userLen = userBytes.length; int userLen = userBytes.length;
int sessionKeyLen;
if (sessionKey != null)
sessionKeyLen = sessionKey.length;
else
sessionKeyLen = 0;
// Calculate the layout within the packet // Calculate the layout within the packet
int lmRespOffset = 64; int lmRespOffset = 64 + 8; // allocate space for the version
int ntRespOffset = lmRespOffset + lmRespLen; int ntRespOffset = lmRespOffset + lmRespLen;
int domainOffset = ntRespOffset + ntRespLen; int domainOffset = ntRespOffset + ntRespLen;
int userOffset = domainOffset + domainLen; int userOffset = domainOffset + domainLen;
int hostOffset = userOffset + userLen; int hostOffset = userOffset + userLen;
int sessionKeyOffset = hostOffset + hostLen; int sessionKeyOffset = hostOffset + hostLen;
int finalLength = sessionKeyOffset + 0; int finalLength = sessionKeyOffset + sessionKeyLen;
// Start the response. Length includes signature and type // Start the response. Length includes signature and type
prepareResponse(finalLength, 3); prepareResponse(finalLength, 3);
@ -1010,19 +1269,50 @@ final class NTLMEngineImpl implements NTLMEngine {
// Host offset // Host offset
addULong(hostOffset); addULong(hostOffset);
// 4 bytes of zeros - not sure what this is // Session key length (twice)
addULong(0); addUShort(sessionKeyLen);
addUShort(sessionKeyLen);
// Session key offset
addULong(sessionKeyOffset);
// Message length // Message length
addULong(finalLength); addULong(finalLength);
// Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING + // Flags. Currently: WORKSTATION_PRESENT + DOMAIN_PRESENT + UNICODE_ENCODING +
// TARGET_DESIRED + NEGOTIATE_128 // TARGET_DESIRED + NEGOTIATE_128
addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED addULong(
| FLAG_NEGOTIATE_128 | (type2Flags & FLAG_NEGOTIATE_NTLM2) FLAG_WORKSTATION_PRESENT |
| (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL) FLAG_DOMAIN_PRESENT |
| (type2Flags & FLAG_NEGOTIATE_KEY_EXCH)
| (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN)); // 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 // Add the actual data
addBytes(lmResp); addBytes(lmResp);
@ -1030,6 +1320,8 @@ final class NTLMEngineImpl implements NTLMEngine {
addBytes(domainBytes); addBytes(domainBytes);
addBytes(userBytes); addBytes(userBytes);
addBytes(hostBytes); addBytes(hostBytes);
if (sessionKey != null)
addBytes(sessionKey);
return super.getResponse(); return super.getResponse();
} }

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.auth; package org.apache.http.impl.auth;
import org.junit.Test; import org.junit.Test;
import org.junit.Assert;
public class TestNTLMEngineImpl { public class TestNTLMEngineImpl {
@ -45,18 +46,18 @@ public class TestNTLMEngineImpl {
} }
/* Test suite helper */ /* Test suite helper */
static byte checkToNibble(char c) { static byte toNibble(char c) {
if (c >= 'a' && c <= 'f') if (c >= 'a' && c <= 'f')
return (byte) (c - 'a' + 0x0a); return (byte) (c - 'a' + 0x0a);
return (byte) (c - '0'); return (byte) (c - '0');
} }
/* Test suite helper */ /* Test suite helper */
static byte[] checkToBytes(String hex) { static byte[] toBytes(String hex) {
byte[] rval = new byte[hex.length() / 2]; byte[] rval = new byte[hex.length() / 2];
int i = 0; int i = 0;
while (i < rval.length) { 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)))); .charAt(i * 2 + 1))));
i++; i++;
} }
@ -69,7 +70,7 @@ public class TestNTLMEngineImpl {
md4 = new NTLMEngineImpl.MD4(); md4 = new NTLMEngineImpl.MD4();
md4.update(input.getBytes("ASCII")); md4.update(input.getBytes("ASCII"));
byte[] answer = md4.getOutput(); byte[] answer = md4.getOutput();
byte[] correctAnswer = checkToBytes(hexOutput); byte[] correctAnswer = toBytes(hexOutput);
if (answer.length != correctAnswer.length) if (answer.length != correctAnswer.length)
throw new Exception("Answer length disagrees for MD4('" + input + "')"); throw new Exception("Answer length disagrees for MD4('" + input + "')");
int i = 0; 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]);
}
}
} }