// 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.
package rdpclient.ntlmssp;

import com.cloud.utils.ConstantTimeComparator;

import java.nio.charset.Charset;

import rdpclient.ntlmssp.asn1.NegoItem;
import rdpclient.ntlmssp.asn1.SubjectPublicKeyInfo;
import rdpclient.ntlmssp.asn1.TSRequest;
import rdpclient.rdp.RdpConstants;
import streamer.ByteBuffer;
import streamer.Element;
import streamer.Link;
import streamer.OneTimeSwitch;
import streamer.Pipeline;
import streamer.PipelineImpl;
import streamer.debug.Dumper;
import streamer.debug.MockSink;
import streamer.debug.MockSource;
import streamer.ssl.SSLState;
import common.asn1.Tag;

/**
 * @see http://msdn.microsoft.com/en-us/library/cc236643.aspx
 */
public class ClientNtlmsspPubKeyAuth extends OneTimeSwitch implements NtlmConstants, Dumper {

    /**
     * Offset of first byte of allocated block after NTLMSSP header and block
     * descriptors.
     */
    private static final int BLOCKS_OFFSET = 88;

    protected NtlmState ntlmState;
    protected SSLState sslState;

    protected String targetDomain;
    protected String user;
    protected String password;
    protected String workstation;
    protected String serverHostName;

    public ClientNtlmsspPubKeyAuth(String id, NtlmState ntlmState, SSLState sslState, String serverHostName, String targetDomain, String workstation,
            String user, String password) {
        super(id);
        this.ntlmState = ntlmState;
        this.sslState = sslState;
        this.serverHostName = serverHostName;
        this.targetDomain = targetDomain;
        this.workstation = workstation;
        this.user = user;
        this.password = password;
    }

    @Override
    protected void handleOneTimeData(ByteBuffer buf, Link link) {
        if (buf == null)
            return;

        throw new RuntimeException("Unexpected packet: " + buf + ".");
    }

    @Override
    protected void onStart() {
        super.onStart();

        /*
         * @see
         * http://blogs.msdn.com/b/openspecification/archive/2010/04/20/ntlm-keys
         * -and-sundry-stuff.aspx
         */
        ntlmState.domain = targetDomain;
        ntlmState.user = user;
        ntlmState.password = password;
        ntlmState.workstation = workstation;
        ntlmState.generateServicePrincipalName(serverHostName);
        ntlmState.ntlm_construct_authenticate_target_info();
        ntlmState.ntlm_generate_timestamp();
        ntlmState.ntlm_generate_client_challenge();
        ntlmState.ntlm_compute_lm_v2_response();
        ntlmState.ntlm_compute_ntlm_v2_response();
        ntlmState.ntlm_generate_key_exchange_key();
        ntlmState.ntlm_generate_random_session_key();
        ntlmState.ntlm_generate_exported_session_key();
        ntlmState.ntlm_encrypt_random_session_key();
        ntlmState.ntlm_init_rc4_seal_states();

        ByteBuffer authenticateMessage = generateAuthenticateMessage(ntlmState);
        ByteBuffer messageSignatureAndEncryptedServerPublicKey = generateMessageSignatureAndEncryptedServerPublicKey(ntlmState);

        // Length of packet
        ByteBuffer buf = new ByteBuffer(4096, true);

        TSRequest tsRequest = new TSRequest("TSRequest");
        tsRequest.version.value = 2L;
        NegoItem negoItem = new NegoItem("NegoItem");
        negoItem.negoToken.value = authenticateMessage;

        tsRequest.negoTokens.tags = new Tag[] {negoItem};

        tsRequest.pubKeyAuth.value = messageSignatureAndEncryptedServerPublicKey;

        tsRequest.writeTag(buf);

        // Trim buffer to actual length of data written
        buf.trimAtCursor();

        pushDataToOTOut(buf);

        switchOff();
    }

    private byte[] getServerPublicKey() {
        // SSL certificate public key with algorithm
        ByteBuffer subjectPublicKeyInfo = new ByteBuffer(sslState.serverCertificateSubjectPublicKeyInfo);

        // Parse subjectPublicKeyInfo
        SubjectPublicKeyInfo parser = new SubjectPublicKeyInfo("SubjectPublicKeyInfo");
        parser.readTag(subjectPublicKeyInfo);

        // Copy subjectPublicKey subfield to separate byte buffer
        ByteBuffer subjectPublicKey = new ByteBuffer(subjectPublicKeyInfo.length);
        parser.subjectPublicKey.writeTag(subjectPublicKey);

        subjectPublicKeyInfo.unref();
        subjectPublicKey.trimAtCursor();

        // Skip tag:
        // 03 82 01 0f (tag) 00 (padding byte)
        subjectPublicKey.trimHeader(5);// FIXME: parse it properly
        // * DEBUG */System.out.println("DEBUG: subjectPublicKey:\n" +
        // subjectPublicKey.dump());

        ntlmState.subjectPublicKey = subjectPublicKey.toByteArray();

        return ntlmState.subjectPublicKey;
    }

