diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java b/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java
index 6ed5c0ecf..7e8eb7eac 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java
@@ -1,2102 +1,2102 @@
-/*
- * ====================================================================
- * 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
- * .
- *
- */
-package org.apache.http.impl.auth;
-
-import java.nio.charset.Charset;
-import org.apache.http.Consts;
-import java.security.Key;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.Certificate;
-
-import org.apache.commons.codec.binary.Base64;
-
-/**
- * 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 = Consts.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.2
- 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(Consts.ASCII);
-
- private static byte[] getNullTerminatedAsciiString( final String source )
- {
- final byte[] bytesWithoutNull = source.getBytes(Consts.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() {
- }
-
- /**
- * 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 String 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.
- * @throws NTLMEngineException
- * If {@encrypt(byte[],byte[])} fails.
- */
- static String getType3Message(final String user, final String 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) throws NTLMEngineException {
- 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) throws NTLMEngineException {
- 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) throws NTLMEngineException {
- 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) throws NTLMEngineException {
- 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) throws NTLMEngineException {
- final byte[] rval = new byte[16];
- synchronized (random) {
- random.nextBytes(rval);
- }
- return rval;
- }
-
- protected static class CipherGen {
-
- protected final Random random;
- protected final long currentTime;
-
- protected final String domain;
- protected final String user;
- protected final String password;
- protected final byte[] challenge;
- protected final String target;
- protected final byte[] targetInformation;
-
- // Information we can generate but may be passed in (for testing)
- protected byte[] clientChallenge;
- protected byte[] clientChallenge2;
- protected byte[] secondaryKey;
- protected byte[] timestamp;
-
- // Stuff we always generate
- protected byte[] lmHash = null;
- protected byte[] lmResponse = null;
- protected byte[] ntlmHash = null;
- protected byte[] ntlmResponse = null;
- protected byte[] ntlmv2Hash = null;
- protected byte[] lmv2Hash = null;
- protected byte[] lmv2Response = null;
- protected byte[] ntlmv2Blob = null;
- protected byte[] ntlmv2Response = null;
- protected byte[] ntlm2SessionResponse = null;
- protected byte[] lm2SessionResponse = null;
- protected byte[] lmUserSessionKey = null;
- protected byte[] ntlmUserSessionKey = null;
- protected byte[] ntlmv2UserSessionKey = null;
- protected byte[] ntlm2SessionResponseUserSessionKey = null;
- protected byte[] lanManagerSessionKey = null;
-
- @Deprecated
- public CipherGen(final String domain, final String user, final String password,
- final byte[] challenge, final String target, final byte[] targetInformation,
- final byte[] clientChallenge, final byte[] clientChallenge2,
- final byte[] secondaryKey, final byte[] timestamp) {
- this(RND_GEN, System.currentTimeMillis(),
- domain, user, password, challenge, target, targetInformation,
- clientChallenge, clientChallenge2,
- secondaryKey, timestamp);
- }
-
- public CipherGen(final Random random, final long currentTime,
- final String domain, final String user, final String 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;
- }
-
- @Deprecated
- public CipherGen(final String domain,
- final String user,
- final String password,
- final byte[] challenge,
- final String target,
- final byte[] targetInformation) {
- this(RND_GEN, System.currentTimeMillis(), domain, user, password, challenge, target, targetInformation);
- }
-
- public CipherGen(final Random random, final long currentTime,
- final String domain,
- final String user,
- final String 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()
- throws NTLMEngineException {
- if (clientChallenge == null) {
- clientChallenge = makeRandomChallenge(random);
- }
- return clientChallenge;
- }
-
- /** Calculate and return second client challenge */
- public byte[] getClientChallenge2()
- throws NTLMEngineException {
- if (clientChallenge2 == null) {
- clientChallenge2 = makeRandomChallenge(random);
- }
- return clientChallenge2;
- }
-
- /** Calculate and return random secondary key */
- public byte[] getSecondaryKey()
- throws NTLMEngineException {
- 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()
- throws NTLMEngineException {
- 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()
- throws NTLMEngineException {
- 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)
- throws NTLMEngineException {
- 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 String password) throws NTLMEngineException {
- try {
- final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII);
- 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(Consts.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 String password) throws NTLMEngineException {
- if (UNICODE_LITTLE_UNMARKED == null) {
- throw new NTLMEngineException("Unicode not supported");
- }
- final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED);
- 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)
- throws NTLMEngineException {
- 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;
- }
-
- static enum Mode
- {
- CLIENT, SERVER;
- }
-
- static class Handle
- {
- final private byte[] exportedSessionKey;
- private 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.exportedSessionKey = exportedSessionKey;
- 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 ) throws NTLMEngineException
- {
- return rc4.update( data );
- }
-
- private byte[] decrypt( final byte[] data ) throws NTLMEngineException
- {
- return rc4.update( data );
- }
-
- private byte[] computeSignature( final byte[] message ) throws NTLMEngineException
- {
- 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[] ) throws NTLMEngineException
- {
- 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;
- } else {
- if (UNICODE_LITTLE_UNMARKED == null) {
- throw new NTLMEngineException( "Unicode not supported" );
- }
- return UNICODE_LITTLE_UNMARKED;
- }
- }
-
- /** Strip dot suffix from a name */
- private static String stripDotSuffix(final String value) {
- if (value == null) {
- return null;
- }
- final int index = value.indexOf(".");
- if (index != -1) {
- return value.substring(0, index);
- }
- return value;
- }
-
- /** Convert host to standard form */
- private static String convertHost(final String host) {
- return stripDotSuffix(host);
- }
-
- /** Convert domain to standard form */
- private static String convertDomain(final String domain) {
- return stripDotSuffix(domain);
- }
-
- /** NTLM message generation, base class */
- static class NTLMMessage {
- /** The current response */
- protected byte[] messageContents = null;
-
- /** The current output position */
- protected 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.
- */
- protected int getPreambleLength() {
- return SIGNATURE.length + 4;
- }
-
- /** Get the message length */
- protected int getMessageLength() {
- return currentOutputPosition;
- }
-
- /** Read a byte from a position within the message buffer */
- protected 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 */
- protected 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 */
- protected int readUShort(final int position) throws NTLMEngineException {
- return NTLMEngineImpl.readUShort(messageContents, position);
- }
-
- /** Read a ulong from a position within the message buffer */
- protected int readULong(final int position) throws NTLMEngineException {
- return NTLMEngineImpl.readULong(messageContents, position);
- }
-
- /** Read a security buffer from a position within the message buffer */
- protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException {
- 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).
- */
- protected 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.
- */
- protected void addByte(final byte b) {
- messageContents[currentOutputPosition] = b;
- currentOutputPosition++;
- }
-
- /**
- * Adds the given bytes to the response.
- *
- * @param bytes
- * the bytes to add.
- */
- protected void addBytes(final byte[] bytes) {
- if (bytes == null) {
- return;
- }
- for (final byte b : bytes) {
- messageContents[currentOutputPosition] = b;
- currentOutputPosition++;
- }
- }
-
- /** Adds a USHORT to the response */
- protected void addUShort(final int value) {
- addByte((byte) (value & 0xff));
- addByte((byte) (value >> 8 & 0xff));
- }
-
- /** Adds a ULong to the response */
- protected 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()), Consts.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;
- }
-
- protected 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) throws NTLMEngineException {
- this(domain, host, null);
- }
-
- Type1Message(final String domain, final String host, final Integer flags) throws NTLMEngineException {
- super();
- this.flags = ((flags == null)?getDefaultFlags():flags);
-
- // Strip off domain name from the host!
- final String unqualifiedHost = convertHost(host);
- // Use only the base domain name!
- final String unqualifiedDomain = convertDomain(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
- protected 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 {
- protected final byte[] challenge;
- protected String target;
- protected byte[] targetInfo;
- protected 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
- protected final byte[] type1Message;
- protected final byte[] type2Message;
- // Response flags from the type2 message
- protected final int type2Flags;
-
- protected final byte[] domainBytes;
- protected final byte[] hostBytes;
- protected final byte[] userBytes;
-
- protected byte[] lmResp;
- protected byte[] ntResp;
- protected final byte[] sessionKey;
- protected final byte[] exportedSessionKey;
-
- protected final boolean computeMic;
-
- /** More primitive constructor: don't include cert or previous messages.
- */
- Type3Message(final String domain,
- final String host,
- final String user,
- final String 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 String 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 String 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 String 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;
-
- // Strip off domain name from the host!
- final String unqualifiedHost = convertHost(host);
- // Use only the base domain name!
- final String unqualifiedDomain = convertDomain(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
- protected 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 e )
- {
- throw new NTLMEngineException( e.getMessage(), e );
- }
- catch ( final 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 {
- protected int A = 0x67452301;
- protected int B = 0xefcdab89;
- protected int C = 0x98badcfe;
- protected int D = 0x10325476;
- protected long count = 0L;
- protected 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;
- }
-
- protected 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;
-
- }
-
- protected 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);
- }
-
- protected 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);
-
- }
-
- protected 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 {
- protected final byte[] ipad;
- protected final byte[] opad;
- protected 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 String 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());
- }
-
-}
+/*
+ * ====================================================================
+ * 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
+ * .
+ *
+ */
+package org.apache.http.impl.auth;
+
+import java.nio.charset.Charset;
+import org.apache.http.Consts;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.Certificate;
+
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * 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 = Consts.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.2
+ 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(Consts.ASCII);
+
+ private static byte[] getNullTerminatedAsciiString( final String source )
+ {
+ final byte[] bytesWithoutNull = source.getBytes(Consts.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() {
+ }
+
+ /**
+ * 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 String 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.
+ * @throws NTLMEngineException
+ * If {@encrypt(byte[],byte[])} fails.
+ */
+ static String getType3Message(final String user, final String 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) throws NTLMEngineException {
+ 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) throws NTLMEngineException {
+ 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) throws NTLMEngineException {
+ 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) throws NTLMEngineException {
+ 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) throws NTLMEngineException {
+ final byte[] rval = new byte[16];
+ synchronized (random) {
+ random.nextBytes(rval);
+ }
+ return rval;
+ }
+
+ protected static class CipherGen {
+
+ protected final Random random;
+ protected final long currentTime;
+
+ protected final String domain;
+ protected final String user;
+ protected final String password;
+ protected final byte[] challenge;
+ protected final String target;
+ protected final byte[] targetInformation;
+
+ // Information we can generate but may be passed in (for testing)
+ protected byte[] clientChallenge;
+ protected byte[] clientChallenge2;
+ protected byte[] secondaryKey;
+ protected byte[] timestamp;
+
+ // Stuff we always generate
+ protected byte[] lmHash = null;
+ protected byte[] lmResponse = null;
+ protected byte[] ntlmHash = null;
+ protected byte[] ntlmResponse = null;
+ protected byte[] ntlmv2Hash = null;
+ protected byte[] lmv2Hash = null;
+ protected byte[] lmv2Response = null;
+ protected byte[] ntlmv2Blob = null;
+ protected byte[] ntlmv2Response = null;
+ protected byte[] ntlm2SessionResponse = null;
+ protected byte[] lm2SessionResponse = null;
+ protected byte[] lmUserSessionKey = null;
+ protected byte[] ntlmUserSessionKey = null;
+ protected byte[] ntlmv2UserSessionKey = null;
+ protected byte[] ntlm2SessionResponseUserSessionKey = null;
+ protected byte[] lanManagerSessionKey = null;
+
+ @Deprecated
+ public CipherGen(final String domain, final String user, final String password,
+ final byte[] challenge, final String target, final byte[] targetInformation,
+ final byte[] clientChallenge, final byte[] clientChallenge2,
+ final byte[] secondaryKey, final byte[] timestamp) {
+ this(RND_GEN, System.currentTimeMillis(),
+ domain, user, password, challenge, target, targetInformation,
+ clientChallenge, clientChallenge2,
+ secondaryKey, timestamp);
+ }
+
+ public CipherGen(final Random random, final long currentTime,
+ final String domain, final String user, final String 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;
+ }
+
+ @Deprecated
+ public CipherGen(final String domain,
+ final String user,
+ final String password,
+ final byte[] challenge,
+ final String target,
+ final byte[] targetInformation) {
+ this(RND_GEN, System.currentTimeMillis(), domain, user, password, challenge, target, targetInformation);
+ }
+
+ public CipherGen(final Random random, final long currentTime,
+ final String domain,
+ final String user,
+ final String 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()
+ throws NTLMEngineException {
+ if (clientChallenge == null) {
+ clientChallenge = makeRandomChallenge(random);
+ }
+ return clientChallenge;
+ }
+
+ /** Calculate and return second client challenge */
+ public byte[] getClientChallenge2()
+ throws NTLMEngineException {
+ if (clientChallenge2 == null) {
+ clientChallenge2 = makeRandomChallenge(random);
+ }
+ return clientChallenge2;
+ }
+
+ /** Calculate and return random secondary key */
+ public byte[] getSecondaryKey()
+ throws NTLMEngineException {
+ 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()
+ throws NTLMEngineException {
+ 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()
+ throws NTLMEngineException {
+ 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)
+ throws NTLMEngineException {
+ 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 String password) throws NTLMEngineException {
+ try {
+ final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII);
+ 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(Consts.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 String password) throws NTLMEngineException {
+ if (UNICODE_LITTLE_UNMARKED == null) {
+ throw new NTLMEngineException("Unicode not supported");
+ }
+ final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED);
+ 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)
+ throws NTLMEngineException {
+ 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;
+ }
+
+ static enum Mode
+ {
+ CLIENT, SERVER;
+ }
+
+ static class Handle
+ {
+ final private byte[] exportedSessionKey;
+ private 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.exportedSessionKey = exportedSessionKey;
+ 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 ) throws NTLMEngineException
+ {
+ return rc4.update( data );
+ }
+
+ private byte[] decrypt( final byte[] data ) throws NTLMEngineException
+ {
+ return rc4.update( data );
+ }
+
+ private byte[] computeSignature( final byte[] message ) throws NTLMEngineException
+ {
+ 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[] ) throws NTLMEngineException
+ {
+ 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;
+ } else {
+ if (UNICODE_LITTLE_UNMARKED == null) {
+ throw new NTLMEngineException( "Unicode not supported" );
+ }
+ return UNICODE_LITTLE_UNMARKED;
+ }
+ }
+
+ /** Strip dot suffix from a name */
+ private static String stripDotSuffix(final String value) {
+ if (value == null) {
+ return null;
+ }
+ final int index = value.indexOf('.');
+ if (index != -1) {
+ return value.substring(0, index);
+ }
+ return value;
+ }
+
+ /** Convert host to standard form */
+ private static String convertHost(final String host) {
+ return stripDotSuffix(host);
+ }
+
+ /** Convert domain to standard form */
+ private static String convertDomain(final String domain) {
+ return stripDotSuffix(domain);
+ }
+
+ /** NTLM message generation, base class */
+ static class NTLMMessage {
+ /** The current response */
+ protected byte[] messageContents = null;
+
+ /** The current output position */
+ protected 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.
+ */
+ protected int getPreambleLength() {
+ return SIGNATURE.length + 4;
+ }
+
+ /** Get the message length */
+ protected int getMessageLength() {
+ return currentOutputPosition;
+ }
+
+ /** Read a byte from a position within the message buffer */
+ protected 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 */
+ protected 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 */
+ protected int readUShort(final int position) throws NTLMEngineException {
+ return NTLMEngineImpl.readUShort(messageContents, position);
+ }
+
+ /** Read a ulong from a position within the message buffer */
+ protected int readULong(final int position) throws NTLMEngineException {
+ return NTLMEngineImpl.readULong(messageContents, position);
+ }
+
+ /** Read a security buffer from a position within the message buffer */
+ protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException {
+ 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).
+ */
+ protected 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.
+ */
+ protected void addByte(final byte b) {
+ messageContents[currentOutputPosition] = b;
+ currentOutputPosition++;
+ }
+
+ /**
+ * Adds the given bytes to the response.
+ *
+ * @param bytes
+ * the bytes to add.
+ */
+ protected void addBytes(final byte[] bytes) {
+ if (bytes == null) {
+ return;
+ }
+ for (final byte b : bytes) {
+ messageContents[currentOutputPosition] = b;
+ currentOutputPosition++;
+ }
+ }
+
+ /** Adds a USHORT to the response */
+ protected void addUShort(final int value) {
+ addByte((byte) (value & 0xff));
+ addByte((byte) (value >> 8 & 0xff));
+ }
+
+ /** Adds a ULong to the response */
+ protected 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()), Consts.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;
+ }
+
+ protected 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) throws NTLMEngineException {
+ this(domain, host, null);
+ }
+
+ Type1Message(final String domain, final String host, final Integer flags) throws NTLMEngineException {
+ super();
+ this.flags = ((flags == null)?getDefaultFlags():flags);
+
+ // Strip off domain name from the host!
+ final String unqualifiedHost = convertHost(host);
+ // Use only the base domain name!
+ final String unqualifiedDomain = convertDomain(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
+ protected 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 {
+ protected final byte[] challenge;
+ protected String target;
+ protected byte[] targetInfo;
+ protected 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
+ protected final byte[] type1Message;
+ protected final byte[] type2Message;
+ // Response flags from the type2 message
+ protected final int type2Flags;
+
+ protected final byte[] domainBytes;
+ protected final byte[] hostBytes;
+ protected final byte[] userBytes;
+
+ protected byte[] lmResp;
+ protected byte[] ntResp;
+ protected final byte[] sessionKey;
+ protected final byte[] exportedSessionKey;
+
+ protected final boolean computeMic;
+
+ /** More primitive constructor: don't include cert or previous messages.
+ */
+ Type3Message(final String domain,
+ final String host,
+ final String user,
+ final String 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 String 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 String 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 String 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;
+
+ // Strip off domain name from the host!
+ final String unqualifiedHost = convertHost(host);
+ // Use only the base domain name!
+ final String unqualifiedDomain = convertDomain(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
+ protected 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 e )
+ {
+ throw new NTLMEngineException( e.getMessage(), e );
+ }
+ catch ( final 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 {
+ protected int A = 0x67452301;
+ protected int B = 0xefcdab89;
+ protected int C = 0x98badcfe;
+ protected int D = 0x10325476;
+ protected long count = 0L;
+ protected 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;
+ }
+
+ protected 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;
+
+ }
+
+ protected 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);
+ }
+
+ protected 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);
+
+ }
+
+ protected 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 {
+ protected final byte[] ipad;
+ protected final byte[] opad;
+ protected 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 String 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());
+ }
+
+}