blob: 375c6a3651115a6729d6eb756fde6f13d8ae9fe9 [file] [log] [blame]
Index: src/java/org/apache/commons/httpclient/HostConfiguration.java
===================================================================
--- src/java/org/apache/commons/httpclient/HostConfiguration.java (.../httpcomponents/oac.hc3x/trunk) (revision 915934)
+++ src/java/org/apache/commons/httpclient/HostConfiguration.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (working copy)
@@ -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);
}
Index: src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java
===================================================================
--- src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java (.../httpcomponents/oac.hc3x/trunk) (revision 0)
+++ src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (revision 1308601)
@@ -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
+ * <http://www.apache.org/>.
+ *
+ */
+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.
+ *
+ * <p>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:
+ * <pre>
+ * Protocol myHTTPS = new Protocol( "https", new MySSLSocketFactory(), 443 );
+ *
+ * Protocol.registerProtocol( "https", myHTTPS );
+ * </pre>
+ *
+ * @author Michael Becke
+ * @author Jeff Dever
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *
+ * @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 + "'");
+ }
+}
Property changes on: src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/java/org/apache/commons/httpclient/auth/NTLMScheme.java
===================================================================
--- src/java/org/apache/commons/httpclient/auth/NTLMScheme.java (.../httpcomponents/oac.hc3x/trunk) (revision 915934)
+++ src/java/org/apache/commons/httpclient/auth/NTLMScheme.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (working copy)
@@ -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;
Index: src/java/org/apache/commons/httpclient/auth/NTLM.java
===================================================================
--- src/java/org/apache/commons/httpclient/auth/NTLM.java (.../httpcomponents/oac.hc3x/trunk) (revision 915934)
+++ src/java/org/apache/commons/httpclient/auth/NTLM.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (working copy)
@@ -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.</p>
*
+ * THIS IS A VERY HELPFUL REFERENCE: http://en.wikipedia.org/wiki/NTLM
+ *
* <p>Please note that an implementation of JCE must be correctly installed and configured when
* using NTLM support.</p>
*
+ * NTLMv2 protocol description provided by Michael B Allen <jcifs at samba.org>
+ *
* <p>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.</p>
*
@@ -67,18 +74,52 @@
*/
final class NTLM {
+ // 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);
+ }
+ }
+
+ /** Signature string for NTLM messages */
+ private static final String signatureString = "NTLMSSP";
+
/** Character encoding */
public static final String DEFAULT_CHARSET = "ASCII";
- /** The current response */
- private byte[] currentResponse;
-
- /** The current position */
- private int currentPosition = 0;
-
/** 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.
+ * 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.
*/
- 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);
- }
+ 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.
+ * 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.
*/
- 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;
+ 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);
+ /**
+ * 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);
+ }
- // Type
- addByte((byte) 1);
- addByte((byte) 0);
- addByte((byte) 0);
- addByte((byte) 0);
+ /**
+ * 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);
+ }
- // Flags
- addByte((byte) 6);
- addByte((byte) 82);
- addByte((byte) 0);
- addByte((byte) 0);
+ /**
+ * 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);
+ }
- // Domain length (first time).
- int iDomLen = domainBytes.length;
- byte[] domLen = convertShort(iDomLen);
- addByte(domLen[0]);
- addByte(domLen[1]);
+ /**
+ * 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);
- // Domain length (second time).
- addByte(domLen[0]);
- addByte(domLen[1]);
+ // 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);
+ }
+ }
- // Domain offset.
- byte[] domOff = convertShort(hostBytes.length + 32);
- addByte(domOff[0]);
- addByte(domOff[1]);
- addByte((byte) 0);
- addByte((byte) 0);
+ /**
+ * 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);
+ }
+ }
- // Host length (first time).
- byte[] hostLen = convertShort(hostBytes.length);
- addByte(hostLen[0]);
- addByte(hostLen[1]);
+ /**
+ * Creates the NTLM Hash of the user's password.
+ *
+ * @param password The password.
+ *
+ * @return The NTLM Hash of the given password, used in the calculation
+ * of the NTLM Response and the NTLMv2 and LMv2 Hashes.
+ */
+ 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);
+ }
+ }
- // Host length (second time).
- addByte(hostLen[0]);
- addByte(hostLen[1]);
+ /**
+ * 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);
+ }
+ }
- // Host offset (always 32).
- byte[] hostOff = convertShort(32);
- addByte(hostOff[0]);
- addByte(hostOff[1]);
- addByte((byte) 0);
- addByte((byte) 0);
+ /**
+ * 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);
+ }
+ }
- // Host String.
- addBytes(hostBytes);
-
- // Domain String.
- addBytes(domainBytes);
-
- return getResponse();
+ /**
+ * 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;
}
- /**
- * 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.
+ /**
+ * 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.
*/
- 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.
+ 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++) {
- nonce[i] = msg[i + 24];
+ timestamp[i] = (byte) time;
+ time >>>= 8;
}
- return nonce;
+ 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 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 a DES encryption key from the given key material.
*
- * @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.
+ * @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.
*/
- public String getType3Message(String user, String password,
- String host, String domain, byte[] nonce)
- throws AuthenticationException {
+ 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 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);
+ /**
+ * 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;
- // LM Resp Length (twice)
- addBytes(convertShort(24));
- addBytes(convertShort(24));
+ /** The current output position */
+ private int currentOutputPosition = 0;
- // LM Resp Offset
- addBytes(convertShort(finalLength - 24));
- addByte((byte) 0);
- addByte((byte) 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++;
+ }
- // NT Resp Length (twice)
- addBytes(convertShort(0));
- addBytes(convertShort(0));
+ // 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;
+ }
- // NT Resp Offset
- addBytes(convertShort(finalLength));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Domain length (twice)
- addBytes(convertShort(domainLen));
- addBytes(convertShort(domainLen));
+ /** 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;
+ }
- // Domain offset.
- addBytes(convertShort(64));
- addByte((byte) 0);
- addByte((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];
+ }
- // User Length (twice)
- addBytes(convertShort(userLen));
- addBytes(convertShort(userLen));
+ /** 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);
+ }
- // User offset
- addBytes(convertShort(64 + domainLen));
- addByte((byte) 0);
- addByte((byte) 0);
+ /**
+ * Adds the given byte to the response.
+ * @param b the byte to add.
+ */
+ protected void addByte(byte b) {
+ messageContents[currentOutputPosition] = b;
+ currentOutputPosition++;
+ }
- // Host length (twice)
- addBytes(convertShort(hostLen));
- addBytes(convertShort(hostLen));
+ /**
+ * 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++;
+ }
+ }
- // Host offset
- addBytes(convertShort(64 + domainLen + userLen));
+ /** 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));
+ }
- for (int i = 0; i < 6; i++) {
- addByte((byte) 0);
+ /**
+ * 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));
}
- // Message length
- addBytes(convertShort(finalLength));
- addByte((byte) 0);
- addByte((byte) 0);
+ }
+
+ /** Type 1 message assembly class */
+ public static class Type1Message extends NTLMMessage
+ {
+ protected byte[] hostBytes;
+ protected byte[] domainBytes;
- // Flags
- addByte((byte) 6);
- addByte((byte) 82);
- addByte((byte) 0);
- addByte((byte) 0);
+ /** 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);
- addBytes(domainBytes);
- addBytes(userBytes);
- addBytes(hostBytes);
- addBytes(hashPassword(password, nonce));
- return getResponse();
- }
+ // Domain length (two times).
+ addUShort(domainBytes.length);
+ addUShort(domainBytes.length);
- /**
- * 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.
- */
- 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];
+ // Domain offset.
+ addULong(hostBytes.length + 32);
- int len = passw.length;
- if (len > 7) {
- len = 7;
+ // Host length (two times).
+ addUShort(hostBytes.length);
+ addUShort(hostBytes.length);
+
+ // Host offset (always 32).
+ addULong(32);
+
+ // Host String.
+ addBytes(hostBytes);
+
+ // Domain String.
+ addBytes(domainBytes);
+
+ return super.getResponse();
}
- int idx;
- for (idx = 0; idx < len; idx++) {
- lmPw1[idx] = passw[idx];
+ }
+
+ /** 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 (; idx < 7; idx++) {
- lmPw1[idx] = (byte) 0;
+
+ /** Retrieve the challenge */
+ public byte[] getChallenge()
+ {
+ return challenge;
}
-
- len = passw.length;
- if (len > 14) {
- len = 14;
+
+ /** Retrieve the target */
+ public String getTarget()
+ {
+ return target;
}
- for (idx = 7; idx < len; idx++) {
- lmPw2[idx - 7] = passw[idx];
+
+ /** Retrieve the target info */
+ public byte[] getTargetInfo()
+ {
+ return targetInfo;
}
- for (; idx < 14; idx++) {
- lmPw2[idx - 7] = (byte) 0;
+
+ /** Retrieve the response flags */
+ public int getFlags()
+ {
+ return flags;
}
- // Create LanManager hashed Password
- byte[] magic = {
- (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21,
- (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
- };
+ }
- byte[] lmHpw1;
- lmHpw1 = encrypt(lmPw1, magic);
+ /** Type 3 message assembly class */
+ public static class Type3Message extends NTLMMessage
+ {
+ // Response flags from the type2 message
+ protected int type2Flags;
- byte[] lmHpw2 = encrypt(lmPw2, magic);
+ protected byte[] domainBytes;
+ protected byte[] hostBytes;
+ protected byte[] userBytes;
+
+ protected byte[] lmResp;
+ protected byte[] ntResp;
- byte[] lmHpw = new byte[21];
- for (int i = 0; i < lmHpw1.length; i++) {
- lmHpw[i] = lmHpw1[i];
+ /** 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);
+ }
+
+ 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);
+ }
}
- for (int i = 0; i < lmHpw2.length; i++) {
- lmHpw[i + 8] = lmHpw2[i];
+
+ /** 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);
+
+ return super.getResponse();
}
- for (int i = 0; i < 5; i++) {
- lmHpw[i + 16] = (byte) 0;
- }
+ }
- // Create the responses.
- byte[] lmResp = new byte[24];
- calcResp(lmHpw, nonce, lmResp);
+ 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);
+ }
- return lmResp;
+ protected static int rotintlft(int val, int numbits) {
+ return((val << numbits) | (val >>> (32 - numbits)));
}
- /**
- * 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];
+ /** 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;
+ }
+ }
+
+ 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);
- for (int i = 0; i < 7; i++) {
- keys2[i] = keys[i + 7];
+ // 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;
}
- for (int i = 0; i < 7; i++) {
- keys3[i] = keys[i + 14];
+ 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;
+
}
- byte[] results1 = encrypt(keys1, plaintext);
- byte[] results2 = encrypt(keys2, plaintext);
+ 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);
- byte[] results3 = encrypt(keys3, plaintext);
+ 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);
- for (int i = 0; i < 8; i++) {
- results[i] = results1[i];
+ 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);
+
}
- for (int i = 0; i < 8; i++) {
- results[i + 8] = results2[i];
+
+ 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);
+
}
- for (int i = 0; i < 8; i++) {
- results[i + 16] = results3[i];
- }
+
}
- /**
- * Converts a given number to a two byte array in little endian order.
- * @param num the number to convert.
- * @return The byte representation of <i>num</i> 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;
+ /** 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);
+
}
- 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;
+
+ /** 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++;
+ }
+ }
}
Index: src/java/org/apache/commons/httpclient/cookie/CookieSpecMediumSecurity.java
===================================================================
--- src/java/org/apache/commons/httpclient/cookie/CookieSpecMediumSecurity.java (.../httpcomponents/oac.hc3x/trunk) (revision 0)
+++ src/java/org/apache/commons/httpclient/cookie/CookieSpecMediumSecurity.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (revision 1308601)
@@ -0,0 +1,153 @@
+/*
+ * $HeadURL: https://svn.apache.org/repos/asf/incubator/lcf/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/cookie/CookieSpecBase.java $
+ * $Revision: 653067 $
+ * $Date: 2008-05-03 08:42:39 -0400 (Sat, 03 May 2008) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.commons.httpclient.cookie;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.httpclient.Cookie;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HeaderElement;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.util.DateParseException;
+import org.apache.commons.httpclient.util.DateUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * Cookie management functions shared by all specification.
+ *
+ * @author B.C. Holmes
+ * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a>
+ * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
+ * @author Rod Waldhoff
+ * @author dIon Gillard
+ * @author Sean C. Sullivan
+ * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a>
+ * @author Marc A. Saegesser
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *
+ * @since 2.0
+ */
+public class CookieSpecMediumSecurity extends CookieSpecBase {
+
+ /** Log object */
+ protected static final Log LOG = LogFactory.getLog(CookieSpecMediumSecurity.class);
+
+ /** Default constructor */
+ public CookieSpecMediumSecurity() {
+ super();
+ }
+
+ /**
+ * Performs most common {@link Cookie} validation
+ *
+ * @param host the host from which the {@link Cookie} was received
+ * @param port the port from which the {@link Cookie} was received
+ * @param path the path from which the {@link Cookie} was received
+ * @param secure <tt>true</tt> when the {@link Cookie} was received using a
+ * secure connection
+ * @param cookie The cookie to validate.
+ * @throws MalformedCookieException if an exception occurs during
+ * validation
+ */
+
+ public void validate(String host, int port, String path,
+ boolean secure, final Cookie cookie)
+ throws MalformedCookieException {
+
+ LOG.trace("enter CookieSpecMediumSecurity.validate("
+ + "String, port, path, boolean, Cookie)");
+ if (host == null) {
+ throw new IllegalArgumentException(
+ "Host of origin may not be null");
+ }
+ if (host.trim().equals("")) {
+ throw new IllegalArgumentException(
+ "Host of origin may not be blank");
+ }
+ if (port < 0) {
+ throw new IllegalArgumentException("Invalid port: " + port);
+ }
+ if (path == null) {
+ throw new IllegalArgumentException(
+ "Path of origin may not be null.");
+ }
+ if (path.trim().equals("")) {
+ path = PATH_DELIM;
+ }
+ host = host.toLowerCase(Locale.ENGLISH);
+ // check version
+ if (cookie.getVersion() < 0) {
+ throw new MalformedCookieException ("Illegal version number "
+ + cookie.getValue());
+ }
+
+ // security check... we musn't allow the server to give us an
+ // invalid domain scope
+
+ // Validate the cookies domain attribute. NOTE: Domains without
+ // any dots are allowed to support hosts on private LANs that don't
+ // have DNS names. Since they have no dots, to domain-match the
+ // request-host and domain must be identical for the cookie to sent
+ // back to the origin-server.
+ if (host.indexOf(".") >= 0) {
+ // Not required to have at least two dots. RFC 2965.
+ // A Set-Cookie2 with Domain=ajax.com will be accepted.
+
+ // domain must match host
+ if (!host.endsWith(cookie.getDomain())) {
+ String s = cookie.getDomain();
+ if (s.startsWith(".")) {
+ s = s.substring(1, s.length());
+ }
+ if (!host.equals(s)) {
+ throw new MalformedCookieException(
+ "Illegal domain attribute \"" + cookie.getDomain()
+ + "\". Domain of origin: \"" + host + "\"");
+ }
+ }
+ } else {
+ if (!host.equals(cookie.getDomain())) {
+ throw new MalformedCookieException(
+ "Illegal domain attribute \"" + cookie.getDomain()
+ + "\". Domain of origin: \"" + host + "\"");
+ }
+ }
+
+ }
+
+}
Property changes on: src/java/org/apache/commons/httpclient/cookie/CookieSpecMediumSecurity.java
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+Id
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java
===================================================================
--- src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java (.../httpcomponents/oac.hc3x/trunk) (revision 915934)
+++ src/java/org/apache/commons/httpclient/cookie/CookiePolicy.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (working copy)
@@ -69,6 +69,11 @@
*/
public static final String BROWSER_COMPATIBILITY = "compatibility";
+ /**
+ * Medium-security browser compatibility setting.
+ */
+ public static final String BROWSER_COMPATIBILITY_MEDIUM_SECURITY = "compatibilitymediumsecurity";
+
/**
* The Netscape cookie draft compliant policy.
*
@@ -109,6 +114,7 @@
CookiePolicy.registerCookieSpec(RFC_2109, RFC2109Spec.class);
CookiePolicy.registerCookieSpec(RFC_2965, RFC2965Spec.class);
CookiePolicy.registerCookieSpec(BROWSER_COMPATIBILITY, CookieSpecBase.class);
+ CookiePolicy.registerCookieSpec(BROWSER_COMPATIBILITY_MEDIUM_SECURITY, CookieSpecMediumSecurity.class);
CookiePolicy.registerCookieSpec(NETSCAPE, NetscapeDraftSpec.class);
CookiePolicy.registerCookieSpec(IGNORE_COOKIES, IgnoreCookiesSpec.class);
}
Index: src/java/org/apache/commons/httpclient/params/HttpClientParams.java
===================================================================
--- src/java/org/apache/commons/httpclient/params/HttpClientParams.java (.../httpcomponents/oac.hc3x/trunk) (revision 915934)
+++ src/java/org/apache/commons/httpclient/params/HttpClientParams.java (.../incubator/lcf/upstream/commons-httpclient-3.1-mcf/trunk) (working copy)
@@ -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.
+ * <p>
+ * This parameter expects a value of type {@link ProtocolFactory}.
+ * </p>
+ */
+ 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.