{ +-------------------------------------------------------------+ }
{ |                                                             | }
{ |   GM-Software                                               | }
{ |   ===========                                               | }
{ |                                                             | }
{ |   Project: All Projects                                     | }
{ |                                                             | }
{ |   Description: NTLM authentication messages.                | }
{ |                                                             | }
{ |                                                             | }
{ |   Copyright (C) - 2018 - Gerrit Moeller.                    | }
{ |                                                             | }
{ |   Source code dstributed under MIT license.                 | }
{ |                                                             | }
{ |   See: https://www.gm-software.de                           | }
{ |                                                             | }
{ +-------------------------------------------------------------+ }


{$INCLUDE GMCompilerSettings.inc}

unit GMNtlm;

interface

uses GMStrDef;

const

//
// ToDo:
// =====
//  - Bequeme Routine zum Anfügen von Daten eines TNTLMDynDataDesc
//  - CalcTimeStamp nochmal prüfen
//

  //
  // see: https://curl.haxx.se/rfc/ntlm.html
  //

  NTLM_Unicode_Charset = $00000001;           // Indicates that Unicode strings are supported for use in security buffer data.
  NTLM_OEM_Charset = $00000002;               // Indicates that OEM strings are supported for use in security buffer data.
  NTLM_Request_Target = $00000004;            // Requests that the server's authentication realm be included in the Type 2 message.
//$00000008	unknown	This flag's usage has not been identified.
  NTLM_Sign = $00000010;                      // Specifies that authenticated communication between the client and server should carry a digital signature (message integrity).
  NTLM_Seal = $00000020;                      // Specifies that authenticated communication between the client and server should be encrypted (message confidentiality).
  NTLM_Datagram_Style = $00000040;            // Indicates that datagram authentication is being used.
  NTLM_Lan_Manager_Key = $00000080;           // Indicates that the Lan Manager Session Key should be used for signing and sealing authenticated communications.

  NTLM_Netware = $00000100;                   // This flag's usage has not been identified.
  NTLM_NTLM = $00000200;                      // Indicates that NTLM authentication is being used.
//$00000400	unknown	This flag's usage has not been identified.
  NTLM_Anonymous = $00000800;                 // Sent by the client in the Type 3 message to indicate that an anonymous context has been established. This also affects the response fields (as detailed in the "Anonymous Response" section).

  NTLM_Domain_Supplied = $00001000;           // Sent by the client in the Type 1 message to indicate that the name of the domain in which the client workstation has membership is included in the message. This is used by the server to determine whether the client is eligible for local authentication.
  NTLM_Host_Supplied = $00002000;             // Sent by the client in the Type 1 message to indicate that the client workstation's name is included in the message. This is used by the server to determine whether the client is eligible for local authentication.
  NTLM_Local_Call = $00004000;                // Sent by the server to indicate that the server and client are on the same machine. Implies that the client may use the established local credentials for authentication instead of calculating a response to the challenge.
  NTLM_Always_Sign = $00008000;               // Indicates that authenticated communication between the client and server should be signed with a "dummy" signature.

  NTLM_Target_Type_Domain = $00010000;        // Sent by the server in the Type 2 message to indicate that the target authentication realm is a domain.
  NTLM_Target_Type_Server = $00020000;        // Sent by the server in the Type 2 message to indicate that the target authentication realm is a server.
  NTLM_Target_Type_Share = $00040000;         // Sent by the server in the Type 2 message to indicate that the target authentication realm is a share. Presumably, this is for share-level authentication. Usage is unclear.
  NTLM_NTLM2_Key = $00080000;                 // Indicates that the NTLM2 signing and sealing scheme should be used for protecting authenticated communications. Note that this refers to a particular session security scheme, and is not related to the use of NTLMv2 authentication. This flag can, however, have an effect on the response calculations (as detailed in the "NTLM2 Session Response" section).

  NTLM_Request_Init_Response = $00100000;     // This flag's usage has not been identified.
  NTLM_Request_Accept_Response = $00200000;   // This flag's usage has not been identified.
  NTLM_Request_Non_NT_Session_Key = $00400000;// This flag's usage has not been identified.
  NTLM_Target_Info = $00800000;               // Sent by the server in the Type 2 message to indicate that it is including a Target Information block in the message. The Target Information block is used in the calculation of the NTLMv2 response.

  NTLM_Encrypt_128Bit = $20000000;            // Indicates that 128-bit encryption is supported.
  NTLM_Key_Exchange = $40000000;              // Indicates that the client will provide an encrypted master key in the "Session Key" field of the Type 3 message.
  NTLM_Encrypt_56Bit = $80000000;             // Indicates that 56-bit encryption is supported.


type

  //
  // NTLM types
  //

  PNTLMDynDataDesc = ^TNTLMDynDataDesc;
  TNTLMDynDataDesc = packed record
   LenInBytes: Word;
   AllocSizeInBytes: Word;
   Offset: LongInt;
  end;


  PNTLMClientStartMsg = ^TNTLMClientStartMsg;
  TNTLMClientStartMsg = packed record
   Protocol: array [0..7] of AnsiChar;
   MSgKind: LongInt;
   Flags: LongWord;
   Domain: TNTLMDynDataDesc;
   Host: TNTLMDynDataDesc;
  end;


  PNTLMServerChallengeMsg = ^TNTLMServerChallengeMsg;
  TNTLMServerChallengeMsg = packed record
   Protocol: array [0..7] of AnsiChar;
   MSgKind: LongInt;
   Domain: TNTLMDynDataDesc;
   Flags: LongWord;
   Nonce: array [0..7] of AnsiChar;
   context: array [0..1] of LongWord;
   TargetInfo: TNTLMDynDataDesc;
  end;


  PNTLNClientCredentialMsg = ^TNTLNClientCredentialMsg;
  TNTLNClientCredentialMsg = packed record
   Protocol: array [0..7] of AnsiChar;
   MSgKind: LongInt;

   LMPwdHash: TNTLMDynDataDesc;
   NTPwdHash: TNTLMDynDataDesc;
   Domain: TNTLMDynDataDesc;
   User: TNTLMDynDataDesc;
   Host: TNTLMDynDataDesc;
   SessionKey: TNTLMDynDataDesc;

   Flags: LongWord;
  end;


  //
  // Convenience types
  //

  PNTLMServerResponse = ^TNTLMServerResponse;
  TNTLMServerResponse = record
   Protocol: AnsiString;
   MSgKind: LongInt;
   Flags: LongWord;
   Nonce: AnsiString;
   Domain: TGMString;
   TargetInfoBlockData: AnsiString;
  end;


  TNTLMClientData = record
   Protocol: AnsiString;
   MSgKind: LongInt;
   Flags: LongWord;
   Domain: TGMString;
   Host: TGMString;
  end;


  TNTLMTargetInfoData = record
   ServerName: TGMString;
   DomainName: TGMString;
   DNSHost: TGMString;
   DNSDomain: TGMString;
  end;



const

  cStrNTLMProtocolSignature: AnsiString = 'NTLMSSP'#0;


function BuildNTLMClientStartMsg: TGMString;
function DecodeNTLMClientStartMsg(const ABase64DataStr: TGMString): TNTLMClientData;

function DecodeNTLMServerChallengeMsg(const ABase64DataStr: TGMString): TNTLMServerResponse;

function BuildNTLMClientCredentialsMsg(const AUserName, APassword: TGMString; AServerResponse: PNTLMServerResponse): TGMString;

procedure NTLMClearServerResponse(var AServerResponse: TNTLMServerResponse);

function NTLMParseTargetInfoData(const ATargetInfoData: AnsiString): TNTLMTargetInfoData;


implementation

uses SysUtils, GMCommon, GMINetBase, GMCharCoding
     {$IFDEF TLS_SUPPORT},jwaWinCrypt, GMWinCrypt, GMOpenSSLApi{$ENDIF}
     ;


const

  cResponseHashSize = 21;


procedure ConvertDynBufferDescToLittleEndian(var ADynBufferDesc: TNTLMDynDataDesc);
begin
  ADynBufferDesc.LenInBytes := UInt16ToLittleEndian(ADynBufferDesc.LenInBytes);
  ADynBufferDesc.AllocSizeInBytes := UInt16ToLittleEndian(ADynBufferDesc.AllocSizeInBytes);
  ADynBufferDesc.Offset := Int32ToLittleEndian(ADynBufferDesc.Offset);
end;

procedure ConvertDynBufferDescFromLittleEndian(var ADynBufferDesc: TNTLMDynDataDesc);
begin
  ADynBufferDesc.LenInBytes := UInt16FromLittleEndian(ADynBufferDesc.LenInBytes);
  ADynBufferDesc.AllocSizeInBytes := UInt16FromLittleEndian(ADynBufferDesc.AllocSizeInBytes);
  ADynBufferDesc.Offset := Int32FromLittleEndian(ADynBufferDesc.Offset);
end;


procedure NTLMClearServerResponse(var AServerResponse: TNTLMServerResponse);
begin
  AServerResponse.Protocol := '';
  AServerResponse.MSgKind := 0;
  AServerResponse.Flags := 0;
  AServerResponse.Nonce := '';
  AServerResponse.Domain := '';
  AServerResponse.TargetInfoBlockData := '';
end;

procedure ClearTargetInfoData(var ATargetInfo: TNTLMTargetInfoData);
begin
  ATargetInfo.ServerName := '';
  ATargetInfo.DomainName := '';
  ATargetInfo.DNSHost := '';
  ATargetInfo.DNSDomain := '';
end;


function GetNTLMDynDataAsString(var ADataDesc: TNTLMDynDataDesc; const AData: AnsiString; const AFlags: LongWord): TGMString;
var targetNameA: AnsiString; targetNameW: UnicodeString; len: Integer;
begin
  Result := '';
//if ADataDesc = nil then Exit;

  if (ADataDesc.LenInBytes > 0) and (ADataDesc.Offset < Length(AData)) then
   begin
    len := Min(ADataDesc.LenInBytes, Length(AData) - ADataDesc.Offset);
    if len > 0 then
     begin
      if AFlags and NTLM_OEM_Charset <> 0 then
       begin
        SetLength(targetNameA, len);
        Move(GMAddPtr(PAnsiChar(AData), ADataDesc.Offset)^, PAnsiChar(targetNameA)^, len);
        Result := targetNameA;
       end else
      if AFlags and NTLM_Unicode_Charset <> 0 then
       begin
        SetLength(targetNameW, len div SizeOf(WideChar));
        Move(GMAddPtr(PAnsiChar(AData), ADataDesc.Offset)^, PWideChar(targetNameW)^, len);
        Result := targetNameW;
       end;
     end;
   end;
end;


function BuildNTLMClientStartMsg: TGMString;
var bufStr: AnsiString; hostName, domainName: AnsiString; userW, domainW: TGMString;
begin
  hostName := GMUpperCase(GMThisComputerName);
  GMGetUserAndDomainNames(userW, domainW);
  domainName := GMUpperCase(domainW);

  SetLength(bufStr, SizeOf(TNTLMClientStartMsg) + Length(hostName) + Length(domainName));
  FillChar(PAnsiChar(bufStr)^, Length(bufStr), 0);

  with PNTLMClientStartMsg(PAnsiChar(bufStr))^ do
   begin
    Move(cStrNTLMProtocolSignature[1], Protocol, Length(cStrNTLMProtocolSignature));
    MSgKind := Int32ToLittleEndian(1);
                                                       // NTLM_Request_Target
    Flags := UInt32ToLittleEndian(NTLM_Unicode_Charset or NTLM_OEM_Charset or NTLM_NTLM or NTLM_Domain_Supplied or NTLM_Host_Supplied or NTLM_Always_Sign);

    // hostName name must be provided as AnsiString!
    Host.LenInBytes := Length(hostName);
    Host.AllocSizeInBytes := Host.LenInBytes;
    Host.Offset := SizeOf(TNTLMClientStartMsg);
    Move(PAnsiChar(hostName)^, (PAnsiChar(bufStr) + Host.Offset)^, Host.LenInBytes);

    // domainName name must be provided as AnsiString!
    Domain.LenInBytes := Length(domainName);
    Domain.AllocSizeInBytes := Domain.LenInBytes;
    Domain.Offset := Host.Offset + Host.LenInBytes;
    Move(PAnsiChar(domainName)^, (PAnsiChar(bufStr) + Domain.Offset)^, Domain.LenInBytes);

    // Convert buffer members to little endian memory layout
    ConvertDynBufferDescToLittleEndian(Host);
    ConvertDynBufferDescToLittleEndian(Domain);
   end;

  Result := GMEncodeBase64Str(bufStr);
end;

function DecodeNTLMClientStartMsg(const ABase64DataStr: TGMString): TNTLMClientData;
var decodedData: AnsiString; clientMsg: TNTLMClientStartMsg;
begin
  //FillChar(clientMsg, SizeOf(clientMsg), 0);
  clientMsg := Default(TNTLMClientStartMsg);

  decodedData := GMDecodeBase64Str(ABase64DataStr);
  Move(PAnsiChar(decodedData)^, clientMsg, Max(0, Min(SizeOf(clientMsg), Length(decodedData))));
//clientMsg := PNTLMClientStartMsg(PAnsichar(decodedData));

  clientMsg.MSgKind := Int32FromLittleEndian(clientMsg.MSgKind);
  clientMsg.Flags := UInt32FromLittleEndian(clientMsg.Flags);

  ConvertDynBufferDescFromLittleEndian(clientMsg.Domain);
  ConvertDynBufferDescFromLittleEndian(clientMsg.Host);

  Result.Protocol := clientMsg.Protocol;
  Result.MSgKind := clientMsg.MSgKind;
  Result.Flags := clientMsg.Flags;
  Result.Host := GetNTLMDynDataAsString(clientMsg.Host, decodedData, NTLM_OEM_Charset);
  Result.Domain := GetNTLMDynDataAsString(clientMsg.Domain, decodedData, NTLM_OEM_Charset);
end;


type

  TTargetInfoPart = record
    Kind: Word;
    Value: UnicodeString;
  end;


function ReadNextTargetInfoPart(var ADataPtr: PByte; var ARestLen: LongInt): TTargetInfoPart;
var blockLen: Word;
begin
  Result.Kind := 0;
  Result.Value := '';

  if ARestLen < (SizeOf(Result.Kind) + SizeOf(blockLen)) then begin ARestLen := 0; Exit; end;

  Result.Kind := UInt16ToLittleEndian(PWord(ADataPtr)^);
  Inc(ADataPtr, SizeOf(Result.Kind));
  Dec(ARestLen, SizeOf(Result.Kind));

  blockLen := UInt16ToLittleEndian(PWord(ADataPtr)^);
  Inc(ADataPtr, SizeOf(blockLen));
  Dec(ARestLen, SizeOf(blockLen));

  blockLen := Min(blockLen, ARestLen);

  SetLength(Result.Value, blockLen div SizeOf(WideChar));
  Move(ADataPtr^, PWideChar(Result.Value)^, Length(Result.Value) * SizeOf(WideChar));
  Inc(ADataPtr, blockLen);
  Dec(ARestLen, blockLen);
end;


function NTLMParseTargetInfoData(const ATargetInfoData: AnsiString): TNTLMTargetInfoData;
var blockPtr: PByte; blockLen: LongInt; infoPart: TTargetInfoPart;
begin
  ClearTargetInfoData(Result);

  blockPtr := PByte(PAnsiChar(ATargetInfoData));
  blockLen := Length(ATargetInfoData);

  while blockLen >= 2 * SizeOf(Word) do
   begin
    infoPart := ReadNextTargetInfoPart(blockPtr, blockLen);
    case infoPart.Kind of
     1: Result.ServerName := infoPart.Value;
     2: Result.DomainName := infoPart.Value;
     3: Result.DNSHost := infoPart.Value;
     4: Result.DNSDomain := infoPart.Value;
    end;
   end;
end;


function DecodeNTLMServerChallengeMsg(const ABase64DataStr: TGMString): TNTLMServerResponse;
var decodedData: AnsiString; blockPtr: PByte; blockLen: LongInt; serverMsg: TNTLMServerChallengeMsg;
//  targetInfo: TNTLMTargetInfoData;
begin
  NTLMClearServerResponse(Result);
  //FillChar(serverMsg, SizeOf(serverMsg), 0);
  serverMsg := Default(TNTLMServerChallengeMsg);

  decodedData := GMDecodeBase64Str(ABase64DataStr);
  Move(PAnsiChar(decodedData)^, serverMsg, Max(0, Min(SizeOf(serverMsg), Length(decodedData))));
//serverMsg := PNTLMServerChallengeMsg(PAnsiChar(decodedData));

  serverMsg.MSgKind := Int32FromLittleEndian(serverMsg.MSgKind);

  ConvertDynBufferDescFromLittleEndian(serverMsg.Domain);
  ConvertDynBufferDescFromLittleEndian(serverMsg.TargetInfo);

  serverMsg.Flags := UInt32FromLittleEndian(serverMsg.Flags);

  Result.Protocol := serverMsg.Protocol;
  Result.MSgKind := serverMsg.MSgKind;
  Result.Flags := serverMsg.Flags;
  Result.Nonce := serverMsg.Nonce;
  Result.Domain := GetNTLMDynDataAsString(serverMsg.Domain, decodedData, serverMsg.Flags);

  blockPtr := GMAddPtr(PAnsiChar(decodedData), serverMsg.TargetInfo.Offset);
  blockLen := Max(0, Min(serverMsg.TargetInfo.LenInBytes, Length(decodedData) - serverMsg.TargetInfo.Offset));

  SetLength(Result.TargetInfoBlockData, blockLen);
  if blockLen > 0 then Move(blockPtr^, PAnsiChar(Result.TargetInfoBlockData)^, blockLen);

//targetInfo := NTLMParseTargetInfoData(Result.TargetInfoBlockData);
//blockLen := 0;
end;


procedure GetUserAndDomainNameFromInput(const AUserAndDomainCombined: TGMString; var AUserName, ADomainName: TGMString);
var pCh: PGMChar;
begin
  pCh := GMStrLScan(PGMChar(AUserAndDomainCombined), '\', Length(AUserAndDomainCombined));
  if pCh <> nil then
   begin
    ADomainName := Copy(AUserAndDomainCombined, 1, pCh - PGMChar(AUserAndDomainCombined));
    AUserName := Copy(AUserAndDomainCombined, pCh - PGMChar(AUserAndDomainCombined) + 2, Length(AUserAndDomainCombined) - (pCh - PGMChar(AUserAndDomainCombined)) - 1);
   end
  else
   begin
    pCh := GMStrLScan(PGMChar(AUserAndDomainCombined), '@', Length(AUserAndDomainCombined));
    if pCh <> nil then
     begin
      AUserName := Copy(AUserAndDomainCombined, 1, pCh - PGMChar(AUserAndDomainCombined));
      ADomainName := Copy(AUserAndDomainCombined, pCh - PGMChar(AUserAndDomainCombined) + 2, Length(AUserAndDomainCombined) - (pCh - PGMChar(AUserAndDomainCombined)) - 1);
     end else
      begin ADomainName := ''; AUserName := AUserAndDomainCombined; end
   end;
end;


procedure AssignDESKey(AKey_56: PDES_cblock; AKs: PDES_key_schedule);
var key: des_cblock;
begin
  key[0] := AKey_56[0];
  key[1] := ((AKey_56[0] shl 7) and $FF) or (AKey_56[1] shr 1);
  key[2] := ((AKey_56[1] shl 6) and $FF) or (AKey_56[2] shr 2);
  key[3] := ((AKey_56[2] shl 5) and $FF) or (AKey_56[3] shr 3);
  key[4] := ((AKey_56[3] shl 4) and $FF) or (AKey_56[4] shr 4);
  key[5] := ((AKey_56[4] shl 3) and $FF) or (AKey_56[5] shr 5);
  key[6] := ((AKey_56[5] shl 2) and $FF) or (AKey_56[6] shr 6);
  key[7] :=  (AKey_56[6] shl 1) and $FF;

  DES_set_odd_parity(@key); // <- OpenSSL routine
  DES_set_key(@key, AKs);    // <- OpenSSL routine
end;


function CalcDESHash(key: Pointer; data: PDES_cblock): AnsiString;
var ks: des_key_schedule;
begin
  //FillChar(ks, SizeOf(ks), 0);
  ks := Default(des_key_schedule);
  SetLength(Result, 24);

  AssignDESKey(PDES_cblock(key), @ks);
  DES_ecb_encrypt(data, PDES_cblock(PAnsiChar(Result)), @ks, DES_ENCRYPT);

  AssignDESKey(PDES_cblock(GMAddPtr(key, 7)), @ks);
  DES_ecb_encrypt(data, PDES_cblock(PAnsiChar(Result) + 8), @ks, DES_ENCRYPT);

  AssignDESKey(PDES_cblock(GMAddPtr(key, 14)), @ks);
  DES_ecb_encrypt(data, PDES_cblock(PAnsiChar(Result) + 16), @ks, DES_ENCRYPT);
end;


function BuildLMData(const APassword, ANonce: AnsiString): AnsiString;
const cFixedPwdLen = 14; cMagic: des_cblock = ($4B, $47, $53, $21, $40, $23, $24, $25);
var pw, pwHash: AnsiString; ks: des_key_schedule; padLen: Integer;
begin
  //FillChar(ks, SizeOf(ks), 0);
  ks := Default(des_key_schedule);

  pw := GMUpperCaseA(APassword);
  padLen := cFixedPwdLen - Length(pw);
  SetLength(pw, cFixedPwdLen);
  if padLen > 0 then FillChar(pw[Length(pw) - padLen + 1], padLen, 0);

  SetLength(pwHash, cResponseHashSize);
  FillChar(ks, SizeOf(ks), 0);

  AssignDESKey(PDES_cblock(PAnsiChar(pw)), @ks);
  DES_ecb_encrypt(@cMagic, PDES_cblock(@pwHash[1]), @ks, DES_ENCRYPT);

  AssignDESKey(PDES_cblock(PAnsiChar(pw) + 7), @ks);
  DES_ecb_encrypt(@cMagic, PDES_cblock(@pwHash[9]), @ks, DES_ENCRYPT);

  FillChar(pwHash[17], 5, 0);

  Result := CalcDESHash(PAnsiChar(pwHash), PDES_cblock(PAnsiChar(ANonce)));
end;


function BuildNTData(const APassword: UnicodeString; const ANonce: AnsiString): AnsiString;
var padLen: Integer;
begin
  Result := GMCalcHashValue(PWideChar(APassword), Length(APassword) * SizeOf(WideChar), CALG_MD4);
  padLen := cResponseHashSize - Length(Result);
  SetLength(Result, cResponseHashSize);
  if padLen > 0 then FillChar(Result[Length(Result) - padLen + 1], padLen, 0);
  Result := CalcDESHash(PAnsiChar(Result), PDES_cblock(PAnsiChar(ANonce)));
end;


function WideStrContensAsAnsiStr(const AValue: UnicodeString): AnsiString;
begin
  SetLength(Result, Length(AValue) * SizeOf(WideChar));
  Move(PWideChar(AValue)^, PAnsiChar(Result)^, Length(Result));
end;


function CalcNTLMv2Hash(const AUserName, APassword, ADomain: UnicodeString): AnsiString;
var md4PwdHash: AnsiString; // identity: UnicodeString;
begin
  md4PwdHash := GMCalcHashValue(PWideChar(APassword), Length(APassword) * SizeOf(WideChar), CALG_MD4);
//identity := GMUpperCaseW(AUserName) + ADomain;
  Result := GMHmacMd5(WideStrContensAsAnsiStr(GMUpperCaseW(AUserName) + ADomain), md4PwdHash);
end;


function BuildLMv2Data(const AUserName, APassword, ADomain: UnicodeString; const AServerNonce, AClientNonce: AnsiString): AnsiString;
var ntlmV2Hash, hmacMD5: AnsiString;
begin
  ntlmV2Hash := CalcNTLMv2Hash(AUserName, APassword, ADomain);
  hmacMD5 := GMHmacMd5(AServerNonce + AClientNonce, ntlmV2Hash);
  Result := hmacMD5 + AClientNonce;
end;


function BuildNtlmV2Blob(const ATargetInfoBlock, AClientNonce: AnsiString): AnsiString;
var dataPtr: PByte;
  function CalcTimeStamp(const ADateTime: TDatetime): Int64;
  const cUnixStartDate: TDateTime = 25569.0;
  begin
    Result := Round((((ADateTime - cUnixStartDate) * 86400) + 11644473600) * 10000000);
  end;
begin
  SetLength(Result, 4 * SizeOf(LongInt) + SizeOf(Int64) + Length(AClientNonce) + Length(ATargetInfoBlock));
  FillChar(PAnsiChar(Result)^, Length(Result), 0);

  dataPtr := PByte(PAnsiChar(Result));

  PLongInt(dataPtr)^ := Int32ToLittleEndian($00000101);
  Inc(dataPtr, SizeOf(LongInt));

  PLongInt(dataPtr)^ := Int32ToLittleEndian(0);
  Inc(dataPtr, SizeOf(LongInt));

  PInt64(dataPtr)^ := Int64ToLittleEndian(CalcTimeStamp(Now));
  Inc(dataPtr, SizeOf(Int64));

  Move(PAnsiChar(AClientNonce)^, dataPtr^, Length(AClientNonce));
  Inc(dataPtr, Length(AClientNonce));

  PLongInt(dataPtr)^ := Int32ToLittleEndian(0);
  Inc(dataPtr, SizeOf(LongInt));

  Move(PAnsiChar(ATargetInfoBlock)^, dataPtr^, Length(ATargetInfoBlock));
  Inc(dataPtr, Length(ATargetInfoBlock));

  PLongInt(dataPtr)^ := Int32ToLittleEndian(0);
//Inc(dataPtr, SizeOf(LongInt));
end;


function BuildNTv2Data(const AUserName, APassword, ADomain: UnicodeString; const AServerNonce, AClientNonce, ATargetInfoBlock: AnsiString): AnsiString;
var ntlmV2Hash, blobData, hmacMD5: AnsiString;
begin
  ntlmV2Hash := CalcNTLMv2Hash(AUserName, APassword, ADomain);
  blobData := BuildNtlmV2Blob(ATargetInfoBlock, AClientNonce);
  hmacMD5 := GMHmacMd5(AServerNonce + blobData, ntlmV2Hash);
  Result := hmacMD5 + blobData;
end;


function CreateRandomClientNonce: AnsiString;
var i: Integer;
begin
  SetLength(Result, 8);
  for i:=1 to Length(Result) do Result[i] := Chr(Random(256));
end;


function BuildNTLMClientCredentialsMsg(const AUserName, APassword: TGMString; AServerResponse: PNTLMServerResponse): TGMString;
var bufStr, lmData, ntData, clientNonce: AnsiString; hostName, domainName, userName: UnicodeString;
//  targetInfo: TNTLMTargetInfoData;
begin
  Result := '';
  if AServerResponse = nil then Exit;

//targetInfo := NTLMParseTargetInfoData(AServerResponse.TargetInfoBlockData);

  hostName := GMUpperCase(GMThisComputerName);
  GetUserAndDomainNameFromInput(AUserName, userName, domainName);
  domainName := GMUpperCaseW(domainName);

  lmData := '';
  ntData := '';

  clientNonce := CreateRandomClientNonce;

  if AServerResponse.Flags and NTLM_NTLM <> 0 then
   begin
    if Length(AServerResponse.TargetInfoBlockData) >= 2 * SizeOf(Word) then
      ntData := BuildNTv2Data(userName, APassword, domainName, AServerResponse.Nonce, clientNonce, AServerResponse.TargetInfoBlockData)
    else
      ntData := BuildNTData(APassword, AServerResponse.Nonce);
   end
  else
    if Length(AServerResponse.TargetInfoBlockData) >= 2 * SizeOf(Word) then
      lmData := BuildLMv2Data(userName, APassword, domainName, AServerResponse.Nonce, clientNonce)
    else
      lmData := BuildLMData(APassword, AServerResponse.Nonce);

  SetLength(bufStr, SizeOf(TNTLNClientCredentialMsg) + (Length(userName) + Length(hostName) + Length(domainName)) * SizeOf(WideChar) + Length(lmData) + Length(ntData));
  FillChar(PAnsiChar(bufStr)^, Length(bufStr), 0);

  with PNTLNClientCredentialMsg(PAnsiChar(bufStr))^ do
   begin
    Move(cStrNTLMProtocolSignature[1], Protocol, Length(cStrNTLMProtocolSignature));
    MSgKind := 3;

    Domain.LenInBytes := Length(domainName) * SizeOf(WideChar);
    Domain.AllocSizeInBytes := Domain.LenInBytes;
    Domain.Offset := SizeOf(TNTLNClientCredentialMsg);
    Move(PWideChar(domainName)^, (PAnsiChar(bufStr) + Domain.Offset)^, Domain.LenInBytes);

    User.LenInBytes := Length(userName) * SizeOf(WideChar);
    User.AllocSizeInBytes := User.LenInBytes;
    User.Offset := Domain.Offset + Domain.LenInBytes;
    Move(PWideChar(userName)^, (PAnsiChar(bufStr) + User.Offset)^, User.LenInBytes);

    Host.LenInBytes := Length(hostName) * SizeOf(WideChar);
    Host.AllocSizeInBytes := Host.LenInBytes;
    Host.Offset := User.Offset + User.LenInBytes;
    Move(PWideChar(hostName)^, (PAnsiChar(bufStr) + Host.Offset)^, Host.LenInBytes);

    LMPwdHash.LenInBytes := Length(lmData);
    LMPwdHash.AllocSizeInBytes := LMPwdHash.LenInBytes;
    LMPwdHash.Offset := Host.Offset + Host.LenInBytes;
    Move(PAnsiChar(lmData)^, (PAnsiChar(bufStr) + LMPwdHash.Offset)^, LMPwdHash.LenInBytes);

    NTPwdHash.LenInBytes := Length(ntData);
    NTPwdHash.AllocSizeInBytes := NTPwdHash.LenInBytes;
    NTPwdHash.Offset := LMPwdHash.Offset + LMPwdHash.LenInBytes;
    Move(PAnsiChar(ntData)^, (PAnsiChar(bufStr) + NTPwdHash.Offset)^, NTPwdHash.LenInBytes);

    SessionKey.Offset := Length(bufStr);

    ConvertDynBufferDescToLittleEndian(LMPwdHash);
    ConvertDynBufferDescToLittleEndian(NTPwdHash);
    ConvertDynBufferDescToLittleEndian(Host);
    ConvertDynBufferDescToLittleEndian(User);
    ConvertDynBufferDescToLittleEndian(Domain);
    ConvertDynBufferDescToLittleEndian(SessionKey);

    Flags := UInt32ToLittleEndian(NTLM_Always_Sign or NTLM_NTLM or NTLM_Unicode_Charset);
   end;

  Result := GMEncodeBase64Str(bufStr);
end;


initialization

  Randomize;

end.