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:
parent
9b6aeffa80
commit
264d2038ea
|
@ -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>
|
||||
|
|
|
@ -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.EncodingUtils;
|
|||
*/
|
||||
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 NTLMEngine {
|
|||
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;
|
||||
}
|
||||
|
||||
/** 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 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)
|
||||
throws NTLMEngineException {
|
||||
byte[] ntlmHash = ntlmHash(password);
|
||||
return lmResponse(ntlmHash, challenge);
|
||||
/** Calculates HMAC-MD5 */
|
||||
static byte[] hmacMD5(byte[] value, byte[] key)
|
||||
throws NTLMEngineException {
|
||||
HMACMD5 hmacMD5 = new HMACMD5(key);
|
||||
hmacMD5.update(value);
|
||||
return hmacMD5.getOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
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);
|
||||
/** 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 NTLMEngine {
|
|||
* 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 NTLMEngine {
|
|||
*
|
||||
* @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 NTLMEngine {
|
|||
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 NTLMEngine {
|
|||
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 NTLMEngine {
|
|||
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 NTLMEngine {
|
|||
|
||||
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 NTLMEngine {
|
|||
// 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 NTLMEngine {
|
|||
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 NTLMEngine {
|
|||
// 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 NTLMEngine {
|
|||
addBytes(domainBytes);
|
||||
addBytes(userBytes);
|
||||
addBytes(hostBytes);
|
||||
if (sessionKey != null)
|
||||
addBytes(sessionKey);
|
||||
|
||||
return super.getResponse();
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue