Return-Path: Delivered-To: apmail-incubator-connectors-commits-archive@minotaur.apache.org Received: (qmail 343 invoked from network); 5 Mar 2010 10:34:44 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 5 Mar 2010 10:34:44 -0000 Received: (qmail 64036 invoked by uid 500); 5 Mar 2010 10:34:30 -0000 Delivered-To: apmail-incubator-connectors-commits-archive@incubator.apache.org Received: (qmail 63990 invoked by uid 500); 5 Mar 2010 10:34:30 -0000 Mailing-List: contact connectors-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: connectors-dev@incubator.apache.org Delivered-To: mailing list connectors-commits@incubator.apache.org Received: (qmail 63983 invoked by uid 99); 5 Mar 2010 10:34:29 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 05 Mar 2010 10:34:29 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.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; Fri, 05 Mar 2010 10:34:21 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id DCC6F23888E7; Fri, 5 Mar 2010 10:33:58 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r919362 - in /incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient: HostConfiguration.java auth/NTLM.java auth/NTLMScheme.java params/HttpClientParams.java protocol/ProtocolFactory.java Date: Fri, 05 Mar 2010 10:33:58 -0000 To: connectors-commits@incubator.apache.org From: kwright@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100305103358.DCC6F23888E7@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: kwright Date: Fri Mar 5 10:33:58 2010 New Revision: 919362 URL: http://svn.apache.org/viewvc?rev=919362&view=rev Log: Apply required LCF patches to httpclient 3x code stream. Added: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java (with props) Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java?rev=919362&r1=919361&r2=919362&view=diff ============================================================================== --- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java (original) +++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java Fri Mar 5 10:33:58 2010 @@ -31,7 +31,9 @@ package org.apache.commons.httpclient; import org.apache.commons.httpclient.params.HostParams; +import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolFactory; import org.apache.commons.httpclient.util.LangUtils; import java.net.InetAddress; @@ -250,7 +252,10 @@ * @param protocol The protocol. */ public synchronized void setHost(final String host, int port, final String protocol) { - this.host = new HttpHost(host, port, Protocol.getProtocol(protocol)); + if (this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY) != null) + this.host = new HttpHost(host, port, ((ProtocolFactory)this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY)).getProtocol(protocol)); + else + this.host = new HttpHost(host, port, Protocol.getProtocol(protocol)); } /** @@ -293,7 +298,7 @@ * @param port The port */ public synchronized void setHost(final String host, int port) { - setHost(host, port, Protocol.getProtocol("http")); + setHost(host, port, "http"); } /** @@ -302,7 +307,11 @@ * @param host The host(IP or DNS name). */ public synchronized void setHost(final String host) { - Protocol defaultProtocol = Protocol.getProtocol("http"); + Protocol defaultProtocol; + if (this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY) != null) + defaultProtocol = ((ProtocolFactory)this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY)).getProtocol("http"); + else + defaultProtocol = Protocol.getProtocol("http"); setHost(host, defaultProtocol.getDefaultPort(), defaultProtocol); } Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java?rev=919362&r1=919361&r2=919362&view=diff ============================================================================== --- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java (original) +++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java Fri Mar 5 10:33:58 2010 @@ -32,6 +32,9 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.Key; +import java.security.MessageDigest; + import java.util.Locale; import javax.crypto.BadPaddingException; @@ -52,9 +55,13 @@ * exists for it. This class is based upon the reverse engineering * efforts of a wide range of people.

* + * THIS IS A VERY HELPFUL REFERENCE: http://en.wikipedia.org/wiki/NTLM + * *

Please note that an implementation of JCE must be correctly installed and configured when * using NTLM support.

* + * NTLMv2 protocol description provided by Michael B Allen + * *

This class should not be used externally to HttpClient as it's API is specifically * designed to work with HttpClient's use case, in particular it's connection management.

