{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { 2005-04-22 BTaylor Fixed AV from incorrect object being freed Fixed memory leak Improved parsing Rev 1.6 1/3/05 4:48:24 PM RLebeau Removed reference to StrUtils unit, not being used. Rev 1.5 12/1/2004 1:57:50 PM JPMugaas Updated with some code posted by: Interpulse Systeemontwikkeling Interpulse Automatisering B.V. http://www.interpulse.nl Rev 1.1 2004.11.25 06:17:00 PM EDMeester Rev 1.0 2002.11.12 10:30:44 PM czhower } unit IdAuthenticationDigest; { Implementation of the digest authentication as specified in RFC2617 rev 1.1: Edwin Meester (systeemontwikkeling@interpulse.nl) Author: Doychin Bondzhev (doychin@dsoft-bg.com) Copyright: (c) Chad Z. Hower and The Winshoes Working Group. } interface {$i IdCompilerDefines.inc} uses Classes, IdAuthentication, IdException, IdGlobal, IdHashMessageDigest, IdHeaderList; type EIdInvalidAlgorithm = class(EIdException); TIdDigestAuthentication = class(TIdAuthentication) protected FRealm: String; FStale: Boolean; FOpaque: String; FDomain: TStringList; FNonce: String; FNonceCount: integer; FAlgorithm: String; FMethod, FUri: string; //needed for digest FEntityBody: String; //needed for auth-int, Somebody make this nice :D FQopOptions: TStringList; FOther: TStringList; function DoNext: TIdAuthWhatsNext; override; function GetSteps: Integer; override; public constructor Create; override; destructor Destroy; override; function Authentication: String; override; procedure SetRequest(const AMethod, AUri: String); override; property Method: String read FMethod write FMethod; property Uri: String read FUri write FUri; property EntityBody: String read FEntityBody write FEntityBody; end; // RLebeau 4/17/10: this forces C++Builder to link to this unit so // RegisterAuthenticationMethod can be called correctly at program startup... {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT} {$HPPEMIT LINKUNIT} {$ELSE} {$HPPEMIT '#pragma link "IdAuthenticationDigest"'} {$ENDIF} implementation uses IdGlobalProtocols, IdFIPS, IdHash, IdResourceStrings, IdResourceStringsProtocols, SysUtils; { TIdDigestAuthentication } constructor TIdDigestAuthentication.Create; begin inherited Create; CheckMD5Permitted; end; destructor TIdDigestAuthentication.Destroy; begin FreeAndNil(FDomain); FreeAndNil(FQopOptions); inherited Destroy; end; procedure TIdDigestAuthentication.SetRequest(const AMethod, AUri: String); begin FMethod := AMethod; FUri := AUri; end; function TIdDigestAuthentication.Authentication: String; function Hash(const S: String): String; var LMD5: TIdHashMessageDigest5; begin LMD5 := TIdHashMessageDigest5.Create; try Result := LowerCase(LMD5.HashStringAsHex(S)); finally LMD5.Free; end; end; var LA1, LA2, LCNonce, LResponse, LQop: string; begin Result := ''; {do not localize} case FCurrentStep of 0: begin //Just be save with this one Result := 'Digest'; {do not localize} end; 1: begin //Build request LCNonce := Hash(DateTimeToStr(Now)); LA1 := Username + ':' + FRealm + ':' + Password; {do not localize} if TextIsSame(FAlgorithm, 'MD5-sess') then begin {do not localize} LA1 := Hash(LA1) + ':' + FNonce + ':' + LCNonce; {do not localize} end; LA2 := FMethod + ':' + FUri; {do not localize} //Qop header present if FQopOptions.IndexOf('auth-int') > -1 then begin {do not localize} LQop := 'auth-int'; {do not localize} LA2 := LA2 + ':' + Hash(FEntityBody); {do not localize} end else if FQopOptions.IndexOf('auth') > -1 then begin {do not localize} LQop := 'auth'; {do not localize} end; if LQop <> '' then begin LResponse := IntToHex(FNonceCount, 8) + ':' + LCNonce + ':' + LQop + ':'; {do not localize} end; LResponse := Hash( Hash(LA1) + ':' + FNonce + ':' + LResponse + Hash(LA2) ); {do not localize} Result := 'Digest ' + {do not localize} 'username="' + Username + '", ' + {do not localize} 'realm="' + FRealm + '", ' + {do not localize} 'nonce="' + FNonce + '", ' + {do not localize} 'algorithm="' + FAlgorithm + '", ' + {do not localize} 'uri="' + FUri + '", '; //Qop header present if LQop <> '' then begin {do not localize} Result := Result + 'qop="' + LQop + '", ' + {do not localize} 'nc=' + IntToHex(FNonceCount, 8) + ', ' + {do not localize} 'cnonce="' + LCNonce + '", '; {do not localize} end; Result := Result + 'response="' + LResponse + '"'; {do not localize} if FOpaque <> '' then begin Result := Result + ', opaque="' + FOpaque + '"'; {do not localize} end; Inc(FNonceCount); FCurrentStep := 0; end; end; end; function Unquote(var S: String): String; var I, Len: Integer; begin Len := Length(S); I := 2; // skip first quote while I <= Len do begin if S[I] = '"' then begin Break; end; if S[I] = '\' then begin Inc(I); end; Inc(I); end; Result := Copy(S, 2, I-2); S := Copy(S, I+1, MaxInt); end; function TIdDigestAuthentication.DoNext: TIdAuthWhatsNext; var S, LName, LValue, LTempNonce: String; LParams: TStringList; begin Result := wnDoRequest; case FCurrentStep of 0: begin //gather info if not Assigned(FDomain) then begin FDomain := TStringList.Create; end else begin FDomain.Clear; end; if not Assigned(FQopOptions) then begin FQopOptions := TStringList.Create; end else begin FQopOptions.Clear; end; S := ReadAuthInfo('Digest'); {do not localize} Fetch(S); LParams := TStringList.Create; try while Length(S) > 0 do begin // RLebeau: Apache sends a space after each comma, but IIS does not! LName := Trim(Fetch(S, '=')); {do not localize} S := TrimLeft(S); if TextStartsWith(S, '"') then begin {do not localize} LValue := Unquote(S); {do not localize} Fetch(S, ','); {do not localize} end else begin LValue := Trim(Fetch(S, ',')); end; LParams.Add(LName + '=' + LValue); S := TrimLeft(S); end; FRealm := LParams.Values['realm']; {do not localize} LTempNonce := LParams.Values['nonce']; {do not localize} if FNonce <> LTempNonce then begin FNonceCount := 1; FNonce := LTempNonce; end; S := LParams.Values['domain']; {do not localize} while Length(S) > 0 do begin FDomain.Add(Fetch(S)); end; FOpaque := LParams.Values['opaque']; {do not localize} FStale := TextIsSame(LParams.Values['stale'], 'True'); {do not localize} FAlgorithm := LParams.Values['algorithm']; {do not localize} FQopOptions.CommaText := LParams.Values['qop']; {do not localize} if FAlgorithm = '' then begin FAlgorithm := 'MD5'; {do not localize} end else if PosInStrArray(FAlgorithm, ['MD5', 'MD5-sess'], False) = -1 then begin {do not localize} raise EIdInvalidAlgorithm.Create(RSHTTPAuthInvalidHash); end; finally FreeAndNil(LParams); end; if Length(Username) > 0 then begin FCurrentStep := 1; Result := wnDoRequest; end else begin Result := wnAskTheProgram; end; end; end; end; function TIdDigestAuthentication.GetSteps: Integer; begin Result := 1; end; initialization RegisterAuthenticationMethod('Digest', TIdDigestAuthentication); {do not localize} finalization UnregisterAuthenticationMethod('Digest'); {do not localize} end.