| /* |
| * ==================================================================== |
| * 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.hc.client5.http.impl.auth; |
| |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.security.Key; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateEncodingException; |
| import java.util.Arrays; |
| import java.util.Locale; |
| import java.util.Random; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| import org.apache.commons.codec.binary.Base64; |
| import org.apache.hc.client5.http.utils.ByteArrayBuilder; |
| |
| /** |
| * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM |
| * authentication protocol. |
| * |
| * @since 4.1 |
| */ |
| final class NTLMEngineImpl implements NTLMEngine { |
| |
| /** Unicode encoding */ |
| private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked"); |
| /** Character encoding */ |
| private static final Charset DEFAULT_CHARSET = StandardCharsets.US_ASCII; |
| |
| // Flags we use; descriptions according to: |
| // http://davenport.sourceforge.net/ntlm.html |
| // and |
| // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx |
| // [MS-NLMP] section 2.2.2.5 |
| static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested |
| static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002; // OEM string encoding requested |
| static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field |
| static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message. |
| static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT. |
| static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key |
| static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both |
| static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message |
| static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message |
| static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL. |
| static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security |
| static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version |
| static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present |
| static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange |
| static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange |
| static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL |
| |
| // Attribute-value identifiers (AvId) |
| // according to [MS-NLMP] section 2.2.2.1 |
| static final int MSV_AV_EOL = 0x0000; // Indicates that this is the last AV_PAIR in the list. |
| static final int MSV_AV_NB_COMPUTER_NAME = 0x0001; // The server's NetBIOS computer name. |
| static final int MSV_AV_NB_DOMAIN_NAME = 0x0002; // The server's NetBIOS domain name. |
| static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003; // The fully qualified domain name (FQDN) of the computer. |
| static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004; // The FQDN of the domain. |
| static final int MSV_AV_DNS_TREE_NAME = 0x0005; // The FQDN of the forest. |
| static final int MSV_AV_FLAGS = 0x0006; // A 32-bit value indicating server or client configuration. |
| static final int MSV_AV_TIMESTAMP = 0x0007; // server local time |
| static final int MSV_AV_SINGLE_HOST = 0x0008; // A Single_Host_Data structure. |
| static final int MSV_AV_TARGET_NAME = 0x0009; // The SPN of the target server. |
| static final int MSV_AV_CHANNEL_BINDINGS = 0x000A; // A channel bindings hash. |
| |
| static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001; // Indicates to the client that the account authentication is constrained. |
| static final int MSV_AV_FLAGS_MIC = 0x00000002; // Indicates that the client is providing message integrity in the MIC field in the AUTHENTICATE_MESSAGE. |
| static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004; // Indicates that the client is providing a target SPN generated from an untrusted source. |
| |
| /** Secure random generator */ |
| private static final java.security.SecureRandom RND_GEN; |
| static { |
| java.security.SecureRandom rnd = null; |
| try { |
| rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); |
| } catch (final Exception ignore) { |
| } |
| RND_GEN = rnd; |
| } |
| |
| /** The signature string as bytes in the default encoding */ |
| private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP"); |
| |
| // Key derivation magic strings for the SIGNKEY algorithm defined in |
| // [MS-NLMP] section 3.4.5.2ASCII |
| private static final byte[] SIGN_MAGIC_SERVER = getNullTerminatedAsciiString( |
| "session key to server-to-client signing key magic constant"); |
| private static final byte[] SIGN_MAGIC_CLIENT = getNullTerminatedAsciiString( |
| "session key to client-to-server signing key magic constant"); |
| private static final byte[] SEAL_MAGIC_SERVER = getNullTerminatedAsciiString( |
| "session key to server-to-client sealing key magic constant"); |
| private static final byte[] SEAL_MAGIC_CLIENT = getNullTerminatedAsciiString( |
| "session key to client-to-server sealing key magic constant"); |
| |
| // prefix for GSS API channel binding |
| private static final byte[] MAGIC_TLS_SERVER_ENDPOINT = "tls-server-end-point:".getBytes(StandardCharsets.US_ASCII); |
| |
| private static byte[] getNullTerminatedAsciiString( final String source ) |
| { |
| final byte[] bytesWithoutNull = source.getBytes(StandardCharsets.US_ASCII); |
| final byte[] target = new byte[bytesWithoutNull.length + 1]; |
| System.arraycopy(bytesWithoutNull, 0, target, 0, bytesWithoutNull.length); |
| target[bytesWithoutNull.length] = (byte) 0x00; |
| return target; |
| } |
| |
| private static final String TYPE_1_MESSAGE = new Type1Message().getResponse(); |
| |
| NTLMEngineImpl() { |
| } |
| |
| /** |
| * Returns the response for the given message. |
| * |
| * @param message |
| * the message that was received from the server. |
| * @param username |
| * the username to authenticate with. |
| * @param password |
| * the password to authenticate with. |
| * @param host |
| * The host. |
| * @param domain |
| * the NT domain to authenticate in. |
| * @return The response. |
| */ |
| static String getResponseFor(final String message, final String username, final char[] password, |
| final String host, final String domain) throws NTLMEngineException { |
| |
| final String response; |
| if (message == null || message.trim().equals("")) { |
| response = getType1Message(host, domain); |
| } else { |
| final Type2Message t2m = new Type2Message(message); |
| response = getType3Message(username, password, host, domain, t2m.getChallenge(), |
| t2m.getFlags(), t2m.getTarget(), t2m.getTargetInfo()); |
| } |
| return response; |
| } |
| |
| /** |
| * Returns the response for the given message. |
| * |
| * @param message |
| * the message that was received from the server. |
| * @param username |
| * the username to authenticate with. |
| * @param password |
| * the password to authenticate with. |
| * @param host |
| * The host. |
| * @param domain |
| * the NT domain to authenticate in. |
| * @return The response. |
| */ |
| static String getResponseFor(final String message, final String username, final char[] password, |
| final String host, final String domain, final Certificate peerServerCertificate) throws NTLMEngineException { |
| |
| final String response; |
| if (message == null || message.trim().equals("")) { |
| response = new Type1Message(host, domain).getResponse(); |
| } else { |
| final Type1Message t1m = new Type1Message(host, domain); |
| final Type2Message t2m = new Type2Message(message); |
| response = getType3Message(username, password, host, domain, t2m.getChallenge(), |
| t2m.getFlags(), t2m.getTarget(), t2m.getTargetInfo(), |
| peerServerCertificate, t1m.getBytes(), t2m.getBytes()); |
| } |
| return response; |
| } |
| |
| /** |
| * 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. |
| */ |
| static String getType1Message(final String host, final String domain) { |
| // For compatibility reason do not include domain and host in type 1 message |
| //return new Type1Message(domain, host).getResponse(); |
| return TYPE_1_MESSAGE; |
| } |
| |
| /** |
| * 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 NTLMEngineException |
| * If {@encrypt(byte[],byte[])} fails. |
| */ |
| static String getType3Message(final String user, final char[] password, final String host, final String domain, |
| final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation) |
| throws NTLMEngineException { |
| return new Type3Message(domain, host, user, password, nonce, type2Flags, target, |
| targetInformation).getResponse(); |
| } |
| |
| /** |
| * 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. |
| */ |
| static String getType3Message(final String user, final char[] password, final String host, final String domain, |
| final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation, |
| final Certificate peerServerCertificate, final byte[] type1Message, final byte[] type2Message) |
| throws NTLMEngineException { |
| return new Type3Message(domain, host, user, password, nonce, type2Flags, target, |
| targetInformation, peerServerCertificate, type1Message, type2Message).getResponse(); |
| } |
| |
| private static int readULong(final byte[] src, final int index) { |
| if (src.length < index + 4) { |
| return 0; |
| } |
| return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) |
| | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); |
| } |
| |
| private static int readUShort(final byte[] src, final int index) { |
| if (src.length < index + 2) { |
| return 0; |
| } |
| return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); |
| } |
| |
| private static byte[] readSecurityBuffer(final byte[] src, final int index) { |
| final int length = readUShort(src, index); |
| final int offset = readULong(src, index + 4); |
| if (src.length < offset + length) { |
| return new byte[length]; |
| } |
| final byte[] buffer = new byte[length]; |
| System.arraycopy(src, offset, buffer, 0, length); |
| return buffer; |
| } |
| |
| /** Calculate a challenge block */ |
| private static byte[] makeRandomChallenge(final Random random) { |
| final byte[] rval = new byte[8]; |
| synchronized (random) { |
| random.nextBytes(rval); |
| } |
| return rval; |
| } |
| |
| /** Calculate a 16-byte secondary key */ |
| private static byte[] makeSecondaryKey(final Random random) { |
| final byte[] rval = new byte[16]; |
| synchronized (random) { |
| random.nextBytes(rval); |
| } |
| return rval; |
| } |
| |
| static class CipherGen { |
| |
| final Random random; |
| final long currentTime; |
| |
| final String domain; |
| final String user; |
| final char[] password; |
| final byte[] challenge; |
| final String target; |
| final byte[] targetInformation; |
| |
| // Information we can generate but may be passed in (for testing) |
| byte[] clientChallenge; |
| byte[] clientChallenge2; |
| byte[] secondaryKey; |
| byte[] timestamp; |
| |
| // Stuff we always generate |
| byte[] lmHash = null; |
| byte[] lmResponse = null; |
| byte[] ntlmHash = null; |
| byte[] ntlmResponse = null; |
| byte[] ntlmv2Hash = null; |
| byte[] lmv2Hash = null; |
| byte[] lmv2Response = null; |
| byte[] ntlmv2Blob = null; |
| byte[] ntlmv2Response = null; |
| byte[] ntlm2SessionResponse = null; |
| byte[] lm2SessionResponse = null; |
| byte[] lmUserSessionKey = null; |
| byte[] ntlmUserSessionKey = null; |
| byte[] ntlmv2UserSessionKey = null; |
| byte[] ntlm2SessionResponseUserSessionKey = null; |
| byte[] lanManagerSessionKey = null; |
| |
| public CipherGen(final Random random, final long currentTime, |
| final String domain, final String user, final char[] password, |
| final byte[] challenge, final String target, final byte[] targetInformation, |
| final byte[] clientChallenge, final byte[] clientChallenge2, |
| final byte[] secondaryKey, final byte[] timestamp) { |
| this.random = random; |
| this.currentTime = currentTime; |
| |
| this.domain = domain; |
| this.target = target; |
| this.user = user; |
| this.password = password; |
| this.challenge = challenge; |
| this.targetInformation = targetInformation; |
| this.clientChallenge = clientChallenge; |
| this.clientChallenge2 = clientChallenge2; |
| this.secondaryKey = secondaryKey; |
| this.timestamp = timestamp; |
| } |
| |
| public CipherGen(final Random random, final long currentTime, |
| final String domain, |
| final String user, |
| final char[] password, |
| final byte[] challenge, |
| final String target, |
| final byte[] targetInformation) { |
| this(random, currentTime, domain, user, password, challenge, target, targetInformation, null, null, null, null); |
| } |
| |
| /** Calculate and return client challenge */ |
| public byte[] getClientChallenge() { |
| if (clientChallenge == null) { |
| clientChallenge = makeRandomChallenge(random); |
| } |
| return clientChallenge; |
| } |
| |
| /** Calculate and return second client challenge */ |
| public byte[] getClientChallenge2() { |
| if (clientChallenge2 == null) { |
| clientChallenge2 = makeRandomChallenge(random); |
| } |
| return clientChallenge2; |
| } |
| |
| /** Calculate and return random secondary key */ |
| public byte[] getSecondaryKey() { |
| if (secondaryKey == null) { |
| secondaryKey = makeSecondaryKey(random); |
| } |
| return secondaryKey; |
| } |
| |
| /** Calculate and return the LMHash */ |
| public byte[] getLMHash() |
| throws NTLMEngineException { |
| if (lmHash == null) { |
| lmHash = lmHash(password); |
| } |
| return lmHash; |
| } |
| |
| /** Calculate and return the LMResponse */ |
| public byte[] getLMResponse() |
| throws NTLMEngineException { |
| if (lmResponse == null) { |
| lmResponse = lmResponse(getLMHash(),challenge); |
| } |
| return lmResponse; |
| } |
| |
| /** Calculate and return the NTLMHash */ |
| public byte[] getNTLMHash() |
| throws NTLMEngineException { |
| if (ntlmHash == null) { |
| ntlmHash = ntlmHash(password); |
| } |
| return ntlmHash; |
| } |
| |
| /** Calculate and return the NTLMResponse */ |
| public byte[] getNTLMResponse() |
| throws NTLMEngineException { |
| if (ntlmResponse == null) { |
| ntlmResponse = lmResponse(getNTLMHash(),challenge); |
| } |
| return ntlmResponse; |
| } |
| |
| /** Calculate the LMv2 hash */ |
| public byte[] getLMv2Hash() |
| throws NTLMEngineException { |
| if (lmv2Hash == null) { |
| lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); |
| } |
| return lmv2Hash; |
| } |
| |
| /** Calculate the NTLMv2 hash */ |
| public byte[] getNTLMv2Hash() |
| throws NTLMEngineException { |
| if (ntlmv2Hash == null) { |
| ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash()); |
| } |
| return ntlmv2Hash; |
| } |
| |
| /** Calculate a timestamp */ |
| public byte[] getTimestamp() { |
| if (timestamp == null) { |
| long time = this.currentTime; |
| time += 11644473600000L; // milliseconds from January 1, 1601 -> epoch. |
| time *= 10000; // tenths of a microsecond. |
| // convert to little-endian byte array. |
| timestamp = new byte[8]; |
| for (int i = 0; i < 8; i++) { |
| timestamp[i] = (byte) time; |
| time >>>= 8; |
| } |
| } |
| return timestamp; |
| } |
| |
| /** Calculate the NTLMv2Blob */ |
| public byte[] getNTLMv2Blob() { |
| if (ntlmv2Blob == null) { |
| ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); |
| } |
| return ntlmv2Blob; |
| } |
| |
| /** Calculate the NTLMv2Response */ |
| public byte[] getNTLMv2Response() |
| throws NTLMEngineException { |
| if (ntlmv2Response == null) { |
| ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob()); |
| } |
| return ntlmv2Response; |
| } |
| |
| /** Calculate the LMv2Response */ |
| public byte[] getLMv2Response() |
| throws NTLMEngineException { |
| if (lmv2Response == null) { |
| lmv2Response = lmv2Response(getLMv2Hash(),challenge,getClientChallenge()); |
| } |
| return lmv2Response; |
| } |
| |
| /** Get NTLM2SessionResponse */ |
| public byte[] getNTLM2SessionResponse() |
| throws NTLMEngineException { |
| if (ntlm2SessionResponse == null) { |
| ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge()); |
| } |
| return ntlm2SessionResponse; |
| } |
| |
| /** Calculate and return LM2 session response */ |
| public byte[] getLM2SessionResponse() { |
| if (lm2SessionResponse == null) { |
| final byte[] clntChallenge = getClientChallenge(); |
| lm2SessionResponse = new byte[24]; |
| System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length); |
| Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00); |
| } |
| return lm2SessionResponse; |
| } |
| |
| /** Get LMUserSessionKey */ |
| public byte[] getLMUserSessionKey() |
| throws NTLMEngineException { |
| if (lmUserSessionKey == null) { |
| lmUserSessionKey = new byte[16]; |
| System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8); |
| Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00); |
| } |
| return lmUserSessionKey; |
| } |
| |
| /** Get NTLMUserSessionKey */ |
| public byte[] getNTLMUserSessionKey() |
| throws NTLMEngineException { |
| if (ntlmUserSessionKey == null) { |
| final MD4 md4 = new MD4(); |
| md4.update(getNTLMHash()); |
| ntlmUserSessionKey = md4.getOutput(); |
| } |
| return ntlmUserSessionKey; |
| } |
| |
| /** GetNTLMv2UserSessionKey */ |
| public byte[] getNTLMv2UserSessionKey() |
| throws NTLMEngineException { |
| if (ntlmv2UserSessionKey == null) { |
| final byte[] ntlmv2hash = getNTLMv2Hash(); |
| final byte[] truncatedResponse = new byte[16]; |
| System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16); |
| ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); |
| } |
| return ntlmv2UserSessionKey; |
| } |
| |
| /** Get NTLM2SessionResponseUserSessionKey */ |
| public byte[] getNTLM2SessionResponseUserSessionKey() |
| throws NTLMEngineException { |
| if (ntlm2SessionResponseUserSessionKey == null) { |
| final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse(); |
| final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length]; |
| System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length); |
| System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length); |
| ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce,getNTLMUserSessionKey()); |
| } |
| return ntlm2SessionResponseUserSessionKey; |
| } |
| |
| /** Get LAN Manager session key */ |
| public byte[] getLanManagerSessionKey() |
| throws NTLMEngineException { |
| if (lanManagerSessionKey == null) { |
| try { |
| final byte[] keyBytes = new byte[14]; |
| System.arraycopy(getLMHash(), 0, keyBytes, 0, 8); |
| Arrays.fill(keyBytes, 8, keyBytes.length, (byte)0xbd); |
| final Key lowKey = createDESKey(keyBytes, 0); |
| final Key highKey = createDESKey(keyBytes, 7); |
| final byte[] truncatedResponse = new byte[8]; |
| System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length); |
| Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); |
| des.init(Cipher.ENCRYPT_MODE, lowKey); |
| final byte[] lowPart = des.doFinal(truncatedResponse); |
| des = Cipher.getInstance("DES/ECB/NoPadding"); |
| des.init(Cipher.ENCRYPT_MODE, highKey); |
| final byte[] highPart = des.doFinal(truncatedResponse); |
| lanManagerSessionKey = new byte[16]; |
| System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length); |
| System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length); |
| } catch (final Exception e) { |
| throw new NTLMEngineException(e.getMessage(), e); |
| } |
| } |
| return lanManagerSessionKey; |
| } |
| } |
| |
| /** Calculates HMAC-MD5 */ |
| static byte[] hmacMD5(final byte[] value, final byte[] key) { |
| final HMACMD5 hmacMD5 = new HMACMD5(key); |
| hmacMD5.update(value); |
| return hmacMD5.getOutput(); |
| } |
| |
| /** Calculates RC4 */ |
| static byte[] RC4(final byte[] value, final byte[] key) |
| throws NTLMEngineException { |
| try { |
| final Cipher rc4 = Cipher.getInstance("RC4"); |
| rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4")); |
| return rc4.doFinal(value); |
| } catch (final Exception e) { |
| throw new NTLMEngineException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Calculates the NTLM2 Session Response for the given challenge, using the |
| * specified password and 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. |
| */ |
| static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, |
| final byte[] clientChallenge) throws NTLMEngineException { |
| try { |
| final MessageDigest md5 = getMD5(); |
| md5.update(challenge); |
| md5.update(clientChallenge); |
| final byte[] digest = md5.digest(); |
| |
| final byte[] sessionHash = new byte[8]; |
| System.arraycopy(digest, 0, sessionHash, 0, 8); |
| return lmResponse(ntlmHash, sessionHash); |
| } catch (final Exception e) { |
| if (e instanceof NTLMEngineException) { |
| throw (NTLMEngineException) e; |
| } |
| throw new NTLMEngineException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * 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(final char[] password) throws NTLMEngineException { |
| try { |
| final char[] tmp = new char[password.length]; |
| for (int i = 0; i < password.length; i++) { |
| tmp[i] = Character.toUpperCase(password[i]); |
| } |
| final byte[] oemPassword = new ByteArrayBuilder().append(tmp).toByteArray(); |
| final int length = Math.min(oemPassword.length, 14); |
| final byte[] keyBytes = new byte[14]; |
| System.arraycopy(oemPassword, 0, keyBytes, 0, length); |
| final Key lowKey = createDESKey(keyBytes, 0); |
| final Key highKey = createDESKey(keyBytes, 7); |
| final byte[] magicConstant = "KGS!@#$%".getBytes(StandardCharsets.US_ASCII); |
| final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); |
| des.init(Cipher.ENCRYPT_MODE, lowKey); |
| final byte[] lowHash = des.doFinal(magicConstant); |
| des.init(Cipher.ENCRYPT_MODE, highKey); |
| final byte[] highHash = des.doFinal(magicConstant); |
| final byte[] lmHash = new byte[16]; |
| System.arraycopy(lowHash, 0, lmHash, 0, 8); |
| System.arraycopy(highHash, 0, lmHash, 8, 8); |
| return lmHash; |
| } catch (final Exception e) { |
| throw new NTLMEngineException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * 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(final char[] password) throws NTLMEngineException { |
| if (UNICODE_LITTLE_UNMARKED == null) { |
| throw new NTLMEngineException("Unicode not supported"); |
| } |
| final byte[] unicodePassword = new ByteArrayBuilder() |
| .charset(UNICODE_LITTLE_UNMARKED).append(password).toByteArray(); |
| final MD4 md4 = new MD4(); |
| md4.update(unicodePassword); |
| return md4.getOutput(); |
| } |
| |
| /** |
| * Creates the LMv2 Hash of the user's password. |
| * |
| * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 |
| * Responses. |
| */ |
| private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) |
| throws NTLMEngineException { |
| if (UNICODE_LITTLE_UNMARKED == null) { |
| throw new NTLMEngineException("Unicode not supported"); |
| } |
| final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); |
| // Upper case username, upper case domain! |
| hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); |
| if (domain != null) { |
| hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); |
| } |
| return hmacMD5.getOutput(); |
| } |
| |
| /** |
| * Creates the NTLMv2 Hash of the user's password. |
| * |
| * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 |
| * Responses. |
| */ |
| private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) |
| throws NTLMEngineException { |
| if (UNICODE_LITTLE_UNMARKED == null) { |
| throw new NTLMEngineException("Unicode not supported"); |
| } |
| final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); |
| // Upper case username, mixed case target!! |
| hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); |
| if (domain != null) { |
| hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED)); |
| } |
| return hmacMD5.getOutput(); |
| } |
| |
| /** |
| * 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(final byte[] hash, final byte[] challenge) throws NTLMEngineException { |
| try { |
| final byte[] keyBytes = new byte[21]; |
| System.arraycopy(hash, 0, keyBytes, 0, 16); |
| final Key lowKey = createDESKey(keyBytes, 0); |
| final Key middleKey = createDESKey(keyBytes, 7); |
| final Key highKey = createDESKey(keyBytes, 14); |
| final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); |
| des.init(Cipher.ENCRYPT_MODE, lowKey); |
| final byte[] lowResponse = des.doFinal(challenge); |
| des.init(Cipher.ENCRYPT_MODE, middleKey); |
| final byte[] middleResponse = des.doFinal(challenge); |
| des.init(Cipher.ENCRYPT_MODE, highKey); |
| final byte[] highResponse = des.doFinal(challenge); |
| final 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 (final Exception e) { |
| throw new NTLMEngineException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Creates the LMv2 Response from the given hash, client data, and Type 2 |
| * challenge. |
| * |
| * @param hash |
| * The NTLMv2 Hash. |
| * @param clientData |
| * The client data (blob or client challenge). |
| * @param challenge |
| * The server challenge from the Type 2 message. |
| * |
| * @return The response (either NTLMv2 or LMv2, depending on the client |
| * data). |
| */ |
| private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) { |
| final HMACMD5 hmacMD5 = new HMACMD5(hash); |
| hmacMD5.update(challenge); |
| hmacMD5.update(clientData); |
| final byte[] mac = hmacMD5.getOutput(); |
| final 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; |
| } |
| |
| enum Mode |
| { |
| CLIENT, SERVER; |
| } |
| |
| static class Handle |
| { |
| private final byte[] signingKey; |
| private byte[] sealingKey; |
| private final Cipher rc4; |
| final Mode mode; |
| final private boolean isConnection; |
| int sequenceNumber = 0; |
| |
| |
| Handle( final byte[] exportedSessionKey, final Mode mode, final boolean isConnection ) throws NTLMEngineException |
| { |
| this.isConnection = isConnection; |
| this.mode = mode; |
| try |
| { |
| final MessageDigest signMd5 = getMD5(); |
| final MessageDigest sealMd5 = getMD5(); |
| signMd5.update( exportedSessionKey ); |
| sealMd5.update( exportedSessionKey ); |
| if ( mode == Mode.CLIENT ) |
| { |
| signMd5.update( SIGN_MAGIC_CLIENT ); |
| sealMd5.update( SEAL_MAGIC_CLIENT ); |
| } |
| else |
| { |
| signMd5.update( SIGN_MAGIC_SERVER ); |
| sealMd5.update( SEAL_MAGIC_SERVER ); |
| } |
| signingKey = signMd5.digest(); |
| sealingKey = sealMd5.digest(); |
| } |
| catch ( final Exception e ) |
| { |
| throw new NTLMEngineException( e.getMessage(), e ); |
| } |
| rc4 = initCipher(); |
| } |
| |
| public byte[] getSigningKey() |
| { |
| return signingKey; |
| } |
| |
| |
| public byte[] getSealingKey() |
| { |
| return sealingKey; |
| } |
| |
| private Cipher initCipher() throws NTLMEngineException |
| { |
| final Cipher cipher; |
| try |
| { |
| cipher = Cipher.getInstance( "RC4" ); |
| if ( mode == Mode.CLIENT ) |
| { |
| cipher.init( Cipher.ENCRYPT_MODE, new SecretKeySpec( sealingKey, "RC4" ) ); |
| } |
| else |
| { |
| cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( sealingKey, "RC4" ) ); |
| } |
| } |
| catch ( final Exception e ) |
| { |
| throw new NTLMEngineException( e.getMessage(), e ); |
| } |
| return cipher; |
| } |
| |
| |
| private void advanceMessageSequence() throws NTLMEngineException |
| { |
| if ( !isConnection ) |
| { |
| final MessageDigest sealMd5 = getMD5(); |
| sealMd5.update( sealingKey ); |
| final byte[] seqNumBytes = new byte[4]; |
| writeULong( seqNumBytes, sequenceNumber, 0 ); |
| sealMd5.update( seqNumBytes ); |
| sealingKey = sealMd5.digest(); |
| initCipher(); |
| } |
| sequenceNumber++; |
| } |
| |
| private byte[] encrypt( final byte[] data ) |
| { |
| return rc4.update( data ); |
| } |
| |
| private byte[] decrypt( final byte[] data ) |
| { |
| return rc4.update( data ); |
| } |
| |
| private byte[] computeSignature( final byte[] message ) |
| { |
| final byte[] sig = new byte[16]; |
| |
| // version |
| sig[0] = 0x01; |
| sig[1] = 0x00; |
| sig[2] = 0x00; |
| sig[3] = 0x00; |
| |
| // HMAC (first 8 bytes) |
| final HMACMD5 hmacMD5 = new HMACMD5( signingKey ); |
| hmacMD5.update( encodeLong( sequenceNumber ) ); |
| hmacMD5.update( message ); |
| final byte[] hmac = hmacMD5.getOutput(); |
| final byte[] trimmedHmac = new byte[8]; |
| System.arraycopy( hmac, 0, trimmedHmac, 0, 8 ); |
| final byte[] encryptedHmac = encrypt( trimmedHmac ); |
| System.arraycopy( encryptedHmac, 0, sig, 4, 8 ); |
| |
| // sequence number |
| encodeLong( sig, 12, sequenceNumber ); |
| |
| return sig; |
| } |
| |
| private boolean validateSignature( final byte[] signature, final byte message[] ) |
| { |
| final byte[] computedSignature = computeSignature( message ); |
| // log.info( "SSSSS validateSignature("+seqNumber+")\n" |
| // + " received: " + DebugUtil.dump( signature ) + "\n" |
| // + " computed: " + DebugUtil.dump( computedSignature ) ); |
| return Arrays.equals( signature, computedSignature ); |
| } |
| |
| public byte[] signAndEncryptMessage( final byte[] cleartextMessage ) throws NTLMEngineException |
| { |
| final byte[] encryptedMessage = encrypt( cleartextMessage ); |
| final byte[] signature = computeSignature( cleartextMessage ); |
| final byte[] outMessage = new byte[signature.length + encryptedMessage.length]; |
| System.arraycopy( signature, 0, outMessage, 0, signature.length ); |
| System.arraycopy( encryptedMessage, 0, outMessage, signature.length, encryptedMessage.length ); |
| advanceMessageSequence(); |
| return outMessage; |
| } |
| |
| public byte[] decryptAndVerifySignedMessage( final byte[] inMessage ) throws NTLMEngineException |
| { |
| final byte[] signature = new byte[16]; |
| System.arraycopy( inMessage, 0, signature, 0, signature.length ); |
| final byte[] encryptedMessage = new byte[inMessage.length - 16]; |
| System.arraycopy( inMessage, 16, encryptedMessage, 0, encryptedMessage.length ); |
| final byte[] cleartextMessage = decrypt( encryptedMessage ); |
| if ( !validateSignature( signature, cleartextMessage ) ) |
| { |
| throw new NTLMEngineException( "Wrong signature" ); |
| } |
| advanceMessageSequence(); |
| return cleartextMessage; |
| } |
| |
| } |
| |
| private static byte[] encodeLong( final int value ) |
| { |
| final byte[] enc = new byte[4]; |
| encodeLong( enc, 0, value ); |
| return enc; |
| } |
| |
| private static void encodeLong( final byte[] buf, final int offset, final int value ) |
| { |
| buf[offset + 0] = ( byte ) ( value & 0xff ); |
| buf[offset + 1] = ( byte ) ( value >> 8 & 0xff ); |
| buf[offset + 2] = ( byte ) ( value >> 16 & 0xff ); |
| buf[offset + 3] = ( byte ) ( value >> 24 & 0xff ); |
| } |
| |
| /** |
| * Creates the NTLMv2 blob from the given target information block and |
| * client challenge. |
| * |
| * @param targetInformation |
| * The target information block from the Type 2 message. |
| * @param clientChallenge |
| * The random 8-byte client challenge. |
| * |
| * @return The blob, used in the calculation of the NTLMv2 Response. |
| */ |
| private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) { |
| final byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; |
| final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; |
| final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; |
| final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; |
| final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 |
| + unknown1.length + targetInformation.length + unknown2.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); |
| offset += targetInformation.length; |
| System.arraycopy(unknown2, 0, blob, offset, unknown2.length); |
| offset += unknown2.length; |
| return blob; |
| } |
| |
| /** |
| * Creates a DES encryption key from the given key material. |
| * |
| * @param bytes |
| * A byte array containing the DES key material. |
| * @param offset |
| * The offset in the given byte array at which the 7-byte key |
| * material starts. |
| * |
| * @return A DES encryption key created from the key material starting at |
| * the specified offset in the given byte array. |
| */ |
| private static Key createDESKey(final byte[] bytes, final int offset) { |
| final byte[] keyBytes = new byte[7]; |
| System.arraycopy(bytes, offset, keyBytes, 0, 7); |
| final 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"); |
| } |
| |
| /** |
| * 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(final byte[] bytes) { |
| for (int i = 0; i < bytes.length; i++) { |
| final byte b = bytes[i]; |
| final 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; |
| } |
| } |
| } |
| |
| /** |
| * Find the character set based on the flags. |
| * @param flags is the flags. |
| * @return the character set. |
| */ |
| private static Charset getCharset(final int flags) throws NTLMEngineException |
| { |
| if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) { |
| return DEFAULT_CHARSET; |
| } |
| if (UNICODE_LITTLE_UNMARKED == null) { |
| throw new NTLMEngineException( "Unicode not supported" ); |
| } |
| return UNICODE_LITTLE_UNMARKED; |
| } |
| |
| /** NTLM message generation, base class */ |
| static class NTLMMessage { |
| /** The current response */ |
| byte[] messageContents = null; |
| |
| /** The current output position */ |
| int currentOutputPosition = 0; |
| |
| /** Constructor to use when message contents are not yet known */ |
| NTLMMessage() { |
| } |
| |
| /** Constructor taking a string */ |
| NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException { |
| this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)), expectedType); |
| } |
| |
| /** Constructor to use when message bytes are known */ |
| NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException { |
| messageContents = message; |
| // Look for NTLM message |
| if (messageContents.length < SIGNATURE.length) { |
| throw new NTLMEngineException("NTLM message decoding error - packet too short"); |
| } |
| int i = 0; |
| while (i < SIGNATURE.length) { |
| if (messageContents[i] != SIGNATURE[i]) { |
| throw new NTLMEngineException( |
| "NTLM message expected - instead got unrecognized bytes"); |
| } |
| i++; |
| } |
| |
| // Check to be sure there's a type 2 message indicator next |
| final int type = readULong(SIGNATURE.length); |
| if (type != expectedType) { |
| throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) |
| + " message expected - instead got type " + Integer.toString(type)); |
| } |
| |
| currentOutputPosition = messageContents.length; |
| } |
| |
| /** |
| * Get the length of the signature and flags, so calculations can adjust |
| * offsets accordingly. |
| */ |
| int getPreambleLength() { |
| return SIGNATURE.length + 4; |
| } |
| |
| /** Get the message length */ |
| int getMessageLength() { |
| return currentOutputPosition; |
| } |
| |
| /** Read a byte from a position within the message buffer */ |
| byte readByte(final int position) throws NTLMEngineException { |
| if (messageContents.length < position + 1) { |
| throw new NTLMEngineException("NTLM: Message too short"); |
| } |
| return messageContents[position]; |
| } |
| |
| /** Read a bunch of bytes from a position in the message buffer */ |
| void readBytes(final byte[] buffer, final int position) throws NTLMEngineException { |
| if (messageContents.length < position + buffer.length) { |
| throw new NTLMEngineException("NTLM: Message too short"); |
| } |
| System.arraycopy(messageContents, position, buffer, 0, buffer.length); |
| } |
| |
| /** Read a ushort from a position within the message buffer */ |
| int readUShort(final int position) { |
| return NTLMEngineImpl.readUShort(messageContents, position); |
| } |
| |
| /** Read a ulong from a position within the message buffer */ |
| int readULong(final int position) { |
| return NTLMEngineImpl.readULong(messageContents, position); |
| } |
| |
| /** Read a security buffer from a position within the message buffer */ |
| byte[] readSecurityBuffer(final int position) { |
| return NTLMEngineImpl.readSecurityBuffer(messageContents, position); |
| } |
| |
| /** |
| * Prepares the object to create a response of the given length. |
| * |
| * @param maxlength |
| * the maximum length of the response to prepare, |
| * including the type and the signature (which this method |
| * adds). |
| */ |
| void prepareResponse(final int maxlength, final int messageType) { |
| messageContents = new byte[maxlength]; |
| currentOutputPosition = 0; |
| addBytes(SIGNATURE); |
| addULong(messageType); |
| } |
| |
| /** |
| * Adds the given byte to the response. |
| * |
| * @param b |
| * the byte to add. |
| */ |
| void addByte(final byte b) { |
| messageContents[currentOutputPosition] = b; |
| currentOutputPosition++; |
| } |
| |
| /** |
| * Adds the given bytes to the response. |
| * |
| * @param bytes |
| * the bytes to add. |
| */ |
| void addBytes(final byte[] bytes) { |
| if (bytes == null) { |
| return; |
| } |
| for (final byte b : bytes) { |
| messageContents[currentOutputPosition] = b; |
| currentOutputPosition++; |
| } |
| } |
| |
| /** Adds a USHORT to the response */ |
| void addUShort(final int value) { |
| addByte((byte) (value & 0xff)); |
| addByte((byte) (value >> 8 & 0xff)); |
| } |
| |
| /** Adds a ULong to the response */ |
| void addULong(final int value) { |
| addByte((byte) (value & 0xff)); |
| addByte((byte) (value >> 8 & 0xff)); |
| addByte((byte) (value >> 16 & 0xff)); |
| addByte((byte) (value >> 24 & 0xff)); |
| } |
| |
| /** |
| * Returns the response that has been generated after shrinking the |
| * array if required and base64 encodes the response. |
| * |
| * @return The response as above. |
| */ |
| public String getResponse() { |
| return new String(Base64.encodeBase64(getBytes()), StandardCharsets.US_ASCII); |
| } |
| |
| public byte[] getBytes() { |
| if (messageContents == null) { |
| buildMessage(); |
| } |
| final byte[] resp; |
| if ( messageContents.length > currentOutputPosition ) { |
| final byte[] tmp = new byte[currentOutputPosition]; |
| System.arraycopy( messageContents, 0, tmp, 0, currentOutputPosition ); |
| messageContents = tmp; |
| } |
| return messageContents; |
| } |
| |
| void buildMessage() { |
| throw new RuntimeException("Message builder not implemented for "+getClass().getName()); |
| } |
| } |
| |
| /** Type 1 message assembly class */ |
| static class Type1Message extends NTLMMessage { |
| |
| private final byte[] hostBytes; |
| private final byte[] domainBytes; |
| private final int flags; |
| |
| Type1Message(final String domain, final String host) { |
| this(domain, host, null); |
| } |
| |
| Type1Message(final String domain, final String host, final Integer flags) { |
| super(); |
| this.flags = ((flags == null)?getDefaultFlags():flags); |
| |
| // See HTTPCLIENT-1662 |
| final String unqualifiedHost = host; |
| final String unqualifiedDomain = domain; |
| |
| hostBytes = unqualifiedHost != null ? |
| unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null; |
| domainBytes = unqualifiedDomain != null ? |
| unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null; |
| } |
| |
| Type1Message() { |
| super(); |
| hostBytes = null; |
| domainBytes = null; |
| flags = getDefaultFlags(); |
| } |
| |
| private int getDefaultFlags() { |
| return |
| //FLAG_WORKSTATION_PRESENT | |
| //FLAG_DOMAIN_PRESENT | |
| |
| // Required flags |
| //FLAG_REQUEST_LAN_MANAGER_KEY | |
| FLAG_REQUEST_NTLMv1 | |
| FLAG_REQUEST_NTLM2_SESSION | |
| |
| // Protocol version request |
| FLAG_REQUEST_VERSION | |
| |
| // Recommended privacy settings |
| FLAG_REQUEST_ALWAYS_SIGN | |
| //FLAG_REQUEST_SEAL | |
| //FLAG_REQUEST_SIGN | |
| |
| // These must be set according to documentation, based on use of SEAL above |
| FLAG_REQUEST_128BIT_KEY_EXCH | |
| FLAG_REQUEST_56BIT_ENCRYPTION | |
| //FLAG_REQUEST_EXPLICIT_KEY_EXCH | |
| |
| FLAG_REQUEST_UNICODE_ENCODING; |
| |
| } |
| |
| /** |
| * Getting the response involves building the message before returning |
| * it |
| */ |
| @Override |
| void buildMessage() { |
| int domainBytesLength = 0; |
| if ( domainBytes != null ) { |
| domainBytesLength = domainBytes.length; |
| } |
| int hostBytesLength = 0; |
| if ( hostBytes != null ) { |
| hostBytesLength = hostBytes.length; |
| } |
| |
| // Now, build the message. Calculate its length first, including |
| // signature or type. |
| final int finalLength = 32 + 8 + hostBytesLength + domainBytesLength; |
| |
| // 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(flags); |
| |
| // Domain length (two times). |
| addUShort(domainBytesLength); |
| addUShort(domainBytesLength); |
| |
| // Domain offset. |
| addULong(hostBytesLength + 32 + 8); |
| |
| // Host length (two times). |
| addUShort(hostBytesLength); |
| addUShort(hostBytesLength); |
| |
| // Host offset (always 32 + 8). |
| addULong(32 + 8); |
| |
| // Version |
| addUShort(0x0105); |
| // Build |
| addULong(2600); |
| // NTLM revision |
| addUShort(0x0f00); |
| |
| // Host (workstation) String. |
| if (hostBytes != null) { |
| addBytes(hostBytes); |
| } |
| // Domain String. |
| if (domainBytes != null) { |
| addBytes(domainBytes); |
| } |
| } |
| |
| } |
| |
| /** Type 2 message class */ |
| static class Type2Message extends NTLMMessage { |
| final byte[] challenge; |
| String target; |
| byte[] targetInfo; |
| final int flags; |
| |
| Type2Message(final String messageBody) throws NTLMEngineException { |
| this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET))); |
| } |
| |
| Type2Message(final byte[] message) throws NTLMEngineException { |
| super(message, 2); |
| |
| // Type 2 message is laid out as follows: |
| // First 8 bytes: NTLMSSP[0] |
| // Next 4 bytes: Ulong, value 2 |
| // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset) |
| // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235 |
| // Next 8 bytes, starting at offset 24: Challenge |
| // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros) |
| // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset) |
| // Next 2 bytes, major/minor version number (e.g. 0x05 0x02) |
| // Next 8 bytes, build number |
| // Next 2 bytes, protocol version number (e.g. 0x00 0x0f) |
| // Next, various text fields, and a ushort of value 0 at the end |
| |
| // Parse out the rest of the info we need from the message |
| // The nonce is the 8 bytes starting from the byte in position 24. |
| challenge = new byte[8]; |
| readBytes(challenge, 24); |
| |
| flags = readULong(20); |
| |
| // 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) { |
| final byte[] bytes = readSecurityBuffer(12); |
| if (bytes.length != 0) { |
| target = new String(bytes, getCharset(flags)); |
| } |
| } |
| |
| // Do the target info! |
| targetInfo = null; |
| // TARGET_DESIRED flag cannot be relied on, so use packet length |
| if (getMessageLength() >= 40 + 8) { |
| final byte[] bytes = readSecurityBuffer(40); |
| if (bytes.length != 0) { |
| targetInfo = bytes; |
| } |
| } |
| } |
| |
| /** Retrieve the challenge */ |
| byte[] getChallenge() { |
| return challenge; |
| } |
| |
| /** Retrieve the target */ |
| String getTarget() { |
| return target; |
| } |
| |
| /** Retrieve the target info */ |
| byte[] getTargetInfo() { |
| return targetInfo; |
| } |
| |
| /** Retrieve the response flags */ |
| int getFlags() { |
| return flags; |
| } |
| |
| } |
| |
| /** Type 3 message assembly class */ |
| static class Type3Message extends NTLMMessage { |
| // For mic computation |
| final byte[] type1Message; |
| final byte[] type2Message; |
| // Response flags from the type2 message |
| final int type2Flags; |
| |
| final byte[] domainBytes; |
| final byte[] hostBytes; |
| final byte[] userBytes; |
| |
| byte[] lmResp; |
| byte[] ntResp; |
| final byte[] sessionKey; |
| final byte[] exportedSessionKey; |
| |
| final boolean computeMic; |
| |
| /** More primitive constructor: don't include cert or previous messages. |
| */ |
| Type3Message(final String domain, |
| final String host, |
| final String user, |
| final char[] password, |
| final byte[] nonce, |
| final int type2Flags, |
| final String target, |
| final byte[] targetInformation) |
| throws NTLMEngineException { |
| this(domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null); |
| } |
| |
| /** More primitive constructor: don't include cert or previous messages. |
| */ |
| Type3Message(final Random random, final long currentTime, |
| final String domain, |
| final String host, |
| final String user, |
| final char[] password, |
| final byte[] nonce, |
| final int type2Flags, |
| final String target, |
| final byte[] targetInformation) |
| throws NTLMEngineException { |
| this(random, currentTime, domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null); |
| } |
| |
| /** Constructor. Pass the arguments we will need */ |
| Type3Message(final String domain, |
| final String host, |
| final String user, |
| final char[] password, |
| final byte[] nonce, |
| final int type2Flags, |
| final String target, |
| final byte[] targetInformation, |
| final Certificate peerServerCertificate, |
| final byte[] type1Message, |
| final byte[] type2Message) |
| throws NTLMEngineException { |
| this(RND_GEN, System.currentTimeMillis(), domain, host, user, password, nonce, type2Flags, target, targetInformation, peerServerCertificate, type1Message, type2Message); |
| } |
| |
| /** Constructor. Pass the arguments we will need */ |
| Type3Message(final Random random, final long currentTime, |
| final String domain, |
| final String host, |
| final String user, |
| final char[] password, |
| final byte[] nonce, |
| final int type2Flags, |
| final String target, |
| final byte[] targetInformation, |
| final Certificate peerServerCertificate, |
| final byte[] type1Message, |
| final byte[] type2Message) |
| throws NTLMEngineException { |
| |
| if (random == null) { |
| throw new NTLMEngineException("Random generator not available"); |
| } |
| |
| // Save the flags |
| this.type2Flags = type2Flags; |
| this.type1Message = type1Message; |
| this.type2Message = type2Message; |
| |
| // All host name manipulations now take place in the credentials |
| final String unqualifiedHost = host; |
| // All domain name manipulations now take place in the credentials |
| final String unqualifiedDomain = domain; |
| |
| byte[] responseTargetInformation = targetInformation; |
| if (peerServerCertificate != null) { |
| responseTargetInformation = addGssMicAvsToTargetInfo(targetInformation, peerServerCertificate); |
| computeMic = true; |
| } else { |
| computeMic = false; |
| } |
| |
| // Create a cipher generator class. Use domain BEFORE it gets modified! |
| final CipherGen gen = new CipherGen(random, currentTime, |
| unqualifiedDomain, |
| user, |
| password, |
| nonce, |
| target, |
| responseTargetInformation); |
| |
| // Use the new code to calculate the responses, including v2 if that |
| // seems warranted. |
| byte[] userSessionKey; |
| try { |
| // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet |
| // been tested |
| if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) && |
| targetInformation != null && target != null) { |
| // NTLMv2 |
| ntResp = gen.getNTLMv2Response(); |
| lmResp = gen.getLMv2Response(); |
| if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { |
| userSessionKey = gen.getLanManagerSessionKey(); |
| } else { |
| userSessionKey = gen.getNTLMv2UserSessionKey(); |
| } |
| } else { |
| // NTLMv1 |
| if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) { |
| // NTLM2 session stuff is requested |
| ntResp = gen.getNTLM2SessionResponse(); |
| lmResp = gen.getLM2SessionResponse(); |
| if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { |
| userSessionKey = gen.getLanManagerSessionKey(); |
| } else { |
| userSessionKey = gen.getNTLM2SessionResponseUserSessionKey(); |
| } |
| } else { |
| ntResp = gen.getNTLMResponse(); |
| lmResp = gen.getLMResponse(); |
| if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { |
| userSessionKey = gen.getLanManagerSessionKey(); |
| } else { |
| userSessionKey = gen.getNTLMUserSessionKey(); |
| } |
| } |
| } |
| } catch (final NTLMEngineException e) { |
| // This likely means we couldn't find the MD4 hash algorithm - |
| // fail back to just using LM |
| ntResp = new byte[0]; |
| lmResp = gen.getLMResponse(); |
| if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { |
| userSessionKey = gen.getLanManagerSessionKey(); |
| } else { |
| userSessionKey = gen.getLMUserSessionKey(); |
| } |
| } |
| |
| if ((type2Flags & FLAG_REQUEST_SIGN) != 0) { |
| if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) { |
| exportedSessionKey = gen.getSecondaryKey(); |
| sessionKey = RC4(exportedSessionKey, userSessionKey); |
| } else { |
| sessionKey = userSessionKey; |
| exportedSessionKey = sessionKey; |
| } |
| } else { |
| if (computeMic) { |
| throw new NTLMEngineException("Cannot sign/seal: no exported session key"); |
| } |
| sessionKey = null; |
| exportedSessionKey = null; |
| } |
| final Charset charset = getCharset(type2Flags); |
| hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(charset) : null; |
| domainBytes = unqualifiedDomain != null ? unqualifiedDomain |
| .toUpperCase(Locale.ROOT).getBytes(charset) : null; |
| userBytes = user.getBytes(charset); |
| } |
| |
| public byte[] getEncryptedRandomSessionKey() { |
| return sessionKey; |
| } |
| |
| public byte[] getExportedSessionKey() { |
| return exportedSessionKey; |
| } |
| |
| /** Assemble the response */ |
| @Override |
| void buildMessage() { |
| final int ntRespLen = ntResp.length; |
| final int lmRespLen = lmResp.length; |
| |
| final int domainLen = domainBytes != null ? domainBytes.length : 0; |
| final int hostLen = hostBytes != null ? hostBytes.length: 0; |
| final int userLen = userBytes.length; |
| final int sessionKeyLen; |
| if (sessionKey != null) { |
| sessionKeyLen = sessionKey.length; |
| } else { |
| sessionKeyLen = 0; |
| } |
| |
| // Calculate the layout within the packet |
| final int lmRespOffset = 72 + // allocate space for the version |
| ( computeMic ? 16 : 0 ); // and MIC |
| final int ntRespOffset = lmRespOffset + lmRespLen; |
| final int domainOffset = ntRespOffset + ntRespLen; |
| final int userOffset = domainOffset + domainLen; |
| final int hostOffset = userOffset + userLen; |
| final int sessionKeyOffset = hostOffset + hostLen; |
| final int finalLength = sessionKeyOffset + sessionKeyLen; |
| |
| // 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); |
| |
| // Session key length (twice) |
| addUShort(sessionKeyLen); |
| addUShort(sessionKeyLen); |
| |
| // Session key offset |
| addULong(sessionKeyOffset); |
| |
| // Flags. |
| addULong( |
| /* |
| //FLAG_WORKSTATION_PRESENT | |
| //FLAG_DOMAIN_PRESENT | |
| |
| // Required flags |
| (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | |
| (type2Flags & FLAG_REQUEST_NTLMv1) | |
| (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | |
| |
| // Protocol version request |
| FLAG_REQUEST_VERSION | |
| |
| // Recommended privacy settings |
| (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | |
| (type2Flags & FLAG_REQUEST_SEAL) | |
| (type2Flags & FLAG_REQUEST_SIGN) | |
| |
| // These must be set according to documentation, based on use of SEAL above |
| (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | |
| (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | |
| (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | |
| |
| (type2Flags & FLAG_TARGETINFO_PRESENT) | |
| (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | |
| (type2Flags & FLAG_REQUEST_TARGET) |
| */ |
| type2Flags |
| ); |
| |
| // Version |
| addUShort(0x0105); |
| // Build |
| addULong(2600); |
| // NTLM revision |
| addUShort(0x0f00); |
| |
| int micPosition = -1; |
| if ( computeMic ) { |
| micPosition = currentOutputPosition; |
| currentOutputPosition += 16; |
| } |
| |
| // Add the actual data |
| addBytes(lmResp); |
| addBytes(ntResp); |
| addBytes(domainBytes); |
| addBytes(userBytes); |
| addBytes(hostBytes); |
| if (sessionKey != null) { |
| addBytes(sessionKey); |
| } |
| |
| // Write the mic back into its slot in the message |
| |
| if (computeMic) { |
| // Computation of message integrity code (MIC) as specified in [MS-NLMP] section 3.2.5.1.2. |
| final HMACMD5 hmacMD5 = new HMACMD5( exportedSessionKey ); |
| hmacMD5.update( type1Message ); |
| hmacMD5.update( type2Message ); |
| hmacMD5.update( messageContents ); |
| final byte[] mic = hmacMD5.getOutput(); |
| System.arraycopy( mic, 0, messageContents, micPosition, mic.length ); |
| } |
| } |
| |
| /** |
| * Add GSS channel binding hash and MIC flag to the targetInfo. |
| * Looks like this is needed if we want to use exported session key for GSS wrapping. |
| */ |
| private byte[] addGssMicAvsToTargetInfo( final byte[] originalTargetInfo, |
| final Certificate peerServerCertificate ) throws NTLMEngineException |
| { |
| final byte[] newTargetInfo = new byte[originalTargetInfo.length + 8 + 20]; |
| final int appendLength = originalTargetInfo.length - 4; // last tag is MSV_AV_EOL, do not copy that |
| System.arraycopy( originalTargetInfo, 0, newTargetInfo, 0, appendLength ); |
| writeUShort( newTargetInfo, MSV_AV_FLAGS, appendLength ); |
| writeUShort( newTargetInfo, 4, appendLength + 2 ); |
| writeULong( newTargetInfo, MSV_AV_FLAGS_MIC, appendLength + 4 ); |
| writeUShort( newTargetInfo, MSV_AV_CHANNEL_BINDINGS, appendLength + 8 ); |
| writeUShort( newTargetInfo, 16, appendLength + 10 ); |
| |
| final byte[] channelBindingsHash; |
| try |
| { |
| final byte[] certBytes = peerServerCertificate.getEncoded(); |
| final MessageDigest sha256 = MessageDigest.getInstance( "SHA-256" ); |
| final byte[] certHashBytes = sha256.digest( certBytes ); |
| final byte[] channelBindingStruct = new byte[16 + 4 + MAGIC_TLS_SERVER_ENDPOINT.length |
| + certHashBytes.length]; |
| writeULong( channelBindingStruct, 0x00000035, 16 ); |
| System.arraycopy( MAGIC_TLS_SERVER_ENDPOINT, 0, channelBindingStruct, 20, |
| MAGIC_TLS_SERVER_ENDPOINT.length ); |
| System.arraycopy( certHashBytes, 0, channelBindingStruct, 20 + MAGIC_TLS_SERVER_ENDPOINT.length, |
| certHashBytes.length ); |
| final MessageDigest md5 = getMD5(); |
| channelBindingsHash = md5.digest( channelBindingStruct ); |
| } |
| catch (final CertificateEncodingException | NoSuchAlgorithmException e ) |
| { |
| throw new NTLMEngineException( e.getMessage(), e ); |
| } |
| |
| System.arraycopy( channelBindingsHash, 0, newTargetInfo, appendLength + 12, 16 ); |
| return newTargetInfo; |
| } |
| |
| } |
| |
| static void writeUShort(final byte[] buffer, final int value, final int offset) { |
| buffer[offset] = ( byte ) ( value & 0xff ); |
| buffer[offset + 1] = ( byte ) ( value >> 8 & 0xff ); |
| } |
| |
| static void writeULong(final byte[] buffer, final int value, final 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); |
| } |
| |
| static int F(final int x, final int y, final int z) { |
| return ((x & y) | (~x & z)); |
| } |
| |
| static int G(final int x, final int y, final int z) { |
| return ((x & y) | (x & z) | (y & z)); |
| } |
| |
| static int H(final int x, final int y, final int z) { |
| return (x ^ y ^ z); |
| } |
| |
| static int rotintlft(final int val, final int numbits) { |
| return ((val << numbits) | (val >>> (32 - numbits))); |
| } |
| |
| static MessageDigest getMD5() { |
| try { |
| return MessageDigest.getInstance("MD5"); |
| } catch (final NoSuchAlgorithmException ex) { |
| throw new RuntimeException("MD5 message digest doesn't seem to exist - fatal error: "+ex.getMessage(), ex); |
| } |
| } |
| |
| /** |
| * 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). |
| */ |
| static class MD4 { |
| int A = 0x67452301; |
| int B = 0xefcdab89; |
| int C = 0x98badcfe; |
| int D = 0x10325476; |
| long count = 0L; |
| final byte[] dataBuffer = new byte[64]; |
| |
| MD4() { |
| } |
| |
| void update(final 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 |
| final 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) { |
| final int transferAmt = input.length - inputIndex; |
| System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); |
| count += transferAmt; |
| curBufferPos += transferAmt; |
| } |
| } |
| |
| byte[] getOutput() { |
| // Feed pad/length data into engine. This must round out the input |
| // to a multiple of 512 bits. |
| final int bufferIndex = (int) (count & 63L); |
| final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); |
| final byte[] postBytes = new byte[padLen + 8]; |
| // Leading 0x80, specified amount of zero padding, then length in |
| // bits. |
| postBytes[0] = (byte) 0x80; |
| // Fill out the last 8 bytes with the length |
| for (int i = 0; i < 8; i++) { |
| postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); |
| } |
| |
| // Update the engine |
| update(postBytes); |
| |
| // Calculate final result |
| final byte[] result = new byte[16]; |
| writeULong(result, A, 0); |
| writeULong(result, B, 4); |
| writeULong(result, C, 8); |
| writeULong(result, D, 12); |
| return result; |
| } |
| |
| void processBuffer() { |
| // Convert current buffer to 16 ulongs |
| final 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 |
| final int AA = A; |
| final int BB = B; |
| final int CC = C; |
| final int DD = D; |
| round1(d); |
| round2(d); |
| round3(d); |
| A += AA; |
| B += BB; |
| C += CC; |
| D += DD; |
| |
| } |
| |
| void round1(final int[] d) { |
| A = rotintlft((A + F(B, C, D) + d[0]), 3); |
| D = rotintlft((D + F(A, B, C) + d[1]), 7); |
| C = rotintlft((C + F(D, A, B) + d[2]), 11); |
| B = rotintlft((B + F(C, D, A) + d[3]), 19); |
| |
| A = rotintlft((A + F(B, C, D) + d[4]), 3); |
| D = rotintlft((D + F(A, B, C) + d[5]), 7); |
| C = rotintlft((C + F(D, A, B) + d[6]), 11); |
| B = rotintlft((B + F(C, D, A) + d[7]), 19); |
| |
| A = rotintlft((A + F(B, C, D) + d[8]), 3); |
| D = rotintlft((D + F(A, B, C) + d[9]), 7); |
| C = rotintlft((C + F(D, A, B) + d[10]), 11); |
| B = rotintlft((B + F(C, D, A) + d[11]), 19); |
| |
| A = rotintlft((A + F(B, C, D) + d[12]), 3); |
| D = rotintlft((D + F(A, B, C) + d[13]), 7); |
| C = rotintlft((C + F(D, A, B) + d[14]), 11); |
| B = rotintlft((B + F(C, D, A) + d[15]), 19); |
| } |
| |
| void round2(final 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); |
| |
| } |
| |
| void round3(final 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); |
| |
| } |
| |
| } |
| |
| /** |
| * Cryptography support - HMACMD5 - algorithmically based on various web |
| * resources by Karl Wright |
| */ |
| static class HMACMD5 { |
| final byte[] ipad; |
| final byte[] opad; |
| final MessageDigest md5; |
| |
| HMACMD5(final byte[] input) { |
| byte[] key = input; |
| md5 = getMD5(); |
| |
| // 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: processChallenge the digest with the ipad buffer |
| md5.reset(); |
| md5.update(ipad); |
| |
| } |
| |
| /** Grab the current digest. This is the "answer". */ |
| byte[] getOutput() { |
| final byte[] digest = md5.digest(); |
| md5.update(opad); |
| return md5.digest(digest); |
| } |
| |
| /** Update by adding a complete array */ |
| void update(final byte[] input) { |
| md5.update(input); |
| } |
| |
| /** Update the algorithm */ |
| void update(final byte[] input, final int offset, final int length) { |
| md5.update(input, offset, length); |
| } |
| |
| } |
| |
| @Override |
| public String generateType1Msg( |
| final String domain, |
| final String workstation) throws NTLMEngineException { |
| return getType1Message(workstation, domain); |
| } |
| |
| @Override |
| public String generateType3Msg( |
| final String username, |
| final char[] password, |
| final String domain, |
| final String workstation, |
| final String challenge) throws NTLMEngineException { |
| final Type2Message t2m = new Type2Message(challenge); |
| return getType3Message( |
| username, |
| password, |
| workstation, |
| domain, |
| t2m.getChallenge(), |
| t2m.getFlags(), |
| t2m.getTarget(), |
| t2m.getTargetInfo()); |
| } |
| |
| } |