    /**
     * The client encrypts the public key it received from the server (contained
     * in the X.509 certificate) in the TLS handshake from step 1, by using the
     * confidentiality support of SPNEGO.
     *
     * The public key that is encrypted is the ASN.1-encoded SubjectPublicKey
     * sub-field of SubjectPublicKeyInfo from the X.509 certificate, as specified
     * in [RFC3280] section 4.1. The encrypted key is encapsulated in the
     * pubKeyAuth field of the TSRequest structure and is sent over the TLS
     * channel to the server.
     */
    private ByteBuffer generateMessageSignatureAndEncryptedServerPublicKey(NtlmState ntlmState) {
        return new ByteBuffer(ntlmState.ntlm_EncryptMessage(getServerPublicKey()));
    }

    public static ByteBuffer generateAuthenticateMessage(NtlmState ntlmState) {

        // Allocate memory for blocks from given fixed offset
        int blocksCursor = BLOCKS_OFFSET;

        ByteBuffer buf = new ByteBuffer(4096);

        // Signature: "NTLMSSP\0"
        buf.writeString(NTLMSSP, RdpConstants.CHARSET_8);
        buf.writeByte(0);

        // NTLM Message Type: NTLMSSP_AUTH (0x00000003)
        buf.writeIntLE(NtlmConstants.NTLMSSP_AUTH);

        // Although the protocol allows authentication to succeed if the client
        // provides either LmChallengeResponse or NtChallengeResponse, Windows
        // implementations provide both.

        // LM V2 response
        blocksCursor = writeBlock(buf, ntlmState.lmChallengeResponse, blocksCursor);

        // NT v2 response
        blocksCursor = writeBlock(buf, ntlmState.ntChallengeResponse, blocksCursor);

        // DomainName
        blocksCursor = writeStringBlock(buf, ntlmState.domain, blocksCursor, RdpConstants.CHARSET_16);

        // UserName
        blocksCursor = writeStringBlock(buf, ntlmState.user, blocksCursor, RdpConstants.CHARSET_16);

        // Workstation
        blocksCursor = writeStringBlock(buf, ntlmState.workstation, blocksCursor, RdpConstants.CHARSET_16);

        // EncryptedRandomSessionKey, 16 bytes
        blocksCursor = writeBlock(buf, ntlmState.encryptedRandomSessionKey, blocksCursor);

        // NegotiateFlags (4 bytes): In connection-oriented mode, a NEGOTIATE
        // structure that contains the set of bit flags (section 2.2.2.5) negotiated
        // in the previous messages.
        buf.writeIntLE(/*ntlmState.negotiatedFlags.value*/0xe288b235); // FIXME: remove hardcoded value

        buf.writeBytes(generateVersion());

        // If the CHALLENGE_MESSAGE TargetInfo field (section 2.2.1.2) has an
        // MsvAvTimestamp present, the client SHOULD provide a MIC(Message Integrity
        // Check)

        int savedCursorForMIC = buf.cursor; // Save cursor position to write MIC
        // later
        buf.writeBytes(new byte[16]); // Write 16 zeroes

        if (BLOCKS_OFFSET != buf.cursor)
            throw new RuntimeException("BUG: Actual offset of first byte of allocated blocks is not equal hardcoded offset. Hardcoded offset: " + BLOCKS_OFFSET
                    + ", actual offset: " + buf.cursor + ". Update hardcoded offset to match actual offset.");

        buf.cursor = blocksCursor;
        buf.trimAtCursor();

        ntlmState.authenticateMessage = buf.toByteArray();

        // Calculate and write MIC to reserved position
        ntlmState.ntlm_compute_message_integrity_check();
        buf.cursor = savedCursorForMIC;
        buf.writeBytes(ntlmState.messageIntegrityCheck);
        buf.rewindCursor();

        return buf;
    }

    /**
     * Write string as security buffer, using given charset, without trailing '\0'
     * character.
     */
    private static int writeStringBlock(ByteBuffer buf, String string, int blocksCursor, Charset charset) {
        return writeBlock(buf, string.getBytes(charset), blocksCursor);
    }

    /**
     * Write block to blocks buffer and block descriptor to main buffer.
     */
    private static int writeBlock(ByteBuffer buf, byte[] block, int blocksCursor) {

        // Write block descriptor

        // Length
        buf.writeShortLE(block.length);
        // Allocated
        buf.writeShortLE(block.length);
        // Offset
        buf.writeIntLE(blocksCursor);

        // Write block to position pointed by blocksCursor instead of buf.cursor
        int savedCursor = buf.cursor;
        buf.cursor = blocksCursor;
        buf.writeBytes(block);
        blocksCursor = buf.cursor;
        buf.cursor = savedCursor;

        return blocksCursor;
    }

    /**
     * Version (8 bytes): A VERSION structure (section 2.2.2.10) that is present
     * only when the NTLMSSP_NEGOTIATE_VERSION flag is set in the NegotiateFlags
     * field. This structure is used for debugging purposes only. In normal
     * protocol messages, it is ignored and does not affect the NTLM message
     * processing.
     */
    private static byte[] generateVersion() {
        // Version (6.1, Build 7601), NTLM current revision: 15
        return new byte[] {0x06, 0x01, (byte)0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f};
    }

