/*
* Copyright (c) 2016 Dell EMC Software
* All Rights Reserved
*/
package com.iwave.ext.windows.winrm.ntlm;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.iwave.ext.windows.winrm.Pair;
/**
* This class contains the common elements of an NTLM message. Additionally it holds the functionality to convert from a byte
* array header to an ntlm message and vice-versa. One of the things that is important to note is that every value in the
* arrays is in little endian format.
*
*/
public abstract class NTLMMessage {
/** Constant for the beginning of the header. */
public static final String NTLMSSP = "NTLMSSP\0";
/** Constant in bytes. */
public static final byte[] NTLMSSP_BYTES = NTLMSSP.getBytes(NTLMUtils.DEFAULT_CHARSET);
/** A list of the flags used by the NTLM protocol. */
@SuppressWarnings("serial")
private static final List<Pair<Integer, String>> FLAGS = new ArrayList<Pair<Integer, String>>() {
{
add(new Pair<Integer, String>(1, "NEGOTIATE_UNICODE"));
add(new Pair<Integer, String>(1 << 1, "NEGOTIATE_OEM"));
add(new Pair<Integer, String>(1 << 2, "REQUEST_TARGET"));
add(new Pair<Integer, String>(1 << 3, "UNKNOWN_1"));
add(new Pair<Integer, String>(1 << 4, "NEGOTIATE_SIGN"));
add(new Pair<Integer, String>(1 << 5, "NEGOTIATE_SEAL"));
add(new Pair<Integer, String>(1 << 6, "NEGOTIATE_DATAGRAM_STYLE"));
add(new Pair<Integer, String>(1 << 7, "NEGOTITATE_LAN_MANAGER_KEY"));
add(new Pair<Integer, String>(1 << 8, "NEGOTIATE_NETWARE"));
add(new Pair<Integer, String>(1 << 9, "NEGOTIATE_NTLM"));
add(new Pair<Integer, String>(1 << 10, "UNKNOWN_2"));
add(new Pair<Integer, String>(1 << 11, "NEGOTIATE_ANONYMOUS"));
add(new Pair<Integer, String>(1 << 12, "NEGOTIATE_DOMAIN_SUPPLIED"));
add(new Pair<Integer, String>(1 << 13, "NEGOTIATE_WORKSTATION_SUPPLIED"));
add(new Pair<Integer, String>(1 << 14, "NEGOTIATE_LOCAL_CALL"));
add(new Pair<Integer, String>(1 << 15, "NEGOTIATE_ALWAYS_SIGN"));
add(new Pair<Integer, String>(1 << 16, "TARGET_TYPE_DOMAIN"));
add(new Pair<Integer, String>(1 << 17, "TARGET_TYPE_SERVER"));
add(new Pair<Integer, String>(1 << 18, "TARGET_TYPE_SHARE"));
add(new Pair<Integer, String>(1 << 19, "NEGOTIATE_NTLM2_KEY"));
add(new Pair<Integer, String>(1 << 20, "REQUEST_INIT_RESPONSE"));
add(new Pair<Integer, String>(1 << 21, "REQUEST_ACCEPT_RESPONSE"));
add(new Pair<Integer, String>(1 << 22, "REQUEST_NON_NT_SESSION_KEY"));
add(new Pair<Integer, String>(1 << 23, "NEGOTIATE_TARGET_INFO"));
add(new Pair<Integer, String>(1 << 24, "UNKNOWN_3"));
add(new Pair<Integer, String>(1 << 25, "UNKNOWN_4"));
add(new Pair<Integer, String>(1 << 26, "UNKNOWN_5"));
add(new Pair<Integer, String>(1 << 27, "UNKNOWN_6"));
add(new Pair<Integer, String>(1 << 28, "UNKNOWN_7"));
add(new Pair<Integer, String>(1 << 29, "NEGOTIATE_128"));
add(new Pair<Integer, String>(1 << 30, "NEGOTIATE_KEY_EXCHANGE"));
add(new Pair<Integer, String>(1 << 31, "NEGOTIATE_56"));
}
};
/**
* Byte container for the version. It had the following internal structure.
*
* <pre>
* Description Content
* 0 Major Version Number 1 byte
* 1 Minor Version Number 1 byte
* 2 Build Number short
* 4 Unknown 0x0000000f
* </pre>
*/
private byte[] version;
/**
* Byte container for the flags. There are 32 different flags, you can find them in NTLMUtils, or by reading the
* documentation.
*/
private byte[] flags;
/**
* @return the version
*/
public byte[] getVersion() {
return version;
}
/**
* @param version
* the version to set
*/
public void setVersion(byte[] version) {
this.version = version;
}
/**
* @return the flags
*/
public byte[] getFlags() {
return flags;
}
/**
* @param flags
* the flags to set
*/
public void setFlags(byte[] flags) {
this.flags = flags;
}
/**
* Turns a flag on.
*
* @param flag
* the flag to turn on
*/
public void addFlag(int flag) {
int flagAsInt = ByteBuffer.wrap(flags).order(ByteOrder.LITTLE_ENDIAN).getInt();
flagAsInt = flagAsInt | flag;
flags = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(flagAsInt).array();
}
/**
* Checks if the flag is present.
*
* @param flag
* the flag to check
* @return true if it's turned on
*/
public boolean hasFlag(int flag) {
return (ByteBuffer.wrap(flags).order(ByteOrder.LITTLE_ENDIAN).getInt() & flag) == flag;
}
/**
*
* @param flags
* the flags of the message
* @param version
* the version of the host sending the message
*/
public NTLMMessage(byte[] flags, byte[] version) {
this.flags = flags;
this.version = version;
}
/**
* Version in human readable format.
*
* @return the pretty printed string.
*/
public String getVersionAsString() {
return version[0] + "." + version[1] + " build " + NTLMUtils.getShort(2, version);
}
/**
* Flags in human readable format.
*
* @return the readable flags
*/
public String getFlagsAsString() {
StringBuilder builder = new StringBuilder();
int flagAsInt = ByteBuffer.wrap(flags).order(ByteOrder.LITTLE_ENDIAN).getInt();
for (Pair<Integer, String> flag : FLAGS) {
if ((flag.getFirstElement() & flagAsInt) == flag.getFirstElement()) {
builder.append(flag.getSecondElement()).append("\n");
}
}
return builder.toString();
}
/**
* Parses a header that has already been base64 decoded and returns an NTLMMessage corresponding to that header.
*
* @param header
* the base64 decoded header
* @return the NTLMMessage
* @throws Exception
* if something goes wrong
*/
public static NTLMMessage parse(byte[] header) throws Exception {
if (!Arrays.equals(NTLMSSP_BYTES, Arrays.copyOfRange(header, 0, 8))) {
throw new Exception("This is not an NTLM header");
}
int type = NTLMUtils.getInt(8, header);
if (type == 1) {
return NTLMType1Message.parse(header);
} else if (type == 2) {
return NTLMType2Message.parse(header);
} else if (type == 3) {
return NTLMType3Message.parse(header);
} else {
throw new Exception("Message is of type " + type + " and is not supported");
}
}
/**
* Transform the message into the byte array that will be put into an HTTP header.
*
* @return the byte array
*/
public abstract byte[] toHeaderBytes();
/**
* Retrieve the type of the message.
*
* @return the type of the mesage
*/
public abstract int getMessageType();
}