| 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. |