IdAuthenticationDigest.pas 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. 2005-04-22 BTaylor
  18. Fixed AV from incorrect object being freed
  19. Fixed memory leak
  20. Improved parsing
  21. Rev 1.6 1/3/05 4:48:24 PM RLebeau
  22. Removed reference to StrUtils unit, not being used.
  23. Rev 1.5 12/1/2004 1:57:50 PM JPMugaas
  24. Updated with some code posted by:
  25. Interpulse Systeemontwikkeling
  26. Interpulse Automatisering B.V.
  27. http://www.interpulse.nl
  28. Rev 1.1 2004.11.25 06:17:00 PM EDMeester
  29. Rev 1.0 2002.11.12 10:30:44 PM czhower
  30. }
  31. unit IdAuthenticationDigest;
  32. {
  33. Implementation of the digest authentication as specified in RFC2617
  34. rev 1.1: Edwin Meester ([email protected])
  35. Author: Doychin Bondzhev ([email protected])
  36. Copyright: (c) Chad Z. Hower and The Winshoes Working Group.
  37. }
  38. interface
  39. {$i IdCompilerDefines.inc}
  40. uses
  41. Classes,
  42. IdAuthentication,
  43. IdException,
  44. IdGlobal,
  45. IdHashMessageDigest;
  46. type
  47. EIdInvalidAlgorithm = class(EIdException);
  48. TIdDigestAuthentication = class(TIdAuthentication)
  49. protected
  50. FRealm: String;
  51. FStale: Boolean;
  52. FOpaque: String;
  53. FDomain: TStringList;
  54. FNonce: String;
  55. FNonceCount: integer;
  56. FAlgorithm: String;
  57. FMethod, FUri: string; //needed for digest
  58. FEntityBody: String; //needed for auth-int, Somebody make this nice :D
  59. FQopOptions: TStringList;
  60. FOther: TStringList;
  61. function DoNext: TIdAuthWhatsNext; override;
  62. function GetSteps: Integer; override;
  63. public
  64. constructor Create; override;
  65. destructor Destroy; override;
  66. function Authentication: String; override;
  67. procedure SetRequest(const AMethod, AUri: String); override;
  68. property Method: String read FMethod write FMethod;
  69. property Uri: String read FUri write FUri;
  70. property EntityBody: String read FEntityBody write FEntityBody;
  71. end;
  72. // RLebeau 4/17/10: this forces C++Builder to link to this unit so
  73. // RegisterAuthenticationMethod can be called correctly at program startup...
  74. {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT}
  75. {$HPPEMIT LINKUNIT}
  76. {$ELSE}
  77. {$HPPEMIT '#pragma link "IdAuthenticationDigest"'}
  78. {$ENDIF}
  79. implementation
  80. uses
  81. IdGlobalProtocols, IdFIPS, IdHash, IdResourceStringsProtocols,
  82. SysUtils;
  83. { TIdDigestAuthentication }
  84. constructor TIdDigestAuthentication.Create;
  85. begin
  86. inherited Create;
  87. CheckMD5Permitted;
  88. end;
  89. destructor TIdDigestAuthentication.Destroy;
  90. begin
  91. FreeAndNil(FDomain);
  92. FreeAndNil(FQopOptions);
  93. inherited Destroy;
  94. end;
  95. procedure TIdDigestAuthentication.SetRequest(const AMethod, AUri: String);
  96. begin
  97. FMethod := AMethod;
  98. FUri := AUri;
  99. end;
  100. function TIdDigestAuthentication.Authentication: String;
  101. function Hash(const S: String): String;
  102. var
  103. LMD5: TIdHashMessageDigest5;
  104. begin
  105. LMD5 := TIdHashMessageDigest5.Create;
  106. try
  107. Result := LowerCase(LMD5.HashStringAsHex(S));
  108. finally
  109. LMD5.Free;
  110. end;
  111. end;
  112. var
  113. LA1, LA2, LCNonce, LResponse, LQop: string;
  114. begin
  115. Result := ''; {do not localize}
  116. case FCurrentStep of
  117. 0:
  118. begin
  119. //Just be safe with this one
  120. Result := 'Digest'; {do not localize}
  121. end;
  122. 1:
  123. begin
  124. //Build request
  125. LCNonce := Hash(DateTimeToStr(Now));
  126. LA1 := Username + ':' + FRealm + ':' + Password; {do not localize}
  127. if TextIsSame(FAlgorithm, 'MD5-sess') then begin {do not localize}
  128. LA1 := Hash(LA1) + ':' + FNonce + ':' + LCNonce; {do not localize}
  129. end;
  130. LA2 := FMethod + ':' + FUri; {do not localize}
  131. //Qop header present
  132. if FQopOptions.IndexOf('auth-int') > -1 then begin {do not localize}
  133. LQop := 'auth-int'; {do not localize}
  134. LA2 := LA2 + ':' + Hash(FEntityBody); {do not localize}
  135. end
  136. else if FQopOptions.IndexOf('auth') > -1 then begin {do not localize}
  137. LQop := 'auth'; {do not localize}
  138. end;
  139. if LQop <> '' then begin
  140. LResponse := IntToHex(FNonceCount, 8) + ':' + LCNonce + ':' + LQop + ':'; {do not localize}
  141. end;
  142. LResponse := Hash( Hash(LA1) + ':' + FNonce + ':' + LResponse + Hash(LA2) ); {do not localize}
  143. Result := 'Digest ' + {do not localize}
  144. 'username="' + Username + '", ' + {do not localize}
  145. 'realm="' + FRealm + '", ' + {do not localize}
  146. 'nonce="' + FNonce + '", ' + {do not localize}
  147. 'algorithm="' + FAlgorithm + '", ' + {do not localize}
  148. 'uri="' + FUri + '", ';
  149. //Qop header present
  150. if LQop <> '' then begin {do not localize}
  151. Result := Result +
  152. 'qop="' + LQop + '", ' + {do not localize}
  153. 'nc=' + IntToHex(FNonceCount, 8) + ', ' + {do not localize}
  154. 'cnonce="' + LCNonce + '", '; {do not localize}
  155. end;
  156. Result := Result + 'response="' + LResponse + '"'; {do not localize}
  157. if FOpaque <> '' then begin
  158. Result := Result + ', opaque="' + FOpaque + '"'; {do not localize}
  159. end;
  160. Inc(FNonceCount);
  161. FCurrentStep := 0;
  162. end;
  163. end;
  164. end;
  165. // TODO: move this to the IdAuthentication unit, or maybe the IdGlobalProtocols unit...
  166. function Unquote(var S: String): String;
  167. var
  168. I, Len: Integer;
  169. begin
  170. Len := Length(S);
  171. I := 2; // skip first quote
  172. while I <= Len do
  173. begin
  174. if S[I] = '"' then begin
  175. Break;
  176. end;
  177. if S[I] = '\' then begin
  178. Inc(I);
  179. end;
  180. Inc(I);
  181. end;
  182. Result := Copy(S, 2, I-2);
  183. S := Copy(S, I+1, MaxInt);
  184. // TODO: use a PosEx() loop instead
  185. {
  186. I := Pos('\', Result);
  187. while I <> 0 do
  188. begin
  189. IdDelete(Result, I, 1);
  190. I := PosEx('\', Result, I+1);
  191. end;
  192. }
  193. Len := Length(Result);
  194. I := 1;
  195. while I <= Len do
  196. begin
  197. if Result[I] = '\' then begin
  198. IdDelete(Result, I, 1);
  199. end;
  200. Inc(I);
  201. end;
  202. end;
  203. function TIdDigestAuthentication.DoNext: TIdAuthWhatsNext;
  204. var
  205. S, LName, LValue, LTempNonce: String;
  206. LParams: TStringList;
  207. begin
  208. Result := wnDoRequest;
  209. case FCurrentStep of
  210. 0:
  211. begin
  212. //gather info
  213. if not Assigned(FDomain) then begin
  214. FDomain := TStringList.Create;
  215. end else begin
  216. FDomain.Clear;
  217. end;
  218. if not Assigned(FQopOptions) then begin
  219. FQopOptions := TStringList.Create;
  220. end else begin
  221. FQopOptions.Clear;
  222. end;
  223. S := ReadAuthInfo('Digest'); {do not localize}
  224. Fetch(S);
  225. LParams := TStringList.Create;
  226. try
  227. {$IFDEF HAS_TStringList_CaseSensitive}
  228. LParams.CaseSensitive := False;
  229. {$ENDIF}
  230. while Length(S) > 0 do begin
  231. // RLebeau: Apache sends a space after each comma, but IIS does not!
  232. LName := Trim(Fetch(S, '=')); {do not localize}
  233. S := TrimLeft(S);
  234. if TextStartsWith(S, '"') then begin {do not localize}
  235. LValue := Unquote(S); {do not localize}
  236. Fetch(S, ','); {do not localize}
  237. end else begin
  238. LValue := Trim(Fetch(S, ','));
  239. end;
  240. IndyAddPair(LParams, LName, LValue);
  241. S := TrimLeft(S);
  242. end;
  243. FRealm := LParams.Values['realm']; {do not localize}
  244. LTempNonce := LParams.Values['nonce']; {do not localize}
  245. if FNonce <> LTempNonce then
  246. begin
  247. FNonceCount := 1;
  248. FNonce := LTempNonce;
  249. end;
  250. S := LParams.Values['domain']; {do not localize}
  251. while Length(S) > 0 do begin
  252. FDomain.Add(Fetch(S));
  253. end;
  254. FOpaque := LParams.Values['opaque']; {do not localize}
  255. FStale := TextIsSame(LParams.Values['stale'], 'True'); {do not localize}
  256. FAlgorithm := LParams.Values['algorithm']; {do not localize}
  257. FQopOptions.CommaText := LParams.Values['qop']; {do not localize}
  258. if FAlgorithm = '' then begin
  259. FAlgorithm := 'MD5'; {do not localize}
  260. end
  261. else if PosInStrArray(FAlgorithm, ['MD5', 'MD5-sess'], False) = -1 then begin {do not localize}
  262. raise EIdInvalidAlgorithm.Create(RSHTTPAuthInvalidHash);
  263. end;
  264. finally
  265. FreeAndNil(LParams);
  266. end;
  267. if Length(Username) > 0 then begin
  268. FCurrentStep := 1;
  269. Result := wnDoRequest;
  270. end else begin
  271. Result := wnAskTheProgram;
  272. end;
  273. end;
  274. end;
  275. end;
  276. function TIdDigestAuthentication.GetSteps: Integer;
  277. begin
  278. Result := 1;
  279. end;
  280. initialization
  281. RegisterAuthenticationMethod('Digest', TIdDigestAuthentication); {do not localize}
  282. finalization
  283. UnregisterAuthenticationMethod('Digest'); {do not localize}
  284. end.