diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java
deleted file mode 100644
index ce7624d2b..000000000
--- a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java
+++ /dev/null
@@ -1,1585 +0,0 @@
-/*
- * ====================================================================
- * 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
- * .
- *
- */
-// fork from Apache HttpComponents
-package org.asynchttpclient.ntlm;
-
-import org.jetbrains.annotations.Contract;
-import org.jetbrains.annotations.Nullable;
-
-import javax.crypto.Cipher;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.Key;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Locale;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-/**
- * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM
- * authentication protocol.
- *
- * @since 4.1
- */
-@SuppressWarnings("unused")
-public final class NtlmEngine {
-
- public static final NtlmEngine INSTANCE = new NtlmEngine();
-
- /**
- * Unicode encoding
- */
- private static final Charset UNICODE_LITTLE_UNMARKED = StandardCharsets.UTF_16LE;
-
- private static final byte[] MAGIC_CONSTANT = "KGS!@#$%".getBytes(US_ASCII);
-
- // Flags we use; descriptions according to:
- // http://davenport.sourceforge.net/ntlm.html
- // and
- // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx
- private static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested
- private static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field
- private static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message.
- private static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT.
- private static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key
- private static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both
- private static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message
- private static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message
- private static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL.
- private static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security
- private static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version
- private static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present
- private static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange
- private static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange
- private static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL
-
- /**
- * Secure random generator
- */
- private static final @Nullable SecureRandom RND_GEN;
-
- static {
- SecureRandom rnd = null;
- try {
- rnd = SecureRandom.getInstance("SHA1PRNG");
- } catch (final Exception ignore) {
- }
- RND_GEN = rnd;
- }
-
- /**
- * The signature string as bytes in the default encoding
- */
- private static final byte[] SIGNATURE;
-
- static {
- final byte[] bytesWithoutNull = "NTLMSSP".getBytes(US_ASCII);
- SIGNATURE = new byte[bytesWithoutNull.length + 1];
- System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length);
- SIGNATURE[bytesWithoutNull.length] = 0x00;
- }
-
- private static final String TYPE_1_MESSAGE = new Type1Message().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 username. 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.
- */
- private static String getType3Message(final String user, final String password, final String host, final String domain, final byte[] nonce,
- final int type2Flags, final @Nullable String target, final byte @Nullable [] targetInformation) {
- return new Type3Message(domain, host, user, password, nonce, type2Flags, target, targetInformation).getResponse();
- }
-
- /**
- * Strip dot suffix from a name
- */
- private static String stripDotSuffix(final String value) {
- final int index = value.indexOf('.');
- if (index != -1) {
- return value.substring(0, index);
- }
- return value;
- }
-
- /**
- * Convert host to standard form
- */
- @Contract(value = "!null -> !null", pure = true)
- private static @Nullable String convertHost(final String host) {
- return host != null ? stripDotSuffix(host).toUpperCase() : null;
- }
-
- /**
- * Convert domain to standard form
- */
- @Contract(value = "!null -> !null", pure = true)
- private static @Nullable String convertDomain(final String domain) {
- return domain != null ? stripDotSuffix(domain).toUpperCase() : null;
- }
-
- private static int readULong(final byte[] src, final int index) {
- if (src.length < index + 4) {
- throw new NtlmEngineException("NTLM authentication - buffer too small for DWORD");
- }
- return src[index] & 0xff | (src[index + 1] & 0xff) << 8 | (src[index + 2] & 0xff) << 16 | (src[index + 3] & 0xff) << 24;
- }
-
- private static int readUShort(final byte[] src, final int index) {
- if (src.length < index + 2) {
- throw new NtlmEngineException("NTLM authentication - buffer too small for WORD");
- }
- return src[index] & 0xff | (src[index + 1] & 0xff) << 8;
- }
-
- private static byte[] readSecurityBuffer(final byte[] src, final int index) {
- final int length = readUShort(src, index);
- final int offset = readULong(src, index + 4);
- if (src.length < offset + length) {
- throw new NtlmEngineException("NTLM authentication - buffer too small for data item");
- }
- final byte[] buffer = new byte[length];
- System.arraycopy(src, offset, buffer, 0, length);
- return buffer;
- }
-
- /**
- * Calculate a challenge block
- */
- private static byte[] makeRandomChallenge() {
- if (RND_GEN == null) {
- throw new NtlmEngineException("Random generator not available");
- }
- final byte[] rval = new byte[8];
- synchronized (RND_GEN) {
- RND_GEN.nextBytes(rval);
- }
- return rval;
- }
-
- /**
- * Calculate a 16-byte secondary key
- */
- private static byte[] makeSecondaryKey() {
- if (RND_GEN == null) {
- throw new NtlmEngineException("Random generator not available");
- }
- final byte[] rval = new byte[16];
- synchronized (RND_GEN) {
- RND_GEN.nextBytes(rval);
- }
- return rval;
- }
-
- private static class CipherGen {
-
- protected final String domain;
- protected final String user;
- protected final String password;
- protected final byte[] challenge;
- protected final @Nullable String target;
- protected final byte @Nullable [] targetInformation;
-
- // Information we can generate but may be passed in (for testing)
- protected byte @Nullable [] clientChallenge;
- protected byte @Nullable [] clientChallenge2;
- protected byte @Nullable [] secondaryKey;
- protected byte @Nullable [] timestamp;
-
- // Stuff we always generate
- protected byte @Nullable [] lmHash;
- protected byte @Nullable [] lmResponse;
- protected byte @Nullable [] ntlmHash;
- protected byte @Nullable [] ntlmResponse;
- protected byte @Nullable [] ntlmv2Hash;
- protected byte @Nullable [] lmv2Hash;
- protected byte @Nullable [] lmv2Response;
- protected byte @Nullable [] ntlmv2Blob;
- protected byte @Nullable [] ntlmv2Response;
- protected byte @Nullable [] ntlm2SessionResponse;
- protected byte @Nullable [] lm2SessionResponse;
- protected byte @Nullable [] lmUserSessionKey;
- protected byte @Nullable [] ntlmUserSessionKey;
- protected byte @Nullable [] ntlmv2UserSessionKey;
- protected byte @Nullable [] ntlm2SessionResponseUserSessionKey;
- protected byte @Nullable [] lanManagerSessionKey;
-
- CipherGen(final String domain, final String user, final String password, final byte[] challenge, final @Nullable String target,
- final byte @Nullable [] targetInformation, final byte @Nullable [] clientChallenge, final byte @Nullable [] clientChallenge2,
- final byte @Nullable [] secondaryKey, final byte @Nullable [] timestamp) {
- 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;
- }
-
- CipherGen(final String domain, final String user, final String password, final byte[] challenge, final @Nullable String target,
- final byte @Nullable [] targetInformation) {
- this(domain, user, password, challenge, target, targetInformation, null, null, null, null);
- }
-
- /**
- * Calculate and return client challenge
- */
- public byte[] getClientChallenge() {
- if (clientChallenge == null) {
- clientChallenge = makeRandomChallenge();
- }
- return clientChallenge;
- }
-
- /**
- * Calculate and return second client challenge
- */
- public byte[] getClientChallenge2() {
- if (clientChallenge2 == null) {
- clientChallenge2 = makeRandomChallenge();
- }
- return clientChallenge2;
- }
-
- /**
- * Calculate and return random secondary key
- */
- public byte[] getSecondaryKey() {
- if (secondaryKey == null) {
- secondaryKey = makeSecondaryKey();
- }
- return secondaryKey;
- }
-
- /**
- * Calculate and return the LMHash
- */
- public byte[] getLMHash() {
- if (lmHash == null) {
- lmHash = lmHash(password);
- }
- return lmHash;
- }
-
- /**
- * Calculate and return the LMResponse
- */
- public byte[] getLMResponse() {
- if (lmResponse == null) {
- lmResponse = lmResponse(getLMHash(), challenge);
- }
- return lmResponse;
- }
-
- /**
- * Calculate and return the NTLMHash
- */
- public byte[] getNTLMHash() {
- if (ntlmHash == null) {
- ntlmHash = ntlmHash(password);
- }
- return ntlmHash;
- }
-
- /**
- * Calculate and return the NTLMResponse
- */
- public byte[] getNTLMResponse() {
- if (ntlmResponse == null) {
- ntlmResponse = lmResponse(getNTLMHash(), challenge);
- }
- return ntlmResponse;
- }
-
- /**
- * Calculate the LMv2 hash
- */
- public byte[] getLMv2Hash() {
- if (lmv2Hash == null) {
- lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
- }
- return lmv2Hash;
- }
-
- /**
- * Calculate the NTLMv2 hash
- */
- public byte[] getNTLMv2Hash() {
- if (ntlmv2Hash == null) {
- ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
- }
- return ntlmv2Hash;
- }
-
- /**
- * Calculate a timestamp
- */
- public byte[] getTimestamp() {
- if (timestamp == null) {
- long time = System.currentTimeMillis();
- 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
- *
- * @param targetInformation this parameter is the same object as the field targetInformation,
- * but guaranteed to be not null. This is done to satisfy NullAway requirements
- */
- public byte[] getNTLMv2Blob(byte[] targetInformation) {
- if (ntlmv2Blob == null) {
- ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
- }
- return ntlmv2Blob;
- }
-
- /**
- * Calculate the NTLMv2Response
- *
- * @param targetInformation this parameter is the same object as the field targetInformation,
- * but guaranteed to be not null. This is done to satisfy NullAway requirements
- */
- public byte[] getNTLMv2Response(byte[] targetInformation) {
- if (ntlmv2Response == null) {
- ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob(targetInformation));
- }
- return ntlmv2Response;
- }
-
- /**
- * Calculate the LMv2Response
- */
- public byte[] getLMv2Response() {
- if (lmv2Response == null) {
- lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge());
- }
- return lmv2Response;
- }
-
- /**
- * Get NTLM2SessionResponse
- */
- public byte[] getNTLM2SessionResponse() {
- if (ntlm2SessionResponse == null) {
- ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge());
- }
- return ntlm2SessionResponse;
- }
-
- /**
- * Calculate and return LM2 session response
- */
- public byte[] getLM2SessionResponse() {
- if (lm2SessionResponse == null) {
- final byte[] clntChallenge = getClientChallenge();
- lm2SessionResponse = new byte[24];
- System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length);
- Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00);
- }
- return lm2SessionResponse;
- }
-
- /**
- * Get LMUserSessionKey
- */
- public byte[] getLMUserSessionKey() {
- 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() {
- if (ntlmUserSessionKey == null) {
- final MD4 md4 = new MD4();
- md4.update(getNTLMHash());
- ntlmUserSessionKey = md4.getOutput();
- }
- return ntlmUserSessionKey;
- }
-
- /**
- * GetNTLMv2UserSessionKey
- *
- * @param targetInformation this parameter is the same object as the field targetInformation,
- * but guaranteed to be not null. This is done to satisfy NullAway requirements
- */
- public byte[] getNTLMv2UserSessionKey(byte[] targetInformation) {
- if (ntlmv2UserSessionKey == null) {
- final byte[] ntlmv2hash = getNTLMv2Hash();
- final byte[] truncatedResponse = new byte[16];
- System.arraycopy(getNTLMv2Response(targetInformation), 0, truncatedResponse, 0, 16);
- ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash);
- }
- return ntlmv2UserSessionKey;
- }
-
- /**
- * Get NTLM2SessionResponseUserSessionKey
- */
- public byte[] getNTLM2SessionResponseUserSessionKey() {
- 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() {
- 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
- */
- private static byte[] hmacMD5(final byte[] value, final byte[] key) {
- final HMACMD5 hmacMD5 = new HMACMD5(key);
- hmacMD5.update(value);
- return hmacMD5.getOutput();
- }
-
- /**
- * Calculates RC4
- */
- private static byte[] RC4(final byte[] value, final byte[] key) {
- 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.
- */
- private static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, final byte[] clientChallenge) {
- try {
- final MessageDigest md5 = MessageDigest.getInstance("MD5");
- 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) {
- try {
- final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(US_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 Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
- des.init(Cipher.ENCRYPT_MODE, lowKey);
- final byte[] lowHash = des.doFinal(MAGIC_CONSTANT);
- des.init(Cipher.ENCRYPT_MODE, highKey);
- final byte[] highHash = des.doFinal(MAGIC_CONSTANT);
- 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) {
- 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) {
- 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) {
- 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) {
- try {
- final byte[] keyBytes = new byte[21];
- System.arraycopy(hash, 0, keyBytes, 0, 16);
- final Key lowKey = createDESKey(keyBytes, 0);
- final Key middleKey = createDESKey(keyBytes, 7);
- final Key highKey = createDESKey(keyBytes, 14);
- final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
- des.init(Cipher.ENCRYPT_MODE, lowKey);
- final byte[] lowResponse = des.doFinal(challenge);
- des.init(Cipher.ENCRYPT_MODE, middleKey);
- final byte[] middleResponse = des.doFinal(challenge);
- des.init(Cipher.ENCRYPT_MODE, highKey);
- final byte[] highResponse = des.doFinal(challenge);
- final byte[] lmResponse = new byte[24];
- System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
- System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
- System.arraycopy(highResponse, 0, lmResponse, 16, 8);
- return lmResponse;
- } catch (final Exception e) {
- throw new NtlmEngineException(e.getMessage(), e);
- }
- }
-
- /**
- * Creates the LMv2 Response from the given hash, client data, and Type 2
- * challenge.
- *
- * @param hash The NTLMv2 Hash.
- * @param clientData The client data (blob or client challenge).
- * @param challenge The server challenge from the Type 2 message.
- * @return The response (either NTLMv2 or LMv2, depending on the client
- * data).
- */
- private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) {
- final HMACMD5 hmacMD5 = new HMACMD5(hash);
- hmacMD5.update(challenge);
- hmacMD5.update(clientData);
- final byte[] mac = hmacMD5.getOutput();
- final byte[] lmv2Response = new byte[mac.length + clientData.length];
- System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
- System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length);
- return lmv2Response;
- }
-
- /**
- * 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 = {(byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00};
- final byte[] reserved = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
- final byte[] unknown1 = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
- final byte[] unknown2 = {(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);
- 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] |= 0x01;
- } else {
- bytes[i] &= (byte) 0xfe;
- }
- }
- }
-
- /**
- * NTLM message generation, base class
- */
- private static class NTLMMessage {
- private static final byte[] EMPTY_BYTE_ARRAY = {};
- /**
- * The current response
- */
- private byte[] messageContents = EMPTY_BYTE_ARRAY;
-
- /**
- * The current output position
- */
- private int currentOutputPosition;
-
- /**
- * Constructor to use when message contents are not yet known
- */
- NTLMMessage() {
- }
-
- /**
- * Constructor to use when message contents are known
- */
- NTLMMessage(final String messageBody, final int expectedType) {
- messageContents = Base64.getDecoder().decode(messageBody);
- // 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 " + expectedType + " message expected - instead got type "
- + 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 final int getMessageLength() {
- return currentOutputPosition;
- }
-
- /**
- * Read a byte from a position within the message buffer
- */
- protected byte readByte(final int position) {
- 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 final void readBytes(final byte[] buffer, final int position) {
- if (messageContents.length < position + buffer.length) {
- throw new NtlmEngineException("NTLM: Message too short");
- }
- System.arraycopy(messageContents, position, buffer, 0, buffer.length);
- }
-
- /**
- * Read an ushort from a position within the message buffer
- */
- protected int readUShort(final int position) {
- return NtlmEngine.readUShort(messageContents, position);
- }
-
- /**
- * Read an ulong from a position within the message buffer
- */
- protected final int readULong(final int position) {
- return NtlmEngine.readULong(messageContents, position);
- }
-
- /**
- * Read a security buffer from a position within the message buffer
- */
- protected final byte[] readSecurityBuffer(final int position) {
- return NtlmEngine.readSecurityBuffer(messageContents, position);
- }
-
- /**
- * Prepares the object to create a response of the given length.
- *
- * @param maxlength the maximum length of the response to prepare, not
- * 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 @Nullable [] 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.
- */
- String getResponse() {
- final byte[] resp;
- if (messageContents.length > currentOutputPosition) {
- final byte[] tmp = new byte[currentOutputPosition];
- System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition);
- resp = tmp;
- } else {
- resp = messageContents;
- }
- return Base64.getEncoder().encodeToString(resp);
- }
-
- }
-
- /**
- * Type 1 message assembly class
- */
- private static class Type1Message extends NTLMMessage {
-
- /**
- * Getting the response involves building the message before returning
- * it
- */
- @Override
- String getResponse() {
- // Now, build the message. Calculate its length first, including
- // signature or type.
- final int finalLength = 32 + 8;
-
- // Set up the response. This will initialize the signature, message
- // type, and flags.
- prepareResponse(finalLength, 1);
-
- // Flags. These are the complete set of flags we support.
- addULong(
- //FLAG_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);
-
- // Domain length (two times).
- addUShort(0);
- addUShort(0);
-
- // Domain offset.
- addULong(finalLength);
-
- // Host length (two times).
- addUShort(0);
- addUShort(0);
-
- // Host offset (always 32 + 8).
- addULong(finalLength);
-
- // Version
- addUShort(0x0105);
- // Build
- addULong(2600);
- // NTLM revision
- addUShort(0x0f00);
-
- return super.getResponse();
- }
- }
-
- /**
- * Type 2 message class
- */
- static class Type2Message extends NTLMMessage {
- protected byte[] challenge;
- protected @Nullable String target;
- protected byte @Nullable [] targetInfo;
- protected int flags;
-
- Type2Message(final String message) {
- 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 an 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);
-
- if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) {
- throw new NtlmEngineException("NTLM type 2 message indicates no support for Unicode. Flags are: " + flags);
- }
-
- // Do the target!
- target = null;
- // The TARGET_DESIRED flag is said to not have understood semantics
- // in Type2 messages, so use the length of the packet to decide
- // how to proceed instead
- if (getMessageLength() >= 12 + 8) {
- final byte[] bytes = readSecurityBuffer(12);
- if (bytes.length != 0) {
- try {
- target = new String(bytes, "UnicodeLittleUnmarked");
- } catch (final UnsupportedEncodingException e) {
- throw new NtlmEngineException(e.getMessage(), e);
- }
- }
- }
-
- // 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
- */
- @Nullable String getTarget() {
- return target;
- }
-
- /**
- * Retrieve the target info
- */
- byte @Nullable [] getTargetInfo() {
- return targetInfo;
- }
-
- /**
- * Retrieve the response flags
- */
- int getFlags() {
- return flags;
- }
-
- }
-
- /**
- * Type 3 message assembly class
- */
- static class Type3Message extends NTLMMessage {
- // Response flags from the type2 message
- protected int type2Flags;
-
- protected byte @Nullable [] domainBytes;
- protected byte @Nullable [] hostBytes;
- protected byte[] userBytes;
-
- protected byte[] lmResp;
- protected byte[] ntResp;
- protected byte @Nullable [] sessionKey;
-
- /**
- * 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 @Nullable String target, final byte @Nullable [] targetInformation) {
- // Save the flags
- this.type2Flags = type2Flags;
-
- // Strip off domain name from the host!
- final String unqualifiedHost = convertHost(host);
- // Use only the base domain name!
- final String unqualifiedDomain = convertDomain(domain);
-
- // Create a cipher generator class. Use domain BEFORE it gets modified!
- final CipherGen gen = new CipherGen(unqualifiedDomain, user, password, nonce, target, targetInformation);
-
- // 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(targetInformation);
- lmResp = gen.getLMv2Response();
- if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
- userSessionKey = gen.getLanManagerSessionKey();
- } else {
- userSessionKey = gen.getNTLMv2UserSessionKey(targetInformation);
- }
- } 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) {
- sessionKey = RC4(gen.getSecondaryKey(), userSessionKey);
- } else {
- sessionKey = userSessionKey;
- }
- } else {
- sessionKey = null;
- }
- hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null;
- domainBytes = unqualifiedDomain != null ? unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null;
- userBytes = user.getBytes(UNICODE_LITTLE_UNMARKED);
- }
-
- /**
- * Assemble the response
- */
- @Override
- String getResponse() {
- 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
- 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);
-
- // Version
- addUShort(0x0105);
- // Build
- addULong(2600);
- // NTLM revision
- addUShort(0x0f00);
-
- // Add the actual data
- addBytes(lmResp);
- addBytes(ntResp);
- addBytes(domainBytes);
- addBytes(userBytes);
- addBytes(hostBytes);
- if (sessionKey != null) {
- addBytes(sessionKey);
- }
-
- return super.getResponse();
- }
- }
-
- 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;
- }
-
- /**
- * Cryptography support - MD4. The following class was based loosely on the
- * RFC and on code found at ....
- * Code correctness was verified by looking at MD4.java from the jcifs
- * library (...). 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;
- protected byte[] dataBuffer = new byte[64];
-
- 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;
- }
- }
-
- 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
- */
- private static class HMACMD5 {
- protected byte[] ipad;
- protected byte[] opad;
- protected MessageDigest md5;
-
- HMACMD5(final byte[] input) {
- byte[] key = input;
- try {
- md5 = MessageDigest.getInstance("MD5");
- } catch (final Exception ex) {
- // Umm, the algorithm doesn't exist - throw an
- // NTLMEngineException!
- throw new NtlmEngineException("Error getting md5 message digest implementation: " + ex.getMessage(), ex);
- }
-
- // Initialize the pad buffers with the key
- ipad = new byte[64];
- opad = new byte[64];
-
- int keyLength = key.length;
- if (keyLength > 64) {
- // Use MD5 of the key instead, as described in RFC 2104
- md5.update(key);
- key = md5.digest();
- keyLength = key.length;
- }
- int i = 0;
- while (i < keyLength) {
- ipad[i] = (byte) (key[i] ^ (byte) 0x36);
- opad[i] = (byte) (key[i] ^ (byte) 0x5c);
- i++;
- }
- while (i < 64) {
- ipad[i] = 0x36;
- opad[i] = 0x5c;
- i++;
- }
-
- // Very important: update 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);
- }
- }
-
- /**
- * Creates the first message (type 1 message) in the NTLM authentication
- * sequence. This message includes the username, domain and host for the
- * authentication session.
- *
- * @return String the message to add to the HTTP request header.
- */
- public String generateType1Msg() {
- return TYPE_1_MESSAGE;
- }
-
- public static String generateType3Msg(final String username, final String password, final String domain, final String workstation,
- final String challenge) {
- final Type2Message t2m = new Type2Message(challenge);
- return getType3Message(username, password, workstation, domain, t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(),
- t2m.getTargetInfo());
- }
-
-}
\ No newline at end of file
diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java
deleted file mode 100644
index dd7827cbf..000000000
--- a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * ====================================================================
- *
- * 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.asynchttpclient.ntlm;
-
-
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Signals NTLM protocol failure.
- */
-class NtlmEngineException extends RuntimeException {
-
- private static final long serialVersionUID = 6027981323731768824L;
-
- /**
- * Creates a new NTLMEngineException with the specified message.
- *
- * @param message the exception detail message
- */
- NtlmEngineException(String message) {
- super(message);
- }
-
- /**
- * Creates a new NTLMEngineException with the specified detail message and cause.
- *
- * @param message the exception detail message
- * @param cause the Throwable that caused this exception, or null
- * if the cause is unavailable, unknown, or not a Throwable
- */
- NtlmEngineException(@Nullable String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java
deleted file mode 100644
index 93506925e..000000000
--- a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.asynchttpclient.ntlm;
-
-import io.github.artsok.RepeatedIfExceptionsTest;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.apache.commons.io.output.ByteArrayOutputStream;
-import org.asynchttpclient.AbstractBasicTest;
-import org.asynchttpclient.AsyncHttpClient;
-import org.asynchttpclient.Realm;
-import org.asynchttpclient.Response;
-import org.asynchttpclient.ntlm.NtlmEngine.Type2Message;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-import static org.asynchttpclient.Dsl.asyncHttpClient;
-import static org.asynchttpclient.Dsl.config;
-import static org.asynchttpclient.Dsl.get;
-import static org.asynchttpclient.Dsl.ntlmAuthRealm;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-public class NtlmTest extends AbstractBasicTest {
-
- private static byte[] longToBytes(long x) {
- ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
- buffer.putLong(x);
- return buffer.array();
- }
-
- @Override
- public AbstractHandler configureHandler() throws Exception {
- return new NTLMHandler();
- }
-
- private static Realm.Builder realmBuilderBase() {
- return ntlmAuthRealm("Zaphod", "Beeblebrox")
- .setNtlmDomain("Ursa-Minor")
- .setNtlmHost("LightCity");
- }
-
- private void ntlmAuthTest(Realm.Builder realmBuilder) throws IOException, InterruptedException, ExecutionException {
- try (AsyncHttpClient client = asyncHttpClient(config().setRealm(realmBuilder))) {
- Future responseFuture = client.executeRequest(get(getTargetUrl()));
- int status = responseFuture.get().getStatusCode();
- assertEquals(200, status);
- }
- }
-
- @Test
- public void testUnicodeLittleUnmarkedEncoding() {
- final Charset unicodeLittleUnmarked = Charset.forName("UnicodeLittleUnmarked");
- final Charset utf16le = StandardCharsets.UTF_16LE;
- assertEquals(unicodeLittleUnmarked, utf16le);
- assertArrayEquals("Test @ テスト".getBytes(unicodeLittleUnmarked), "Test @ テスト".getBytes(utf16le));
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void lazyNTLMAuthTest() throws Exception {
- ntlmAuthTest(realmBuilderBase());
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void preemptiveNTLMAuthTest() throws Exception {
- ntlmAuthTest(realmBuilderBase().setUsePreemptiveAuth(true));
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType1Msg() {
- NtlmEngine engine = new NtlmEngine();
- String message = engine.generateType1Msg();
- assertEquals(message, "TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==", "Incorrect type1 message generated");
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() {
- NtlmEngine engine = new NtlmEngine();
- assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
- Base64.getEncoder().encodeToString("a".getBytes())),
- "An NtlmEngineException must have occurred as challenge length is too short");
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType3MsgThrowsExceptionWhenChallengeDoesNotFollowCorrectFormat() {
- NtlmEngine engine = new NtlmEngine();
- assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
- Base64.getEncoder().encodeToString("challenge".getBytes())),
- "An NtlmEngineException must have occurred as challenge length is too short");
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType3MsgThworsExceptionWhenType2IndicatorNotPresent() throws IOException {
- try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
- buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII));
- buf.write(0);
- // type 2 indicator
- buf.write(3);
- buf.write(0);
- buf.write(0);
- buf.write(0);
- buf.write("challenge".getBytes());
- NtlmEngine engine = new NtlmEngine();
- assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
- Base64.getEncoder().encodeToString(buf.toByteArray())), "An NtlmEngineException must have occurred as type 2 indicator is incorrect");
- }
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType3MsgThrowsExceptionWhenUnicodeSupportNotIndicated() throws IOException {
- try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
- buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII));
- buf.write(0);
- // type 2 indicator
- buf.write(2);
- buf.write(0);
- buf.write(0);
- buf.write(0);
-
- buf.write(longToBytes(1L)); // we want to write a Long
-
- // flags
- buf.write(0);// unicode support indicator
- buf.write(0);
- buf.write(0);
- buf.write(0);
-
- buf.write(longToBytes(1L));// challenge
- NtlmEngine engine = new NtlmEngine();
- assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation",
- Base64.getEncoder().encodeToString(buf.toByteArray())),
- "An NtlmEngineException must have occurred as unicode support is not indicated");
- }
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType2Msg() {
- Type2Message type2Message = new Type2Message("TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==");
- assertEquals(40, type2Message.getMessageLength(), "This is a sample challenge that should return 40");
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testGenerateType3Msg() throws IOException {
- try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
- buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII));
- buf.write(0);
- // type 2 indicator
- buf.write(2);
- buf.write(0);
- buf.write(0);
- buf.write(0);
-
- buf.write(longToBytes(0L)); // we want to write a Long
-
- // flags
- buf.write(1);// unicode support indicator
- buf.write(0);
- buf.write(0);
- buf.write(0);
-
- buf.write(longToBytes(1L));// challenge
- NtlmEngine engine = new NtlmEngine();
- String type3Msg = engine.generateType3Msg("username", "password", "localhost", "workstation",
- Base64.getEncoder().encodeToString(buf.toByteArray()));
- assertEquals(type3Msg,
- "TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABIAEgB4AAAAEAAQAIoAAAAWABYAmgAAAAAAAACwAAAAAQAAAgUBKAoAAAAP1g6lqqN1HZ0wSSxeQ5riQkyh7/UexwVlCPQm0SHU2vsDQm2wM6NbT2zPonPzLJL0TABPAEMAQQBMAEgATwBTAFQAdQBzAGUAcgBuAGEAbQBlAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=",
- "Incorrect type3 message generated");
- }
- }
-
- @RepeatedIfExceptionsTest(repeats = 5)
- public void testWriteULong() {
- // test different combinations so that different positions in the byte array will be written
- byte[] buffer = new byte[4];
- NtlmEngine.writeULong(buffer, 1, 0);
- assertArrayEquals(new byte[]{1, 0, 0, 0}, buffer, "Unsigned long value 1 was not written correctly to the buffer");
-
- buffer = new byte[4];
- NtlmEngine.writeULong(buffer, 257, 0);
- assertArrayEquals(new byte[]{1, 1, 0, 0}, buffer, "Unsigned long value 257 was not written correctly to the buffer");
-
- buffer = new byte[4];
- NtlmEngine.writeULong(buffer, 16777216, 0);
- assertArrayEquals(new byte[]{0, 0, 0, 1}, buffer, "Unsigned long value 16777216 was not written correctly to the buffer");
- }
-
- public static class NTLMHandler extends AbstractHandler {
-
- @Override
- public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException {
- String authorization = httpRequest.getHeader("Authorization");
- if (authorization == null) {
- httpResponse.setStatus(401);
- httpResponse.setHeader("WWW-Authenticate", "NTLM");
-
- } else if ("NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==".equals(authorization)) {
- httpResponse.setStatus(401);
- httpResponse.setHeader("WWW-Authenticate", "NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==");
-
- } else if ("NTLM TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABQAFAB4AAAADAAMAIwAAAASABIAmAAAAAAAAACqAAAAAYIAAgUBKAoAAAAPrYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADTVQBSAFMAQQAtAE0ASQBOAE8AUgBaAGEAcABoAG8AZABMAEkARwBIAFQAQwBJAFQAWQA="
- .equals(authorization)) {
- httpResponse.setStatus(200);
- } else {
- httpResponse.setStatus(401);
- }
-
- httpResponse.setContentLength(0);
- httpResponse.getOutputStream().flush();
- httpResponse.getOutputStream().close();
- }
- }
-}