OverbyteIcsNtlmMsgs.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. {*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2. Author: François PIETTE
  3. Creation: Jan 01, 2004
  4. Version: 6.00
  5. Description: This is an implementation of the NTLM authentification
  6. messages used within HTTP protocol (client side).
  7. NTLM protocol documentation can be found at:
  8. http://davenport.sourceforge.net/ntlm.html
  9. Credit: This code is based on a work by Diego Ariel Degese
  10. <crapher@utenet.com.ar>. The code was not working at all but
  11. helped me a lot starting with NTLM.
  12. Csonka Tibor <bee@rawbite.ro> worked a lot on my original code,
  13. fixing it and making it work properly.
  14. EMail: francois.piette@overbyte.be http://www.overbyte.be
  15. Support: Use the mailing list twsocket@elists.org
  16. Follow "support" link at http://www.overbyte.be for subscription.
  17. Legal issues: Copyright (C) 2004-2006 by François PIETTE
  18. Rue de Grady 24, 4053 Embourg, Belgium. Fax: +32-4-365.74.56
  19. <francois.piette@overbyte.be>
  20. This software is provided 'as-is', without any express or
  21. implied warranty. In no event will the author be held liable
  22. for any damages arising from the use of this software.
  23. Permission is granted to anyone to use this software for any
  24. purpose, including commercial applications, and to alter it
  25. and redistribute it freely, subject to the following
  26. restrictions:
  27. 1. The origin of this software must not be misrepresented,
  28. you must not claim that you wrote the original software.
  29. If you use this software in a product, an acknowledgment
  30. in the product documentation would be appreciated but is
  31. not required.
  32. 2. Altered source versions must be plainly marked as such, and
  33. must not be misrepresented as being the original software.
  34. 3. This notice may not be removed or altered from any source
  35. distribution.
  36. 4. You must register this software by sending a picture postcard
  37. to the author. Use a nice stamp and mention your name, street
  38. address, EMail address and any comment you like to say.
  39. Updates:
  40. Mar 26, 2006 V6.00 New version 6 started
  41. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  42. unit OverbyteIcsNtlmMsgs;
  43. {$I OverbyteIcsDefs.inc}
  44. {$R-}
  45. {$Q-}
  46. interface
  47. uses
  48. SysUtils, OverbyteIcsDES, OverbyteIcsMD4, OverbyteIcsMimeUtils;
  49. const
  50. IcsNtlmMsgsVersion = 600;
  51. CopyRight : String = ' IcsNtlmMsgs (c) 2004-2006 F. Piette V6.00 ';
  52. const
  53. Flags_Negotiate_Unicode = $00000001;
  54. Flags_Negotiate_OEM = $00000002;
  55. Flags_Request_Target = $00000004;
  56. Flags_Negotiate_8 = $00000008; // unknown
  57. Flags_Negotiate_Sign = $00000010;
  58. Flags_Negotiate_Seal = $00000020;
  59. Flags_Negotiate_Datagram_Style = $00000040;
  60. Flags_Negotiate_LAN_Manager_Key = $00000080;
  61. Flags_Negotiate_Netware = $00000100;
  62. Flags_Negotiate_NTLM = $00000200;
  63. Flags_Negotiate_400 = $00000400; // unknown
  64. Flags_Negotiate_800 = $00000800; // unknown
  65. Flags_Negotiate_Domain_Supplied = $00001000;
  66. Flags_Negotiate_Workstation_Supplied = $00002000;
  67. Flags_Negotiate_Local_Call = $00004000;
  68. Flags_Negotiate_Allways_Sign = $00008000;
  69. Flags_Target_Type_Domain = $00010000;
  70. Flags_Target_Type_Server = $00020000;
  71. Flags_Target_Type_Share = $00040000;
  72. Flags_Negotiate_NTLM2_Key = $00080000;
  73. Flags_Request_Init_Response = $00100000;
  74. Flags_Request_Accept_Response = $00200000;
  75. Flags_Request_Non_NT_Session_Key = $00400000;
  76. Flags_Negotiate_Target_Info = $00800000;
  77. Flags_Negotiate_1000000 = $01000000; // unknown
  78. Flags_Negotiate_2000000 = $02000000; // unknown
  79. Flags_Negotiate_4000000 = $04000000; // unknown
  80. Flags_Negotiate_8000000 = $08000000; // unknown
  81. Flags_Negotiate_10000000 = $10000000; // unknown
  82. Flags_Negotiate_128_Bit_Encryption = $20000000;
  83. Flags_Negotiate_Key_Exchange = $40000000;
  84. Flags_Negotiate_56_Bit_Encryption = $80000000;
  85. // target information block types
  86. TIB_Type_Server = 1;
  87. TIB_Type_Domain = 2;
  88. TIB_Type_DNS_Full = 3;
  89. TIB_Type_DNS_Domain = 4;
  90. TIB_Type_ask_microsoft_or_god = 5;
  91. type
  92. // security buffer
  93. TNTLM_SecBuff = record
  94. Length : Word;
  95. Space : Word;
  96. Offset : Cardinal;
  97. end;
  98. // first message
  99. TNTLM_Message1 = record
  100. Protocol : TArrayOf8Bytes;
  101. MsgType : Cardinal;
  102. Flags : Cardinal;
  103. Domain : TNTLM_SecBuff;
  104. Host : TNTLM_SecBuff;
  105. end;
  106. // second message
  107. TNTLM_Message2 = record
  108. Protocol : TArrayOf8Bytes;
  109. MsgType : Cardinal;
  110. TargetName : TNTLM_SecBuff;
  111. Flags : Cardinal;
  112. Challenge : TArrayOf8Bytes;
  113. Context : TArrayOf8Bytes; // reserved, not used
  114. TargetInfo : TNTLM_SecBuff;
  115. end;
  116. // interesting information from message 2
  117. TNTLM_Msg2_Info = record
  118. SrvRespOk : boolean; // server response was ok ?
  119. Target : WideString;
  120. Domain : WideString;
  121. Server : WideString;
  122. Challenge : TArrayOf8Bytes;
  123. end;
  124. // third message
  125. TNTLM_Message3 = record
  126. Protocol : TArrayOf8Bytes;
  127. MsgType : Cardinal;
  128. LM : TNTLM_SecBuff;
  129. NTLM : TNTLM_SecBuff;
  130. Domain : TNTLM_SecBuff;
  131. User : TNTLM_SecBuff;
  132. Host : TNTLM_SecBuff;
  133. SessionKey : TNTLM_SecBuff;
  134. Flags : Cardinal;
  135. end;
  136. function NtlmGetMessage1(const AHost, ADomain: String): String;
  137. function NtlmGetMessage2(const AServerReply: String): TNTLM_Msg2_Info;
  138. function NtlmGetMessage3(const ADomain, AHost, AUser, APassword: String; AChallenge: TArrayOf8Bytes): String;
  139. implementation
  140. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  141. { Convert an text to a unicode text stored }
  142. function Unicode(const AData: String): String;
  143. var
  144. I, J : Integer;
  145. begin
  146. SetLength(Result, Length(AData) * 2);
  147. J := 1;
  148. for I := 1 to Length(AData) do begin
  149. Result[J] := AData[I];
  150. Inc(J);
  151. Result[J] := #0;
  152. Inc(J);
  153. end;
  154. end;
  155. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  156. function DesEcbEncrypt(
  157. const AKey : String; // Must be exactly 8 characters
  158. const AData : TArrayOf8Bytes): String;
  159. var
  160. i, j, t, bit : Integer;
  161. XKey : TArrayOf8Bytes;
  162. begin
  163. XKey[0] := Byte(AKey[1]);
  164. XKey[1] := ((Byte(AKey[1]) shl 7) and $FF) or (Byte(AKey[2]) shr 1);
  165. XKey[2] := ((Byte(AKey[2]) shl 6) and $FF) or (Byte(AKey[3]) shr 2);
  166. XKey[3] := ((Byte(AKey[3]) shl 5) and $FF) or (Byte(AKey[4]) shr 3);
  167. XKey[4] := ((Byte(AKey[4]) shl 4) and $FF) or (Byte(AKey[5]) shr 4);
  168. XKey[5] := ((Byte(AKey[5]) shl 3) and $FF) or (Byte(AKey[6]) shr 5);
  169. XKey[6] := ((Byte(AKey[6]) shl 2) and $FF) or (Byte(AKey[7]) shr 6);
  170. XKey[7] := ((Byte(AKey[7]) shl 1) and $FF);
  171. for i := 0 to 7 do begin
  172. for j := 1 to 7 do begin
  173. bit := 0;
  174. t := XKey[i] shl j;
  175. bit := (t xor bit) and 1;
  176. end;
  177. XKey[i] := Byte((XKey[i] and $FE) or bit);
  178. end;
  179. SetLength(Result, 8);
  180. DES(AData, PArrayOf8Bytes(@Result[1])^, XKey, TRUE);
  181. end;
  182. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  183. function NtlmGetLMHash(
  184. const APassword : String;
  185. const ANonce : TArrayOf8Bytes): String;
  186. const
  187. Magic: TArrayOf8Bytes = ($4B, $47, $53, $21, $40, $23, $24, $25);
  188. var
  189. I : Integer;
  190. Pass : String;
  191. PassHash : String;
  192. begin
  193. Pass := Copy(UpperCase(APassword), 1, 14);
  194. for I := Length(APassword) to 14 do
  195. Pass := Pass + #0;
  196. PassHash := DesEcbEncrypt(Copy(Pass, 1, 7), Magic) +
  197. DesEcbEncrypt(Copy(Pass, 8, 7), Magic) +
  198. #0#0#0#0#0;
  199. Result := DesEcbEncrypt(Copy(PassHash, 1, 7), ANonce) +
  200. DesEcbEncrypt(Copy(PassHash, 8, 7), ANonce) +
  201. DesEcbEncrypt(Copy(PassHash, 15, 7), ANonce);
  202. end;
  203. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  204. function NtlmGetNTHash(
  205. const APassword : String;
  206. const ANonce : TArrayOf8Bytes): String;
  207. var
  208. PassHash: String;
  209. begin
  210. PassHash := MD4String(Unicode(APassword)) + #0#0#0#0#0;
  211. Result := DesEcbEncrypt(Copy(PassHash, 1, 7), ANonce) +
  212. DesEcbEncrypt(Copy(PassHash, 8, 7), ANonce) +
  213. DesEcbEncrypt(Copy(PassHash, 15, 7), ANonce);
  214. end;
  215. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  216. function NtlmGetMessage1(const AHost, ADomain: String): String;
  217. var
  218. Msg : TNTLM_Message1;
  219. MessageAux : String;
  220. Host : String;
  221. Domain : String;
  222. begin
  223. Host := UpperCase(AHost);
  224. Domain := UpperCase(ADomain);
  225. FillChar(Msg, SizeOf(Msg), #0);
  226. // signature
  227. Move('NTLMSSP' + #0, Msg.Protocol, 8);
  228. // message type (negotiate)
  229. Msg.MsgType := 1;
  230. // prepare flags
  231. Msg.Flags := Flags_Negotiate_Unicode or
  232. Flags_Negotiate_OEM or
  233. Flags_Request_Target or
  234. Flags_Negotiate_NTLM or
  235. Flags_Negotiate_Allways_Sign { or
  236. Flags_Negotiate_NTLM2_Key};
  237. // host and/or domain supplied ?
  238. // host
  239. if Length(Host) > 0 then
  240. Msg.Flags := Msg.Flags or Flags_Negotiate_Workstation_Supplied;
  241. // domain
  242. if Length(Domain) > 0 then
  243. Msg.Flags := Msg.Flags or Flags_Negotiate_Domain_Supplied;
  244. // host
  245. Msg.Host.Length := Length(Host);
  246. Msg.Host.Space := Msg.Host.Length;
  247. if Msg.Host.Length > 0 then
  248. Msg.Host.Offset := $20
  249. else
  250. Msg.Host.Offset := 0;
  251. // domain
  252. Msg.Domain.Length := Length(Domain);
  253. Msg.Domain.Space := Msg.Domain.Length;
  254. if Msg.Domain.Length > 0 then
  255. Msg.Domain.Offset := Msg.Host.Offset + Msg.Domain.Length
  256. else
  257. Msg.Domain.Offset := 0;
  258. SetLength(MessageAux, SizeOf(Msg));
  259. Move(Msg, MessageAux[1], SizeOf(Msg));
  260. MessageAux := MessageAux + Host + Domain;
  261. Result := Base64Encode(MessageAux);
  262. end;
  263. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  264. function NtlmGetMessage2(const AServerReply: String): TNTLM_Msg2_Info;
  265. var
  266. Msg : TNTLM_Message2;
  267. MsgInfo : TNTLM_Msg2_Info;
  268. InfoType : Word;
  269. InfoLength : Word;
  270. InfoStr : WideString;
  271. I : Integer;
  272. NTLMReply : String;
  273. begin
  274. NTLMReply := Base64Decode(AServerReply);
  275. if Length(AServerReply) > 0 then begin
  276. // we have a response
  277. MsgInfo.SrvRespOk := TRUE;
  278. Move(NTLMReply[1], Msg, SizeOf(Msg));
  279. // extract target
  280. MsgInfo.Target := Copy(NTLMReply, Msg.TargetName.Offset + 1,
  281. Msg.TargetName.Length);
  282. // extract challenge
  283. Move(Msg.Challenge, MsgInfo.Challenge, SizeOf(Msg.Challenge));
  284. // let's extract the other information
  285. I := Msg.TargetInfo.Offset + 1;
  286. // loop through target information blocks
  287. while I < Length(NTLMReply) do begin
  288. // extract type
  289. Move(NTLMReply[I], InfoType, SizeOf(InfoType));
  290. I := I + SizeOf(InfoType);
  291. // extract length
  292. Move(NTLMReply[I], InfoLength, SizeOf(InfoLength));
  293. I := I + SizeOf(InfoLength);
  294. // terminator block ?
  295. if (InfoType = 0) and (InfoLength = 0) then
  296. break
  297. else begin
  298. // extract information
  299. InfoStr := Copy(NTLMReply, I, InfoLength);
  300. if InfoType = TIB_Type_Server then
  301. MsgInfo.Server := InfoStr
  302. else if InfoType = TIB_Type_Domain then
  303. MsgInfo.Domain := InfoStr;
  304. // jump to next block
  305. I := I + InfoLength;
  306. end;
  307. end;
  308. end
  309. else begin
  310. // no response from server
  311. MsgInfo.SrvRespOk := FALSE;
  312. end;
  313. Result := MsgInfo;
  314. end;
  315. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  316. function NtlmGetMessage3(const ADomain, AHost, AUser,
  317. APassword: String; AChallenge: TArrayOf8Bytes): String;
  318. var
  319. Msg : TNTLM_Message3;
  320. MessageAux : String;
  321. LM_Resp : String[30];
  322. NT_Resp : String[30];
  323. UDomain : String;
  324. UHost : String;
  325. UUser : String;
  326. begin
  327. UDomain := Unicode(ADomain);
  328. UHost := Unicode(AHost);
  329. UUser := Unicode(AUser);
  330. FillChar(Msg, SizeOf(Msg), #0);
  331. Move('NTLMSSP' + #0, Msg.Protocol, 8);
  332. Msg.MsgType := 3;
  333. // prepare domain
  334. Msg.Domain.Length := Length(UDomain);
  335. Msg.Domain.Space := Msg.Domain.Length;
  336. Msg.Domain.Offset := $40;
  337. // prepare user
  338. Msg.User.Length := Length(UUser);
  339. Msg.User.Space := Msg.User.Length;
  340. Msg.User.Offset := Msg.Domain.Offset + Msg.Domain.Length;
  341. // preapre host
  342. Msg.Host.Length := Length(UHost);
  343. Msg.Host.Space := Msg.Host.Length;
  344. Msg.Host.Offset := Msg.User.Offset + Msg.User.Length;
  345. // prepare LM and NTLM responses
  346. Msg.LM.Length := $18;
  347. Msg.LM.Space := Msg.LM.Length;
  348. Msg.LM.Offset := Msg.Host.Offset + Msg.Host.Length;
  349. Msg.NTLM.Length := $18;
  350. Msg.NTLM.Space := Msg.LM.Length;
  351. Msg.NTLM.Offset := Msg.LM.Offset + Msg.LM.Length;
  352. // no session key
  353. Msg.SessionKey.Length := 0;
  354. Msg.SessionKey.Space := 0;
  355. Msg.SessionKey.Offset := 0;
  356. // prepare flags
  357. Msg.Flags := Flags_Negotiate_Unicode or
  358. Flags_Request_Target or
  359. Flags_Negotiate_NTLM or
  360. Flags_Negotiate_Allways_Sign {or
  361. Flags_Negotiate_NTLM2_Key};
  362. LM_Resp := NtlmGetLMHash(APassword, AChallenge);
  363. NT_Resp := NtlmGetNTHash(APassword, AChallenge);
  364. SetLength(MessageAux, SizeOf(Msg));
  365. Move(Msg, MessageAux[1], SizeOf(Msg));
  366. MessageAux := MessageAux + UDomain + UUser + UHost + LM_Resp + NT_Resp;
  367. Result := Base64Encode(MessageAux);
  368. end;
  369. {* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
  370. end.