* @@ -67,18 +74,52 @@ */ final class NTLM { - /** Character encoding */ - public static final String DEFAULT_CHARSET = "ASCII"; + // 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; + + /** Secure random generator */ + private static java.security.SecureRandom randomGenerator; + static + { + try + { + randomGenerator = java.security.SecureRandom.getInstance("SHA1PRNG"); + } + catch (Exception e) + { + // If exception, nothing we can really do about it - can't even count on log being initialized + System.err.println("Couldn't initialize random generator: "+e.getMessage()); + e.printStackTrace(System.err); + } + } - /** The current response */ - private byte[] currentResponse; + /** Signature string for NTLM messages */ + private static final String signatureString = "NTLMSSP"; - /** The current position */ - private int currentPosition = 0; + /** Character encoding */ + public static final String DEFAULT_CHARSET = "ASCII"; /** The character set to use for encoding the credentials */ private String credentialCharset = DEFAULT_CHARSET; + /** The signature string as bytes in the default encoding */ + private static byte[] signatureBytes; + static + { + byte[] bytesWithoutNull = EncodingUtil.getBytes(signatureString, "ASCII"); + signatureBytes = new byte[bytesWithoutNull.length + 1]; + System.arraycopy(bytesWithoutNull,0,signatureBytes,0,bytesWithoutNull.length); + signatureBytes[bytesWithoutNull.length] = (byte) 0x00; + } + /** * Returns the response for the given message. * @@ -98,469 +139,1268 @@ if (message == null || message.trim().equals("")) { response = getType1Message(host, domain); } else { - response = getType3Message(username, password, host, domain, - parseType2Message(message)); + Type2Message t2m = new Type2Message(message); + response = getType3Message(username, password, host, domain, + t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(), t2m.getTargetInfo()); } return response; } /** - * Return the cipher for the specified key. - * @param key The key. - * @return Cipher The cipher. - * @throws AuthenticationException If the cipher cannot be retrieved. - */ - private Cipher getCipher(byte[] key) throws AuthenticationException { - try { - final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding"); - key = setupKey(key); - ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES")); - return ecipher; - } catch (NoSuchAlgorithmException e) { - throw new AuthenticationException("DES encryption is not available.", e); - } catch (InvalidKeyException e) { - throw new AuthenticationException("Invalid key for DES encryption.", e); - } catch (NoSuchPaddingException e) { - throw new AuthenticationException( - "NoPadding option for DES is not available.", e); - } + * Creates the first message (type 1 message) in the NTLM authentication sequence. + * This message includes the user name, domain and host for the authentication session. + * + * @param host the computer name of the host requesting authentication. + * @param domain The domain to authenticate with. + * @return String the message to add to the HTTP request header. + */ + public String getType1Message(String host, String domain) + throws AuthenticationException { + return new Type1Message(domain,host).getResponse(); } /** - * Adds parity bits to the key. - * @param key56 The key - * @return The modified key. - */ - private byte[] setupKey(byte[] key56) { - byte[] key = new byte[8]; - key[0] = (byte) ((key56[0] >> 1) & 0xff); - key[1] = (byte) ((((key56[0] & 0x01) << 6) - | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff); - key[2] = (byte) ((((key56[1] & 0x03) << 5) - | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff); - key[3] = (byte) ((((key56[2] & 0x07) << 4) - | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff); - key[4] = (byte) ((((key56[3] & 0x0f) << 3) - | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff); - key[5] = (byte) ((((key56[4] & 0x1f) << 2) - | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff); - key[6] = (byte) ((((key56[5] & 0x3f) << 1) - | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff); - key[7] = (byte) (key56[6] & 0x7f); - - for (int i = 0; i < key.length; i++) { - key[i] = (byte) (key[i] << 1); - } - return key; + * Creates the type 3 message using the given server nonce. The type 3 message includes all the + * information for authentication, host, domain, username and the result of encrypting the + * nonce sent by the server using the user's password as the key. + * + * @param user The user name. This should not include the domain name. + * @param password The password. + * @param host The host that is originating the authentication request. + * @param domain The domain to authenticate within. + * @param nonce the 8 byte array the server sent. + * @return The type 3 message. + * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails. + */ + public String getType3Message(String user, String password, + String host, String domain, byte[] nonce, int type2Flags, String target, byte[] targetInformation) + throws AuthenticationException { + return new Type3Message(domain,host,user,password,nonce,type2Flags,target,targetInformation).getResponse(); } /** - * Encrypt the data. - * @param key The key. - * @param bytes The data - * @return byte[] The encrypted data - * @throws HttpException If {@link Cipher.doFinal(byte[])} fails + * @return Returns the credentialCharset. */ - private byte[] encrypt(byte[] key, byte[] bytes) - throws AuthenticationException { - Cipher ecipher = getCipher(key); - try { - byte[] enc = ecipher.doFinal(bytes); - return enc; - } catch (IllegalBlockSizeException e) { - throw new AuthenticationException("Invalid block size for DES encryption.", e); - } catch (BadPaddingException e) { - throw new AuthenticationException("Data not padded correctly for DES encryption.", e); - } + public String getCredentialCharset() { + return credentialCharset; } /** - * Prepares the object to create a response of the given length. - * @param length the length of the response to prepare. + * @param credentialCharset The credentialCharset to set. */ - private void prepareResponse(int length) { - currentResponse = new byte[length]; - currentPosition = 0; + public void setCredentialCharset(String credentialCharset) { + this.credentialCharset = credentialCharset; } - /** - * Adds the given byte to the response. - * @param b the byte to add. - */ - private void addByte(byte b) { - currentResponse[currentPosition] = b; - currentPosition++; + /** Strip dot suffix from a name */ + private static String stripDotSuffix(String value) + { + int index = value.indexOf("."); + if (index != -1) + return value.substring(0,index); + return value; } - /** - * Adds the given bytes to the response. - * @param bytes the bytes to add. - */ - private void addBytes(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - currentResponse[currentPosition] = bytes[i]; - currentPosition++; - } + /** Convert host to standard form */ + private static String convertHost(String host) + { + return stripDotSuffix(host); } - /** - * Returns the response that has been generated after shrinking the array if - * required and base64 encodes the response. - * @return The response as above. - */ - private String getResponse() { - byte[] resp; - if (currentResponse.length > currentPosition) { - byte[] tmp = new byte[currentPosition]; - for (int i = 0; i < currentPosition; i++) { - tmp[i] = currentResponse[i]; - } - resp = tmp; - } else { - resp = currentResponse; + /** Convert domain to standard form */ + private static String convertDomain(String domain) + { + return stripDotSuffix(domain); + } + + private static int readULong(byte[] src, int index) + throws AuthenticationException { + if (src.length < index + 4) + throw new AuthenticationException("NTLM authentication - buffer too small for DWORD"); + return (src[index] & 0xff) | + ((src[index + 1] & 0xff) << 8) | + ((src[index + 2] & 0xff) << 16) | + ((src[index + 3] & 0xff) << 24); + } + + private static int readUShort(byte[] src, int index) + throws AuthenticationException { + if (src.length < index + 2) + throw new AuthenticationException("NTLM authentication - buffer too small for WORD"); + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); + } + + private static byte[] readSecurityBuffer(byte[] src, int index) + throws AuthenticationException { + int length = readUShort(src, index); + int offset = readULong(src, index + 4); + if (src.length < offset + length) + throw new AuthenticationException("NTLM authentication - buffer too small for data item"); + byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + /** Calculate a challenge block */ + private static byte[] makeRandomChallenge() + { + byte[] rval = new byte[8]; + synchronized (randomGenerator) + { + randomGenerator.nextBytes(rval); } - return EncodingUtil.getAsciiString(Base64.encodeBase64(resp)); + return rval; + } + + /** Calculate an NTLM2 challenge block */ + private static byte[] makeNTLM2RandomChallenge() + { + byte[] rval = new byte[24]; + synchronized (randomGenerator) + { + randomGenerator.nextBytes(rval); + } + // 8-byte challenge, padded with zeros to 24 bytes. + java.util.Arrays.fill(rval,8,24,(byte)0x00); + return rval; } + /** - * Creates the first message (type 1 message) in the NTLM authentication sequence. - * This message includes the user name, domain and host for the authentication session. + * Calculates the LM Response for the given challenge, using the specified + * password. * - * @param host the computer name of the host requesting authentication. - * @param domain The domain to authenticate with. - * @return String the message to add to the HTTP request header. + * @param password The user's password. + * @param challenge The Type 2 challenge from the server. + * + * @return The LM Response. */ - public String getType1Message(String host, String domain) { - host = host.toUpperCase(Locale.ENGLISH); - domain = domain.toUpperCase(Locale.ENGLISH); - byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET); - byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET); + public static byte[] getLMResponse(String password, byte[] challenge) + throws AuthenticationException { + byte[] lmHash = lmHash(password); + return lmResponse(lmHash, challenge); + } - int finalLength = 32 + hostBytes.length + domainBytes.length; - prepareResponse(finalLength); - - // The initial id string. - byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET); - addBytes(protocol); - addByte((byte) 0); - - // Type - addByte((byte) 1); - addByte((byte) 0); - addByte((byte) 0); - addByte((byte) 0); - - // Flags - addByte((byte) 6); - addByte((byte) 82); - addByte((byte) 0); - addByte((byte) 0); - - // Domain length (first time). - int iDomLen = domainBytes.length; - byte[] domLen = convertShort(iDomLen); - addByte(domLen[0]); - addByte(domLen[1]); - - // Domain length (second time). - addByte(domLen[0]); - addByte(domLen[1]); - - // Domain offset. - byte[] domOff = convertShort(hostBytes.length + 32); - addByte(domOff[0]); - addByte(domOff[1]); - addByte((byte) 0); - addByte((byte) 0); - - // Host length (first time). - byte[] hostLen = convertShort(hostBytes.length); - addByte(hostLen[0]); - addByte(hostLen[1]); - - // Host length (second time). - addByte(hostLen[0]); - addByte(hostLen[1]); - - // Host offset (always 32). - byte[] hostOff = convertShort(32); - addByte(hostOff[0]); - addByte(hostOff[1]); - addByte((byte) 0); - addByte((byte) 0); + /** + * 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. + */ + public static byte[] getNTLMResponse(String password, byte[] challenge) + throws AuthenticationException { + byte[] ntlmHash = ntlmHash(password); + return lmResponse(ntlmHash, challenge); + } - // Host String. - addBytes(hostBytes); + /** + * 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. + */ + public static byte[] getNTLMv2Response(String target, String user, + String password, byte[] challenge, + byte[] clientChallenge, byte[] targetInformation) + throws AuthenticationException { + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); + byte[] blob = createBlob(clientChallenge, targetInformation); + return lmv2Response(ntlmv2Hash, challenge, blob); + } - // Domain String. - addBytes(domainBytes); + /** + * 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. + */ + public static byte[] getLMv2Response(String target, String user, + String password, byte[] challenge, byte[] clientChallenge) + throws AuthenticationException { + byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); + return lmv2Response(ntlmv2Hash, challenge, clientChallenge); + } - return getResponse(); + /** + * Calculates the NTLM2 Session Response for the given challenge, using the + * specified password and client challenge. + * + * @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 NTLM2 Session Response. This is placed in the NTLM + * response field of the Type 3 message; the LM response field contains + * the client challenge, null-padded to 24 bytes. + */ + public static byte[] getNTLM2SessionResponse(String password, + byte[] challenge, byte[] clientChallenge) throws AuthenticationException + { + 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) + //Class x = Class.forName("gnu.crypto.hash.MD5"); + //Method updateMethod = x.getMethod("update",new Class[]{byte[].class}); + //Method digestMethod = x.getMethod("digest",new Class[0]); + //Object mdInstance = x.newInstance(); + //updateMethod.invoke(mdInstance,new Object[]{challenge}); + //updateMethod.invoke(mdInstance,new Object[]{clientChallenge}); + //byte[] digest = (byte[])digestMethod.invoke(mdInstance,new Object[0]); + + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(challenge); + md5.update(clientChallenge); + byte[] digest = md5.digest(); + + byte[] sessionHash = new byte[8]; + System.arraycopy(digest, 0, sessionHash, 0, 8); + return lmResponse(ntlmHash, sessionHash); + } + catch (Exception e) + { + if (e instanceof AuthenticationException) + throw (AuthenticationException)e; + throw new AuthenticationException(e.getMessage(),e); + `} } - /** - * Extracts the server nonce out of the given message type 2. - * - * @param message the String containing the base64 encoded message. - * @return an array of 8 bytes that the server sent to be used when - * hashing the password. - */ - public byte[] parseType2Message(String message) { - // Decode the message first. - byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET)); - byte[] nonce = new byte[8]; - // The nonce is the 8 bytes starting from the byte in position 24. - for (int i = 0; i < 8; i++) { - nonce[i] = msg[i + 24]; + /** + * Creates the LM Hash of the user's password. + * + * @param password The password. + * + * @return The LM Hash of the given password, used in the calculation + * of the LM Response. + */ + private static byte[] lmHash(String password) throws AuthenticationException + { + try + { + byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII"); + int length = Math.min(oemPassword.length, 14); + byte[] keyBytes = new byte[14]; + System.arraycopy(oemPassword, 0, keyBytes, 0, length); + Key lowKey = createDESKey(keyBytes, 0); + Key highKey = createDESKey(keyBytes, 7); + byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII"); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + byte[] lowHash = des.doFinal(magicConstant); + des.init(Cipher.ENCRYPT_MODE, highKey); + byte[] highHash = des.doFinal(magicConstant); + byte[] lmHash = new byte[16]; + System.arraycopy(lowHash, 0, lmHash, 0, 8); + System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; + } + catch (Exception e) + { + throw new AuthenticationException(e.getMessage(),e); } - return nonce; } - /** - * Creates the type 3 message using the given server nonce. The type 3 message includes all the - * information for authentication, host, domain, username and the result of encrypting the - * nonce sent by the server using the user's password as the key. + /** + * Creates the NTLM Hash of the user's password. * - * @param user The user name. This should not include the domain name. * @param password The password. - * @param host The host that is originating the authentication request. - * @param domain The domain to authenticate within. - * @param nonce the 8 byte array the server sent. - * @return The type 3 message. - * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails. + * + * @return The NTLM Hash of the given password, used in the calculation + * of the NTLM Response and the NTLMv2 and LMv2 Hashes. */ - public String getType3Message(String user, String password, - String host, String domain, byte[] nonce) - throws AuthenticationException { + private static byte[] ntlmHash(String password) throws AuthenticationException + { + try + { + byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); + MD4 md4 = new MD4(); + md4.update(unicodePassword); + return md4.getOutput(); + } + catch (java.io.UnsupportedEncodingException e) + { + throw new AuthenticationException("Unicode not supported: "+e.getMessage(),e); + } + } - int ntRespLen = 0; - int lmRespLen = 24; - domain = domain.toUpperCase(Locale.ENGLISH); - host = host.toUpperCase(Locale.ENGLISH); - user = user.toUpperCase(Locale.ENGLISH); - byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET); - byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET); - byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset); - int domainLen = domainBytes.length; - int hostLen = hostBytes.length; - int userLen = userBytes.length; - int finalLength = 64 + ntRespLen + lmRespLen + domainLen - + userLen + hostLen; - prepareResponse(finalLength); - byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET); - addBytes(ntlmssp); - addByte((byte) 0); - addByte((byte) 3); - addByte((byte) 0); - addByte((byte) 0); - addByte((byte) 0); - - // LM Resp Length (twice) - addBytes(convertShort(24)); - addBytes(convertShort(24)); - - // LM Resp Offset - addBytes(convertShort(finalLength - 24)); - addByte((byte) 0); - addByte((byte) 0); - - // NT Resp Length (twice) - addBytes(convertShort(0)); - addBytes(convertShort(0)); - - // NT Resp Offset - addBytes(convertShort(finalLength)); - addByte((byte) 0); - addByte((byte) 0); - - // Domain length (twice) - addBytes(convertShort(domainLen)); - addBytes(convertShort(domainLen)); - - // Domain offset. - addBytes(convertShort(64)); - addByte((byte) 0); - addByte((byte) 0); - - // User Length (twice) - addBytes(convertShort(userLen)); - addBytes(convertShort(userLen)); - - // User offset - addBytes(convertShort(64 + domainLen)); - addByte((byte) 0); - addByte((byte) 0); - - // Host length (twice) - addBytes(convertShort(hostLen)); - addBytes(convertShort(hostLen)); - - // Host offset - addBytes(convertShort(64 + domainLen + userLen)); - - for (int i = 0; i < 6; i++) { - addByte((byte) 0); - } - - // Message length - addBytes(convertShort(finalLength)); - addByte((byte) 0); - addByte((byte) 0); - - // Flags - addByte((byte) 6); - addByte((byte) 82); - addByte((byte) 0); - addByte((byte) 0); - - addBytes(domainBytes); - addBytes(userBytes); - addBytes(hostBytes); - addBytes(hashPassword(password, nonce)); - return getResponse(); + /** + * Creates the NTLMv2 Hash of the user's password. + * + * @param target The authentication target (i.e., domain). + * @param user The username. + * @param password The password. + * + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 + * and LMv2 Responses. + */ + private static byte[] ntlmv2Hash(String target, String user, + String password) throws AuthenticationException + { + 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 AuthenticationException("Unicode not supported! "+e.getMessage(),e); + } + } + + /** + * Creates the LM Response from the given hash and Type 2 challenge. + * + * @param hash The LM or NTLM Hash. + * @param challenge The server challenge from the Type 2 message. + * + * @return The response (either LM or NTLM, depending on the provided + * hash). + */ + private static byte[] lmResponse(byte[] hash, byte[] challenge) + throws AuthenticationException + { + try + { + byte[] keyBytes = new byte[21]; + System.arraycopy(hash, 0, keyBytes, 0, 16); + Key lowKey = createDESKey(keyBytes, 0); + Key middleKey = createDESKey(keyBytes, 7); + Key highKey = createDESKey(keyBytes, 14); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + byte[] lowResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, middleKey); + byte[] middleResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, highKey); + byte[] highResponse = des.doFinal(challenge); + byte[] lmResponse = new byte[24]; + System.arraycopy(lowResponse, 0, lmResponse, 0, 8); + System.arraycopy(middleResponse, 0, lmResponse, 8, 8); + System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; + } + catch (Exception e) + { + throw new AuthenticationException(e.getMessage(),e); + } + } + + /** + * Creates the LMv2 Response from the given hash, client data, and + * Type 2 challenge. + * + * @param hash The NTLMv2 Hash. + * @param clientData The client data (blob or client challenge). + * @param challenge The server challenge from the Type 2 message. + * + * @return The response (either NTLMv2 or LMv2, depending on the + * client data). + */ + private static byte[] lmv2Response(byte[] hash, byte[] challenge, + byte[] clientData) throws AuthenticationException + { + HMACMD5 hmacMD5 = new HMACMD5(hash); + hmacMD5.update(challenge); + hmacMD5.update(clientData); + byte[] mac = hmacMD5.getOutput(); + byte[] lmv2Response = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, lmv2Response, 0, mac.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, + clientData.length); + return lmv2Response; + } + + /** + * Creates the NTLMv2 blob from the given target information block and + * client challenge. + * + * @param targetInformation The target information block from the Type 2 + * message. + * @param clientChallenge The random 8-byte client challenge. + * + * @return The blob, used in the calculation of the NTLMv2 Response. + */ + private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) { + 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[] blob = new byte[blobSignature.length + reserved.length+ timestamp.length + 8 + + unknown1.length + targetInformation.length]; + int offset = 0; + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); + offset += blobSignature.length; + System.arraycopy(reserved, 0, blob, offset, reserved.length); + offset += reserved.length; + System.arraycopy(timestamp, 0, blob, offset, timestamp.length); + offset += timestamp.length; + System.arraycopy(clientChallenge, 0, blob, offset,8); + offset += 8; + System.arraycopy(unknown1, 0, blob, offset, unknown1.length); + offset += unknown1.length; + System.arraycopy(targetInformation, 0, blob, offset, + targetInformation.length); + return blob; } /** - * Creates the LANManager and NT response for the given password using the - * given nonce. - * @param password the password to create a hash for. - * @param nonce the nonce sent by the server. - * @return The response. - * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. + * Creates a DES encryption key from the given key material. + * + * @param bytes A byte array containing the DES key material. + * @param offset The offset in the given byte array at which + * the 7-byte key material starts. + * + * @return A DES encryption key created from the key material + * starting at the specified offset in the given byte array. */ - private byte[] hashPassword(String password, byte[] nonce) - throws AuthenticationException { - byte[] passw = EncodingUtil.getBytes(password.toUpperCase(Locale.ENGLISH), credentialCharset); - byte[] lmPw1 = new byte[7]; - byte[] lmPw2 = new byte[7]; + private static Key createDESKey(byte[] bytes, int offset) { + byte[] keyBytes = new byte[7]; + System.arraycopy(bytes, offset, keyBytes, 0, 7); + byte[] material = new byte[8]; + material[0] = keyBytes[0]; + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); + material[7] = (byte) (keyBytes[6] << 1); + oddParity(material); + return new SecretKeySpec(material, "DES"); + } - int len = passw.length; - if (len > 7) { - len = 7; + /** + * Applies odd parity to the given byte array. + * + * @param bytes The data whose parity bits are to be adjusted for + * odd parity. + */ + private static void oddParity(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ + (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ + (b >>> 1)) & 0x01) == 0; + if (needsParity) { + bytes[i] |= (byte) 0x01; + } else { + bytes[i] &= (byte) 0xfe; + } } + } + + /** NTLM message generation, base class */ + protected static class NTLMMessage + { + /** The current response */ + private byte[] messageContents = null; + + /** The current output position */ + private int currentOutputPosition = 0; + + /** Constructor to use when message contents are not yet known */ + public NTLMMessage() + { + } + + /** Constructor to use when message contents are known */ + public NTLMMessage(String messageBody, int expectedType) + throws AuthenticationException + { + messageContents = Base64.decodeBase64(EncodingUtil.getBytes(messageBody, DEFAULT_CHARSET)); + // Look for NTLM message + if (messageContents.length < signatureBytes.length) + throw new AuthenticationException("NTLM message decoding error - packet too short"); + int i = 0; + while (i < signatureBytes.length) + { + if (messageContents[i] != signatureBytes[i]) + throw new AuthenticationException("NTLM message expected - instead got unrecognized bytes"); + i++; + } - int idx; - for (idx = 0; idx < len; idx++) { - lmPw1[idx] = passw[idx]; + // Check to be sure there's a type 2 message indicator next + int type = readULong(signatureBytes.length); + if (type != expectedType) + throw new AuthenticationException("NTLM type "+Integer.toString(expectedType)+" message expected - instead got type "+Integer.toString(type)); + + currentOutputPosition = messageContents.length; + } + + /** Get the length of the signature and flags, so calculations can adjust offsets accordingly. + */ + protected int getPreambleLength() + { + return signatureBytes.length + 4; + } + + /** Get the message length */ + protected int getMessageLength() + { + return currentOutputPosition; } - for (; idx < 7; idx++) { - lmPw1[idx] = (byte) 0; + + /** Read a byte from a position within the message buffer */ + protected byte readByte(int position) throws AuthenticationException + { + if (messageContents.length < position + 1) + throw new AuthenticationException("NTLM: Message too short"); + return messageContents[position]; + } + + /** Read a bunch of bytes from a position in the message buffer */ + protected void readBytes(byte[] buffer, int position) + throws AuthenticationException + { + if (messageContents.length < position + buffer.length) + throw new AuthenticationException("NTLM: Message too short"); + System.arraycopy(messageContents,position,buffer,0,buffer.length); + } + + /** Read a ushort from a position within the message buffer */ + protected int readUShort(int position) throws AuthenticationException + { + return NTLM.readUShort(messageContents,position); + } + + /** Read a ulong from a position within the message buffer */ + protected int readULong(int position) throws AuthenticationException + { + return NTLM.readULong(messageContents,position); + } + + /** Read a security buffer from a position within the message buffer */ + protected byte[] readSecurityBuffer(int position) throws AuthenticationException + { + return NTLM.readSecurityBuffer(messageContents,position); + } + + /** + * Prepares the object to create a response of the given length. + * @param length the maximum length of the response to prepare, not including the type and the signature (which this method adds). + */ + protected void prepareResponse(int maxlength, int messageType) { + messageContents = new byte[maxlength]; + currentOutputPosition = 0; + addBytes(signatureBytes); + addULong(messageType); + } + + /** + * Adds the given byte to the response. + * @param b the byte to add. + */ + protected void addByte(byte b) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + + /** + * Adds the given bytes to the response. + * @param bytes the bytes to add. + */ + protected void addBytes(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + messageContents[currentOutputPosition] = bytes[i]; + currentOutputPosition++; + } } - len = passw.length; - if (len > 14) { - len = 14; - } - for (idx = 7; idx < len; idx++) { - lmPw2[idx - 7] = passw[idx]; + /** Adds a USHORT to the response */ + protected void addUShort(int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + } + + /** Adds a ULong to the response */ + protected void addULong(int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + addByte((byte) (value >> 16 & 0xff)); + addByte((byte) (value >> 24 & 0xff)); + } + + /** + * Returns the response that has been generated after shrinking the array if + * required and base64 encodes the response. + * @return The response as above. + */ + public String getResponse() { + byte[] resp; + if (messageContents.length > currentOutputPosition) { + byte[] tmp = new byte[currentOutputPosition]; + for (int i = 0; i < currentOutputPosition; i++) { + tmp[i] = messageContents[i]; + } + resp = tmp; + } else { + resp = messageContents; + } + return EncodingUtil.getAsciiString(Base64.encodeBase64(resp)); } - for (; idx < 14; idx++) { - lmPw2[idx - 7] = (byte) 0; + + } + + /** Type 1 message assembly class */ + public static class Type1Message extends NTLMMessage + { + protected byte[] hostBytes; + protected byte[] domainBytes; + + /** Constructor. Include the arguments the message will need */ + public Type1Message(String domain, String host) + throws AuthenticationException + { + super(); + 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 AuthenticationException("Unicode unsupported: "+e.getMessage(),e); + } } + + /** Getting the response involves building the message before returning it */ + public String getResponse() + { + // Now, build the message. Calculate its length first, including signature or type. + int finalLength = 32 + 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); + + // Domain length (two times). + addUShort(domainBytes.length); + addUShort(domainBytes.length); + + // Domain offset. + addULong(hostBytes.length + 32); + + // Host length (two times). + addUShort(hostBytes.length); + addUShort(hostBytes.length); + + // Host offset (always 32). + addULong(32); + + // Host String. + addBytes(hostBytes); - // Create LanManager hashed Password - byte[] magic = { - (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, - (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25 - }; + // Domain String. + addBytes(domainBytes); - byte[] lmHpw1; - lmHpw1 = encrypt(lmPw1, magic); + return super.getResponse(); + } - byte[] lmHpw2 = encrypt(lmPw2, magic); + } - byte[] lmHpw = new byte[21]; - for (int i = 0; i < lmHpw1.length; i++) { - lmHpw[i] = lmHpw1[i]; - } - for (int i = 0; i < lmHpw2.length; i++) { - lmHpw[i + 8] = lmHpw2[i]; + /** Type 2 message class */ + public static class Type2Message extends NTLMMessage + { + protected byte[] challenge; + protected String target; + protected byte[] targetInfo; + protected int flags; + + public Type2Message(String message) + throws AuthenticationException + { + super(message,2); + + // 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) + throw new AuthenticationException("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 in Type2 messages, so use the length of the packet to decide + // how to proceed instead + if (getMessageLength() >= 12 + 8) + { + byte[] bytes = readSecurityBuffer(12); + if (bytes.length != 0) + { + try + { + target = new String(bytes,"UnicodeLittleUnmarked"); + } + catch (java.io.UnsupportedEncodingException e) + { + throw new AuthenticationException(e.getMessage(),e); + } + } + } + + // Do the target info! + targetInfo = null; + // TARGET_DESIRED flag cannot be relied on, so use packet length + if (getMessageLength() >= 40 + 8) + { + byte[] bytes = readSecurityBuffer(40); + if (bytes.length != 0) + { + targetInfo = bytes; + } + } } - for (int i = 0; i < 5; i++) { - lmHpw[i + 16] = (byte) 0; + + /** Retrieve the challenge */ + public byte[] getChallenge() + { + return challenge; + } + + /** Retrieve the target */ + public String getTarget() + { + return target; + } + + /** Retrieve the target info */ + public byte[] getTargetInfo() + { + return targetInfo; + } + + /** Retrieve the response flags */ + public int getFlags() + { + return flags; } - // Create the responses. - byte[] lmResp = new byte[24]; - calcResp(lmHpw, nonce, lmResp); - - return lmResp; } - /** - * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte - * plaintext is encrypted with each key and the resulting 24 bytes are - * stored in the results array. - * - * @param keys The keys. - * @param plaintext The plain text to encrypt. - * @param results Where the results are stored. - * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails. - */ - private void calcResp(byte[] keys, byte[] plaintext, byte[] results) - throws AuthenticationException { - byte[] keys1 = new byte[7]; - byte[] keys2 = new byte[7]; - byte[] keys3 = new byte[7]; - for (int i = 0; i < 7; i++) { - keys1[i] = keys[i]; - } + /** Type 3 message assembly class */ + public static class Type3Message extends NTLMMessage + { + // Response flags from the type2 message + protected int type2Flags; + + protected byte[] domainBytes; + protected byte[] hostBytes; + protected byte[] userBytes; + + protected byte[] lmResp; + protected byte[] ntResp; + + /** Constructor. Pass the arguments we will need */ + public Type3Message(String domain, String host, String user, String password, + byte[] nonce, int type2Flags, String target, byte[] targetInformation) + throws AuthenticationException + { + // Save the flags + this.type2Flags = type2Flags; + + // Strip off domain name from the host! + host = convertHost(host); + // Use only the base domain name! + domain = convertDomain(domain); + + // Use the new code to calculate the responses, including v2 if that seems warranted. + try + { + if (targetInformation != null && target != null) + { + byte[] clientChallenge = makeRandomChallenge(); + ntResp = getNTLMv2Response(target,user,password,nonce,clientChallenge,targetInformation); + lmResp = getLMv2Response(target,user,password,nonce,clientChallenge); + } + else + { + if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) + { + // NTLM2 session stuff is requested + byte[] clientChallenge = makeNTLM2RandomChallenge(); + + ntResp = getNTLM2SessionResponse(password,nonce,clientChallenge); + lmResp = clientChallenge; + + // 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); + } + } + } + catch (AuthenticationException 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); + } - for (int i = 0; i < 7; i++) { - keys2[i] = keys[i + 7]; + try + { + domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); + hostBytes = host.getBytes("UnicodeLittleUnmarked"); + userBytes = user.getBytes("UnicodeLittleUnmarked"); + } + catch (java.io.UnsupportedEncodingException e) + { + throw new AuthenticationException("Unicode not supported: "+e.getMessage(),e); + } } + + /** Assemble the response */ + public String getResponse() + { + int ntRespLen = ntResp.length; + int lmRespLen = lmResp.length; + + int domainLen = domainBytes.length; + int hostLen = hostBytes.length; + int userLen = userBytes.length; + + // Calculate the layout within the packet + int lmRespOffset = 64; + int ntRespOffset = lmRespOffset + lmRespLen; + int domainOffset = ntRespOffset + ntRespLen; + int userOffset = domainOffset + domainLen; + int hostOffset = userOffset + userLen; + int sessionKeyOffset = hostOffset + hostLen; + int finalLength = sessionKeyOffset + 0; + + // Start the response. Length includes signature and type + prepareResponse(finalLength,3); + + // LM Resp Length (twice) + addUShort(lmRespLen); + addUShort(lmRespLen); + + // LM Resp Offset + addULong(lmRespOffset); + + // NT Resp Length (twice) + addUShort(ntRespLen); + addUShort(ntRespLen); + + // NT Resp Offset + addULong(ntRespOffset); + + // Domain length (twice) + addUShort(domainLen); + addUShort(domainLen); + + // Domain offset. + addULong(domainOffset); + + // User Length (twice) + addUShort(userLen); + addUShort(userLen); + + // User offset + addULong(userOffset); + + // Host length (twice) + addUShort(hostLen); + addUShort(hostLen); + + // Host offset + addULong(hostOffset); + + // 4 bytes of zeros - not sure what this is + addULong(0); + + // Message length + addULong(finalLength); + + // Flags. Currently: NEGOTIATE_NTLM + 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)); + + // Add the actual data + addBytes(lmResp); + addBytes(ntResp); + addBytes(domainBytes); + addBytes(userBytes); + addBytes(hostBytes); - for (int i = 0; i < 7; i++) { - keys3[i] = keys[i + 14]; + return super.getResponse(); } - byte[] results1 = encrypt(keys1, plaintext); + } - byte[] results2 = encrypt(keys2, plaintext); + protected static void writeULong(byte[] buffer, int value, int offset) + { + buffer[offset] = (byte) (value & 0xff); + buffer[offset+1] = (byte) (value >> 8 & 0xff); + buffer[offset+2] = (byte) (value >> 16 & 0xff); + buffer[offset+3] = (byte) (value >> 24 & 0xff); + } + + protected static int F(int x, int y, int z) { + return((x & y) | (~x & z)); + } + protected static int G(int x, int y, int z) { + return((x & y) | (x & z) | (y & z)); + } + protected static int H(int x, int y, int z) { + return(x ^ y ^ z); + } - byte[] results3 = encrypt(keys3, plaintext); + protected static int rotintlft(int val, int numbits) { + return((val << numbits) | (val >>> (32 - numbits))); + } - for (int i = 0; i < 8; i++) { - results[i] = results1[i]; - } - for (int i = 0; i < 8; i++) { - results[i + 8] = results2[i]; + /** Cryptography support - MD4. + * The following class was based loosely on the RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java. + * Code correctness was verified by looking at MD4.java from the jcifs library (http://jcifs.samba.org). + * It was massaged extensively to the final form found here by Karl Wright (kwright@metacarta.com). + */ + protected static class MD4 + { + protected int A = 0x67452301; + protected int B = 0xefcdab89; + protected int C = 0x98badcfe; + protected int D = 0x10325476; + protected long count = 0L; + protected byte[] dataBuffer = new byte[64]; + + public MD4() + { + } + + public void update(byte[] input) + { + // We always deal with 512 bits at a time. Correspondingly, there is a buffer 64 bytes long that we write data into until it gets full. + int curBufferPos = (int)(count & 63L); + int inputIndex = 0; + while (input.length - inputIndex + curBufferPos >= dataBuffer.length) + { + // We have enough data to do the next step. Do a partial copy and a transform, updating inputIndex and curBufferPos accordingly + int transferAmt = dataBuffer.length - curBufferPos; + System.arraycopy(input,inputIndex,dataBuffer,curBufferPos,transferAmt); + count += transferAmt; + curBufferPos = 0; + inputIndex += transferAmt; + processBuffer(); + } + + // If there's anything left, copy it into the buffer and leave it. We know there's not enough left to process. + if (inputIndex < input.length) + { + int transferAmt = input.length - inputIndex; + System.arraycopy(input,inputIndex,dataBuffer,curBufferPos,transferAmt); + count += transferAmt; + curBufferPos += transferAmt; + } } - for (int i = 0; i < 8; i++) { - results[i + 16] = results3[i]; + + public byte[] getOutput() + { + // Feed pad/length data into engine. This must round out the input to a multiple of 512 bits. + int bufferIndex = (int)(count & 63L); + int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); + byte[] postBytes = new byte[padLen + 8]; + // Leading 0x80, specified amount of zero padding, then length in bits. + postBytes[0] = (byte)0x80; + // Fill out the last 8 bytes with the length + for (int i = 0; i < 8; i++) + { + postBytes[padLen + i] = (byte)((count * 8) >>> (8 * i)); + } + + // Update the engine + update(postBytes); + + // Calculate final result + byte[] result = new byte[16]; + writeULong(result,A,0); + writeULong(result,B,4); + writeULong(result,C,8); + writeULong(result,D,12); + return result; + } + + protected void processBuffer() + { + // Convert current buffer to 16 ulongs + int[] d = new int[16]; + + for (int i = 0; i < 16; i++) + { + d[i] = (dataBuffer[i*4] & 0xff) + ((dataBuffer[i*4+1] & 0xff) << 8) + + ((dataBuffer[i*4+2] & 0xff) << 16) + ((dataBuffer[i*4+3] & 0xff) << 24); + } + + // Do a round of processing + int AA = A; int BB = B; int CC = C; int DD = D; + round1(d); + round2(d); + round3(d); + A += AA; B+= BB; C+= CC; D+= DD; + + } + + protected void round1(int[] d) { + A = rotintlft((A + F(B, C, D) + d[0]), 3); + D = rotintlft((D + F(A, B, C) + d[1]), 7); + C = rotintlft((C + F(D, A, B) + d[2]), 11); + B = rotintlft((B + F(C, D, A) + d[3]), 19); + + A = rotintlft((A + F(B, C, D) + d[4]), 3); + D = rotintlft((D + F(A, B, C) + d[5]), 7); + C = rotintlft((C + F(D, A, B) + d[6]), 11); + B = rotintlft((B + F(C, D, A) + d[7]), 19); + + A = rotintlft((A + F(B, C, D) + d[8]), 3); + D = rotintlft((D + F(A, B, C) + d[9]), 7); + C = rotintlft((C + F(D, A, B) + d[10]), 11); + B = rotintlft((B + F(C, D, A) + d[11]), 19); + + A = rotintlft((A + F(B, C, D) + d[12]), 3); + D = rotintlft((D + F(A, B, C) + d[13]), 7); + C = rotintlft((C + F(D, A, B) + d[14]), 11); + B = rotintlft((B + F(C, D, A) + d[15]), 19); + } + + protected void round2(int[] d) { + A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); + + } + + protected void round3(int[] d) { + A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); + } + } - /** - * Converts a given number to a two byte array in little endian order. - * @param num the number to convert. - * @return The byte representation of num in little endian order. - */ - private byte[] convertShort(int num) { - byte[] val = new byte[2]; - String hex = Integer.toString(num, 16); - while (hex.length() < 4) { - hex = "0" + hex; - } - String low = hex.substring(2, 4); - String high = hex.substring(0, 2); - - val[0] = (byte) Integer.parseInt(low, 16); - val[1] = (byte) Integer.parseInt(high, 16); - return val; + /** Cryptography support - HMACMD5 - algorithmically based on various web resources by Karl Wright */ + protected static class HMACMD5 + { + protected byte[] ipad; + protected byte[] opad; + protected MessageDigest md5; + + public HMACMD5(byte[] key) + throws AuthenticationException + { + try + { + md5 = MessageDigest.getInstance("MD5"); + } + catch (Exception ex) + { + // Umm, the algorithm doesn't exist - throw an AuthenticationException! + throw new AuthenticationException("Error getting md5 message digest implementation: "+ex.getMessage(),ex); + } + + // Initialize the pad buffers with the key + ipad = new byte[64]; + opad = new byte[64]; + + int keyLength = key.length; + if (keyLength > 64) + { + // Use MD5 of the key instead, as described in RFC 2104 + md5.update(key); + key = md5.digest(); + keyLength = key.length; + } + int i = 0; + while (i < keyLength) + { + ipad[i] = (byte) (key[i] ^ (byte)0x36); + opad[i] = (byte) (key[i] ^ (byte)0x5c); + i++; + } + while (i < 64) + { + ipad[i] = (byte)0x36; + opad[i] = (byte)0x5c; + i++; + } + + // Very important: update the digest with the ipad buffer + md5.reset(); + md5.update(ipad); + + } + + /** Grab the current digest. This is the "answer". */ + public byte[] getOutput() + { + byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + /** Update by adding a complete array */ + public void update(byte[] input) + { + md5.update(input); + } + + /** Update the algorithm */ + public void update(byte[] input, int offset, int length) + { + md5.update(input,offset,length); + } + } - /** - * @return Returns the credentialCharset. - */ - public String getCredentialCharset() { - return credentialCharset; + /* Run test suite */ + public static void main(String[] args) + throws Exception + { + // MD4 test suite: + checkMD4("","31d6cfe0d16ae931b73c59d7e0c089c0"); + checkMD4("a","bde52cb31de33e46245e05fbdbd6fb24"); + checkMD4("abc","a448017aaf21d8525fc10ae87aa6729d"); + checkMD4("message digest","d9130a8164549fe818874806e1c7014b"); + checkMD4("abcdefghijklmnopqrstuvwxyz","d79e1c308aa5bbcdeea8ed63df412da9"); + checkMD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "043f8582f241db351ce627e153e7f0e4"); + checkMD4("12345678901234567890123456789012345678901234567890123456789012345678901234567890", + "e33b4ddc9c38f2199c3e7b164fcc0536"); + + System.out.println("Tests pass"); } - - /** - * @param credentialCharset The credentialCharset to set. - */ - public void setCredentialCharset(String credentialCharset) { - this.credentialCharset = credentialCharset; + + /* Test suite helper */ + protected static byte checkToNibble(char c) + { + if (c >= 'a' && c <= 'f') + return (byte)(c - 'a' + 0x0a); + return (byte)(c - '0'); + } + + /*Test suite helper */ + protected static byte[] checkToBytes(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.charAt(i*2+1)))); + i++; + } + return rval; + } + + /* Test suite MD4 helper */ + protected static void checkMD4(String input, String hexOutput) + throws Exception + { + MD4 md4; + md4 = new MD4 (); + md4.update(input.getBytes("ASCII")); + byte[] answer = md4.getOutput(); + byte[] correctAnswer = checkToBytes(hexOutput); + if (answer.length != correctAnswer.length) + throw new Exception("Answer length disagrees for MD4('"+input+"')"); + int i = 0; + while (i < answer.length) + { + if (answer[i] != correctAnswer[i]) + throw new Exception("Answer value for MD4('"+input+"') disagrees at position "+Integer.toString(i)); + i++; + } } } Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java?rev=919362&r1=919361&r2=919362&view=diff ============================================================================== --- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java (original) +++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java Fri Mar 5 10:33:58 2010 @@ -336,18 +336,23 @@ NTLM ntlm = new NTLM(); ntlm.setCredentialCharset(method.getParams().getCredentialCharset()); String response = null; + if (this.state == INITIATED || this.state == FAILED) { response = ntlm.getType1Message( ntcredentials.getHost(), ntcredentials.getDomain()); this.state = TYPE1_MSG_GENERATED; } else { + NTLM.Type2Message t2m = new NTLM.Type2Message(this.ntlmchallenge); response = ntlm.getType3Message( ntcredentials.getUserName(), ntcredentials.getPassword(), ntcredentials.getHost(), ntcredentials.getDomain(), - ntlm.parseType2Message(this.ntlmchallenge)); + t2m.getChallenge(), + t2m.getFlags(), + t2m.getTarget(), + t2m.getTargetInfo()); this.state = TYPE3_MSG_GENERATED; } return "NTLM " + response; Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java?rev=919362&r1=919361&r2=919362&view=diff ============================================================================== --- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java (original) +++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java Fri Mar 5 10:33:58 2010 @@ -81,6 +81,14 @@ */ public static final String REJECT_RELATIVE_REDIRECT = "http.protocol.reject-relative-redirect"; + /** + * Supplies a ProtocolFactory object, for custom protocol support even across redirections. + *

+ * This parameter expects a value of type {@link ProtocolFactory}. + *

+ */ + public static final String PROTOCOL_FACTORY = "http.protocol.factory"; + /** * Defines the maximum number of redirects to be followed. * The limit on number of redirects is intended to prevent infinite loops. Added: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java?rev=919362&view=auto ============================================================================== --- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java (added) +++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java Fri Mar 5 10:33:58 2010 @@ -0,0 +1,150 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/protocol/Protocol.java,v 1.10 2004/04/18 23:51:38 jsdever Exp $ + * $Revision: 157457 $ + * $Date: 2005-03-14 15:23:16 -0500 (Mon, 14 Mar 2005) $ + * + * ==================================================================== + * + * Copyright 2002-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.httpclient.protocol; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.util.LangUtils; + +/** + * A class to encapsulate the specifics of a protocol. This class class also + * provides the ability to customize the set and characteristics of the + * protocols used. + * + *

One use case for modifying the default set of protocols would be to set a + * custom SSL socket factory. This would look something like the following: + *

 
+ * Protocol myHTTPS = new Protocol( "https", new MySSLSocketFactory(), 443 );
+ * 
+ * Protocol.registerProtocol( "https", myHTTPS );
+ * 
+ * + * @author Michael Becke + * @author Jeff Dever + * @author Mike Bowler + * + * @since 2.0 + */ +public class ProtocolFactory { + + /** The available protocols */ + private Map PROTOCOLS = Collections.synchronizedMap(new HashMap()); + + /** + * Registers a new protocol with the given identifier. If a protocol with + * the given ID already exists it will be overridden. This ID is the same + * one used to retrieve the protocol from getProtocol(String). + * + * @param id the identifier for this protocol + * @param protocol the protocol to register + * + * @see #getProtocol(String) + */ + public void registerProtocol(String id, Protocol protocol) { + + if (id == null) { + throw new IllegalArgumentException("id is null"); + } + if (protocol == null) { + throw new IllegalArgumentException("protocol is null"); + } + + PROTOCOLS.put(id, protocol); + } + + /** + * Unregisters the protocol with the given ID. + * + * @param id the ID of the protocol to remove + */ + public void unregisterProtocol(String id) { + + if (id == null) { + throw new IllegalArgumentException("id is null"); + } + + PROTOCOLS.remove(id); + } + + /** + * Gets the protocol with the given ID. + * + * @param id the protocol ID + * + * @return Protocol a protocol + * + * @throws IllegalStateException if a protocol with the ID cannot be found + */ + public Protocol getProtocol(String id) + throws IllegalStateException { + + if (id == null) { + throw new IllegalArgumentException("id is null"); + } + + Protocol protocol = (Protocol) PROTOCOLS.get(id); + + if (protocol == null) { + protocol = lazyRegisterProtocol(id); + } + + return protocol; + } + + /** + * Lazily registers the protocol with the given id. + * + * @param id the protocol ID + * + * @return the lazily registered protocol + * + * @throws IllegalStateException if the protocol with id is not recognized + */ + private Protocol lazyRegisterProtocol(String id) + throws IllegalStateException { + + if ("http".equals(id)) { + final Protocol http + = new Protocol("http", DefaultProtocolSocketFactory.getSocketFactory(), 80); + Protocol.registerProtocol("http", http); + return http; + } + + if ("https".equals(id)) { + final Protocol https + = new Protocol("https", SSLProtocolSocketFactory.getSocketFactory(), 443); + Protocol.registerProtocol("https", https); + return https; + } + + throw new IllegalStateException("unsupported protocol: '" + id + "'"); + } +} Propchange: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java ------------------------------------------------------------------------------ svn:eol-style = native