michael@0: /* michael@0: * ==================================================================== michael@0: * michael@0: * Licensed to the Apache Software Foundation (ASF) under one or more michael@0: * contributor license agreements. See the NOTICE file distributed with michael@0: * this work for additional information regarding copyright ownership. michael@0: * The ASF licenses this file to You under the Apache License, Version 2.0 michael@0: * (the "License"); you may not use this file except in compliance with michael@0: * the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl.auth; michael@0: michael@0: import java.security.Key; michael@0: import java.security.MessageDigest; michael@0: import java.util.Arrays; michael@0: michael@0: import javax.crypto.Cipher; michael@0: import javax.crypto.spec.SecretKeySpec; michael@0: michael@0: import org.mozilla.apache.commons.codec.binary.Base64; michael@0: import ch.boye.httpclientandroidlib.util.EncodingUtils; michael@0: michael@0: /** michael@0: * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM michael@0: * authentication protocol. michael@0: * michael@0: * @since 4.1 michael@0: */ michael@0: final class NTLMEngineImpl implements NTLMEngine { michael@0: michael@0: // Flags we use michael@0: protected final static int FLAG_UNICODE_ENCODING = 0x00000001; michael@0: protected final static int FLAG_TARGET_DESIRED = 0x00000004; michael@0: protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010; michael@0: protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020; michael@0: protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200; michael@0: protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; michael@0: protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000; michael@0: protected final static int FLAG_NEGOTIATE_128 = 0x20000000; michael@0: protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000; michael@0: michael@0: /** Secure random generator */ michael@0: private static final java.security.SecureRandom RND_GEN; michael@0: static { michael@0: java.security.SecureRandom rnd = null; michael@0: try { michael@0: rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); michael@0: } catch (Exception e) { michael@0: } michael@0: RND_GEN = rnd; michael@0: } michael@0: michael@0: /** Character encoding */ michael@0: static final String DEFAULT_CHARSET = "ASCII"; michael@0: michael@0: /** The character set to use for encoding the credentials */ michael@0: private String credentialCharset = DEFAULT_CHARSET; michael@0: michael@0: /** The signature string as bytes in the default encoding */ michael@0: private static byte[] SIGNATURE; michael@0: michael@0: static { michael@0: byte[] bytesWithoutNull = EncodingUtils.getBytes("NTLMSSP", "ASCII"); michael@0: SIGNATURE = new byte[bytesWithoutNull.length + 1]; michael@0: System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length); michael@0: SIGNATURE[bytesWithoutNull.length] = (byte) 0x00; michael@0: } michael@0: michael@0: /** michael@0: * Returns the response for the given message. michael@0: * michael@0: * @param message michael@0: * the message that was received from the server. michael@0: * @param username michael@0: * the username to authenticate with. michael@0: * @param password michael@0: * the password to authenticate with. michael@0: * @param host michael@0: * The host. michael@0: * @param domain michael@0: * the NT domain to authenticate in. michael@0: * @return The response. michael@0: * @throws HttpException michael@0: * If the messages cannot be retrieved. michael@0: */ michael@0: final String getResponseFor(String message, String username, String password, michael@0: String host, String domain) throws NTLMEngineException { michael@0: michael@0: final String response; michael@0: if (message == null || message.trim().equals("")) { michael@0: response = getType1Message(host, domain); michael@0: } else { michael@0: Type2Message t2m = new Type2Message(message); michael@0: response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m michael@0: .getFlags(), t2m.getTarget(), t2m.getTargetInfo()); michael@0: } michael@0: return response; michael@0: } michael@0: michael@0: /** michael@0: * Creates the first message (type 1 message) in the NTLM authentication michael@0: * sequence. This message includes the user name, domain and host for the michael@0: * authentication session. michael@0: * michael@0: * @param host michael@0: * the computer name of the host requesting authentication. michael@0: * @param domain michael@0: * The domain to authenticate with. michael@0: * @return String the message to add to the HTTP request header. michael@0: */ michael@0: String getType1Message(String host, String domain) throws NTLMEngineException { michael@0: return new Type1Message(domain, host).getResponse(); michael@0: } michael@0: michael@0: /** michael@0: * Creates the type 3 message using the given server nonce. The type 3 michael@0: * message includes all the information for authentication, host, domain, michael@0: * username and the result of encrypting the nonce sent by the server using michael@0: * the user's password as the key. michael@0: * michael@0: * @param user michael@0: * The user name. This should not include the domain name. michael@0: * @param password michael@0: * The password. michael@0: * @param host michael@0: * The host that is originating the authentication request. michael@0: * @param domain michael@0: * The domain to authenticate within. michael@0: * @param nonce michael@0: * the 8 byte array the server sent. michael@0: * @return The type 3 message. michael@0: * @throws NTLMEngineException michael@0: * If {@encrypt(byte[],byte[])} fails. michael@0: */ michael@0: String getType3Message(String user, String password, String host, String domain, michael@0: byte[] nonce, int type2Flags, String target, byte[] targetInformation) michael@0: throws NTLMEngineException { michael@0: return new Type3Message(domain, host, user, password, nonce, type2Flags, target, michael@0: targetInformation).getResponse(); michael@0: } michael@0: michael@0: /** michael@0: * @return Returns the credentialCharset. michael@0: */ michael@0: String getCredentialCharset() { michael@0: return credentialCharset; michael@0: } michael@0: michael@0: /** michael@0: * @param credentialCharset michael@0: * The credentialCharset to set. michael@0: */ michael@0: void setCredentialCharset(String credentialCharset) { michael@0: this.credentialCharset = credentialCharset; michael@0: } michael@0: michael@0: /** Strip dot suffix from a name */ michael@0: private static String stripDotSuffix(String value) { michael@0: int index = value.indexOf("."); michael@0: if (index != -1) michael@0: return value.substring(0, index); michael@0: return value; michael@0: } michael@0: michael@0: /** Convert host to standard form */ michael@0: private static String convertHost(String host) { michael@0: return stripDotSuffix(host); michael@0: } michael@0: michael@0: /** Convert domain to standard form */ michael@0: private static String convertDomain(String domain) { michael@0: return stripDotSuffix(domain); michael@0: } michael@0: michael@0: private static int readULong(byte[] src, int index) throws NTLMEngineException { michael@0: if (src.length < index + 4) michael@0: throw new NTLMEngineException("NTLM authentication - buffer too small for DWORD"); michael@0: return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) michael@0: | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); michael@0: } michael@0: michael@0: private static int readUShort(byte[] src, int index) throws NTLMEngineException { michael@0: if (src.length < index + 2) michael@0: throw new NTLMEngineException("NTLM authentication - buffer too small for WORD"); michael@0: return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); michael@0: } michael@0: michael@0: private static byte[] readSecurityBuffer(byte[] src, int index) throws NTLMEngineException { michael@0: int length = readUShort(src, index); michael@0: int offset = readULong(src, index + 4); michael@0: if (src.length < offset + length) michael@0: throw new NTLMEngineException( michael@0: "NTLM authentication - buffer too small for data item"); michael@0: byte[] buffer = new byte[length]; michael@0: System.arraycopy(src, offset, buffer, 0, length); michael@0: return buffer; michael@0: } michael@0: michael@0: /** Calculate a challenge block */ michael@0: private static byte[] makeRandomChallenge() throws NTLMEngineException { michael@0: if (RND_GEN == null) { michael@0: throw new NTLMEngineException("Random generator not available"); michael@0: } michael@0: byte[] rval = new byte[8]; michael@0: synchronized (RND_GEN) { michael@0: RND_GEN.nextBytes(rval); michael@0: } michael@0: return rval; michael@0: } michael@0: michael@0: /** Calculate an NTLM2 challenge block */ michael@0: private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException { michael@0: if (RND_GEN == null) { michael@0: throw new NTLMEngineException("Random generator not available"); michael@0: } michael@0: byte[] rval = new byte[24]; michael@0: synchronized (RND_GEN) { michael@0: RND_GEN.nextBytes(rval); michael@0: } michael@0: // 8-byte challenge, padded with zeros to 24 bytes. michael@0: Arrays.fill(rval, 8, 24, (byte) 0x00); michael@0: return rval; michael@0: } michael@0: michael@0: /** michael@0: * Calculates the LM Response for the given challenge, using the specified michael@0: * password. michael@0: * michael@0: * @param password michael@0: * The user's password. michael@0: * @param challenge michael@0: * The Type 2 challenge from the server. michael@0: * michael@0: * @return The LM Response. michael@0: */ michael@0: static byte[] getLMResponse(String password, byte[] challenge) michael@0: throws NTLMEngineException { michael@0: byte[] lmHash = lmHash(password); michael@0: return lmResponse(lmHash, challenge); michael@0: } michael@0: michael@0: /** michael@0: * Calculates the NTLM Response for the given challenge, using the specified michael@0: * password. michael@0: * michael@0: * @param password michael@0: * The user's password. michael@0: * @param challenge michael@0: * The Type 2 challenge from the server. michael@0: * michael@0: * @return The NTLM Response. michael@0: */ michael@0: static byte[] getNTLMResponse(String password, byte[] challenge) michael@0: throws NTLMEngineException { michael@0: byte[] ntlmHash = ntlmHash(password); michael@0: return lmResponse(ntlmHash, challenge); michael@0: } michael@0: michael@0: /** michael@0: * Calculates the NTLMv2 Response for the given challenge, using the michael@0: * specified authentication target, username, password, target information michael@0: * block, and client challenge. michael@0: * michael@0: * @param target michael@0: * The authentication target (i.e., domain). michael@0: * @param user michael@0: * The username. michael@0: * @param password michael@0: * The user's password. michael@0: * @param targetInformation michael@0: * The target information block from the Type 2 message. michael@0: * @param challenge michael@0: * The Type 2 challenge from the server. michael@0: * @param clientChallenge michael@0: * The random 8-byte client challenge. michael@0: * michael@0: * @return The NTLMv2 Response. michael@0: */ michael@0: static byte[] getNTLMv2Response(String target, String user, String password, michael@0: byte[] challenge, byte[] clientChallenge, byte[] targetInformation) michael@0: throws NTLMEngineException { michael@0: byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); michael@0: byte[] blob = createBlob(clientChallenge, targetInformation); michael@0: return lmv2Response(ntlmv2Hash, challenge, blob); michael@0: } michael@0: michael@0: /** michael@0: * Calculates the LMv2 Response for the given challenge, using the specified michael@0: * authentication target, username, password, and client challenge. michael@0: * michael@0: * @param target michael@0: * The authentication target (i.e., domain). michael@0: * @param user michael@0: * The username. michael@0: * @param password michael@0: * The user's password. michael@0: * @param challenge michael@0: * The Type 2 challenge from the server. michael@0: * @param clientChallenge michael@0: * The random 8-byte client challenge. michael@0: * michael@0: * @return The LMv2 Response. michael@0: */ michael@0: static byte[] getLMv2Response(String target, String user, String password, michael@0: byte[] challenge, byte[] clientChallenge) throws NTLMEngineException { michael@0: byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); michael@0: return lmv2Response(ntlmv2Hash, challenge, clientChallenge); michael@0: } michael@0: michael@0: /** michael@0: * Calculates the NTLM2 Session Response for the given challenge, using the michael@0: * specified password and client challenge. michael@0: * michael@0: * @param password michael@0: * The user's password. michael@0: * @param challenge michael@0: * The Type 2 challenge from the server. michael@0: * @param clientChallenge michael@0: * The random 8-byte client challenge. michael@0: * michael@0: * @return The NTLM2 Session Response. This is placed in the NTLM response michael@0: * field of the Type 3 message; the LM response field contains the michael@0: * client challenge, null-padded to 24 bytes. michael@0: */ michael@0: static byte[] getNTLM2SessionResponse(String password, byte[] challenge, michael@0: byte[] clientChallenge) throws NTLMEngineException { michael@0: try { michael@0: byte[] ntlmHash = ntlmHash(password); michael@0: michael@0: // Look up MD5 algorithm (was necessary on jdk 1.4.2) michael@0: // This used to be needed, but java 1.5.0_07 includes the MD5 michael@0: // algorithm (finally) michael@0: // Class x = Class.forName("gnu.crypto.hash.MD5"); michael@0: // Method updateMethod = x.getMethod("update",new michael@0: // Class[]{byte[].class}); michael@0: // Method digestMethod = x.getMethod("digest",new Class[0]); michael@0: // Object mdInstance = x.newInstance(); michael@0: // updateMethod.invoke(mdInstance,new Object[]{challenge}); michael@0: // updateMethod.invoke(mdInstance,new Object[]{clientChallenge}); michael@0: // byte[] digest = (byte[])digestMethod.invoke(mdInstance,new michael@0: // Object[0]); michael@0: michael@0: MessageDigest md5 = MessageDigest.getInstance("MD5"); michael@0: md5.update(challenge); michael@0: md5.update(clientChallenge); michael@0: byte[] digest = md5.digest(); michael@0: michael@0: byte[] sessionHash = new byte[8]; michael@0: System.arraycopy(digest, 0, sessionHash, 0, 8); michael@0: return lmResponse(ntlmHash, sessionHash); michael@0: } catch (Exception e) { michael@0: if (e instanceof NTLMEngineException) michael@0: throw (NTLMEngineException) e; michael@0: throw new NTLMEngineException(e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates the LM Hash of the user's password. michael@0: * michael@0: * @param password michael@0: * The password. michael@0: * michael@0: * @return The LM Hash of the given password, used in the calculation of the michael@0: * LM Response. michael@0: */ michael@0: private static byte[] lmHash(String password) throws NTLMEngineException { michael@0: try { michael@0: byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII"); michael@0: int length = Math.min(oemPassword.length, 14); michael@0: byte[] keyBytes = new byte[14]; michael@0: System.arraycopy(oemPassword, 0, keyBytes, 0, length); michael@0: Key lowKey = createDESKey(keyBytes, 0); michael@0: Key highKey = createDESKey(keyBytes, 7); michael@0: byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII"); michael@0: Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); michael@0: des.init(Cipher.ENCRYPT_MODE, lowKey); michael@0: byte[] lowHash = des.doFinal(magicConstant); michael@0: des.init(Cipher.ENCRYPT_MODE, highKey); michael@0: byte[] highHash = des.doFinal(magicConstant); michael@0: byte[] lmHash = new byte[16]; michael@0: System.arraycopy(lowHash, 0, lmHash, 0, 8); michael@0: System.arraycopy(highHash, 0, lmHash, 8, 8); michael@0: return lmHash; michael@0: } catch (Exception e) { michael@0: throw new NTLMEngineException(e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates the NTLM Hash of the user's password. michael@0: * michael@0: * @param password michael@0: * The password. michael@0: * michael@0: * @return The NTLM Hash of the given password, used in the calculation of michael@0: * the NTLM Response and the NTLMv2 and LMv2 Hashes. michael@0: */ michael@0: private static byte[] ntlmHash(String password) throws NTLMEngineException { michael@0: try { michael@0: byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); michael@0: MD4 md4 = new MD4(); michael@0: md4.update(unicodePassword); michael@0: return md4.getOutput(); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates the NTLMv2 Hash of the user's password. michael@0: * michael@0: * @param target michael@0: * The authentication target (i.e., domain). michael@0: * @param user michael@0: * The username. michael@0: * @param password michael@0: * The password. michael@0: * michael@0: * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 michael@0: * Responses. michael@0: */ michael@0: private static byte[] ntlmv2Hash(String target, String user, String password) michael@0: throws NTLMEngineException { michael@0: try { michael@0: byte[] ntlmHash = ntlmHash(password); michael@0: HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); michael@0: // Upper case username, mixed case target!! michael@0: hmacMD5.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked")); michael@0: hmacMD5.update(target.getBytes("UnicodeLittleUnmarked")); michael@0: return hmacMD5.getOutput(); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates the LM Response from the given hash and Type 2 challenge. michael@0: * michael@0: * @param hash michael@0: * The LM or NTLM Hash. michael@0: * @param challenge michael@0: * The server challenge from the Type 2 message. michael@0: * michael@0: * @return The response (either LM or NTLM, depending on the provided hash). michael@0: */ michael@0: private static byte[] lmResponse(byte[] hash, byte[] challenge) throws NTLMEngineException { michael@0: try { michael@0: byte[] keyBytes = new byte[21]; michael@0: System.arraycopy(hash, 0, keyBytes, 0, 16); michael@0: Key lowKey = createDESKey(keyBytes, 0); michael@0: Key middleKey = createDESKey(keyBytes, 7); michael@0: Key highKey = createDESKey(keyBytes, 14); michael@0: Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); michael@0: des.init(Cipher.ENCRYPT_MODE, lowKey); michael@0: byte[] lowResponse = des.doFinal(challenge); michael@0: des.init(Cipher.ENCRYPT_MODE, middleKey); michael@0: byte[] middleResponse = des.doFinal(challenge); michael@0: des.init(Cipher.ENCRYPT_MODE, highKey); michael@0: byte[] highResponse = des.doFinal(challenge); michael@0: byte[] lmResponse = new byte[24]; michael@0: System.arraycopy(lowResponse, 0, lmResponse, 0, 8); michael@0: System.arraycopy(middleResponse, 0, lmResponse, 8, 8); michael@0: System.arraycopy(highResponse, 0, lmResponse, 16, 8); michael@0: return lmResponse; michael@0: } catch (Exception e) { michael@0: throw new NTLMEngineException(e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Creates the LMv2 Response from the given hash, client data, and Type 2 michael@0: * challenge. michael@0: * michael@0: * @param hash michael@0: * The NTLMv2 Hash. michael@0: * @param clientData michael@0: * The client data (blob or client challenge). michael@0: * @param challenge michael@0: * The server challenge from the Type 2 message. michael@0: * michael@0: * @return The response (either NTLMv2 or LMv2, depending on the client michael@0: * data). michael@0: */ michael@0: private static byte[] lmv2Response(byte[] hash, byte[] challenge, byte[] clientData) michael@0: throws NTLMEngineException { michael@0: HMACMD5 hmacMD5 = new HMACMD5(hash); michael@0: hmacMD5.update(challenge); michael@0: hmacMD5.update(clientData); michael@0: byte[] mac = hmacMD5.getOutput(); michael@0: byte[] lmv2Response = new byte[mac.length + clientData.length]; michael@0: System.arraycopy(mac, 0, lmv2Response, 0, mac.length); michael@0: System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); michael@0: return lmv2Response; michael@0: } michael@0: michael@0: /** michael@0: * Creates the NTLMv2 blob from the given target information block and michael@0: * client challenge. michael@0: * michael@0: * @param targetInformation michael@0: * The target information block from the Type 2 message. michael@0: * @param clientChallenge michael@0: * The random 8-byte client challenge. michael@0: * michael@0: * @return The blob, used in the calculation of the NTLMv2 Response. michael@0: */ michael@0: private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) { michael@0: byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; michael@0: byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; michael@0: byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; michael@0: long time = System.currentTimeMillis(); michael@0: time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. michael@0: time *= 10000; // tenths of a microsecond. michael@0: // convert to little-endian byte array. michael@0: byte[] timestamp = new byte[8]; michael@0: for (int i = 0; i < 8; i++) { michael@0: timestamp[i] = (byte) time; michael@0: time >>>= 8; michael@0: } michael@0: byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 michael@0: + unknown1.length + targetInformation.length]; michael@0: int offset = 0; michael@0: System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); michael@0: offset += blobSignature.length; michael@0: System.arraycopy(reserved, 0, blob, offset, reserved.length); michael@0: offset += reserved.length; michael@0: System.arraycopy(timestamp, 0, blob, offset, timestamp.length); michael@0: offset += timestamp.length; michael@0: System.arraycopy(clientChallenge, 0, blob, offset, 8); michael@0: offset += 8; michael@0: System.arraycopy(unknown1, 0, blob, offset, unknown1.length); michael@0: offset += unknown1.length; michael@0: System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); michael@0: return blob; michael@0: } michael@0: michael@0: /** michael@0: * Creates a DES encryption key from the given key material. michael@0: * michael@0: * @param bytes michael@0: * A byte array containing the DES key material. michael@0: * @param offset michael@0: * The offset in the given byte array at which the 7-byte key michael@0: * material starts. michael@0: * michael@0: * @return A DES encryption key created from the key material starting at michael@0: * the specified offset in the given byte array. michael@0: */ michael@0: private static Key createDESKey(byte[] bytes, int offset) { michael@0: byte[] keyBytes = new byte[7]; michael@0: System.arraycopy(bytes, offset, keyBytes, 0, 7); michael@0: byte[] material = new byte[8]; michael@0: material[0] = keyBytes[0]; michael@0: material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); michael@0: material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); michael@0: material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); michael@0: material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); michael@0: material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); michael@0: material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); michael@0: material[7] = (byte) (keyBytes[6] << 1); michael@0: oddParity(material); michael@0: return new SecretKeySpec(material, "DES"); michael@0: } michael@0: michael@0: /** michael@0: * Applies odd parity to the given byte array. michael@0: * michael@0: * @param bytes michael@0: * The data whose parity bits are to be adjusted for odd parity. michael@0: */ michael@0: private static void oddParity(byte[] bytes) { michael@0: for (int i = 0; i < bytes.length; i++) { michael@0: byte b = bytes[i]; michael@0: boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) michael@0: ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; michael@0: if (needsParity) { michael@0: bytes[i] |= (byte) 0x01; michael@0: } else { michael@0: bytes[i] &= (byte) 0xfe; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** NTLM message generation, base class */ michael@0: static class NTLMMessage { michael@0: /** The current response */ michael@0: private byte[] messageContents = null; michael@0: michael@0: /** The current output position */ michael@0: private int currentOutputPosition = 0; michael@0: michael@0: /** Constructor to use when message contents are not yet known */ michael@0: NTLMMessage() { michael@0: } michael@0: michael@0: /** Constructor to use when message contents are known */ michael@0: NTLMMessage(String messageBody, int expectedType) throws NTLMEngineException { michael@0: messageContents = Base64.decodeBase64(EncodingUtils.getBytes(messageBody, michael@0: DEFAULT_CHARSET)); michael@0: // Look for NTLM message michael@0: if (messageContents.length < SIGNATURE.length) michael@0: throw new NTLMEngineException("NTLM message decoding error - packet too short"); michael@0: int i = 0; michael@0: while (i < SIGNATURE.length) { michael@0: if (messageContents[i] != SIGNATURE[i]) michael@0: throw new NTLMEngineException( michael@0: "NTLM message expected - instead got unrecognized bytes"); michael@0: i++; michael@0: } michael@0: michael@0: // Check to be sure there's a type 2 message indicator next michael@0: int type = readULong(SIGNATURE.length); michael@0: if (type != expectedType) michael@0: throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) michael@0: + " message expected - instead got type " + Integer.toString(type)); michael@0: michael@0: currentOutputPosition = messageContents.length; michael@0: } michael@0: michael@0: /** michael@0: * Get the length of the signature and flags, so calculations can adjust michael@0: * offsets accordingly. michael@0: */ michael@0: protected int getPreambleLength() { michael@0: return SIGNATURE.length + 4; michael@0: } michael@0: michael@0: /** Get the message length */ michael@0: protected int getMessageLength() { michael@0: return currentOutputPosition; michael@0: } michael@0: michael@0: /** Read a byte from a position within the message buffer */ michael@0: protected byte readByte(int position) throws NTLMEngineException { michael@0: if (messageContents.length < position + 1) michael@0: throw new NTLMEngineException("NTLM: Message too short"); michael@0: return messageContents[position]; michael@0: } michael@0: michael@0: /** Read a bunch of bytes from a position in the message buffer */ michael@0: protected void readBytes(byte[] buffer, int position) throws NTLMEngineException { michael@0: if (messageContents.length < position + buffer.length) michael@0: throw new NTLMEngineException("NTLM: Message too short"); michael@0: System.arraycopy(messageContents, position, buffer, 0, buffer.length); michael@0: } michael@0: michael@0: /** Read a ushort from a position within the message buffer */ michael@0: protected int readUShort(int position) throws NTLMEngineException { michael@0: return NTLMEngineImpl.readUShort(messageContents, position); michael@0: } michael@0: michael@0: /** Read a ulong from a position within the message buffer */ michael@0: protected int readULong(int position) throws NTLMEngineException { michael@0: return NTLMEngineImpl.readULong(messageContents, position); michael@0: } michael@0: michael@0: /** Read a security buffer from a position within the message buffer */ michael@0: protected byte[] readSecurityBuffer(int position) throws NTLMEngineException { michael@0: return NTLMEngineImpl.readSecurityBuffer(messageContents, position); michael@0: } michael@0: michael@0: /** michael@0: * Prepares the object to create a response of the given length. michael@0: * michael@0: * @param length michael@0: * the maximum length of the response to prepare, not michael@0: * including the type and the signature (which this method michael@0: * adds). michael@0: */ michael@0: protected void prepareResponse(int maxlength, int messageType) { michael@0: messageContents = new byte[maxlength]; michael@0: currentOutputPosition = 0; michael@0: addBytes(SIGNATURE); michael@0: addULong(messageType); michael@0: } michael@0: michael@0: /** michael@0: * Adds the given byte to the response. michael@0: * michael@0: * @param b michael@0: * the byte to add. michael@0: */ michael@0: protected void addByte(byte b) { michael@0: messageContents[currentOutputPosition] = b; michael@0: currentOutputPosition++; michael@0: } michael@0: michael@0: /** michael@0: * Adds the given bytes to the response. michael@0: * michael@0: * @param bytes michael@0: * the bytes to add. michael@0: */ michael@0: protected void addBytes(byte[] bytes) { michael@0: for (int i = 0; i < bytes.length; i++) { michael@0: messageContents[currentOutputPosition] = bytes[i]; michael@0: currentOutputPosition++; michael@0: } michael@0: } michael@0: michael@0: /** Adds a USHORT to the response */ michael@0: protected void addUShort(int value) { michael@0: addByte((byte) (value & 0xff)); michael@0: addByte((byte) (value >> 8 & 0xff)); michael@0: } michael@0: michael@0: /** Adds a ULong to the response */ michael@0: protected void addULong(int value) { michael@0: addByte((byte) (value & 0xff)); michael@0: addByte((byte) (value >> 8 & 0xff)); michael@0: addByte((byte) (value >> 16 & 0xff)); michael@0: addByte((byte) (value >> 24 & 0xff)); michael@0: } michael@0: michael@0: /** michael@0: * Returns the response that has been generated after shrinking the michael@0: * array if required and base64 encodes the response. michael@0: * michael@0: * @return The response as above. michael@0: */ michael@0: String getResponse() { michael@0: byte[] resp; michael@0: if (messageContents.length > currentOutputPosition) { michael@0: byte[] tmp = new byte[currentOutputPosition]; michael@0: for (int i = 0; i < currentOutputPosition; i++) { michael@0: tmp[i] = messageContents[i]; michael@0: } michael@0: resp = tmp; michael@0: } else { michael@0: resp = messageContents; michael@0: } michael@0: return EncodingUtils.getAsciiString(Base64.encodeBase64(resp)); michael@0: } michael@0: michael@0: } michael@0: michael@0: /** Type 1 message assembly class */ michael@0: static class Type1Message extends NTLMMessage { michael@0: protected byte[] hostBytes; michael@0: protected byte[] domainBytes; michael@0: michael@0: /** Constructor. Include the arguments the message will need */ michael@0: Type1Message(String domain, String host) throws NTLMEngineException { michael@0: super(); michael@0: try { michael@0: // Strip off domain name from the host! michael@0: host = convertHost(host); michael@0: // Use only the base domain name! michael@0: domain = convertDomain(domain); michael@0: michael@0: hostBytes = host.getBytes("UnicodeLittleUnmarked"); michael@0: domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: throw new NTLMEngineException("Unicode unsupported: " + e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Getting the response involves building the message before returning michael@0: * it michael@0: */ michael@0: @Override michael@0: String getResponse() { michael@0: // Now, build the message. Calculate its length first, including michael@0: // signature or type. michael@0: int finalLength = 32 + hostBytes.length + domainBytes.length; michael@0: michael@0: // Set up the response. This will initialize the signature, message michael@0: // type, and flags. michael@0: prepareResponse(finalLength, 1); michael@0: michael@0: // Flags. These are the complete set of flags we support. michael@0: addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN michael@0: | FLAG_NEGOTIATE_SEAL | michael@0: /* michael@0: * FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH | michael@0: */ michael@0: FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128); michael@0: michael@0: // Domain length (two times). michael@0: addUShort(domainBytes.length); michael@0: addUShort(domainBytes.length); michael@0: michael@0: // Domain offset. michael@0: addULong(hostBytes.length + 32); michael@0: michael@0: // Host length (two times). michael@0: addUShort(hostBytes.length); michael@0: addUShort(hostBytes.length); michael@0: michael@0: // Host offset (always 32). michael@0: addULong(32); michael@0: michael@0: // Host String. michael@0: addBytes(hostBytes); michael@0: michael@0: // Domain String. michael@0: addBytes(domainBytes); michael@0: michael@0: return super.getResponse(); michael@0: } michael@0: michael@0: } michael@0: michael@0: /** Type 2 message class */ michael@0: static class Type2Message extends NTLMMessage { michael@0: protected byte[] challenge; michael@0: protected String target; michael@0: protected byte[] targetInfo; michael@0: protected int flags; michael@0: michael@0: Type2Message(String message) throws NTLMEngineException { michael@0: super(message, 2); michael@0: michael@0: // Parse out the rest of the info we need from the message michael@0: // The nonce is the 8 bytes starting from the byte in position 24. michael@0: challenge = new byte[8]; michael@0: readBytes(challenge, 24); michael@0: michael@0: flags = readULong(20); michael@0: if ((flags & FLAG_UNICODE_ENCODING) == 0) michael@0: throw new NTLMEngineException( michael@0: "NTLM type 2 message has flags that make no sense: " michael@0: + Integer.toString(flags)); michael@0: // Do the target! michael@0: target = null; michael@0: // The TARGET_DESIRED flag is said to not have understood semantics michael@0: // in Type2 messages, so use the length of the packet to decide michael@0: // how to proceed instead michael@0: if (getMessageLength() >= 12 + 8) { michael@0: byte[] bytes = readSecurityBuffer(12); michael@0: if (bytes.length != 0) { michael@0: try { michael@0: target = new String(bytes, "UnicodeLittleUnmarked"); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: throw new NTLMEngineException(e.getMessage(), e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Do the target info! michael@0: targetInfo = null; michael@0: // TARGET_DESIRED flag cannot be relied on, so use packet length michael@0: if (getMessageLength() >= 40 + 8) { michael@0: byte[] bytes = readSecurityBuffer(40); michael@0: if (bytes.length != 0) { michael@0: targetInfo = bytes; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** Retrieve the challenge */ michael@0: byte[] getChallenge() { michael@0: return challenge; michael@0: } michael@0: michael@0: /** Retrieve the target */ michael@0: String getTarget() { michael@0: return target; michael@0: } michael@0: michael@0: /** Retrieve the target info */ michael@0: byte[] getTargetInfo() { michael@0: return targetInfo; michael@0: } michael@0: michael@0: /** Retrieve the response flags */ michael@0: int getFlags() { michael@0: return flags; michael@0: } michael@0: michael@0: } michael@0: michael@0: /** Type 3 message assembly class */ michael@0: static class Type3Message extends NTLMMessage { michael@0: // Response flags from the type2 message michael@0: protected int type2Flags; michael@0: michael@0: protected byte[] domainBytes; michael@0: protected byte[] hostBytes; michael@0: protected byte[] userBytes; michael@0: michael@0: protected byte[] lmResp; michael@0: protected byte[] ntResp; michael@0: michael@0: /** Constructor. Pass the arguments we will need */ michael@0: Type3Message(String domain, String host, String user, String password, byte[] nonce, michael@0: int type2Flags, String target, byte[] targetInformation) michael@0: throws NTLMEngineException { michael@0: // Save the flags michael@0: this.type2Flags = type2Flags; michael@0: michael@0: // Strip off domain name from the host! michael@0: host = convertHost(host); michael@0: // Use only the base domain name! michael@0: domain = convertDomain(domain); michael@0: michael@0: // Use the new code to calculate the responses, including v2 if that michael@0: // seems warranted. michael@0: try { michael@0: if (targetInformation != null && target != null) { michael@0: byte[] clientChallenge = makeRandomChallenge(); michael@0: ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge, michael@0: targetInformation); michael@0: lmResp = getLMv2Response(target, user, password, nonce, clientChallenge); michael@0: } else { michael@0: if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) { michael@0: // NTLM2 session stuff is requested michael@0: byte[] clientChallenge = makeNTLM2RandomChallenge(); michael@0: michael@0: ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge); michael@0: lmResp = clientChallenge; michael@0: michael@0: // All the other flags we send (signing, sealing, key michael@0: // exchange) are supported, but they don't do anything michael@0: // at all in an michael@0: // NTLM2 context! So we're done at this point. michael@0: } else { michael@0: ntResp = getNTLMResponse(password, nonce); michael@0: lmResp = getLMResponse(password, nonce); michael@0: } michael@0: } michael@0: } catch (NTLMEngineException e) { michael@0: // This likely means we couldn't find the MD4 hash algorithm - michael@0: // fail back to just using LM michael@0: ntResp = new byte[0]; michael@0: lmResp = getLMResponse(password, nonce); michael@0: } michael@0: michael@0: try { michael@0: domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); michael@0: hostBytes = host.getBytes("UnicodeLittleUnmarked"); michael@0: userBytes = user.getBytes("UnicodeLittleUnmarked"); michael@0: } catch (java.io.UnsupportedEncodingException e) { michael@0: throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); michael@0: } michael@0: } michael@0: michael@0: /** Assemble the response */ michael@0: @Override michael@0: String getResponse() { michael@0: int ntRespLen = ntResp.length; michael@0: int lmRespLen = lmResp.length; michael@0: michael@0: int domainLen = domainBytes.length; michael@0: int hostLen = hostBytes.length; michael@0: int userLen = userBytes.length; michael@0: michael@0: // Calculate the layout within the packet michael@0: int lmRespOffset = 64; michael@0: int ntRespOffset = lmRespOffset + lmRespLen; michael@0: int domainOffset = ntRespOffset + ntRespLen; michael@0: int userOffset = domainOffset + domainLen; michael@0: int hostOffset = userOffset + userLen; michael@0: int sessionKeyOffset = hostOffset + hostLen; michael@0: int finalLength = sessionKeyOffset + 0; michael@0: michael@0: // Start the response. Length includes signature and type michael@0: prepareResponse(finalLength, 3); michael@0: michael@0: // LM Resp Length (twice) michael@0: addUShort(lmRespLen); michael@0: addUShort(lmRespLen); michael@0: michael@0: // LM Resp Offset michael@0: addULong(lmRespOffset); michael@0: michael@0: // NT Resp Length (twice) michael@0: addUShort(ntRespLen); michael@0: addUShort(ntRespLen); michael@0: michael@0: // NT Resp Offset michael@0: addULong(ntRespOffset); michael@0: michael@0: // Domain length (twice) michael@0: addUShort(domainLen); michael@0: addUShort(domainLen); michael@0: michael@0: // Domain offset. michael@0: addULong(domainOffset); michael@0: michael@0: // User Length (twice) michael@0: addUShort(userLen); michael@0: addUShort(userLen); michael@0: michael@0: // User offset michael@0: addULong(userOffset); michael@0: michael@0: // Host length (twice) michael@0: addUShort(hostLen); michael@0: addUShort(hostLen); michael@0: michael@0: // Host offset michael@0: addULong(hostOffset); michael@0: michael@0: // 4 bytes of zeros - not sure what this is michael@0: addULong(0); michael@0: michael@0: // Message length michael@0: addULong(finalLength); michael@0: michael@0: // Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING + michael@0: // TARGET_DESIRED + NEGOTIATE_128 michael@0: addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED michael@0: | FLAG_NEGOTIATE_128 | (type2Flags & FLAG_NEGOTIATE_NTLM2) michael@0: | (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL) michael@0: | (type2Flags & FLAG_NEGOTIATE_KEY_EXCH) michael@0: | (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN)); michael@0: michael@0: // Add the actual data michael@0: addBytes(lmResp); michael@0: addBytes(ntResp); michael@0: addBytes(domainBytes); michael@0: addBytes(userBytes); michael@0: addBytes(hostBytes); michael@0: michael@0: return super.getResponse(); michael@0: } michael@0: } michael@0: michael@0: static void writeULong(byte[] buffer, int value, int offset) { michael@0: buffer[offset] = (byte) (value & 0xff); michael@0: buffer[offset + 1] = (byte) (value >> 8 & 0xff); michael@0: buffer[offset + 2] = (byte) (value >> 16 & 0xff); michael@0: buffer[offset + 3] = (byte) (value >> 24 & 0xff); michael@0: } michael@0: michael@0: static int F(int x, int y, int z) { michael@0: return ((x & y) | (~x & z)); michael@0: } michael@0: michael@0: static int G(int x, int y, int z) { michael@0: return ((x & y) | (x & z) | (y & z)); michael@0: } michael@0: michael@0: static int H(int x, int y, int z) { michael@0: return (x ^ y ^ z); michael@0: } michael@0: michael@0: static int rotintlft(int val, int numbits) { michael@0: return ((val << numbits) | (val >>> (32 - numbits))); michael@0: } michael@0: michael@0: /** michael@0: * Cryptography support - MD4. The following class was based loosely on the michael@0: * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java. michael@0: * Code correctness was verified by looking at MD4.java from the jcifs michael@0: * library (http://jcifs.samba.org). It was massaged extensively to the michael@0: * final form found here by Karl Wright (kwright@metacarta.com). michael@0: */ michael@0: static class MD4 { michael@0: protected int A = 0x67452301; michael@0: protected int B = 0xefcdab89; michael@0: protected int C = 0x98badcfe; michael@0: protected int D = 0x10325476; michael@0: protected long count = 0L; michael@0: protected byte[] dataBuffer = new byte[64]; michael@0: michael@0: MD4() { michael@0: } michael@0: michael@0: void update(byte[] input) { michael@0: // We always deal with 512 bits at a time. Correspondingly, there is michael@0: // a buffer 64 bytes long that we write data into until it gets michael@0: // full. michael@0: int curBufferPos = (int) (count & 63L); michael@0: int inputIndex = 0; michael@0: while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { michael@0: // We have enough data to do the next step. Do a partial copy michael@0: // and a transform, updating inputIndex and curBufferPos michael@0: // accordingly michael@0: int transferAmt = dataBuffer.length - curBufferPos; michael@0: System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); michael@0: count += transferAmt; michael@0: curBufferPos = 0; michael@0: inputIndex += transferAmt; michael@0: processBuffer(); michael@0: } michael@0: michael@0: // If there's anything left, copy it into the buffer and leave it. michael@0: // We know there's not enough left to process. michael@0: if (inputIndex < input.length) { michael@0: int transferAmt = input.length - inputIndex; michael@0: System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); michael@0: count += transferAmt; michael@0: curBufferPos += transferAmt; michael@0: } michael@0: } michael@0: michael@0: byte[] getOutput() { michael@0: // Feed pad/length data into engine. This must round out the input michael@0: // to a multiple of 512 bits. michael@0: int bufferIndex = (int) (count & 63L); michael@0: int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); michael@0: byte[] postBytes = new byte[padLen + 8]; michael@0: // Leading 0x80, specified amount of zero padding, then length in michael@0: // bits. michael@0: postBytes[0] = (byte) 0x80; michael@0: // Fill out the last 8 bytes with the length michael@0: for (int i = 0; i < 8; i++) { michael@0: postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); michael@0: } michael@0: michael@0: // Update the engine michael@0: update(postBytes); michael@0: michael@0: // Calculate final result michael@0: byte[] result = new byte[16]; michael@0: writeULong(result, A, 0); michael@0: writeULong(result, B, 4); michael@0: writeULong(result, C, 8); michael@0: writeULong(result, D, 12); michael@0: return result; michael@0: } michael@0: michael@0: protected void processBuffer() { michael@0: // Convert current buffer to 16 ulongs michael@0: int[] d = new int[16]; michael@0: michael@0: for (int i = 0; i < 16; i++) { michael@0: d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) michael@0: + ((dataBuffer[i * 4 + 2] & 0xff) << 16) michael@0: + ((dataBuffer[i * 4 + 3] & 0xff) << 24); michael@0: } michael@0: michael@0: // Do a round of processing michael@0: int AA = A; michael@0: int BB = B; michael@0: int CC = C; michael@0: int DD = D; michael@0: round1(d); michael@0: round2(d); michael@0: round3(d); michael@0: A += AA; michael@0: B += BB; michael@0: C += CC; michael@0: D += DD; michael@0: michael@0: } michael@0: michael@0: protected void round1(int[] d) { michael@0: A = rotintlft((A + F(B, C, D) + d[0]), 3); michael@0: D = rotintlft((D + F(A, B, C) + d[1]), 7); michael@0: C = rotintlft((C + F(D, A, B) + d[2]), 11); michael@0: B = rotintlft((B + F(C, D, A) + d[3]), 19); michael@0: michael@0: A = rotintlft((A + F(B, C, D) + d[4]), 3); michael@0: D = rotintlft((D + F(A, B, C) + d[5]), 7); michael@0: C = rotintlft((C + F(D, A, B) + d[6]), 11); michael@0: B = rotintlft((B + F(C, D, A) + d[7]), 19); michael@0: michael@0: A = rotintlft((A + F(B, C, D) + d[8]), 3); michael@0: D = rotintlft((D + F(A, B, C) + d[9]), 7); michael@0: C = rotintlft((C + F(D, A, B) + d[10]), 11); michael@0: B = rotintlft((B + F(C, D, A) + d[11]), 19); michael@0: michael@0: A = rotintlft((A + F(B, C, D) + d[12]), 3); michael@0: D = rotintlft((D + F(A, B, C) + d[13]), 7); michael@0: C = rotintlft((C + F(D, A, B) + d[14]), 11); michael@0: B = rotintlft((B + F(C, D, A) + d[15]), 19); michael@0: } michael@0: michael@0: protected void round2(int[] d) { michael@0: A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); michael@0: D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); michael@0: C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); michael@0: B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); michael@0: michael@0: A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); michael@0: D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); michael@0: C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); michael@0: B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); michael@0: michael@0: A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); michael@0: D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); michael@0: C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); michael@0: B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); michael@0: michael@0: A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); michael@0: D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); michael@0: C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); michael@0: B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); michael@0: michael@0: } michael@0: michael@0: protected void round3(int[] d) { michael@0: A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); michael@0: D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); michael@0: C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); michael@0: B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); michael@0: michael@0: A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); michael@0: D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); michael@0: C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); michael@0: B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); michael@0: michael@0: A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); michael@0: D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); michael@0: C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); michael@0: B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); michael@0: michael@0: A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); michael@0: D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); michael@0: C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); michael@0: B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); michael@0: michael@0: } michael@0: michael@0: } michael@0: michael@0: /** michael@0: * Cryptography support - HMACMD5 - algorithmically based on various web michael@0: * resources by Karl Wright michael@0: */ michael@0: static class HMACMD5 { michael@0: protected byte[] ipad; michael@0: protected byte[] opad; michael@0: protected MessageDigest md5; michael@0: michael@0: HMACMD5(byte[] key) throws NTLMEngineException { michael@0: try { michael@0: md5 = MessageDigest.getInstance("MD5"); michael@0: } catch (Exception ex) { michael@0: // Umm, the algorithm doesn't exist - throw an michael@0: // NTLMEngineException! michael@0: throw new NTLMEngineException( michael@0: "Error getting md5 message digest implementation: " + ex.getMessage(), ex); michael@0: } michael@0: michael@0: // Initialize the pad buffers with the key michael@0: ipad = new byte[64]; michael@0: opad = new byte[64]; michael@0: michael@0: int keyLength = key.length; michael@0: if (keyLength > 64) { michael@0: // Use MD5 of the key instead, as described in RFC 2104 michael@0: md5.update(key); michael@0: key = md5.digest(); michael@0: keyLength = key.length; michael@0: } michael@0: int i = 0; michael@0: while (i < keyLength) { michael@0: ipad[i] = (byte) (key[i] ^ (byte) 0x36); michael@0: opad[i] = (byte) (key[i] ^ (byte) 0x5c); michael@0: i++; michael@0: } michael@0: while (i < 64) { michael@0: ipad[i] = (byte) 0x36; michael@0: opad[i] = (byte) 0x5c; michael@0: i++; michael@0: } michael@0: michael@0: // Very important: update the digest with the ipad buffer michael@0: md5.reset(); michael@0: md5.update(ipad); michael@0: michael@0: } michael@0: michael@0: /** Grab the current digest. This is the "answer". */ michael@0: byte[] getOutput() { michael@0: byte[] digest = md5.digest(); michael@0: md5.update(opad); michael@0: return md5.digest(digest); michael@0: } michael@0: michael@0: /** Update by adding a complete array */ michael@0: void update(byte[] input) { michael@0: md5.update(input); michael@0: } michael@0: michael@0: /** Update the algorithm */ michael@0: void update(byte[] input, int offset, int length) { michael@0: md5.update(input, offset, length); michael@0: } michael@0: michael@0: } michael@0: michael@0: public String generateType1Msg( michael@0: final String domain, michael@0: final String workstation) throws NTLMEngineException { michael@0: return getType1Message(workstation, domain); michael@0: } michael@0: michael@0: public String generateType3Msg( michael@0: final String username, michael@0: final String password, michael@0: final String domain, michael@0: final String workstation, michael@0: final String challenge) throws NTLMEngineException { michael@0: Type2Message t2m = new Type2Message(challenge); michael@0: return getType3Message( michael@0: username, michael@0: password, michael@0: workstation, michael@0: domain, michael@0: t2m.getChallenge(), michael@0: t2m.getFlags(), michael@0: t2m.getTarget(), michael@0: t2m.getTargetInfo()); michael@0: } michael@0: michael@0: }