1771 lines
55 KiB
Plaintext
1771 lines
55 KiB
Plaintext
unit IdNTLMv2;
|
|
|
|
interface
|
|
|
|
{$i IdCompilerDefines.inc}
|
|
|
|
uses
|
|
IdGlobal,
|
|
IdStruct
|
|
{$IFNDEF DOTNET}, IdCTypes, IdSSLOpenSSLHeaders{$ENDIF}
|
|
;
|
|
|
|
type
|
|
ProtocolArray = array [ 1.. 8] of TIdAnsiChar;
|
|
nonceArray = Array[ 1.. 8] of TIdAnsiChar;
|
|
|
|
const
|
|
//These are from:
|
|
// http://svn.xmpp.ru/repos/tkabber/trunk/tkabber/jabberlib/ntlm.tcl
|
|
// and the code is under a BSD license
|
|
IdNTLMSSP_NEGOTIATE_UNICODE = $00000001; //A - NTLMSSP_NEGOTIATE_UNICODE
|
|
IdNTLM_NEGOTIATE_OEM = $00000002; //B - NTLM_NEGOTIATE_OEM
|
|
IdNTLMSSP_REQUEST_TARGET = $00000004; //C - NTLMSSP_REQUEST_TARGET
|
|
IdNTLM_Unknown1 = $00000008; //r9 - should be zero
|
|
IdNTLMSSP_NEGOTIATE_SIGN = $00000010; //D - NTLMSSP_NEGOTIATE_SIGN
|
|
IdNTLMSSP_NEGOTIATE_SEAL = $00000020; //E - NTLMSSP_NEGOTIATE_SEAL
|
|
IdNTLMSSP_NEGOTIATE_DATAGRAM = $00000040; //F - NTLMSSP_NEGOTIATE_DATAGRAM
|
|
IdNTLMSSP_NEGOTIATE_LM_KEY = $00000080; //G - NTLMSSP_NEGOTIATE_LM_KEY
|
|
//mentioned in http://svn.xmpp.ru/repos/tkabber/trunk/tkabber/jabberlib/ntlm.tcl
|
|
//and http://doc.ddart.net/msdn/header/include/ntlmsp.h.html from an old ntlmsp.h
|
|
//header. MS now says that is unused so it should be zero.
|
|
IdNTLMSSP_NEGOTIATE_NETWARE = $00000100; //r8 - should be zero
|
|
IdNTLMSSP_NEGOTIATE_NTLM = $00000200; //H - NTLMSSP_NEGOTIATE_NTLM
|
|
IdNTLMSSP_NEGOTIATE_NT_ONLY = $00000400; //I - NTLMSSP_NEGOTIATE_NT_ONLY
|
|
IdNTLMSSP_ANONYMOUS = $00000800; //J -
|
|
IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = $00001000; //K - NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
|
|
IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = $00002000; //L - NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
|
|
//mentioned in http://svn.xmpp.ru/repos/tkabber/trunk/tkabber/jabberlib/ntlm.tcl
|
|
//and http://doc.ddart.net/msdn/header/include/ntlmsp.h.html from an old ntlmsp.h
|
|
//header. MS now says that is unused so it should be zero.
|
|
IdNTLMSSP_NEGOTIATE_LOCAL_CALL = $00004000; //r6 - should be zero
|
|
|
|
IdNTLMSSP_NEGOTIATE_ALWAYS_SIGN = $00008000; //M - NTLMSSP_NEGOTIATE_ALWAYS_SIGN
|
|
IdNTLMSSP_TARGET_TYPE_DOMAIN = $00010000; //N - NTLMSSP_TARGET_TYPE_DOMAIN
|
|
IdNTLMSSP_TARGET_TYPE_SERVER = $00020000; //O - NTLMSSP_TARGET_TYPE_SERVER
|
|
IdNTLMSSP_TARGET_TYPE_SHARE = $00040000; //P - NTLMSSP_TARGET_TYPE_SHARE
|
|
IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = $00080000; //Q - NTLMSSP_NEGOTIATE_NTLM2
|
|
//was NTLMSSP_REQUEST_INIT_RESPONSE 0x100000
|
|
IdNTLMSSP_NEGOTIATE_IDENTIFY = $00100000; //R - NTLMSSP_NEGOTIATE_IDENTIFY
|
|
|
|
IdNTLMSSP_REQUEST_ACCEPT_RESPONSE = $00200000; //r5 - should be zero
|
|
//mentioned in http://doc.ddart.net/msdn/header/include/ntlmsp.h.html from an old ntlmsp.h
|
|
//header. MS now says that is unused so it should be zero.
|
|
IdIdNTLMSSP_REQUEST_NON_NT_SESSION_KEY = $00400000; //S - NTLMSSP_REQUEST_NON_NT_SESSION_KEY
|
|
IdNTLMSSP_NEGOTIATE_TARGET_INFO = $00800000; //T - NTLMSSP_NEGOTIATE_TARGET_INFO
|
|
IdNTLM_Unknown4 = $01000000; //r4 - should be zero
|
|
IdNTLMSSP_NEGOTIATE_VERSION = $02000000; //U - NTLMSSP_NEGOTIATE_VERSION
|
|
// 400000
|
|
IdNTLM_Unknown6 = $04000000; //r3 - should be zero
|
|
|
|
IdNTLM_Unknown7 = $08000000; //r2 - should be zero
|
|
IdNTLM_Unknown8 = $10000000; //r1 - should be zero
|
|
IdNTLMSSP_NEGOTIATE_128 = $20000000; //V - NTLMSSP_NEGOTIATE_128
|
|
IdNTLMSSP_NEGOTIATE_KEY_EXCH = $40000000; //W - NTLMSSP_NEGOTIATE_KEY_EXCH
|
|
IdNTLMSSP_NEGOTIATE_56 = $80000000; //X - NTLMSSP_NEGOTIATE_56
|
|
|
|
const
|
|
//LC2 supports NTLMv2 only
|
|
IdNTLM_TYPE1_FLAGS_LC2 = IdNTLMSSP_NEGOTIATE_UNICODE or
|
|
IdNTLM_NEGOTIATE_OEM or
|
|
IdNTLMSSP_REQUEST_TARGET or
|
|
IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
|
|
IdNTLM_TYPE1_FLAGS = IdNTLMSSP_NEGOTIATE_UNICODE or
|
|
IdNTLM_NEGOTIATE_OEM or
|
|
IdNTLMSSP_REQUEST_TARGET or
|
|
IdNTLMSSP_NEGOTIATE_NTLM or
|
|
// IdNTLMSSP_NEGOTIATE_ALWAYS_SIGN or
|
|
IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
|
|
IdNTLM_TYPE1_MARKER : UInt32 = 1;
|
|
IdNTLM_TYPE2_MARKER : UInt32 = 2;
|
|
IdNTLM_TYPE3_MARKER : UInt32 = 3;
|
|
|
|
IdNTLM_NTLMSSP_DES_KEY_LENGTH =7;
|
|
|
|
IdNTLM_NTLMSSP_CHALLENGE_SIZE = 8;
|
|
|
|
IdNTLM_LM_HASH_SIZE = 16;
|
|
IdNTLM_LM_SESS_HASH_SIZE = 16;
|
|
IdNTLM_LM_RESPONSE_SIZE = 24;
|
|
|
|
IdNTLM_NTLMSSP_HASH_SIZE = 16;
|
|
IdNTLM_NTLMSSP_RESPONSE_SIZE = 24;
|
|
|
|
IdNTLM_NTLMSSP_V2_HASH_SIZE = 6;
|
|
IdNTLM_NTLMSSP_V2_RESPONSE_SIZE = 16;
|
|
|
|
IdNTLM_NONCE_LEN = 8;
|
|
IdNTLM_TYPE2_TNAME_LEN_OFS = 12;
|
|
IdNTLM_TYPE2_TNAME_OFFSET_OFS = 16;
|
|
IdNTLM_TYPE2_FLAGS_OFS = 20;
|
|
IdNTLM_TYPE2_NONCE_OFS = 24;
|
|
IdNTLM_TYPE2_TINFO_LEN_OFS = 40;
|
|
IdNTLM_TYPE2_TINFO_OFFSET_OFS = 44;
|
|
|
|
IdNTLM_TYPE3_DOM_OFFSET = 64;
|
|
|
|
//version info constants
|
|
IdNTLM_WINDOWS_MAJOR_VERSION_5 = $05;
|
|
IdNTLM_WINDOWS_MAJOR_VERSION_6 = $06;
|
|
IdNTLM_WINDOWS_MINOR_VERSION_0 = $00;
|
|
IdNTLM_WINDOWS_MINOR_VERSION_1 = $01;
|
|
IdNTLM_WINDOWS_MINOR_VERSION_2 = $02;
|
|
//original rel. build version of Vista
|
|
//This isn't in the headers but I found easy enough.
|
|
//It's provided because some NTLM servers might want ver info
|
|
//from us. So we want to provide some dummy version and
|
|
//Windows Vista is logical enough.
|
|
IdNTLM_WINDOWS_BUILD_ORIG_VISTA = 6000;
|
|
|
|
var
|
|
IdNTLM_SSP_SIG : TIdBytes;
|
|
UnixEpoch : TDateTime;
|
|
|
|
{$DEFINE TESTSUITE}
|
|
{$IFDEF TESTSUITE}
|
|
procedure TestNTLM;
|
|
{$ENDIF}
|
|
|
|
function BuildType1Msg(const ADomain : String = ''; const AHost : String = ''; const ALMCompatibility : UInt32 = 0) : TIdBytes;
|
|
procedure ReadType2Msg(const AMsg : TIdBytes; var VFlags : UInt32; var VTargetName : TIdBytes; var VTargetInfo : TIdBytes; var VNonce : TIdBytes );
|
|
function BuildType3Msg(const ADomain, AHost, AUsername, APassword : String;
|
|
const AFlags : UInt32; const AServerNonce : TIdBytes;
|
|
const ATargetName, ATargetInfo : TIdBytes;
|
|
const ALMCompatibility : UInt32 = 0) : TIdBytes;
|
|
|
|
|
|
{
|
|
function BuildType1Message( ADomain, AHost: String; const AEncodeMsg : Boolean = True): String;
|
|
procedure ReadType2Message(const AMsg : String; var VNonce : nonceArray; var Flags : UInt32);
|
|
function BuildType3Message( ADomain, AHost, AUsername: TIdUnicodeString; APassword : String; AServerNonce : nonceArray): String;
|
|
}
|
|
|
|
function NTLMFunctionsLoaded : Boolean;
|
|
|
|
procedure GetDomain(const AUserName : String; var VUserName, VDomain : String);
|
|
|
|
function DumpFlags(const ABytes : TIdBytes): String; overload;
|
|
function DumpFlags(const AFlags : UInt32): String; overload;
|
|
|
|
function LoadRC4 : Boolean;
|
|
function RC4FunctionsLoaded : Boolean;
|
|
|
|
{$IFNDEF DOTNET}
|
|
//const char *RC4_options(void);
|
|
var
|
|
GRC4_Options : function () : PIdAnsiChar; cdecl = nil;
|
|
//void RC4_set_key(RC4_KEY *key, int len, const unsigned char *data);
|
|
GRC4_set_key : procedure(key : PRC4_KEY; len : TIdC_INT; data : PIdAnsiChar); cdecl = nil;
|
|
//void RC4(RC4_KEY *key, unsigned long len, const unsigned char *indata,
|
|
// unsigned char *outdata);
|
|
GRC4 : procedure (key : PRC4_KEY; len : TIdC_ULONG; indata, outdata : PIdAnsiChar) ; cdecl = nil;
|
|
{$ENDIF}
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils,
|
|
{$IFDEF USE_VCL_POSIX}
|
|
PosixTime,
|
|
{$ENDIF}
|
|
{$IFDEF DOTNET}
|
|
Classes,
|
|
System.Runtime.InteropServices,
|
|
System.Runtime.InteropServices.ComTypes,
|
|
System.Security.Cryptography,
|
|
System.Text,
|
|
{$ELSE}
|
|
{$IFDEF FPC}
|
|
DynLibs, // better add DynLibs only for fpc
|
|
{$ENDIF}
|
|
{$IFDEF WINDOWS}
|
|
//Windows should really not be included but this protocol does use
|
|
//some windows internals and is Windows-based.
|
|
Windows,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
IdFIPS,
|
|
IdGlobalProtocols,
|
|
IdHash,
|
|
IdHMACMD5,
|
|
IdHashMessageDigest,
|
|
IdCoderMIME;
|
|
|
|
const
|
|
{$IFDEF DOTNET}
|
|
MAGIC : array [ 0.. 7] of byte = ( $4b, $47, $53, $21, $40, $23, $24, $25);
|
|
{$ELSE}
|
|
Magic: des_cblock = ( $4B, $47, $53, $21, $40, $23, $24, $25 );
|
|
Magic_const : array [0..7] of byte = ( $4b, $47, $53, $21, $40, $23, $24, $25 );
|
|
{$ENDIF}
|
|
TYPE1_MARKER = 1;
|
|
TYPE2_MARGER = 2;
|
|
TYPE3_MARKER = 3;
|
|
|
|
//const
|
|
// NUL_USER_SESSION_KEY : TIdBytes[0..15] = ($00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00);
|
|
|
|
{$IFNDEF DOTNET}
|
|
type
|
|
Pdes_key_schedule = ^des_key_schedule;
|
|
{$IFNDEF WINDOWS}
|
|
FILETIME = record
|
|
dwLowDateTime : UInt32;
|
|
dwHighDateTime : UInt32;
|
|
end;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
function NulUserSessionKey : TIdBytes;
|
|
begin
|
|
SetLength(Result,16);
|
|
FillChar(Result,16,0);
|
|
end;
|
|
|
|
//misc routines that might be helpful in some other places
|
|
//===================
|
|
{$IFDEF DOTNET}
|
|
function Int64ToFileTime(const AInt64 : Int64) : System.Runtime.InteropServices.ComTypes.FILETIME;
|
|
var
|
|
LBytes : TIdBytes;
|
|
begin
|
|
LBytes := BitConverter.GetBytes(AInt64);
|
|
Result.dwLowDateTime := BitConverter.ToInt32(Lbytes, 0);
|
|
Result.dwHighDateTime := BitConverter.ToInt32(Lbytes, 4);
|
|
end;
|
|
|
|
function UnixTimeToFileTime(const AUnixTime : UInt32) : System.Runtime.InteropServices.ComTypes.FILETIME;
|
|
{$ELSE}
|
|
{
|
|
We do things this way because in Win64, FILETIME may not be a simple typecast
|
|
of Int64. It might be a two dword record that's aligned on an 8-byte
|
|
boundery.
|
|
}
|
|
{$UNDEF USE_FILETIME_TYPECAST}
|
|
{$IFDEF WINCE}
|
|
{$DEFINE USE_FILETIME_TYPECAST}
|
|
{$ENDIF}
|
|
{$IFDEF WIN32}
|
|
{$DEFINE USE_FILETIME_TYPECAST}
|
|
{$ENDIF}
|
|
|
|
function UnixTimeToFileTime(const AUnixTime : UInt32) : FILETIME;
|
|
var
|
|
i : Int64;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF DOTNET}
|
|
Result := Int64ToFileTime((AUnixTime + 11644473600) * 10000000);
|
|
{$ELSE}
|
|
i := (AUnixTime + 11644473600) * 10000000;
|
|
{$IFDEF USE_FILETIME_TYPECAST}
|
|
Result := FILETIME(i);
|
|
{$ELSE}
|
|
Result.dwLowDateTime := i and $FFFFFFFF;
|
|
Result.dwHighDateTime := i shr 32;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function NowAsFileTime : FILETIME;
|
|
{$IFDEF DOTNET}
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
{$ENDIF}
|
|
|
|
{$IFDEF UNIX}
|
|
{$IFNDEF USE_VCL_POSIX}
|
|
var
|
|
TheTms: tms;
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF WINDOWS}
|
|
{$IFDEF WINCE}
|
|
// TODO
|
|
{$ELSE}
|
|
Windows.GetSystemTimeAsFileTime(Result);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IFDEF UNIX}
|
|
{$IFDEF USE_VCL_BASEUNIX}
|
|
Result := UnixTimeToFileTime( fptimes (TheTms));
|
|
{$ENDIF}
|
|
//Is the following correct?
|
|
{$IFDEF KYLIXCOMPAT}
|
|
Result := UnixTimeToFileTime(Times(TheTms));
|
|
{$ENDIF}
|
|
{$IFDEF USE_VCL_POSIX}
|
|
Result := UnixTimeToFileTime(PosixTime.time(nil));
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
{$IFDEF DOTNET}
|
|
Result := Int64ToFileTime(DateTime.Now.ToFileTimeUtc);
|
|
// Result := System.DateTime.Now.Ticks;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function ConcateBytes(const A1, A2 : TIdBytes) : TIdBytes;
|
|
begin
|
|
Result := A1;
|
|
IdGlobal.AppendBytes(Result,A2);
|
|
end;
|
|
|
|
procedure CharArrayToBytes(const AArray : Array of char; var VBytes : TIdBytes; const AIndex : Integer=0);
|
|
var
|
|
i, ll, lh : Integer;
|
|
begin
|
|
ll := Low( AArray);
|
|
lh := High( AArray);
|
|
for i := ll to lh do begin
|
|
VBytes[i] := Ord( AArray[ i]);
|
|
end;
|
|
end;
|
|
|
|
procedure BytesToCharArray(const ABytes : TIdBytes; var VArray : Array of char; const AIndex : Integer=0);
|
|
var
|
|
i, ll, lh : Integer;
|
|
begin
|
|
ll := Low( VArray);
|
|
lh := High( Varray);
|
|
for i := ll to lh do begin
|
|
VArray[ i] := Char( Abytes[ (i - ll) + AIndex]);
|
|
end;
|
|
end;
|
|
|
|
procedure BytesToByteArray(const ABytes : TIdBytes; var VArray : Array of byte; const AIndex : Integer=0);
|
|
var
|
|
i, ll, lh : Integer;
|
|
begin
|
|
ll := Low(VArray);
|
|
lh := High(Varray);
|
|
for i := ll to lh do begin
|
|
VArray[i] := Abytes[ (i - ll) + AIndex];
|
|
end;
|
|
end;
|
|
|
|
procedure ByteArrayToBytes(const VArray : array of byte; const ABytes : TIdBytes; const AIndex : Integer=0);
|
|
var
|
|
i, ll, lh : Integer;
|
|
begin
|
|
ll := Low( VArray);
|
|
lh := High( Varray);
|
|
for i := ll to lh do begin
|
|
Abytes[ (i - ll) + AIndex] := VArray[i];
|
|
end;
|
|
end;
|
|
|
|
//---------------------------
|
|
//end misc routines
|
|
|
|
function DumpFlags(const ABytes : TIdBytes): String;
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
begin
|
|
Result := DumpFlags( LittleEndianToHost(BytesToUInt32(ABytes)));
|
|
end;
|
|
|
|
function DumpFlags(const AFlags : UInt32): String;
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
begin
|
|
Result := IntToHex(AFlags,8)+' -';
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_UNICODE <> 0 then
|
|
begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_UNICODE';
|
|
end;
|
|
if AFlags and IdNTLM_NEGOTIATE_OEM <> 0 then begin
|
|
Result := Result + ' IdNTLM_NEGOTIATE_OEM';
|
|
end;
|
|
if AFlags and IdNTLMSSP_REQUEST_TARGET <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_REQUEST_TARGET';
|
|
end;
|
|
if AFlags and IdNTLM_Unknown1 <> 0 then begin
|
|
Result := Result + ' IdNTLM_Unknown1';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_SIGN <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_SIGN';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_SEAL <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_SEAL';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_DATAGRAM <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_DATAGRAM';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_LM_KEY <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_LM_KEY';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NETWARE <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_NETWARE';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NTLM <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_NTLM';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NT_ONLY <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_NT_ONLY';
|
|
end;
|
|
if AFlags and IdNTLMSSP_ANONYMOUS <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_ANONYMOUS';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_LOCAL_CALL <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_LOCAL_CALL';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_ALWAYS_SIGN <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_ALWAYS_SIGN';
|
|
end;
|
|
if AFlags and IdNTLMSSP_TARGET_TYPE_DOMAIN <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_TARGET_TYPE_DOMAIN';
|
|
end;
|
|
if AFlags and IdNTLMSSP_TARGET_TYPE_SERVER <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_TARGET_TYPE_SERVER';
|
|
end;
|
|
if AFlags and IdNTLMSSP_TARGET_TYPE_SHARE <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_TARGET_TYPE_SHARE';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_IDENTIFY <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_IDENTIFY';
|
|
end;
|
|
if AFlags and IdNTLMSSP_REQUEST_ACCEPT_RESPONSE <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_REQUEST_ACCEPT_RESPONSE';
|
|
end;
|
|
if AFlags and IdIdNTLMSSP_REQUEST_NON_NT_SESSION_KEY <> 0 then begin
|
|
Result := Result + ' IdIdNTLMSSP_REQUEST_NON_NT_SESSION_KEY';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_TARGET_INFO <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_TARGET_INFO';
|
|
end;
|
|
if AFlags and IdNTLM_Unknown4 <> 0 then begin
|
|
Result := Result + ' IdNTLM_Unknown4';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_VERSION <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_VERSION';
|
|
end;
|
|
if AFlags and IdNTLM_Unknown8 <> 0 then begin
|
|
Result := Result + ' IdNTLM_Unknown8';
|
|
end;
|
|
if AFlags and IdNTLM_Unknown7 <> 0 then begin
|
|
Result := Result + ' IdNTLM_Unknown7';
|
|
end;
|
|
if AFlags and IdNTLM_Unknown8 <> 0 then begin
|
|
Result := Result + ' IdNTLM_Unknown8';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_128 <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_128';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_KEY_EXCH <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_KEY_EXCH';
|
|
end;
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_56 <> 0 then begin
|
|
Result := Result + ' IdNTLMSSP_NEGOTIATE_56';
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF DOTNET}
|
|
const
|
|
DES_ODD_PARITY : array[ 0.. 255] of byte =
|
|
( 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14,
|
|
16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
|
|
32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
|
|
49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
|
|
64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
|
|
81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
|
|
97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, 110,
|
|
112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127,
|
|
128, 128, 131, 131, 133, 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143,
|
|
145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158,
|
|
161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174,
|
|
176, 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191,
|
|
193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205, 205, 206, 206,
|
|
208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, 220, 223, 223,
|
|
224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239,
|
|
241, 241, 242, 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254);
|
|
|
|
{
|
|
IMPORTANT!!!
|
|
|
|
In the NET framework, the DES API will not accept a weak key. Unfortunately,
|
|
in NTLM's LM password, if a password is less than 8 charactors, the second key
|
|
is weak. This is one flaw in that protocol.
|
|
|
|
To workaround this, we use a precalculated key of zeros with the parity bit set
|
|
and encrypt the MAGIC value with it. The Mono framework also does this.
|
|
}
|
|
const
|
|
MAGIC_NUL_KEY : array [ 0.. 7] of byte = ($AA,$D3,$B4,$35,$B5,$14,$04,$EE);
|
|
|
|
{barrowed from OpenSSL source-code - crypto/des/set_key.c 0.9.8g}
|
|
{
|
|
I barrowed it since it seems to work better than the NTLM sample code and because
|
|
Microsoft.NET does not have this functionality when it really should have it.
|
|
}
|
|
|
|
procedure SetDesKeyOddParity(var VKey : TIdBytes);
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
i, l : Integer;
|
|
begin
|
|
l := Length( VKey);
|
|
for i := 0 to l - 1 do begin
|
|
VKey[ i] := DES_ODD_PARITY[ VKey[ i]];
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure GetDomain(const AUserName : String; var VUserName, VDomain : String);
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
i : Integer;
|
|
begin
|
|
{
|
|
Can be like this:
|
|
DOMAIN\user
|
|
domain.com\user
|
|
user@DOMAIN
|
|
}
|
|
i := IndyPos('\', AUsername);
|
|
if i > 0 then begin //was -1
|
|
VDomain := Copy( AUsername, 1, i - 1);
|
|
VUserName := Copy( AUsername, i + 1, Length( AUserName));
|
|
end else begin
|
|
i := IndyPos('@',AUsername);
|
|
if i > 0 then //was -1
|
|
begin
|
|
VUsername := Copy( AUsername, 1, i - 1);
|
|
VDomain := Copy( AUsername, i + 1, Length( AUserName));
|
|
end
|
|
else
|
|
begin
|
|
VDomain := ''; {do not localize}
|
|
VUserName := AUserName;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF DOTNET}
|
|
|
|
function NTLMFunctionsLoaded : Boolean;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
{$ELSE}
|
|
|
|
function NTLMFunctionsLoaded : Boolean;
|
|
begin
|
|
Result := IdSSLOpenSSLHeaders.Load;
|
|
if Result then begin
|
|
Result := Assigned(des_set_odd_parity) and
|
|
Assigned(DES_set_key) and
|
|
Assigned(DES_ecb_encrypt);
|
|
end;
|
|
end;
|
|
|
|
function LoadRC4 : Boolean;
|
|
var
|
|
h : Integer;
|
|
begin
|
|
Result := IdSSLOpenSSLHeaders.Load;
|
|
if Result then begin
|
|
h := IdSSLOpenSSLHeaders.GetCryptLibHandle;
|
|
GRC4_Options := GetProcAddress(h,'RC4_options');
|
|
GRC4_set_key := GetProcAddress(h,'RC4_set_key');
|
|
GRC4 := GetProcAddress(h,'RC4');
|
|
end;
|
|
Result := RC4FunctionsLoaded;
|
|
end;
|
|
|
|
function RC4FunctionsLoaded : Boolean;
|
|
begin
|
|
Result := Assigned(GRC4_Options) and
|
|
Assigned(GRC4_set_key) and
|
|
Assigned(GRC4);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
//* create NT hashed password */
|
|
function NTOWFv1(const APassword : String): TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
LHash: TIdHashMessageDigest4;
|
|
begin
|
|
CheckMD4Permitted;
|
|
LHash := TIdHashMessageDigest4.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
Result := LHash.HashBytes(IndyTextEncoding_UTF16LE.GetBytes(APassword));
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{$IFNDEF DOTNET}
|
|
{/*
|
|
* turns a 56 bit key into the 64 bit, odd parity key and sets the key.
|
|
* The key schedule ks is also set.
|
|
*/}
|
|
procedure setup_des_key(key_56: des_cblock; Var ks: des_key_schedule);
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
var
|
|
key: des_cblock;
|
|
begin
|
|
key[0] := key_56[0];
|
|
|
|
key[1] := ((key_56[0] SHL 7) and $FF) or (key_56[1] SHR 1);
|
|
key[2] := ((key_56[1] SHL 6) and $FF) or (key_56[2] SHR 2);
|
|
key[3] := ((key_56[2] SHL 5) and $FF) or (key_56[3] SHR 3);
|
|
key[4] := ((key_56[3] SHL 4) and $FF) or (key_56[4] SHR 4);
|
|
key[5] := ((key_56[4] SHL 3) and $FF) or (key_56[5] SHR 5);
|
|
key[6] := ((key_56[5] SHL 2) and $FF) or (key_56[6] SHR 6);
|
|
key[7] := (key_56[6] SHL 1) and $FF;
|
|
|
|
DES_set_odd_parity(@key);
|
|
DES_set_key(@key, ks);
|
|
end;
|
|
|
|
//Returns 8 bytes in length
|
|
procedure _DES(var Res : TIdBytes; const Akey, AData : array of byte; const AKeyIdx, ADataIdx, AResIdx : Integer);
|
|
var
|
|
Lks: des_key_schedule;
|
|
begin
|
|
setup_des_key(pdes_cblock(@Akey[AKeyIdx])^, Lks);
|
|
DES_ecb_encrypt(@AData[ADataIdx], Pconst_DES_cblock(@Res[AResIdx]), Lks, DES_ENCRYPT);
|
|
|
|
end;
|
|
|
|
function LMOWFv1(const Passwd, User, UserDom : TIdBytes) : TIdBytes;
|
|
// ConcatenationOf( DES( UpperCase( Passwd)[0..6],"KGS!@#$%"),
|
|
// DES( UpperCase( Passwd)[7..13],"KGS!@#$%"))
|
|
var
|
|
LBuf : TIdBytes;
|
|
begin
|
|
SetLength(Result,16);
|
|
SetLength(LBuf,14);
|
|
FillBytes( LBuf, 14, 0);
|
|
CopyTIdBytes(Passwd,0,LBuf, 0,14);
|
|
_DES(Result,LBuf, Magic_const,0,0,0);
|
|
_DES(Result,LBuf, Magic_const,7,0,8);
|
|
|
|
end;
|
|
|
|
{/*
|
|
* takes a 21 byte array and treats it as 3 56-bit DES keys. The
|
|
* 8 byte plaintext is encrypted with each key and the resulting 24
|
|
* bytes are stored in the results array.
|
|
*/}
|
|
procedure DESL(const Akeys: TIdBytes; const AServerNonce: TIdBytes; out results: TIdBytes);
|
|
//procedure DESL(keys: TIdBytes; AServerNonce: TIdBytes; results: TIdBytes);
|
|
//procedure DESL(keys: TIdBytes; AServerNonce: TIdBytes; results: Pdes_key_schedule);
|
|
var
|
|
ks: des_key_schedule;
|
|
begin
|
|
SetLength(Results,24);
|
|
setup_des_key(PDES_cblock(@Akeys[0])^, ks);
|
|
DES_ecb_encrypt(@AServerNonce[0], Pconst_DES_cblock(results), ks, DES_ENCRYPT);
|
|
|
|
setup_des_key(PDES_cblock(Integer(Akeys) + 7)^, ks);
|
|
DES_ecb_encrypt(@AServerNonce[0], Pconst_DES_cblock(PtrUInt(results) + 8), ks, DES_ENCRYPT);
|
|
|
|
setup_des_key(PDES_cblock(Integer(Akeys) + 14)^, ks);
|
|
DES_ecb_encrypt(@AServerNonce[0], Pconst_DES_cblock(PtrUInt(results) + 16), ks, DES_ENCRYPT);
|
|
end;
|
|
|
|
|
|
|
|
//* setup LanManager password */
|
|
function SetupLMResponse(var vlmHash : TIdBytes; const APassword : String; AServerNonce : TIdBytes): TIdBytes;
|
|
var
|
|
lm_hpw : TIdBytes;
|
|
lm_pw : TIdBytes;
|
|
ks: des_key_schedule;
|
|
begin
|
|
SetLength( lm_hpw,21);
|
|
FillBytes( lm_hpw, 14, 0);
|
|
SetLength( lm_pw, 21);
|
|
FillBytes( lm_pw, 14, 0);
|
|
SetLength( vlmHash, 16);
|
|
CopyTIdString( UpperCase( APassword), lm_pw, 0, 14);
|
|
|
|
//* create LanManager hashed password */
|
|
setup_des_key(pdes_cblock(@lm_pw[0])^, ks);
|
|
DES_ecb_encrypt(@magic, pconst_des_cblock(@lm_hpw[0]), ks, DES_ENCRYPT);
|
|
|
|
setup_des_key(pdes_cblock(@lm_pw[7])^, ks);
|
|
DES_ecb_encrypt(@magic, pconst_des_cblock(@lm_hpw[8]), ks, DES_ENCRYPT);
|
|
CopyTIdBytes(lm_pw,0,vlmHash,0,16);
|
|
// FillChar(lm_hpw[17], 5, 0);
|
|
SetLength(Result,24);
|
|
DESL(lm_hpw, AServerNonce, Result);
|
|
// DESL(PDes_cblock(@lm_hpw[0]), AServerNonce, Pdes_key_schedule(@Result[0]));
|
|
//
|
|
end;
|
|
|
|
//* create NT hashed password */
|
|
function CreateNTLMResponse(var vntlmhash : TIdBytes; const APassword : String; const nonce : TIdBytes): TIdBytes;
|
|
var
|
|
nt_pw : TIdBytes;
|
|
nt_hpw : TIdBytes; //array [1..21] of Char;
|
|
// nt_hpw128 : TIdBytes;
|
|
LHash: TIdHashMessageDigest4;
|
|
begin
|
|
CheckMD4Permitted;
|
|
nt_pw := IndyTextEncoding_UTF16LE.GetBytes(APassword);
|
|
LHash := TIdHashMessageDigest4.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
vntlmhash := LHash.HashBytes(nt_pw);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF}
|
|
SetLength( nt_hpw, 21);
|
|
FillChar( nt_hpw[ 17], 5, 0);
|
|
CopyTIdBytes( vntlmhash, 0, nt_hpw, 0, 16);
|
|
// done in DESL
|
|
SetLength( Result, 24);
|
|
|
|
// DESL(pdes_cblock(@nt_hpw[0]), nonce, Pdes_key_schedule(@Result[0]));
|
|
|
|
DESL(nt_hpw, nonce, Result);
|
|
end;
|
|
|
|
{
|
|
function CreateNTLMResponse(const APassword : String; const nonce : TIdBytes): TIdBytes;
|
|
var
|
|
nt_pw : TIdBytes;
|
|
nt_hpw : array [ 1.. 21] of AnsiChar;
|
|
nt_hpw128 : TIdBytes;
|
|
LHash: TIdHashMessageDigest4;
|
|
begin
|
|
CheckMD4Permitted;
|
|
SetLength(Result,24);
|
|
nt_pw := IndyTextEncoding_UTF16LE.GetBytes(APassword);
|
|
LHash := TIdHashMessageDigest4.Create;
|
|
{$IFNDEF USE_OBJECT_ARC
|
|
try
|
|
{$ENDIF
|
|
nt_hpw128 := LHash.HashBytes(nt_pw);//LHash.HashString( nt_pw);
|
|
{$IFNDEF USE_OBJECT_ARC
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF
|
|
Move( nt_hpw128[ 0], nt_hpw[ 1], 16);
|
|
FillChar( nt_hpw[ 17], 5, 0);
|
|
DESL(pdes_cblock( @nt_hpw[1]), nonce, Pdes_key_schedule( @Result[ 0]));
|
|
end; }
|
|
|
|
{$ELSE}
|
|
|
|
|
|
procedure setup_des_key(const Akey_56: TIdBytes; out Key : TIdBytes; const AIndex : Integer = 0);
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
begin
|
|
SetLength( key, 8);
|
|
key[0] := Akey_56[ AIndex];
|
|
key[1] := (( Akey_56[ AIndex] SHL 7) and $FF) or (Akey_56[ AIndex + 1] SHR 1);
|
|
key[2] := (( Akey_56[ AIndex + 1] SHL 6) and $FF) or (Akey_56[ AIndex + 2] SHR 2);
|
|
key[3] := (( Akey_56[ AIndex + 2] SHL 5) and $FF) or (Akey_56[ AIndex + 3] SHR 3);
|
|
key[4] := (( Akey_56[ AIndex + 3] SHL 4) and $FF) or (Akey_56[ AIndex + 4] SHR 4);
|
|
key[5] := (( Akey_56[ AIndex + 4] SHL 3) and $FF) or (Akey_56[ AIndex + 5] SHR 5);
|
|
key[6] := (( Akey_56[ AIndex + 5] SHL 2) and $FF) or (Akey_56[ AIndex + 6] SHR 6);
|
|
key[7] := ( AKey_56[ AIndex + 6] SHL 1) and $FF;
|
|
SetDesKeyOddParity( Key);
|
|
end;
|
|
|
|
|
|
|
|
procedure DESL(const Akeys: TIdBytes; const AServerNonce: TIdBytes; out results: TIdBytes);
|
|
var
|
|
LKey : TIdBytes;
|
|
LDes : System.Security.Cryptography.DES;
|
|
LEnc : ICryptoTransform;
|
|
begin
|
|
SetLength( Results, 24);
|
|
SetLength( LKey, 8);
|
|
LDes := DESCryptoServiceProvider.Create;
|
|
LDes.Mode := CipherMode.ECB;
|
|
LDes.Padding := PaddingMode.None;
|
|
setup_des_key( AKeys, LKey, 0);
|
|
LDes.Key := LKey;
|
|
LEnc := LDes.CreateEncryptor;
|
|
LEnc.TransformBlock( AServerNonce, 0, 8, Results, 0);
|
|
setup_des_key( AKeys, LKey, 7);
|
|
LDes.Key := LKey;
|
|
LEnc := LDes.CreateEncryptor;
|
|
LEnc.TransformBlock( AServerNonce, 0, 8, Results, 8);
|
|
|
|
setup_des_key(AKeys,LKey,14);
|
|
LDes.Key := LKey;
|
|
LEnc := LDes.CreateEncryptor;
|
|
LEnc.TransformBlock( AServerNonce, 0, 8, Results, 16);
|
|
end;
|
|
|
|
function SetupLMResponse(const APassword : String; AServerNonce : TIdBytes): TIdBytes;
|
|
var
|
|
lm_hpw : TIdBytes; //array[1..21] of Char;
|
|
lm_pw : TIdBytes; //array[1..21] of Char;
|
|
LDes : System.Security.Cryptography.DES;
|
|
LEnc : ICryptoTransform;
|
|
LKey : TIdBytes;
|
|
begin
|
|
SetLength( lm_hpw,21);
|
|
FillBytes( lm_hpw, 14, 0);
|
|
SetLength( lm_pw, 21);
|
|
FillBytes( lm_pw, 14, 0);
|
|
CopyTIdString( UpperCase( APassword), lm_pw, 0, 14);
|
|
LDes := DESCryptoServiceProvider.Create;
|
|
LDes.Mode := CipherMode.ECB;
|
|
LDes.Padding := PaddingMode.None;
|
|
setup_des_key( lm_pw, LKey, 0);
|
|
LDes.BlockSize := 64;
|
|
LDes.Key := LKey;
|
|
LEnc := LDes.CreateEncryptor;
|
|
LEnc.TransformBlock( MAGIC, 0, 8, lm_hpw, 0);
|
|
setup_des_key( lm_pw, LKey,7);
|
|
if Length( APassword) > 7 then begin
|
|
LDes.Key := LKey;
|
|
LEnc := LDes.CreateEncryptor;
|
|
LEnc.TransformBlock( MAGIC, 0, 8, lm_hpw, 8);
|
|
end else begin
|
|
CopyTIdBytes( MAGIC_NUL_KEY, 0, lm_hpw, 8, 8);
|
|
end;
|
|
DESL( lm_hpw, nonce, Result);
|
|
end;
|
|
|
|
function CreateNTLMResponse(const APassword : String; const nonce : TIdBytes): TIdBytes;
|
|
var
|
|
nt_pw : TIdBytes;
|
|
nt_hpw : TIdBytes; //array [1..21] of Char;
|
|
nt_hpw128 : TIdBytes;
|
|
begin
|
|
CheckMD4Permitted;
|
|
nt_pw := System.Text.Encoding.Unicode.GetBytes( APassword);
|
|
with TIdHashMessageDigest4.Create do try
|
|
nt_hpw128 := HashString( nt_pw);
|
|
finally
|
|
Free;
|
|
end;
|
|
SetLength( nt_hpw, 21);
|
|
FillBytes( nt_hpw, 21, 0);
|
|
CopyTIdBytes( nt_hpw128, 0, nt_hpw, 0, 16);
|
|
// done in DESL
|
|
//SetLength( nt_resp, 24);
|
|
DESL( nt_hpw,nonce, Result);
|
|
end;
|
|
|
|
{$ENDIF}
|
|
|
|
|
|
procedure AddUInt16(var VBytes: TIdBytes; const AWord : UInt16);
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
var
|
|
LBytes : TIdBytes;
|
|
begin
|
|
SetLength(LBytes,SizeOf(AWord));
|
|
CopyTIdUInt16(HostToLittleEndian(AWord),LBytes,0);
|
|
AppendBytes( VBytes,LBytes);
|
|
end;
|
|
|
|
procedure AddUInt32(var VBytes: TIdBytes; const ALongWord : UInt32);
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
var
|
|
LBytes : TIdBytes;
|
|
begin
|
|
SetLength(LBytes,SizeOf(ALongWord));
|
|
CopyTIdUInt32(HostToLittleEndian(ALongWord),LBytes,0);
|
|
AppendBytes( VBytes,LBytes);
|
|
end;
|
|
|
|
procedure AddInt64(var VBytes: TIdBytes; const AInt64 : Int64);
|
|
var
|
|
LBytes : TIdBytes;
|
|
begin
|
|
SetLength(LBytes,SizeOf(AInt64));
|
|
{$IFDEF ENDIAN_LITTLE}
|
|
IdGlobal.CopyTIdInt64(AInt64,LBytes,0);
|
|
{$ELSE}
|
|
//done this way since we need this in little endian byte-order
|
|
CopyTIdUInt32(HostToLittleEndian(Lo( AInt64)));
|
|
CopyTIdUInt32(HostToLittleEndian(Hi( AInt64)));
|
|
{$ENDIF}
|
|
AppendBytes( VBytes,LBytes);
|
|
end;
|
|
|
|
{
|
|
IMPORTANT!!! I think the time feilds in the NTLM blob are really FILETIME
|
|
records.
|
|
|
|
MSDN says that a Contains a 64-bit value representing the number of
|
|
100-nanosecond intervals since January 1, 1601 (UTC).
|
|
|
|
http://davenport.sourceforge.net/ntlm.html says that the time feild is
|
|
Little-endian, 64-bit signed value representing the number of tenths of a
|
|
microsecond since January 1, 1601.
|
|
|
|
In other words, they are the same. We use FILETIME for the API so that
|
|
we can easily obtain a the timestamp for the blob in Microsoft.NET as well as
|
|
Delphi (the work on proper format is already done for us.
|
|
}
|
|
|
|
const
|
|
NTLMv2_BLOB_Sig : array [0..3] of byte = ($01,$01,$00,$00);
|
|
NTLMv2_BLOB_Res : array [0..3] of byte = ($00,$00,$00,$00);
|
|
|
|
function IndyByteArrayLength(const ABuffer: array of byte; const ALength: Integer = -1; const AIndex: Integer = 0): Integer;
|
|
var
|
|
LAvailable: Integer;
|
|
begin
|
|
Assert(AIndex >= 0);
|
|
LAvailable := IndyMax(Length(ABuffer)-AIndex, 0);
|
|
if ALength < 0 then begin
|
|
Result := LAvailable;
|
|
end else begin
|
|
Result := IndyMin(LAvailable, ALength);
|
|
end;
|
|
end;
|
|
|
|
procedure AddByteArray(var VBytes : TIdBytes; const AToAdd : array of byte; const AIndex: Integer = 0);
|
|
var
|
|
LOldLen, LAddLen : Integer;
|
|
begin
|
|
LAddLen := IndyByteArrayLength(AToAdd, -1, AIndex);
|
|
|
|
if LAddLen > 0 then begin
|
|
LOldLen := Length(VBytes);
|
|
SetLength(VBytes, LOldLen + LAddLen);
|
|
CopyTIdByteArray(AToAdd, AIndex, VBytes, LOldLen, LAddLen);
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF DOTNET}
|
|
function MakeBlob(const ANTLMTimeStamp : System.Runtime.InteropServices.ComTypes.FileTime;
|
|
const ATargetInfo : TIdBytes;const cnonce : TIdBytes) : TIdBytes;
|
|
{$ELSE}
|
|
function MakeBlob(const ANTLMTimeStamp : FILETIME; const ATargetInfo : TIdBytes;const cnonce : TIdBytes) : TIdBytes;
|
|
{$ENDIF}
|
|
begin
|
|
SetLength(Result,0);
|
|
|
|
//blob signature - offset 0
|
|
// RespType - offset 0
|
|
// HiRespType - offset 1
|
|
// Reserved1 - offset 2
|
|
AddByteArray( Result, NTLMv2_BLOB_Sig);
|
|
// reserved - offset 4
|
|
// Reserved2 - offset 4
|
|
AddByteArray( Result, NTLMv2_BLOB_Res);
|
|
|
|
// time - offset 8
|
|
IdNTLMv2.AddUInt32(Result, ANTLMTimeStamp.dwLowDateTime );
|
|
IdNTLMv2.AddUInt32(Result, ANTLMTimeStamp.dwHighDateTime );
|
|
|
|
//client nonce (challange)
|
|
// cnonce - offset 16
|
|
IdGlobal.AppendBytes(Result,cnonce);
|
|
// reserved 3
|
|
// offset - offset 24
|
|
AddUInt32(Result,0);
|
|
// AddByteArray( Result, NTLMv2_BLOB_Res);
|
|
// targetinfo - offset 28
|
|
// av pairs - offset 28
|
|
IdGlobal.AppendBytes(Result,ATargetInfo);
|
|
// unknown (0 works)
|
|
AddByteArray( Result, NTLMv2_BLOB_Res);
|
|
end;
|
|
|
|
function InternalCreateNTLMv2Response(var Vntlm2hash : TIdBytes;
|
|
const AUsername, ADomain, APassword : String;
|
|
const ATargetInfo : TIdBytes;
|
|
const ATimestamp : FILETIME;
|
|
const cnonce, nonce : TIdBytes): TIdBytes;
|
|
var
|
|
LLmUserDom : TIdBytes;
|
|
Blob : TIdBytes;
|
|
LEncoding: IIdTextEncoding;
|
|
LHMac: TIdHMACMD5;
|
|
begin
|
|
LEncoding := IndyTextEncoding_UTF16LE;
|
|
LLmUserDom := LEncoding.GetBytes(UpperCase(AUsername));
|
|
AppendBytes(LLmUserDom, LEncoding.GetBytes(ADomain));
|
|
LEncoding := nil;
|
|
|
|
LHMac := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHMac.Key := NTOWFv1(APassword);
|
|
Vntlm2hash := LHMac.HashValue(LLmUserDom);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHMac.Free;
|
|
end;
|
|
{$ENDIF}
|
|
Blob := MakeBlob(ATimestamp,ATargetInfo,cnonce);
|
|
LHMac := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHMac.Key := Vntlm2hash;
|
|
Result := LHMac.HashValue(ConcateBytes(nonce,Blob));
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHMac.Free;
|
|
end;
|
|
{$ENDIF}
|
|
AppendBytes(Result,Blob);
|
|
|
|
end;
|
|
|
|
function CreateNTLMv2Response(var Vntlm2hash : TIdBytes;
|
|
const AUsername, ADomain, APassword : String;
|
|
const TargetName, ATargetInfo : TIdBytes;
|
|
const cnonce, nonce : TIdBytes): TIdBytes;
|
|
begin
|
|
Result := InternalCreateNTLMv2Response(Vntlm2hash, AUsername, ADomain, APassword, ATargetInfo, NowAsFileTime,cnonce, nonce);
|
|
end;
|
|
|
|
function LMUserSessionKey(const AHash : TIdBytes) : TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
// 1. The 16-byte LM hash (calculated previously) is truncated to 8 bytes.
|
|
// 2. This is null-padded to 16 bytes. This value is the LM User Session Key.
|
|
begin
|
|
SetLength(Result,16);
|
|
FillBytes(Result,16,0);
|
|
CopyTIdBytes(AHash,0,Result,0,8);
|
|
end;
|
|
|
|
function UserNTLMv1SessionKey(const AHash : TIdBytes) : TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
LHash: TIdHashMessageDigest4;
|
|
begin
|
|
CheckMD4Permitted;
|
|
LHash := TIdHashMessageDigest4.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
Result := LHash.HashBytes(AHash);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function UserLMv2SessionKey(const AHash : TIdBytes; const ABlob : TIdBytes; ACNonce : TIdBytes) : TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
LBuf : TIdBytes;
|
|
LHMac: TIdHMACMD5;
|
|
begin
|
|
LBuf := ABlob;
|
|
AppendBytes(LBuf,ACNonce);
|
|
LHMac := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHMac.Key := AHash;
|
|
Result := LHMac.HashValue(LHMac.HashValue(LBuf));
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHMac.Free;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function UserNTLMv2SessionKey(const AHash : TIdBytes; const ABlob : TIdBytes; const AServerNonce : TIdBytes) : TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
//extremely similar to the above except that the nonce is used.
|
|
begin
|
|
Result := UserLMv2SessionKey(AHash,ABlob,AServerNonce);
|
|
end;
|
|
|
|
function UserNTLM2SessionSecSessionKey(const ANTLMv1SessionKey : TIdBytes; const AServerNonce : TIdBytes): TIdBytes;
|
|
{$IFDEF USE_INLINE} inline; {$ENDIF}
|
|
var
|
|
LHash: TIdHMACMD5;
|
|
begin
|
|
LHash := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHash.Key := ANTLMv1SessionKey;
|
|
Result := LHash.HashValue(AServerNonce);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function LanManagerSessionKey(const ALMHash : TIdBytes) : TIdBytes;
|
|
var
|
|
LKey : TIdBytes;
|
|
ks : des_key_schedule;
|
|
LHash8 : TIdBytes;
|
|
begin
|
|
LHash8 := ALMHash;
|
|
SetLength(LHash8,8);
|
|
SetLength(LKey,14);
|
|
FillChar(LKey,14,$bd);
|
|
CopyTIdBytes(LHash8,0,LKey,0,8);
|
|
SetLength(Result,16);
|
|
setup_des_key(pdes_cblock(@LKey[0])^, ks);
|
|
DES_ecb_encrypt(@LHash8, pconst_des_cblock(@Result[0]), ks, DES_ENCRYPT);
|
|
|
|
setup_des_key(pdes_cblock(@LKey[7])^, ks);
|
|
DES_ecb_encrypt(@LHash8, pconst_des_cblock(@Result[8]), ks, DES_ENCRYPT);
|
|
|
|
end;
|
|
|
|
function SetupLMv2Response(var VntlmHash : TIdBytes; const AUsername, ADomain : String; const APassword : String; cnonce, AServerNonce : TIdBytes): TIdBytes;
|
|
var
|
|
LLmUserDom : TIdBytes;
|
|
LChall : TIdBytes;
|
|
LEncoding: IIdTextEncoding;
|
|
LHMac: TIdHMACMD5;
|
|
begin
|
|
LEncoding := IndyTextEncoding_UTF16LE;
|
|
LLmUserDom := LEncoding.GetBytes(UpperCase(AUsername));
|
|
AppendBytes(LLmUserDom, LEncoding.GetBytes(ADomain));
|
|
LEncoding := nil;
|
|
|
|
LHMac := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHMac.Key := NTOWFv1(APassword);
|
|
VntlmHash := LHMac.HashValue(LLmUserDom);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHMac.Free;
|
|
end;
|
|
{$ENDIF}
|
|
LChall := AServerNonce;
|
|
IdGlobal.AppendBytes(LChall,cnonce);
|
|
LHMac := TIdHMACMD5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
LHMac.Key := vntlmhash;
|
|
Result := LHMac.HashValue(LChall);
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHMac.Free;
|
|
end;
|
|
{$ENDIF}
|
|
AppendBytes(Result,cnonce);
|
|
end;
|
|
|
|
//function SetupLMResponse(const APassword : String; nonce : TIdBytes): TIdBytes;
|
|
function CreateModNTLMv1Response(var Vntlmseshash : TIdBytes; const AUsername : String; const APassword : String; cnonce, nonce : TIdBytes; var LMFeild : TIdBytes): TIdBytes;
|
|
var
|
|
LChall, LTmp : TIdBytes;
|
|
lntlmseshash : TIdBytes;
|
|
LPassHash : TIdBytes;
|
|
LHash: TIdHashMessageDigest5;
|
|
begin
|
|
CheckMD5Permitted;
|
|
//LM feild value for Type3 message
|
|
SetLength(LMFeild,24);
|
|
FillBytes( LMFeild,24, 0);
|
|
IdGlobal.CopyTIdBytes(cnonce,0,LMFeild,0,8);
|
|
//
|
|
LChall := nonce;
|
|
IdGlobal.AppendBytes(LChall,cnonce);
|
|
LHash := TIdHashMessageDigest5.Create;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
try
|
|
{$ENDIF}
|
|
Vntlmseshash := LHash.HashBytes(LChall);
|
|
//we do this copy because we may need the value later.
|
|
lntlmseshash := Vntlmseshash;
|
|
{$IFNDEF USE_OBJECT_ARC}
|
|
finally
|
|
LHash.Free;
|
|
end;
|
|
{$ENDIF}
|
|
SetLength(lntlmseshash,8);
|
|
SetLength(LPassHash,21);
|
|
FillBytes( LPassHash,21, 0);
|
|
LTmp := NTOWFv1(APassword);
|
|
IdGlobal.CopyTIdBytes(LTmp,0,LPassHash,0,Length(LTmp));
|
|
{$IFNDEF DOTNET}
|
|
SetLength(Result,24);
|
|
DESL( LPassHash,lntlmseshash, Result);
|
|
// DESL(PDes_cblock(@LPassHash[0]), lntlmseshash, Pdes_key_schedule(@Result[0]));
|
|
{$ELSE}
|
|
DESL( LPassHash,ntlmseshash, Result);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
//Todo: This does not match the results from
|
|
//http://davenport.sourceforge.net/ntlm.html
|
|
function TDateTimeToNTLMTime(const ADateTime : TDateTime):Int64;
|
|
{$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
begin
|
|
Result := DateTimeToUnix((ADateTime+11644473600)* 10000);
|
|
end;
|
|
|
|
|
|
function BuildType1Msg(const ADomain : String = ''; const AHost : String = ''; const ALMCompatibility : UInt32 = 0) : TIdBytes;
|
|
var
|
|
LDomain, LWorkStation: TIdBytes;
|
|
LDomLen, LWorkStationLen: UInt16;
|
|
LFlags : UInt32;
|
|
LEncoding: IIdTextEncoding;
|
|
begin
|
|
SetLength(Result,0);
|
|
|
|
LEncoding := IndyTextEncoding_OSDefault;
|
|
LDomain := LEncoding.GetBytes(ADomain); //UpperCase(ADomain));
|
|
LWorkStation := LEncoding.GetBytes(AHost); //UpperCase(AHost));
|
|
LEncoding := nil;
|
|
|
|
LFlags := IdNTLM_TYPE1_FLAGS_LC2;
|
|
case ALMCompatibility of
|
|
0, 1 : LFlags := IdNTLM_TYPE1_FLAGS;
|
|
end;
|
|
|
|
LDomLen := Length(LDomain);
|
|
if LDomLen > 0 then begin
|
|
LFlags := LFlags or IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
|
|
end;
|
|
|
|
LWorkStationLen := Length(LWorkStation);
|
|
if LWorkStationLen > 0 then begin
|
|
LFlags := LFlags or IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED;
|
|
end;
|
|
//signature
|
|
AppendBytes(Result,IdNTLM_SSP_SIG);
|
|
//type
|
|
AddUInt32(Result,IdNTLM_TYPE1_MARKER);
|
|
//flags
|
|
AddUInt32(Result,LFlags);
|
|
//Supplied Domain security buffer
|
|
// - length
|
|
AddUInt16(Result,LDomLen);
|
|
// - allocated space
|
|
AddUInt16(Result,LDomLen);
|
|
// - offset
|
|
AddUInt32(Result,LWorkStationLen+$20);
|
|
//Supplied Workstation security buffer (host)
|
|
// - length
|
|
AddUInt16(Result,LWorkStationLen);
|
|
// - allocated space
|
|
AddUInt16(Result,LWorkStationLen);
|
|
// - offset
|
|
if LWorkStationLen > 0 then begin
|
|
AddUInt32(Result,$20);
|
|
end else begin
|
|
AddUInt32(Result,$08);
|
|
end;
|
|
// Supplied Workstation (host)
|
|
if LWorkStationLen > 0 then begin
|
|
AppendBytes(Result,LWorkStation,0,LWorkStationLen);
|
|
end;
|
|
// Supplied Domain
|
|
if LDomLen > 0 then begin
|
|
AppendBytes(Result,LDomain,0,LDomLen);
|
|
end;
|
|
end;
|
|
|
|
procedure ReadType2Msg(const AMsg : TIdBytes; var VFlags : UInt32; var VTargetName : TIdBytes; var VTargetInfo : TIdBytes; var VNonce : TIdBytes );
|
|
var
|
|
LLen : UInt16;
|
|
LOfs : UInt32;
|
|
begin
|
|
//extract flags
|
|
VFlags := LittleEndianToHost(BytesToUInt32(AMsg,IdNTLM_TYPE2_FLAGS_OFS));
|
|
|
|
//extract target name
|
|
|
|
// - security buffer
|
|
LLen := LittleEndianToHost( BytesToUInt16(AMsg,IdNTLM_TYPE2_TNAME_LEN_OFS));
|
|
LOfs := LittleEndianToHost( BytesToUInt32(AMsg,IdNTLM_TYPE2_TNAME_OFFSET_OFS));
|
|
// - the buffer itself
|
|
SetLength(VTargetName,LLen);
|
|
CopyTIdBytes(AMsg,LOfs,VTargetName,0,LLen);
|
|
//extract targetinfo
|
|
//Note that we should ignore TargetInfo if it is not required.
|
|
if VFlags and IdNTLMSSP_NEGOTIATE_TARGET_INFO > 0 then begin
|
|
// - security buffer
|
|
LLen := LittleEndianToHost( BytesToUInt16(AMsg,IdNTLM_TYPE2_TINFO_LEN_OFS));
|
|
LOfs := LittleEndianToHost( BytesToUInt32(AMsg,IdNTLM_TYPE2_TINFO_OFFSET_OFS));
|
|
// - the buffer itself
|
|
SetLength(VTargetInfo,LLen);
|
|
CopyTIdBytes(AMsg,LOfs,VTargetInfo,0,LLen);
|
|
end else begin
|
|
SetLength(VTargetInfo,0);
|
|
end;
|
|
//extract nonce
|
|
SetLength(VNonce,IdNTLM_NONCE_LEN);
|
|
CopyTIdBytes(AMsg,IdNTLM_TYPE2_NONCE_OFS,VNonce,0,IdNTLM_NONCE_LEN);
|
|
end;
|
|
|
|
function CreateData(const ABytes : TIdBytes; const AOffset : UInt32; var VBuffer : TIdBytes) : UInt32;
|
|
//returns the next buffer value
|
|
//adds security value ptr to Vbuffer
|
|
var
|
|
LLen : UInt16;
|
|
begin
|
|
LLen := Length(ABytes);
|
|
// - length
|
|
AddUInt16(VBuffer,LLen);
|
|
// - allocated space
|
|
AddUInt16(VBuffer,LLen);
|
|
// - offset
|
|
AddUInt32(VBuffer,AOffset);
|
|
Result := AOffset + Llen;
|
|
end;
|
|
|
|
function GenerateCNonce : TIdBytes;
|
|
begin
|
|
SetLength(Result,8);
|
|
Randomize;
|
|
Result[0] := Random(127)+1;
|
|
Result[1] := Random(127)+1;
|
|
Result[2] := Random(127)+1;
|
|
Result[3] := Random(127)+1;
|
|
Result[4] := Random(127)+1;
|
|
Result[5] := Random(127)+1;
|
|
Result[6] := Random(127)+1;
|
|
Result[7] := Random(127)+1;
|
|
end;
|
|
|
|
const
|
|
IdNTLM_IGNORE_TYPE_2_3_MASK = not
|
|
(IdNTLMSSP_TARGET_TYPE_DOMAIN or
|
|
IdNTLMSSP_TARGET_TYPE_SERVER or
|
|
IdNTLMSSP_TARGET_TYPE_SHARE);
|
|
|
|
function BuildType3Msg(const ADomain, AHost, AUsername, APassword : String;
|
|
const AFlags : UInt32; const AServerNonce : TIdBytes;
|
|
const ATargetName, ATargetInfo : TIdBytes;
|
|
const ALMCompatibility : UInt32 = 0) : TIdBytes;
|
|
var
|
|
LDom, LHost, LUser, LLMData, LNTLMData, LCNonce : TIdBytes;
|
|
llmhash, lntlmhash : TIdBytes;
|
|
ll_len, ln_len, ld_len, lh_len, lu_len : UInt16;
|
|
ll_ofs, ln_ofs, ld_ofs, lh_ofs, lu_ofs : UInt32;
|
|
LFlags : UInt32;
|
|
LEncoding: IIdTextEncoding;
|
|
begin
|
|
LFlags := AFlags and IdNTLM_IGNORE_TYPE_2_3_MASK;
|
|
if AFlags and IdNTLMSSP_REQUEST_TARGET > 0 then begin
|
|
LFlags := LFlags or IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
|
|
end;
|
|
if LFlags and IdNTLMSSP_NEGOTIATE_UNICODE <> 0 then begin
|
|
LEncoding := IndyTextEncoding_UTF16LE;
|
|
if LFlags and IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED > 0 then begin
|
|
if Length(ATargetName) > 0 then begin
|
|
LDom := ATargetName;
|
|
end else begin
|
|
LDom := LEncoding.GetBytes(UpperCase(ADomain));
|
|
end;
|
|
end;
|
|
if LFlags and IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED > 0 then begin
|
|
LHost := LEncoding.GetBytes(UpperCase(AHost));
|
|
end;
|
|
LUser := LEncoding.GetBytes(UpperCase(AUsername));
|
|
LEncoding := nil;
|
|
end else begin
|
|
if LFlags and IdNTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED > 0 then begin
|
|
LDom := ToBytes(UpperCase(ADomain));
|
|
end;
|
|
if LFlags and IdNTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED > 0 then begin
|
|
LHost := ToBytes(UpperCase(AHost));
|
|
end;
|
|
LUser := ToBytes(UpperCase(AUsername));
|
|
end;
|
|
if (ALMCompatibility < 3) and
|
|
(AFlags and IdNTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY > 0) then
|
|
begin
|
|
LCNonce := GenerateCNonce;
|
|
LNTLMData := CreateModNTLMv1Response(lntlmhash,AUserName,APassword,LCNonce,AServerNonce,LLMData);
|
|
// LLMData := IdNTLMv2.SetupLMv2Response(AUsername,ADomain,APassword,LCNonce,AServerNonce);
|
|
// LNTLMData := CreateNTLMv2Response(AUserName,ADomain,APassword,ATargetName,ATargetName, ATargetInfo,LCNonce);
|
|
end else begin
|
|
case ALMCompatibility of
|
|
0 :
|
|
begin
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NT_ONLY <> 0 then
|
|
begin
|
|
SetLength(LLMData,0);
|
|
end
|
|
else
|
|
begin
|
|
LLMData := SetupLMResponse(llmhash, APassword, AServerNonce);
|
|
end;
|
|
LNTLMData := CreateNTLMResponse(lntlmhash, APassword, AServerNonce);
|
|
end;
|
|
1 :
|
|
begin
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NT_ONLY <> 0 then
|
|
begin
|
|
SetLength(LLMData,0);
|
|
end
|
|
else
|
|
begin
|
|
LLMData := SetupLMResponse(llmhash, APassword, AServerNonce);
|
|
end;
|
|
LNTLMData := CreateNTLMResponse(lntlmhash,APassword, AServerNonce);
|
|
end;
|
|
2 : //Send NTLM response only
|
|
begin
|
|
LNTLMData := CreateNTLMResponse(lntlmhash,APassword, AServerNonce);
|
|
if AFlags and IdNTLMSSP_NEGOTIATE_NT_ONLY <> 0 then
|
|
begin
|
|
SetLength(LLMData,0);
|
|
end
|
|
else
|
|
begin
|
|
LLMData := LNTLMData;
|
|
// LLMData := SetupLMResponse(APassword, ANonce);
|
|
end;
|
|
end;
|
|
3,4,5 : //Send NTLMv2 response only
|
|
begin
|
|
// LFlags := LFlags and (not IdNTLMSSP_NEGOTIATE_NTLM);
|
|
LCNonce := GenerateCNonce;
|
|
LLMData := IdNTLMv2.SetupLMv2Response(llmhash,AUsername,ADomain,APassword,LCNonce,AServerNonce);
|
|
LNTLMData := CreateNTLMv2Response(lntlmhash,AUserName,ADomain,APassword,ATargetName,ATargetInfo,LCNonce,AServerNonce);
|
|
|
|
end;
|
|
// LCNonce := GenerateCNonce;
|
|
// LNTLMData := IdNTLMv2.CreateModNTLMv1Response(AUserName,APassword,LCNonce,ANonce,LLMData);
|
|
end;
|
|
end;
|
|
//calculations for security buffer pointers
|
|
//We do things this way because the security buffers are in a different order
|
|
//than the data buffers themselves. In theory, you could change it but it's
|
|
//not a good idea.
|
|
ll_len := Length(LLMData);
|
|
ln_len := Length(LNTLMData);
|
|
ld_len := Length(LDom);
|
|
lh_len := Length(LHost);
|
|
lu_len := Length(LUser);
|
|
|
|
LFlags := LFlags and (not IdNTLMSSP_NEGOTIATE_VERSION);
|
|
ld_ofs := IdNTLM_TYPE3_DOM_OFFSET;
|
|
|
|
{ if LFlags and IdNTLMSSP_NEGOTIATE_VERSION <> 0 then
|
|
begin
|
|
ld_ofs := IdNTLM_TYPE3_DOM_OFFSET + 8;
|
|
AppendByte(Result, IdNTLM_WINDOWS_MAJOR_VERSION_6);
|
|
AppendByte(Result, IdNTLM_WINDOWS_MINOR_VERSION_0);
|
|
AddUInt16(Result,IdNTLM_WINDOWS_BUILD_ORIG_VISTA);
|
|
AddUInt32(Result,$F);
|
|
end; }
|
|
lu_ofs := ld_len + ld_ofs;
|
|
lh_ofs := lu_len + lu_ofs;
|
|
ll_ofs := lh_len + lh_ofs;
|
|
ln_ofs := ll_len + ll_ofs;
|
|
|
|
SetLength(Result,0);
|
|
// 0 - signature
|
|
AppendBytes(Result,IdNTLM_SSP_SIG);
|
|
// 8 - type
|
|
AddUInt32(Result,IdNTLM_TYPE3_MARKER);
|
|
|
|
//12 - LM Response Security Buffer:
|
|
// - length
|
|
AddUInt16(Result,ll_len);
|
|
// - allocated space
|
|
AddUInt16(Result,ll_len);
|
|
// - offset
|
|
AddUInt32(Result,ll_ofs);
|
|
|
|
//20 - NTLM Response Security Buffer:
|
|
// - length
|
|
AddUInt16(Result,ln_len);
|
|
// - allocated space
|
|
AddUInt16(Result,ln_len);
|
|
// - offset
|
|
AddUInt32(Result,ln_ofs);
|
|
|
|
//28 - Domain Name Security Buffer: (target)
|
|
// - length
|
|
AddUInt16(Result,ld_len);
|
|
// - allocated space
|
|
AddUInt16(Result,ld_len);
|
|
// - offset
|
|
AddUInt32(Result,ld_ofs);
|
|
|
|
//36 - User Name Security Buffer:
|
|
// - length
|
|
AddUInt16(Result,lu_len);
|
|
// - allocated space
|
|
AddUInt16(Result,lu_len);
|
|
// - offset
|
|
AddUInt32(Result,lu_ofs);
|
|
|
|
//44 - Workstation Name (host) Security Buffer:
|
|
// - length
|
|
AddUInt16(Result,lh_len);
|
|
// - allocated space
|
|
AddUInt16(Result,lh_len);
|
|
// - offset
|
|
AddUInt32(Result,lh_ofs);
|
|
//52 - Session Key Security Buffer:
|
|
// - length
|
|
AddUInt16(Result,0);
|
|
// - allocated space
|
|
AddUInt16(Result,0);
|
|
// - offset
|
|
AddUInt32(Result,ln_ofs+ln_len);
|
|
//60 - Flags:
|
|
//The flags feild is strictly optional. About the only time it matters is
|
|
//with Datagram authentication.
|
|
AddUInt32(Result,LFlags);
|
|
if ld_len > 0 then
|
|
begin
|
|
//64 - Domain Name Data ("DOMAIN")
|
|
AppendBytes(Result,LDom);
|
|
end;
|
|
if lu_len >0 then
|
|
begin
|
|
//User Name Data ("user")
|
|
AppendBytes(Result,LUser);
|
|
end;
|
|
if lh_len > 0 then
|
|
begin
|
|
//Workstation Name Data (host)
|
|
AppendBytes(Result,LHost);
|
|
end;
|
|
//LM Response Data
|
|
AppendBytes(Result,LLMData);
|
|
//NTLM Response Data
|
|
AppendBytes(Result,LNTLMData);
|
|
end;
|
|
|
|
{$IFDEF TESTSUITE}
|
|
const
|
|
TEST_TARGETINFO : array [0..97] of byte = (
|
|
$02,$00,$0c,$00,$44,$00,$4f,$00,
|
|
$4d,$00,$41,$00,$49,$00,$4e,$00,
|
|
$01,$00,$0c,$00,$53,$00,$45,$00,
|
|
$52,$00,$56,$00,$45,$00,$52,$00,
|
|
$04,$00,$14,$00,$64,$00,$6f,$00,
|
|
$6d,$00,$61,$00,$69,$00,$6e,$00,
|
|
$2e,$00,$63,$00,$6f,$00,$6d,$00,
|
|
$03,$00,$22,$00,$73,$00,$65,$00,
|
|
$72,$00,$76,$00,$65,$00,$72,$00,
|
|
$2e,$00,$64,$00,$6f,$00,$6d,$00,
|
|
$61,$00,$69,$00,$6e,$00,$2e,$00,
|
|
$63,$00,$6f,$00,$6d,$00,$00,$00,
|
|
$00,$00);
|
|
|
|
{function StrToHex(const AStr : AnsiString) : AnsiString;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
Result := '';
|
|
for i := 1 to Length(AStr) do begin
|
|
Result := Result + IntToHex(Ord(AStr[i]),2)+ ' ';
|
|
end;
|
|
end; }
|
|
|
|
function BytesToHex(const ABytes : array of byte) : String;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := Low(ABytes) to High(ABytes) do
|
|
begin
|
|
Result := Result + IntToHex(ABytes[i],2)+ ' ';
|
|
end;
|
|
end;
|
|
|
|
procedure DoDavePortTests;
|
|
var
|
|
LNonce,LCNonce, lmhash : TIdBytes;
|
|
LMResp : TIdBytes;
|
|
LTargetINfo : TIdBytes;
|
|
Ltst : String;
|
|
begin
|
|
SetLength(LNonce,8);
|
|
LNonce[0] := $01;
|
|
LNonce[1] := $23;
|
|
LNonce[2] := $45;
|
|
LNonce[3] := $67;
|
|
LNonce[4] := $89;
|
|
LNonce[5] := $ab;
|
|
LNonce[6] := $cd;
|
|
LNonce[7] := $ef;
|
|
|
|
SetLength(LCNonce,8);
|
|
LCNonce[0] := $ff;
|
|
LCNonce[1] := $ff;
|
|
LCNonce[2] := $ff;
|
|
LCNonce[3] := $00;
|
|
LCNonce[4] := $11;
|
|
LCNonce[5] := $22;
|
|
LCNonce[6] := $33;
|
|
LCNonce[7] := $44;
|
|
|
|
SetLength(LTargetInfo,Length(TEST_TARGETINFO));
|
|
CopyTIdByteArray(TEST_TARGETINFO,0,LTargetInfo,0,Length(TEST_TARGETINFO));
|
|
|
|
if ToHex(SetupLMResponse(lmhash,'SecREt01',LNonce)) <> 'C337CD5CBD44FC9782A667AF6D427C6DE67C20C2D3E77C56' then
|
|
begin
|
|
DebugOutput(BytesToHex(LNonce));
|
|
raise Exception.Create('LM Response test failed');
|
|
end;
|
|
if ToHex(CreateNTLMResponse(lmhash,'SecREt01',LNonce)) <> '25A98C1C31E81847466B29B2DF4680F39958FB8C213A9CC6' then
|
|
begin
|
|
raise Exception.Create('NTLM Response test failed');
|
|
end;
|
|
if ToHex(CreateModNTLMv1Response(lmhash,'user','SecREt01',LCNonce,LNonce,LMResp)) <> '10D550832D12B2CCB79D5AD1F4EED3DF82ACA4C3681DD455' then
|
|
begin
|
|
raise Exception.Create ('NTLM2 Session Response failed');
|
|
end;
|
|
if ToHex(LMResp) <> 'FFFFFF001122334400000000000000000000000000000000' then
|
|
begin
|
|
raise Exception.Create ('LM Response for NTLM2 reponse failed');
|
|
end;
|
|
|
|
if ToHex(SetupLMv2Response(lmhash,'user','DOMAIN','SecREt01',LCNonce,LNonce)) <> 'D6E6152EA25D03B7C6BA6629C2D6AAF0FFFFFF0011223344' then
|
|
begin
|
|
raise Exception.Create ( 'LMv2 Response failed');
|
|
end;
|
|
Ltst := ToHex(InternalCreateNTLMv2Response(lmhash,'user','DOMAIN','SecREt01',LTargetInfo,UnixTimeToFileTime(1055844000),LCNonce,LNonce ));
|
|
if LTst <>
|
|
'CBABBCA713EB795D04C97ABC01EE4983'+
|
|
'01010000000000000090D336B734C301'+
|
|
'FFFFFF00112233440000000002000C00' +
|
|
'44004F004D00410049004E0001000C00' +
|
|
'53004500520056004500520004001400' +
|
|
'64006F006D00610069006E002E006300' +
|
|
'6F006D00030022007300650072007600' +
|
|
'650072002E0064006F006D0061006900' +
|
|
'6E002E0063006F006D00000000000000' +
|
|
'0000' then
|
|
begin
|
|
raise Exception.Create ('NTLMv2 Response failed' );
|
|
end;
|
|
end;
|
|
|
|
function ConstArray(const AArray : array of byte) : TIdBytes;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
SetLength(Result,0);
|
|
for i := Low(AArray) to High(AArray) do begin
|
|
AppendByte(Result,AArray[i]);
|
|
end;
|
|
end;
|
|
|
|
|
|
{
|
|
Microsoft tests
|
|
User
|
|
|
|
0000000: 55 00 73 00 65 00 72 00 U.s.e.r.
|
|
0000000: 55 00 53 00 45 00 52 00 U.S.E.R.
|
|
0000000: 55 73 65 72 User
|
|
|
|
UserDom
|
|
0000000: 44 00 6f 00 6d 00 61 00 69 00 6e 00 D.o.m.a.i.n
|
|
|
|
Password
|
|
0000000: 50 00 61 00 73 00 73 00 77 00 6f 00 72 00 64 00 P.a.s.s.w.o.r.d.
|
|
0000000: 50 41 53 53 57 4f 52 44 00 00 00 00 00 00 PASSWORD......
|
|
|
|
Server Name
|
|
00000000: 53 00 65 00 72 00 76 00 65 00 72 00 S.e.r.v.e.r.
|
|
|
|
Workstation Name
|
|
0000000: 43 00 4f 00 4d 00 50 00 55 00 54 00 45 00 52 00 C.O.M.P.U.T.E.R.
|
|
|
|
Random Session Key
|
|
0000000: 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 UUUUUUUUUUUUUUUU
|
|
|
|
Time
|
|
0000000: 00 00 00 00 00 00 00 00 ........
|
|
|
|
Client Challange
|
|
0000000: aa aa aa aa aa aa aa aa ........
|
|
|
|
Server Challange
|
|
0000000: 01 23 45 67 89 ab cd ef .#Eg..═.
|
|
|
|
4.2.2 NTLM v1 Authentication
|
|
|
|
# NTLMSSP_NEGOTIATE_KEY_EXCH
|
|
# NTLMSSP_NEGOTIATE_56
|
|
# NTLMSSP_NEGOTIATE_128
|
|
# NTLMSSP_NEGOTIATE_VERSION
|
|
# NTLMSSP_TARGET_TYPE_SERVER
|
|
# NTLMSSP_NEGOTIATE_ALWAYS_SIGN
|
|
# NTLM NTLMSSP_NEGOTIATE_NTLM
|
|
# NTLMSSP_NEGOTIATE_SEAL
|
|
# NTLMSSP_NEGOTIATE_SIGN
|
|
# NTLM_NEGOTIATE_OEM
|
|
# NTLMSSP_NEGOTIATE_UNICOD
|
|
|
|
NTLMv1 data flags
|
|
33 82 02 e2
|
|
}
|
|
|
|
procedure DoMSTests;
|
|
var
|
|
LFlags : UInt32;
|
|
LEncoding: IIdTextEncoding;
|
|
begin
|
|
LFlags := IdNTLMSSP_NEGOTIATE_KEY_EXCH or
|
|
IdNTLMSSP_NEGOTIATE_56 or IdNTLMSSP_NEGOTIATE_128 or
|
|
IdNTLMSSP_NEGOTIATE_VERSION or IdNTLMSSP_TARGET_TYPE_SERVER or
|
|
IdNTLMSSP_NEGOTIATE_ALWAYS_SIGN or IdNTLMSSP_NEGOTIATE_NTLM or
|
|
IdNTLMSSP_NEGOTIATE_SEAL or IdNTLMSSP_NEGOTIATE_SIGN or
|
|
IdNTLM_NEGOTIATE_OEM or IdNTLMSSP_NEGOTIATE_UNICODE;
|
|
if ToHex(ToBytes(LFlags))<>'338202E2' then
|
|
begin
|
|
raise Exception.Create('MS Tests failed - NTLMv1 data flags');
|
|
end;
|
|
// if ToHex(NTOWFv1('Password') ) <> UpperCase('e52cac67419a9a224a3b108f3fa6cb6d') then
|
|
LEncoding := IndyTextEncoding_ASCII;
|
|
if ToHex(LMOWFv1(
|
|
LEncoding.GetBytes(Uppercase( 'Password')),
|
|
LEncoding.GetBytes(Uppercase( 'User')),
|
|
LEncoding.GetBytes(Uppercase( 'Domain')))) <>
|
|
Uppercase('e52cac67419a9a224a3b108f3fa6cb6d') then
|
|
|
|
begin
|
|
raise Exception.Create('MS Tests failed - LMOWFv1');
|
|
end;
|
|
end;
|
|
|
|
procedure TestNTLM;
|
|
begin
|
|
// DoMSTests;
|
|
DoDavePortTests;
|
|
end;
|
|
|
|
|
|
{$ENDIF}
|
|
|
|
initialization
|
|
SetLength(IdNTLM_SSP_SIG,8);
|
|
IdNTLM_SSP_SIG[0] :=$4e; //N
|
|
IdNTLM_SSP_SIG[1] :=$54; //T
|
|
IdNTLM_SSP_SIG[2] :=$4c; //L
|
|
IdNTLM_SSP_SIG[3] :=$4d; //M
|
|
IdNTLM_SSP_SIG[4] :=$53; //S
|
|
IdNTLM_SSP_SIG[5] :=$53; //S
|
|
IdNTLM_SSP_SIG[6] :=$50; //P
|
|
IdNTLM_SSP_SIG[7] :=$00; //#00
|
|
UnixEpoch := EncodeDate(1970, 1, 1);
|
|
end.
|