    /**
     * Example.
     */
    public static void main(String args[]) {
        System.setProperty("streamer.Element.debug", "true");

        /* @formatter:off */
        //
        // Client NEGOTIATE
        //
        byte[] clientNegotiatePacket = new byte[] {
                (byte)0x30, (byte)0x37, (byte)0xa0, (byte)0x03, (byte)0x02, (byte)0x01, (byte)0x02, (byte)0xa1, (byte)0x30, (byte)0x30, (byte)0x2e, (byte)0x30, (byte)0x2c, (byte)0xa0, (byte)0x2a, (byte)0x04,
                (byte)0x28, (byte)0x4e, (byte)0x54, (byte)0x4c, (byte)0x4d, (byte)0x53, (byte)0x53, (byte)0x50, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xb7, (byte)0x82, (byte)0x08,
                (byte)0xe2, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                (byte)0x00, (byte)0x06, (byte)0x01, (byte)0xb1, (byte)0x1d, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0f,
        };

//
// Server CHALLENGE
//
        byte[] serverChallengePacket = new byte[] {
                0x30, (byte) 0x82, 0x01, 0x02, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 258 bytes

                (byte) 0xa0, 0x03, // TAG: [0] (constructed) LEN: 3 bytes
                0x02, 0x01, 0x03,  // TAG: [UNIVERSAL 2] (primitive) "INTEGER" LEN: 1 bytes, Version: 0x3
                (byte) 0xa1, (byte) 0x81, (byte) 0xfa, // TAG: [1] (constructed) LEN: 250 bytes
                0x30, (byte) 0x81, (byte) 0xf7, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 247 bytes
                0x30, (byte) 0x81, (byte) 0xf4, // TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 244 bytes
                (byte) 0xa0, (byte) 0x81, (byte) 0xf1, // TAG: [0] (constructed) LEN: 241 bytes
                0x04, (byte) 0x81, (byte) 0xee, // TAG: [UNIVERSAL 4] (primitive) "OCTET STRING" LEN: 238 bytes

                0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, // "NTLMSSP\0"

                0x02, 0x00, 0x00, 0x00, // MessageType (CHALLENGE)

                0x1e, 0x00, 0x1e, 0x00, 0x38, 0x00, 0x00, 0x00, // TargetName (length: 30, allocated space: 30, offset: 56)
                0x35, (byte) 0x82, (byte) 0x8a, (byte) 0xe2, // NegotiateFlags: NEGOTIATE_56 NEGOTIATE_KEY_EXCH NEGOTIATE_128 NEGOTIATE_VERSION NEGOTIATE_TARGET_INFO NEGOTIATE_EXTENDED_SESSION_SECURITY TARGET_TYPE_SERVER NEGOTIATE_ALWAYS_SIGN NEGOTIATE_NTLM NEGOTIATE_SEAL NEGOTIATE_SIGN REQUEST_TARGET NEGOTIATE_UNICODE

                (byte)0xc1, (byte)0x4a, (byte)0xc8, (byte)0x98, (byte)0x2f, (byte)0xd1, (byte)0x93, (byte)0xd4, //  ServerChallenge
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  Reserved
                (byte) 0x98, 0x00, (byte) 0x98, 0x00, 0x56, 0x00, 0x00, 0x00, // TargetInfo (length: 152, allocated space: 152, offset: 86)
                0x06, 0x03, (byte) 0xd7, 0x24, 0x00, 0x00, 0x00, 0x0f,  // Version (6.3, build 9431) , NTLM current revision: 15


                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00,  // Target name value: "WIN-LO419B2LSR0"

                // Target Info value:

                // Attribute list

                0x02, 0x00, // Item Type: NetBIOS domain name (0x0002, LE)
                0x1e, 0x00, //  Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0"

                0x01, 0x00,  //  Item Type: NetBIOS computer name (0x0001, LE)
                0x1e, 0x00, //  Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0"

                0x04, 0x00,  // Item Type: DNS domain name (0x0004, LE)
                0x1e, 0x00, //  Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0"

                0x03, 0x00,  // Item Type: DNS computer name (0x0003, LE)
                0x1e, 0x00, //  Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, // "WIN-LO419B2LSR0"

                0x07, 0x00,  // Item Type: Timestamp (0x0007, LE)
                0x08, 0x00, //  Item Length: 8 (LE)
                (byte)0x1d, (byte)0xea, (byte)0x6b, (byte)0x60, (byte)0xf8, (byte)0xc5, (byte)0xce, (byte)0x01, // Time: Oct 10, 2013 23:36:20.056937300 EEST

                // Attribute: End of list
                0x00, 0x00,
                0x00, 0x00,

        };

//
// Client NTLMSSP_AUTH
//
        byte[] clientAuthPacket = new byte[] {
                0x30, (byte) 0x82, 0x03, 0x13, //  TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 787 bytes

                //
                // TSRequest.version
                //
                (byte) 0xa0, 0x03, //  TAG: [0] (constructed) LEN: 3 bytes
                0x02, 0x01, 0x02, //  TAG: [UNIVERSAL 2] (primitive) "INTEGER" LEN: 1 bytes

                //
                // TSRequest.negoData
                //
                (byte) 0xa1, (byte) 0x82, 0x01, (byte) 0xe4, //  TAG: [1] (constructed) LEN: 484 bytes
                0x30, (byte) 0x82, 0x01, (byte) 0xe0, //  TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 480 bytes
                0x30, (byte) 0x82, 0x01, (byte) 0xdc, //  TAG: [UNIVERSAL 16] (constructed) "SEQUENCE" LEN: 476 bytes

                //
                // NegoItem.negoToken
                //
                (byte) 0xa0, (byte) 0x82, 0x01, (byte) 0xd8, //  TAG: [0] (constructed) LEN: 472 bytes
                0x04, (byte) 0x82, 0x01, (byte) 0xd4,  // TAG: [UNIVERSAL 4] (primitive) "OCTET STRING" LEN: 468 bytes

                // NTLMSSP

                0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, //  "NTLMSSP\0"

                0x03, 0x00, 0x00, 0x00, //  NTLM Message Type: NTLMSSP_AUTH (0x00000003)
                0x18, 0x00, 0x18, 0x00, (byte) 0x92, 0x00, 0x00, 0x00, //  LmChallengeResponse (length 24, allocated: 24, offset 146)
                0x1a, 0x01, 0x1a, 0x01, (byte) 0xaa, 0x00, 0x00, 0x00, //  NtChallengeResponse (length 282, allocated: 282, offset 170)
                0x12, 0x00, 0x12, 0x00, 0x58, 0x00, 0x00, 0x00,  // DomainName (length 18, allocated: 88, offset 88)
                0x1a, 0x00, 0x1a, 0x00, 0x6a, 0x00, 0x00, 0x00,  // UserName (length 26, allocated:26, offset 106)
                0x0e, 0x00, 0x0e, 0x00, (byte) 0x84, 0x00, 0x00, 0x00,  // Workstation (length 14, offset 132)
                0x10, 0x00, 0x10, 0x00, (byte) 0xc4, 0x01, 0x00, 0x00,  // EncryptedRandomSessionKey (length 16, offset 452)
                0x35, (byte) 0xb2, (byte) 0x88, (byte) 0xe2,   // NegotiateFlags
                0x06, 0x01, (byte) 0xb1, 0x1d, 0x00, 0x00, 0x00, 0x0f, //  Version (6.1, Build 7601), NTLM current revision: 15

                (byte)0x8c, (byte)0x69, (byte)0x53, (byte)0x1c, (byte)0xbb, (byte)0x6f, (byte)0xfb, (byte)0x9a, (byte)0x5d, (byte)0x2c, (byte)0x63, (byte)0xf2, (byte)0xc9, (byte)0x51, (byte)0xc5, (byte)0x11, // Message integrity check

                0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x67, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x70, 0x00, // Domain name value: "Workgroup"

                0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x72, 0x00,  // User name value: "Administrator"

                0x61, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x33, 0x00, //  Workstation host name value: "apollo3"

                // Lan manager challenge response value
                // Response: HMAC_MD(ResponseKeyLM, concatenate(ServerChallenge, ClientChallenge), where ResponseKeyLM=ntlmv2Hash(target, user, password)
                (byte)0x17, (byte)0x9b, (byte)0x7d, (byte)0x7b, (byte)0x2f, (byte)0x79, (byte)0x9f, (byte)0x19, (byte)0xa0, (byte)0x4b, (byte)0x00, (byte)0xed, (byte)0x2b, (byte)0x39, (byte)0xbb, (byte)0x23,
                // Client challenge (fixed for debugging)
                (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,

                //
                // NTLM challenge response value:
                //

                (byte)0x49, (byte)0xea, (byte)0x27, (byte)0x4f, (byte)0xcc, (byte)0x05, (byte)0x8b, (byte)0x79, (byte)0x20, (byte)0x0b, (byte)0x08, (byte)0x42, (byte)0xa9, (byte)0xc8, (byte)0x0e, (byte)0xc7, //  HMAC

                0x01, 0x01, 0x00, 0x00, // Header: 0x00000101 (LE)
                0x00, 0x00, 0x00, 0x00, // Reserved: 0x00000000
                (byte)0x1d, (byte)0xea, (byte)0x6b, (byte)0x60, (byte)0xf8, (byte)0xc5, (byte)0xce, (byte)0x01, // Time: Oct 10, 2013 23:36:20.056937300 EEST
                (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08, // Client challenge (fixed)
                0x00, 0x00, 0x00, 0x00, //  Reserved

                // Target Info value:

                // Attribute list

                0x02, 0x00, // Item Type: NetBIOS domain name (0x0002, LE)
                0x1e, 0x00, //  Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00,  //  "WIN-LO419B2LSR0 "

                0x01, 0x00,  //  Item Type: NetBIOS computer name (0x0001, LE)
                0x1e, 0x00,  // Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, //  "WIN-LO419B2LSR0 "

                0x04, 0x00,  // Item Type: DNS domain name (0x0004, LE)
                0x1e, 0x00,  // Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, //  "WIN-LO419B2LSR0 "

                0x03, 0x00,  // Item Type: DNS computer name (0x0003, LE)
                0x1e, 0x00,  // Item Length: 30 (LE)
                0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x34, 0x00, 0x31, 0x00, 0x39, 0x00, 0x42, 0x00, 0x32, 0x00, 0x4c, 0x00, 0x53, 0x00, 0x52, 0x00, 0x30, 0x00, //  "WIN-LO419B2LSR0 "

                0x07, 0x00,  // Item Type: Timestamp (0x0007, LE)
                0x08, 0x00,  // Item Length: 8 (LE)
                (byte)0x1d, (byte)0xea, (byte)0x6b, (byte)0x60, (byte)0xf8, (byte)0xc5, (byte)0xce, (byte)0x01, // Timestamp: Oct 10, 2013 23:36:20.056937300 EEST

                0x06, 0x00,  // Item Type: Flags (0x0006, LE)
                0x04, 0x00, // Item Length: 4 (LE)
                0x02, 0x00, 0x00, 0x00, // Flags: 0x00000002

                0x0a, 0x00, // Item Type: Channel Bindings (0x000a, LE)
                0x10, 0x00, // Item Length: 16 (LE)
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Channel Bindings: 00000000000000000000000000000000

                0x09, 0x00, // Item Type: Target Name (0x0009, LE)
                0x2a, 0x00, // Item Length: 42 (LE)
                0x54, 0x00, 0x45, 0x00, 0x52, 0x00, 0x4d, 0x00, 0x53, 0x00, 0x52, 0x00, 0x56, 0x00, 0x2f, 0x00, 0x31, 0x00, 0x39, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x36, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x30, 0x00, 0x31, 0x00, // Target Name: "TERMSRV/192.168.0.101" (UTF-16)

                // Attribute: End of list
                0x00, 0x00,  //
                0x00, 0x00,  //
                // Attribute: End of list
                0x00, 0x00,  //
                0x00, 0x00,  //
                // Attribute: End of list
                0x00, 0x00,  //
                0x00, 0x00,  //
                // Attribute: End of list
                0x00, 0x00,  //
                0x00, 0x00,  //

                // Session Key
                // RC4 key (Server KeyExchangeKey or SessionBaseKey):
                // 6e bd e3 da 83 c2 fd f1 38 a2 78 be 8c e6 75 d6
                //
                // RC4 data (Client nonce):
                // 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
                //
                // RC4 encrypted:
                // 2c 24 da 10 17 cf 40 69 35 49 6f 58 e1 29 9e 79
                (byte)0x2c, (byte)0x24, (byte)0xda, (byte)0x10, (byte)0x17, (byte)0xcf, (byte)0x40, (byte)0x69, (byte)0x35, (byte)0x49, (byte)0x6f, (byte)0x58, (byte)0xe1, (byte)0x29, (byte)0x9e, (byte)0x79,

                //
                // TSRequest.publicKey
                //
                (byte) 0xa3, (byte) 0x82, 0x01, 0x22, // TAG: [3] (constructed) LEN: 290 bytes
                0x04, (byte) 0x82, 0x01, 0x1e,  // TAG: [UNIVERSAL 4] (primitive) "OCTET STRING" LEN: 286 bytes

                // NTLMSSP_MESSAGE_SIGNATURE, @see http://msdn.microsoft.com/en-us/library/cc422952.aspx

                // Version: 0x00000001
                (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00,

                // Checksum (8 bytes): An 8-byte array that contains the checksum for the message.
                (byte)0x72, (byte)0x76, (byte)0x1e, (byte)0x57, (byte)0x49, (byte)0xb5, (byte)0x0f, (byte)0xad,

                // seqNum of the message: 0
                (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,

                // Encrypted public key
                (byte)0x15, (byte)0xf7, (byte)0xf2, (byte)0x54, (byte)0xda, (byte)0xa9, (byte)0xe5, (byte)0xad, (byte)0x85, (byte)0x04, (byte)0x67, (byte)0x4d, (byte)0x0b, (byte)0xcb, (byte)0xf9, (byte)0xb1,
                (byte)0xf8, (byte)0x02, (byte)0x8a, (byte)0x77, (byte)0xc2, (byte)0x63, (byte)0xab, (byte)0xd5, (byte)0x74, (byte)0x23, (byte)0x9f, (byte)0x9d, (byte)0x5d, (byte)0x1f, (byte)0xd3, (byte)0xb3,
                (byte)0xa0, (byte)0xac, (byte)0x16, (byte)0x8a, (byte)0x4b, (byte)0x08, (byte)0xf5, (byte)0x47, (byte)0x70, (byte)0x58, (byte)0x10, (byte)0xb4, (byte)0xe7, (byte)0x87, (byte)0xb3, (byte)0x4b,
                (byte)0xc9, (byte)0xa2, (byte)0xd5, (byte)0xd1, (byte)0xca, (byte)0x0f, (byte)0xd4, (byte)0xe3, (byte)0x8d, (byte)0x76, (byte)0x5a, (byte)0x60, (byte)0x28, (byte)0xf8, (byte)0x06, (byte)0x5d,
                (byte)0xe4, (byte)0x7e, (byte)0x21, (byte)0xc8, (byte)0xbb, (byte)0xac, (byte)0xe5, (byte)0x79, (byte)0x85, (byte)0x30, (byte)0x9b, (byte)0x88, (byte)0x13, (byte)0x2f, (byte)0x8f, (byte)0xfc,
                (byte)0x04, (byte)0x52, (byte)0xfe, (byte)0x87, (byte)0x94, (byte)0xcf, (byte)0xcb, (byte)0x49, (byte)0x4a, (byte)0xda, (byte)0x6f, (byte)0xdd, (byte)0xee, (byte)0x57, (byte)0xa5, (byte)0xe4,
                (byte)0x4d, (byte)0x0e, (byte)0x5c, (byte)0x3d, (byte)0x0b, (byte)0x63, (byte)0x1f, (byte)0xf6, (byte)0x3d, (byte)0x1b, (byte)0xae, (byte)0x5a, (byte)0xf6, (byte)0x42, (byte)0x2a, (byte)0x46,
                (byte)0xfa, (byte)0x42, (byte)0x71, (byte)0x67, (byte)0x46, (byte)0x02, (byte)0x71, (byte)0xea, (byte)0x51, (byte)0x98, (byte)0xf7, (byte)0xd4, (byte)0x43, (byte)0xbf, (byte)0x8e, (byte)0xe8,
                (byte)0x3c, (byte)0xc8, (byte)0xfa, (byte)0x79, (byte)0x9d, (byte)0x8c, (byte)0xfc, (byte)0xc2, (byte)0x42, (byte)0xc9, (byte)0xbb, (byte)0xd0, (byte)0xab, (byte)0x81, (byte)0xc4, (byte)0x53,
                (byte)0xfd, (byte)0x41, (byte)0xda, (byte)0xab, (byte)0x0f, (byte)0x25, (byte)0x79, (byte)0x5f, (byte)0xbd, (byte)0xa3, (byte)0x8c, (byte)0xd3, (byte)0xf5, (byte)0x1b, (byte)0xab, (byte)0x20,
                (byte)0xd1, (byte)0xf4, (byte)0xd8, (byte)0x81, (byte)0x9c, (byte)0x18, (byte)0x4a, (byte)0xa4, (byte)0x77, (byte)0xee, (byte)0xe1, (byte)0x51, (byte)0xee, (byte)0x2a, (byte)0xc1, (byte)0x94,
                (byte)0x37, (byte)0xc5, (byte)0x06, (byte)0x7a, (byte)0x3f, (byte)0x0f, (byte)0x25, (byte)0x5b, (byte)0x4e, (byte)0x6a, (byte)0xdc, (byte)0x0b, (byte)0x62, (byte)0x6f, (byte)0x12, (byte)0x83,
                (byte)0x03, (byte)0xae, (byte)0x4e, (byte)0xce, (byte)0x2b, (byte)0x6e, (byte)0xd4, (byte)0xd5, (byte)0x23, (byte)0x27, (byte)0xf6, (byte)0xa6, (byte)0x38, (byte)0x67, (byte)0xec, (byte)0x95,
                (byte)0x82, (byte)0xc6, (byte)0xba, (byte)0xd4, (byte)0xf6, (byte)0xe6, (byte)0x22, (byte)0x7d, (byte)0xb9, (byte)0xe4, (byte)0x81, (byte)0x97, (byte)0x24, (byte)0xff, (byte)0x40, (byte)0xb2,
                (byte)0x42, (byte)0x3c, (byte)0x11, (byte)0x24, (byte)0xd0, (byte)0x3a, (byte)0x96, (byte)0xd9, (byte)0xc1, (byte)0x13, (byte)0xd6, (byte)0x62, (byte)0x45, (byte)0x21, (byte)0x60, (byte)0x5b,
                (byte)0x7b, (byte)0x2b, (byte)0x62, (byte)0x44, (byte)0xf7, (byte)0x40, (byte)0x93, (byte)0x29, (byte)0x5b, (byte)0x44, (byte)0xb7, (byte)0xda, (byte)0x9c, (byte)0xa6, (byte)0xa9, (byte)0x3b,
                (byte)0xe1, (byte)0x3b, (byte)0x9d, (byte)0x31, (byte)0xf2, (byte)0x21, (byte)0x53, (byte)0x0f, (byte)0xb3, (byte)0x70, (byte)0x55, (byte)0x84, (byte)0x2c, (byte)0xb4,
        };

        SSLState sslState = new SSLState();

        sslState.serverCertificateSubjectPublicKeyInfo = new byte[] {
                0x30, (byte) 0x82, 0x01, 0x22, // Sequence, length: 290 bytes
                0x30, 0x0d, // Sequence, length: 13 bytes {
                0x06, 0x09, // Object ID, length: 9 bytes
                0x2a, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xf7, 0x0d, 0x01, 0x01, 0x01,
                0x05, 0x00, // NULL, length: 0 bytes

                (byte)0x03, (byte)0x82, (byte)0x01, (byte)0x0f, // Bit string, length: 271 bytes

                (byte)0x00, // Pading
                (byte)0x30, (byte)0x82, (byte)0x01, (byte)0x0a, // Sequence
                (byte)0x02, (byte)0x82, (byte)0x01, (byte)0x01, // Integer, length: 257 bytes

                (byte)0x00, (byte)0xa8, (byte)0x56,
                (byte)0x65, (byte)0xd3, (byte)0xce, (byte)0x8a, (byte)0x54, (byte)0x4d, (byte)0x9d, (byte)0xb0,
                (byte)0x84, (byte)0x31, (byte)0x19, (byte)0x71, (byte)0x7f, (byte)0xdd, (byte)0x42, (byte)0xfb,
                (byte)0x2a, (byte)0x7a, (byte)0x72, (byte)0x13, (byte)0xa1, (byte)0xb9, (byte)0x72, (byte)0xbb,
                (byte)0xd3, (byte)0x08, (byte)0xad, (byte)0x7d, (byte)0x6c, (byte)0x15, (byte)0x65, (byte)0x03,
                (byte)0xd1, (byte)0xc4, (byte)0x54, (byte)0xc5, (byte)0x33, (byte)0x6b, (byte)0x7d, (byte)0x69,
                (byte)0x89, (byte)0x5e, (byte)0xfe, (byte)0xe0, (byte)0x01, (byte)0xc0, (byte)0x7e, (byte)0x9b,
                (byte)0xcb, (byte)0x5d, (byte)0x65, (byte)0x36, (byte)0xcd, (byte)0x77, (byte)0x5d, (byte)0xf3,
                (byte)0x7a, (byte)0x5b, (byte)0x29, (byte)0x44, (byte)0x72, (byte)0xd5, (byte)0x38, (byte)0xe2,
                (byte)0xcf, (byte)0xb1, (byte)0xc7, (byte)0x78, (byte)0x9b, (byte)0x58, (byte)0xb9, (byte)0x17,
                (byte)0x7c, (byte)0xb7, (byte)0xd6, (byte)0xc7, (byte)0xc7, (byte)0xbf, (byte)0x90, (byte)0x4e,
                (byte)0x7c, (byte)0x39, (byte)0x93, (byte)0xcb, (byte)0x2e, (byte)0xe0, (byte)0xc2, (byte)0x33,
                (byte)0x2d, (byte)0xa5, (byte)0x7e, (byte)0xe0, (byte)0x7b, (byte)0xb6, (byte)0xf9, (byte)0x91,
                (byte)0x32, (byte)0xb7, (byte)0xd4, (byte)0x85, (byte)0xb7, (byte)0x35, (byte)0x2d, (byte)0x2b,
                (byte)0x00, (byte)0x6d, (byte)0xf8, (byte)0xea, (byte)0x8c, (byte)0x97, (byte)0x5f, (byte)0x51,
                (byte)0x1d, (byte)0x68, (byte)0x04, (byte)0x3c, (byte)0x79, (byte)0x14, (byte)0x71, (byte)0xa7,
                (byte)0xc7, (byte)0xd7, (byte)0x70, (byte)0x7a, (byte)0xe0, (byte)0xba, (byte)0x12, (byte)0x69,
                (byte)0xc8, (byte)0xd3, (byte)0xd9, (byte)0x4e, (byte)0xab, (byte)0x51, (byte)0x47, (byte)0xa3,
                (byte)0xec, (byte)0x99, (byte)0xd4, (byte)0x88, (byte)0xca, (byte)0xda, (byte)0xc2, (byte)0x7f,
                (byte)0x79, (byte)0x4b, (byte)0x66, (byte)0xed, (byte)0x87, (byte)0xbe, (byte)0xc2, (byte)0x5f,
                (byte)0xea, (byte)0xcf, (byte)0xe1, (byte)0xb5, (byte)0xf0, (byte)0x3d, (byte)0x9b, (byte)0xf2,
                (byte)0x19, (byte)0xc3, (byte)0xe0, (byte)0xe1, (byte)0x7a, (byte)0x45, (byte)0x71, (byte)0x12,
                (byte)0x3d, (byte)0x72, (byte)0x1d, (byte)0x6f, (byte)0x2b, (byte)0x1c, (byte)0x46, (byte)0x68,
                (byte)0xc0, (byte)0x8f, (byte)0x4f, (byte)0xce, (byte)0x3a, (byte)0xc5, (byte)0xcd, (byte)0x22,
                (byte)0x65, (byte)0x2d, (byte)0x43, (byte)0xb0, (byte)0x5c, (byte)0xdd, (byte)0x89, (byte)0xae,
                (byte)0xbe, (byte)0x70, (byte)0x59, (byte)0x5e, (byte)0x0c, (byte)0xbd, (byte)0xf5, (byte)0x46,
                (byte)0x82, (byte)0x1e, (byte)0xe4, (byte)0x86, (byte)0x95, (byte)0x7b, (byte)0x60, (byte)0xae,
                (byte)0x45, (byte)0x50, (byte)0xc2, (byte)0x54, (byte)0x08, (byte)0x49, (byte)0x9a, (byte)0x9e,
                (byte)0xfb, (byte)0xb2, (byte)0xb6, (byte)0x78, (byte)0xe5, (byte)0x2f, (byte)0x9c, (byte)0x5a,
                (byte)0xd0, (byte)0x8a, (byte)0x03, (byte)0x77, (byte)0x68, (byte)0x30, (byte)0x93, (byte)0x78,
                (byte)0x6d, (byte)0x90, (byte)0x6d, (byte)0x50, (byte)0xfa, (byte)0xa7, (byte)0x65, (byte)0xfe,
                (byte)0x59, (byte)0x33, (byte)0x27, (byte)0x4e, (byte)0x4b, (byte)0xf8, (byte)0x38, (byte)0x44,
                (byte)0x3a, (byte)0x12, (byte)0xf4, (byte)0x07, (byte)0xa0, (byte)0x8d, (byte)0x02, (byte)0x03,
                (byte)0x01, (byte)0x00, (byte)0x01,
        };
        /* @formatter:on */

        NtlmState ntlmState = new NtlmState();
        MockSource source = new MockSource("source", ByteBuffer.convertByteArraysToByteBuffers(serverChallengePacket, new byte[] {1, 2, 3}));
        Element ntlmssp_negotiate = new ClientNtlmsspNegotiate("ntlmssp_negotiate", ntlmState);
        Element ntlmssp_challenge = new ServerNtlmsspChallenge("ntlmssp_challenge", ntlmState);
        Element ntlmssp_auth = new ClientNtlmsspPubKeyAuth("ntlmssp_auth", ntlmState, sslState, "192.168.0.101", "workgroup", "apollo3", "Administrator",
                "R2Preview!");
        Element sink = new MockSink("sink", ByteBuffer.convertByteArraysToByteBuffers(clientNegotiatePacket, clientAuthPacket), (Dumper)ntlmssp_auth);
        Element mainSink = new MockSink("mainSink", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3}));

        Pipeline pipeline = new PipelineImpl("test");
        pipeline.add(source, ntlmssp_negotiate, ntlmssp_challenge, ntlmssp_auth, sink, mainSink);
        pipeline.link("source", "ntlmssp_negotiate", "ntlmssp_challenge", "ntlmssp_auth", "mainSink");
        pipeline.link("ntlmssp_negotiate >" + OTOUT, "ntlmssp_negotiate< sink");
        pipeline.link("ntlmssp_challenge >" + OTOUT, "ntlmssp_challenge< sink");
        pipeline.link("ntlmssp_auth >" + OTOUT, "ntlmssp_auth< sink");
        pipeline.runMainLoop("source", STDOUT, false, false);

    }

    @Override
    public void dump(ByteBuffer buf) {
        buf.rewindCursor();
        TSRequest request = new TSRequest("TSRequest");
        request.readTag(buf);
        System.out.println("TSRequest version: " + request.version.value);
        System.out.println("TSRequest pubKey: " + request.pubKeyAuth.value.toPlainHexString());

        ByteBuffer negoToken = ((NegoItem)request.negoTokens.tags[0]).negoToken.value;
        System.out.println("TSRequest negotoken: " + negoToken.toPlainHexString());
        dumpNegoToken(negoToken);

        negoToken.unref();
    }

    private void dumpNegoToken(ByteBuffer buf) {
        String signature = buf.readVariableString(RdpConstants.CHARSET_8);
        if (!ConstantTimeComparator.compareStrings(signature, NTLMSSP))
            throw new RuntimeException("Unexpected NTLM message singature: \"" + signature + "\". Expected signature: \"" + NTLMSSP + "\". Data: " + buf + ".");

        // MessageType (CHALLENGE)
        int messageType = buf.readSignedIntLE();
        if (messageType != NtlmConstants.NTLMSSP_AUTH)
            throw new RuntimeException("Unexpected NTLM message type: " + messageType + ". Expected type: CHALLENGE (" + NtlmConstants.CHALLENGE + "). Data: " + buf
                    + ".");

        System.out.println("lmChallengeResponseFields: " + ServerNtlmsspChallenge.readBlockByDescription(buf).toPlainHexString());
        ByteBuffer ntChallengeResponseBuf = ServerNtlmsspChallenge.readBlockByDescription(buf);
        System.out.println("NtChallengeResponse: " + ntChallengeResponseBuf.toPlainHexString());
        System.out.println("DomainName: " + ServerNtlmsspChallenge.readStringByDescription(buf));
        System.out.println("UserName: " + ServerNtlmsspChallenge.readStringByDescription(buf));
        System.out.println("Workstation: " + ServerNtlmsspChallenge.readStringByDescription(buf));
        System.out.println("EncryptedRandomSessionKey: " + ServerNtlmsspChallenge.readBlockByDescription(buf).toPlainHexString());
        System.out.println("NegotiateFlags: " + new NegoFlags(buf.readSignedIntLE()));
        System.out.println("Version: " + buf.readBytes(8).toPlainHexString());

        dumpNtChallengeResponse(ntChallengeResponseBuf);
    }

    private void dumpNtChallengeResponse(ByteBuffer buf) {
        System.out.println("HMAC: " + buf.readBytes(16).toPlainHexString());
        System.out.format("Header: 0x%08x\n", buf.readUnsignedIntLE());
        System.out.format("Reserved: 0x%08x\n", buf.readUnsignedIntLE());
        System.out.println("Time: " + buf.readBytes(8).toPlainHexString());
        System.out.println("Client challenge: " + buf.readBytes(8).toPlainHexString());
        System.out.format("Reserved: 0x%08x\n", buf.readUnsignedIntLE());

        // Parse attribute list

        while (buf.remainderLength() > 0) {
            int type = buf.readUnsignedShortLE();
            int length = buf.readUnsignedShortLE();

            if (type == MSV_AV_EOL)
                // End of list
                break;

            ByteBuffer data = buf.readBytes(length);
            switch (type) {
            case MSV_AV_NETBIOS_DOMAIN_NAME:
                System.out.println("AV Netbios Domain name: " + data.readString(length, RdpConstants.CHARSET_16));
                break;
            case MSV_AV_NETBIOS_COMPUTER_NAME:
                System.out.println("AV Netbios Computer name: " + data.readString(length, RdpConstants.CHARSET_16));
                break;
            case MSV_AV_DNS_DOMAIN_NAME:
                System.out.println("AV DNS Domain name: " + data.readString(length, RdpConstants.CHARSET_16));
                break;
            case MSV_AV_DNS_COMPUTER_NAME:
                System.out.println("AV DNS Computer name: " + data.readString(length, RdpConstants.CHARSET_16));
                break;
            case MSV_AV_CHANNEL_BINDINGS:
                System.out.println("AV Channel Bindings: " + data.readBytes(length).toPlainHexString());
                break;
            case MSV_AV_TIMESTAMP:
                System.out.println("AV Timestamp: " + data.readBytes(length).toPlainHexString());
                break;
            case MSV_AV_FLAGS:
                System.out.println("AV Flags: " + data.readBytes(length).toPlainHexString());
                break;
            case MSV_AV_TARGET_NAME:
                System.out.println("AV Target Name: " + data.readString(length, RdpConstants.CHARSET_16));
                break;
            default:
                System.out.println("Unknown NTLM target info attribute: " + type + ". Data: " + data + ".");
            }
            data.unref();
        }

    }
}
