Return-Path: X-Original-To: apmail-hc-commits-archive@www.apache.org Delivered-To: apmail-hc-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3B238D38C for ; Mon, 10 Dec 2012 11:24:02 +0000 (UTC) Received: (qmail 92195 invoked by uid 500); 10 Dec 2012 11:24:02 -0000 Delivered-To: apmail-hc-commits-archive@hc.apache.org Received: (qmail 92113 invoked by uid 500); 10 Dec 2012 11:23:59 -0000 Mailing-List: contact commits-help@hc.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: "HttpComponents Project" Delivered-To: mailing list commits@hc.apache.org Received: (qmail 92100 invoked by uid 99); 10 Dec 2012 11:23:58 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 10 Dec 2012 11:23:58 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 10 Dec 2012 11:23:55 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 4D8B1238896F for ; Mon, 10 Dec 2012 11:23:35 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1419344 - in /httpcomponents/httpclient/branches/4.2.x: 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 Date: Mon, 10 Dec 2012 11:23:34 -0000 To: commits@hc.apache.org From: kwright@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121210112335.4D8B1238896F@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: kwright Date: Mon Dec 10 11:23:33 2012 New Revision: 1419344 URL: http://svn.apache.org/viewvc?rev=1419344&view=rev Log: Pull up changes for HTTPCLIENT-1266 and HTTPCLIENT-1268 to 4.2.x branch. Modified: httpcomponents/httpclient/branches/4.2.x/RELEASE_NOTES.txt httpcomponents/httpclient/branches/4.2.x/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java httpcomponents/httpclient/branches/4.2.x/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java Modified: httpcomponents/httpclient/branches/4.2.x/RELEASE_NOTES.txt URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/4.2.x/RELEASE_NOTES.txt?rev=1419344&r1=1419343&r2=1419344&view=diff ============================================================================== --- httpcomponents/httpclient/branches/4.2.x/RELEASE_NOTES.txt (original) +++ httpcomponents/httpclient/branches/4.2.x/RELEASE_NOTES.txt Mon Dec 10 11:23:33 2012 @@ -1,6 +1,12 @@ Changes since 4.2.2 ------------------- +* [HTTPCLIENT-1268] NTLM engine refactor fix, to correct a buffer overrun. + Contributed by Karl Wright + +* [HTTPCLIENT-1266] NTLM engine refactoring and compatibility improvements. + Contributed by Karl Wright + * [HTTPCLIENT-1263] BrowserCompatSpec: attribute values containing spaces or special characters should be enclosed with quotes marks for version 1 cookies. Contributed by Francois-Xavier Bonnet Modified: httpcomponents/httpclient/branches/4.2.x/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/4.2.x/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java?rev=1419344&r1=1419343&r2=1419344&view=diff ============================================================================== --- httpcomponents/httpclient/branches/4.2.x/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java (original) +++ httpcomponents/httpclient/branches/4.2.x/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java Mon Dec 10 11:23:33 2012 @@ -29,10 +29,10 @@ package org.apache.http.impl.auth; import java.security.Key; import java.security.MessageDigest; import java.util.Arrays; -import java.util.Locale; 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; @@ -45,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; @@ -83,42 +93,6 @@ final class NTLMEngineImpl implements NT SIGNATURE[bytesWithoutNull.length] = (byte) 0x00; } - private static byte[] convert0(final String text) throws NTLMEngineException { - if (text == null) { - return null; - } - try { - return text.getBytes("UnicodeLittleUnmarked"); - } catch (java.io.UnsupportedEncodingException ex) { - throw new NTLMEngineException("Unicode not supported", ex); - } - } - - private static byte[] convert(final String text) throws NTLMEngineException { - if (text == null) { - return new byte[] {}; - } - return convert0(text); - } - - private static String convert0(final byte[] b) throws NTLMEngineException { - if (b == null) { - return null; - } - try { - return new String(b, "UnicodeLittleUnmarked"); - } catch (java.io.UnsupportedEncodingException ex) { - throw new NTLMEngineException("Unicode not supported", ex); - } - } - - private static String convert(final byte[] b) throws NTLMEngineException { - if (b == null) { - return ""; - } - return convert0(b); - } - /** * Returns the response for the given message. * @@ -209,9 +183,6 @@ final class NTLMEngineImpl implements NT /** Strip dot suffix from a name */ private static String stripDotSuffix(String value) { - if (value == null) { - return null; - } int index = value.indexOf("."); if (index != -1) return value.substring(0, index); @@ -264,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 { - byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); - byte[] blob = createBlob(clientChallenge, targetInformation); - return lmv2Response(ntlmv2Hash, challenge, blob); + 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, 8); + Arrays.fill(keyBytes, 8, 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); + } } /** @@ -378,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) @@ -453,10 +604,14 @@ final class NTLMEngineImpl implements NT * the NTLM Response and the NTLMv2 and LMv2 Hashes. */ private static byte[] ntlmHash(String password) throws NTLMEngineException { - byte[] unicodePassword = convert(password); - MD4 md4 = new MD4(); - md4.update(unicodePassword); - return md4.getOutput(); + try { + byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); + MD4 md4 = new MD4(); + md4.update(unicodePassword); + return md4.getOutput(); + } catch (java.io.UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); + } } /** @@ -474,12 +629,16 @@ final class NTLMEngineImpl implements NT */ private static byte[] ntlmv2Hash(String target, String user, String password) throws NTLMEngineException { - byte[] ntlmHash = ntlmHash(password); - HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); - // Upper case username, mixed case target!! - hmacMD5.update(convert(user.toUpperCase(Locale.US))); - hmacMD5.update(convert(target)); - return hmacMD5.getOutput(); + try { + byte[] ntlmHash = ntlmHash(password); + HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, mixed case target!! + hmacMD5.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked")); + hmacMD5.update(target.getBytes("UnicodeLittleUnmarked")); + return hmacMD5.getOutput(); + } catch (java.io.UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e); + } } /** @@ -553,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; @@ -580,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; } @@ -741,8 +895,8 @@ final class NTLMEngineImpl implements NT * the bytes to add. */ protected void addBytes(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - messageContents[currentOutputPosition] = bytes[i]; + for (byte b : bytes) { + messageContents[currentOutputPosition] = b; currentOutputPosition++; } } @@ -791,13 +945,17 @@ final class NTLMEngineImpl implements NT /** Constructor. Include the arguments the message will need */ Type1Message(String domain, String host) throws NTLMEngineException { super(); - // Strip off domain name from the host! - host = convertHost(host); - // Use only the base domain name! - domain = convertDomain(domain); - - hostBytes = convert(host); - domainBytes = convert(domain != null ? domain.toUpperCase(Locale.US) : null); + try { + // Strip off domain name from the host! + host = convertHost(host); + // Use only the base domain name! + domain = convertDomain(domain); + + hostBytes = host.getBytes("UnicodeLittleUnmarked"); + domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); + } catch (java.io.UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode unsupported: " + e.getMessage(), e); + } } /** @@ -808,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); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(15); - // Host String. + + // Host (workstation) String. addBytes(hostBytes); // Domain String. addBytes(domainBytes); + return super.getResponse(); } @@ -857,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 @@ -875,7 +1075,11 @@ final class NTLMEngineImpl implements NT if (getMessageLength() >= 12 + 8) { byte[] bytes = readSecurityBuffer(12); if (bytes.length != 0) { - target = convert(bytes); + try { + target = new String(bytes, "UnicodeLittleUnmarked"); + } catch (java.io.UnsupportedEncodingException e) { + throw new NTLMEngineException(e.getMessage(), e); + } } } @@ -923,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, @@ -936,40 +1142,69 @@ 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"); + userBytes = user.getBytes("UnicodeLittleUnmarked"); + } catch (java.io.UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); } - domainBytes = convert(domain != null ? domain.toUpperCase(Locale.US) : null); - hostBytes = convert(host); - userBytes = convert(user); } /** Assemble the response */ @@ -981,19 +1216,24 @@ 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 = 76; // 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); - + // LM Resp Length (twice) addUShort(lmRespLen); addUShort(lmRespLen); @@ -1029,19 +1269,53 @@ 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 + if (sessionKeyLen != 0) + addULong(sessionKeyOffset); + else + addULong(0); // 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); @@ -1049,6 +1323,8 @@ final class NTLMEngineImpl implements NT addBytes(domainBytes); addBytes(userBytes); addBytes(hostBytes); + if (sessionKey != null) + addBytes(sessionKey); return super.getResponse(); } Modified: httpcomponents/httpclient/branches/4.2.x/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/branches/4.2.x/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java?rev=1419344&r1=1419343&r2=1419344&view=diff ============================================================================== --- httpcomponents/httpclient/branches/4.2.x/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java (original) +++ httpcomponents/httpclient/branches/4.2.x/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java Mon Dec 10 11:23:33 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,151 @@ 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 testType1Message() throws Exception { + new NTLMEngineImpl().getType1Message("myhost", "mydomain"); + } + + @Test + public void testType3Message() throws Exception { + new NTLMEngineImpl().getType3Message("me", "mypassword", "myhost", "mydomain", + toBytes("0001020304050607"), + 0xffffffff, + null,null); + new NTLMEngineImpl().getType3Message("me", "mypassword", "myhost", "mydomain", + toBytes("0001020304050607"), + 0xffffffff, + "mytarget", + toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000")); + } + + @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]); + } + } }