diff --git a/Indy.SChannel/lib/Execute.IdSSLSChannel.pas b/Indy.SChannel/lib/Execute.IdSSLSChannel.pas index 8166fb6..29f1a1f 100644 --- a/Indy.SChannel/lib/Execute.IdSSLSChannel.pas +++ b/Indy.SChannel/lib/Execute.IdSSLSChannel.pas @@ -2,6 +2,7 @@ { TLS SChannel for Indy (c)2018 Execute SARL + 2018.11.01 - added Proxy support } interface {-$DEFINE LOG} @@ -12,14 +13,28 @@ interface System.SysUtils, IdGlobal, IdSSL, + IdURI, + IdCustomTransparentProxy, Execute.SChannel; type + SChannelError = class(Exception) + CertStatus: Cardinal; + constructor Create(AMsg: string; AStatus: Cardinal); + end; + + TCredentialsEvent = procedure(Sender: TObject) of object; + TIdSSLIOHandlerSocketSChannel = class(TIdSSLIOHandlerSocketBase) private FSSL: THandle; + FOnCredentials: TCredentialsEvent; + procedure SetCredentials(Value: TCredentialsEvent); + procedure ConnectSSL; procedure CloseSSL; + function GetTargetHost: string; protected + procedure SetPassThrough(const Value: Boolean); override; function RecvEnc(var ABuffer: TIdBytes): Integer; override; function SendEnc(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; override; procedure ConnectClient; override; @@ -29,6 +44,7 @@ TIdSSLIOHandlerSocketSChannel = class(TIdSSLIOHandlerSocketBase) procedure Close; override; function Connected: Boolean; override; function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override; + property OnCredentials: TCredentialsEvent read FOnCredentials write SetCredentials; end; implementation @@ -72,6 +88,9 @@ function TIdSSLIOHandlerSocketSChannel.Connected: Boolean; { I'm not sure is this is correct, but when Indy check for KeepAlive connexion, it tries to read data and this can lead to a session timeout } + if Passthrough then + Result := inherited Connected + else Result := FSSL <> 0; {$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.Connected = ', Result);{$ENDIF} end; @@ -82,7 +101,7 @@ function TIdSSLIOHandlerSocketSChannel.Readable(AMSec: Integer): Boolean; Not sure of this code either } {$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.Readable(', AMSec, ')');{$ENDIF} - if FSSL <> 0 then + if (FSSL <> 0) and (PassThrough = False) then Result := True else Result := inherited Readable(AMSec); @@ -121,14 +140,107 @@ function TIdSSLIOHandlerSocketSChannel.SendEnc(const ABuffer: TIdBytes; {$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.SendEnc(', Length(ABuffer) ,') = ', Result);{$ENDIF} end; +procedure DoCredentials(SSL: Integer; UserData: Pointer); +begin + with TIdSSLIOHandlerSocketSChannel(UserData) do + begin + if Assigned(FOnCredentials) then + FOnCredentials(TIdSSLIOHandlerSocketSChannel(UserData)); + end; +end; + +procedure TIdSSLIOHandlerSocketSChannel.SetCredentials( + Value: TCredentialsEvent); +begin + FOnCredentials := Value; + if FSSL <> 0 then + begin + if Assigned(FOnCredentials) then + SSLCredentialsCallBack(FSSL, DoCredentials, Self) + else + SSLCredentialsCallBack(FSSL, nil, nil); + end; +end; + +procedure TIdSSLIOHandlerSocketSChannel.SetPassThrough(const Value: Boolean); +begin +{$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.SetPassThrough (', Value,')');{$ENDIF} + if fPassThrough <> Value then begin + if not Value then begin + if BindingAllocated then begin + ConnectSSL; + end; + end; + fPassThrough := Value; + end; +end; + procedure TIdSSLIOHandlerSocketSChannel.StartSSL; begin -{$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.StartSSL');{$ENDIF} - FSSL := SSLStart(Binding.Handle, AnsiString(Host)); + if not PassThrough then + ConnectSSL; +end; + +procedure TIdSSLIOHandlerSocketSChannel.ConnectSSL; +var + aHost: AnsiString; +begin + aHost := AnsiString(GetTargetHost); +{$IFDEF LOG}System.WriteLn('TIdSSLIOHandlerSocketSChannel.ConnectSSL (', aHost,')');{$ENDIF} + FSSL := SSLStart(Binding.Handle, aHost); if FSSL = 0 then - raise Exception.Create('SChannel initialization fails'#13 + SSLError); + raise SChannelError.Create('SChannel initialization fails'#13 + SSLError, CertStatus); + if Assigned(FOnCredentials) then + SSLCredentialsCallBack(FSSL, DoCredentials, Self); +end; + +function TIdSSLIOHandlerSocketSChannel.GetTargetHost: string; +var + LURI: TIdURI; + LTransparentProxy, LNextTransparentProxy: TIdCustomTransparentProxy; +begin + Result := ''; + + if URIToCheck <> '' then + begin + LURI := TIdURI.Create(URIToCheck); + try + Result := LURI.Host; + finally + LURI.Free; + end; + if Result <> '' then + Exit; + end; + + LTransparentProxy := FTransparentProxy; + if Assigned(LTransparentProxy) then + begin + if LTransparentProxy.Enabled then + begin + repeat + LNextTransparentProxy := LTransparentProxy.ChainedProxy; + if not Assigned(LNextTransparentProxy) then Break; + if not LNextTransparentProxy.Enabled then Break; + LTransparentProxy := LNextTransparentProxy; + until False; + Result := LTransparentProxy.Host; + if Result <> '' then + Exit; + end; + end; + + Result := Host; +end; + +{ SChannelError } + +constructor SChannelError.Create(AMsg: string; AStatus: Cardinal); +begin + CertStatus := AStatus; + inherited create(AMsg); end; initialization {$IFDEF LOG}AllocConsole;{$ENDIF} -end. +end. \ No newline at end of file diff --git a/Indy.SChannel/lib/Execute.SChannel.pas b/Indy.SChannel/lib/Execute.SChannel.pas index 216195c..13f7ba5 100644 --- a/Indy.SChannel/lib/Execute.SChannel.pas +++ b/Indy.SChannel/lib/Execute.SChannel.pas @@ -3,7 +3,10 @@ SChannel for Delphi Tokyo (c)2018 Execute SARL } interface +{$IFDEF DEBUG} {$DEFINE LOG} +{$DEFINE TRACE} +{$ENDIF} {$POINTERMATH ON} uses Winapi.Windows, @@ -23,12 +26,16 @@ TSSLValidator = class function ValidateElement(Element: PCERT_CHAIN_ELEMENT): Boolean; virtual; end; + TCredentialsCallBack = procedure(SSL: Integer; UserData: Pointer); + { Is SChannel available } function SSLAvailable: Boolean; { Start a TLS connexion over a socket } function SSLStart(Socket: Integer; const Host: AnsiString = ''): Integer; +procedure SSLCredentialsCallBack(SSL: Integer; CallBack: TCredentialsCallBack; UserData: Pointer); + { some data left ? } function SSLPending(SSL: Integer): Boolean; { Read from the SSL handle } @@ -41,10 +48,77 @@ function SSLClose(SSL: Integer): Integer; { If you wonder why the code is built like this, I have an Execute.OpenSSL unit that offers the same functions for OpenSSL } var + CertStatus: Cardinal; SSLError: string; +{$IFDEF TRACE} + TraceFile : string = 'TLS.txt'; +{$ENDIF} implementation +{$IFDEF TRACE} +const + HX: array[0..$F] of Char = '0123456789abcdef'; + +var + Trace: TextFile; + TraceDump: Boolean; + +function T: string; +begin + Result := FormatDateTime('[dd/mm/yyyy hh:nn] ', Now); +end; + +function Ascii(Value: Byte): Char; +begin + if (Value < 32) or (Value > 126) then + Result := '.' + else + Result := Char(Value); +end; + +function Dump(var Data; Size: Integer): string; +var + Line: string; + index: Integer; + Source: PByte; + Pad: Integer; +begin + Result := ''; + Source := @Data; + try + while Size > 0 do + begin + SetLength(Line, 3 * 16 + 1 + 16); + Line[3 * 15 + 4] := '`'; + for Index := 0 to 15 do + begin + Line[3 * Index + 1] := HX[Source^ shr 4]; + Line[3 * Index + 2] := HX[Source^ and $f]; + Line[3 * Index + 3] := ' '; + Line[3 * 16 + 2 + Index] := Ascii(Source^); + Inc(Source); + Dec(Size); + if Size = 0 then + begin + for Pad := Index + 1 to 15 do + begin + Line[3 * Pad + 1] := '-'; + Line[3 * Pad + 2] := '-'; + end; + SetLength(Line, 3 * 16 + 2 + Index); + Break; + end; + end; + Result := Result + ' ' + Line + #13#10; + end; + except + on e: Exception do + Result := Result + e.Message; + end; +end; +{$ENDIF} + const IO_BUFFER_SIZE = $10000; @@ -140,6 +214,12 @@ function SendSecBuffer(Socket: Integer; var Buffer: TSecBuffer): Boolean; Result := True; if (Buffer.cbBuffer > 0) and (Buffer.pvBuffer <> nil) then begin + {$IFDEF TRACE} + WriteLn(Trace, T, 'Sending ', Buffer.cbBuffer, ' bytes'); + if TraceDump then + WriteLn(Trace, Dump(Buffer.pvBuffer, Buffer.cbBuffer)); + Flush(Trace); + {$ENDIF} if SendData(Socket, PByte(buffer.pvBuffer), Buffer.cbBuffer) <= 0 then Exit(False); SSPI.FreeContextBuffer(Buffer.pvBuffer); @@ -153,6 +233,9 @@ function SendSecBuffer(Socket: Integer; var Buffer: TSecBuffer): Boolean; TSSLInfo = record Init : TSSLInit; Error : Cardinal; + // Credentials + CredentialsCallBack: TCredentialsCallBack; + UserData : Pointer; // Connected socket Socket : Integer; // Remote server name @@ -196,6 +279,19 @@ function TSSLInfo.Start; begin Result := False; + if Assigned(CredentialsCallBack) then + begin + {$IFDEF LOG}WriteLn('[SSL] CredentialsCallBack');{$ENDIF} + CertCloseStore(MyStore, 0); + CredentialsCallBack(Integer(@Self), UserData); + MyStore := CertOpenSystemStore(0, 'MY'); + if MyStore = 0 then + begin + SSLError := 'CertOpenSystemStore(0, ''MY'') returns 0'; + Exit; + end; + end; + FillChar(SChannel, SizeOf(SChannel), 0); SChannel.dwVersion := SCHANNEL_CRED_VERSION; SChannel.grbitEnabledProtocols := SP_PROT_SSL3TLS1;//SP_PROT_TLS1; @@ -212,7 +308,10 @@ function TSSLInfo.Start; @Credentials, nil ); - +{$IFDEF TRACE} + WriteLn(Trace, T, 'AcquireCredentialsHandle returns 0x', IntToHex(Error, 8)); + Flush(Trace); +{$ENDIF} if Error <> SEC_E_OK then begin SSLError := 'AcquireCredentialsHandle returns ' + IntToHex(Error, 8); @@ -247,6 +346,11 @@ function TSSLInfo.Start; Flags, nil ); +{$IFDEF TRACE} + WriteLn(Trace, T, 'InitializeSecurityContext returns 0x', IntToHex(Error, 8), ' Context = 0x', IntToHex(Int64(Context),8)); + Flush(Trace); +{$ENDIF} + if (Error <> SEC_I_CONTINUE_NEEDED) then begin SSLError := 'First call to InitializeSecurityContext returns ' + IntToHex(Error, 8); @@ -293,8 +397,15 @@ function TSSLInfo.Read: Integer; begin Result := recv(Socket, RecvBuffer[RecvCount], Length(RecvBuffer) - RecvCount, 0); if Result > 0 then - Inc(RecvCount, Result) - else begin + begin + {$IFDEF TRACE} + WriteLn(Trace, T, 'Receiving ', RecvCount, ' bytes'); + if TraceDump then + WriteLn(Trace, Dump(RecvBuffer[RecvCount], Result)); + Flush(Trace); + {$ENDIF} + Inc(RecvCount, Result); + end else begin Error := WSAGetLastError; SSLError := 'recv returns ' + IntToHex(Error, 8); end; @@ -304,7 +415,6 @@ function TSSLInfo.Readable: Integer; var Buffers: array[0..3] of TSecBuffer; Buffer : TSecBufferDesc; - Count : Integer; Index : Integer; begin Result := DataCount - DataStart; @@ -361,9 +471,9 @@ function TSSLInfo.Readable: Integer; // Decrypted data SECBUFFER_DATA: begin - if DataCount + Buffers[Index].cbBuffer > Length(DataBuffer) then + if DataCount + Integer(Buffers[Index].cbBuffer) > Length(DataBuffer) then begin - SetLength(DataBuffer, DataCount + Buffers[Index].cbBuffer); + SetLength(DataBuffer, DataCount + Integer(Buffers[Index].cbBuffer)); end; {$IFDEF LOG}WriteLn('Decrypt ',Buffers[Index].cbBuffer,' bytes ');{$ENDIF} Move(Buffers[Index].pvBuffer^, DataBuffer[DataCount], Buffers[Index].cbBuffer); @@ -372,7 +482,7 @@ function TSSLInfo.Readable: Integer; // Extra data SECBUFFER_EXTRA: begin - Assert(Buffers[Index].cbBuffer <= Length(RecvBuffer)); + Assert(Integer(Buffers[Index].cbBuffer) <= Length(RecvBuffer)); RecvCount := Buffers[Index].cbBuffer; Move(Buffers[Index].pvBuffer^, RecvBuffer[0], RecvCount); end; @@ -458,6 +568,10 @@ function TSSLInfo.ReadLoop: Integer; Flags, nil ); + {$IFDEF TRACE} + WriteLn(Trace, T, 'InitializeSecurityContext returns 0x', IntToHex(Error, 8)); + Flush(Trace); + {$ENDIF} {$IFDEF LOG} WriteLn('Error = ', Error, ' / 0x', IntToHex(Error, 8), ', Output = ', OutBuffers[0].cbBuffer, ', InputLeft = ', InBuffers[1].cbBuffer); @@ -473,7 +587,7 @@ function TSSLInfo.ReadLoop: Integer; if (InBuffers[1].cbBuffer > 0) and (InBuffers[1].BufferType = SECBUFFER_EXTRA) then begin {$IFDEF LOG}WriteLn('[SSL] ReadLoop.SECBUFFER_EXTRA');{$ENDIF} - Source := @RecvBuffer[RecvCount - InBuffers[1].cbBuffer]; + Source := @RecvBuffer[RecvCount - Integer(InBuffers[1].cbBuffer)]; RecvCount := InBuffers[1].cbBuffer; {$IFDEF LOG}WriteLn('[SSL] ReadLoop.Move(InBuffer, RecvBufer, ', RecvCount, ')');{$ENDIF} Move(Source^, RecvBuffer[0], RecvCount); @@ -509,6 +623,7 @@ function TSSLInfo.ReadLoop: Integer; until Error = SEC_E_OK; {$IFDEF LOG}WriteLn('[SSL] ReadLoop.OK');{$ENDIF} + Result := 0; end; function TSSLInfo.GetClientCredentials: Boolean; @@ -522,15 +637,34 @@ function TSSLInfo.GetClientCredentials: Boolean; pCertContext: PCCERT_CONTEXT; Creds: TCredHandle; begin + if Assigned(CredentialsCallBack) then + begin + {$IFDEF LOG}WriteLn('[SSL] CredentialsCallBack');{$ENDIF} +// CertCloseStore(MyStore, 0); + CredentialsCallBack(Integer(@Self), UserData); +// MyStore := CertOpenSystemStore(0, 'MY'); +// if MyStore = 0 then +// begin +// SSLError := 'CertOpenSystemStore(0, ''MY'') returns 0'; +// Exit(False); +// end; + end; {$IFDEF LOG}WriteLn('[SSL] GetClientCredentials');{$ENDIF} FillChar(Issuer, SizeOf(Issuer), 0); Error := SSPI.QueryContextAttributes(@Context, SECPKG_ATTR_ISSUER_LIST_EX, @Issuer); if Error <> SEC_E_OK then begin SSLError := 'QueryContextAttributes(SECPKG_ATTR_ISSUER_LIST_EX) returns ' + IntToHex(Error, 8); + {$IFDEF LOG}WriteLn('[SSL] ', SSLError);{$ENDIF} Exit(False); end; - +{$IFDEF LOG} + WriteLn('[SSL] QueryContextAttributes returns 0x', IntToHex(Error, 8)); +{$ENDIF} +{$IFDEF TRACE} + WriteLn(Trace, T, 'QueryContextAttributes returns 0x', IntToHex(Error, 8)); + Flush(Trace); +{$ENDIF} FillChar(ChainPara, SizeOf(ChainPara), 0); ChainPara.cbSize := SizeOf(ChainPara); ChainPara.pszUsageIdentifier := szOID_PKIX_KP_CLIENT_AUTH; @@ -554,8 +688,15 @@ function TSSLInfo.GetClientCredentials: Boolean; if ChainCtxt = nil then begin SSLError := 'CertFindChainInStore returns nil'; + {$IFDEF LOG} + WriteLn('[SSL] ', SSLError); + {$ENDIF} Exit(False); end; +{$IFDEF TRACE} + WriteLn(Trace, T, 'calling CertFindChainInStore(szOID_PKIX_KP_CLIENT_AUTH) for ', CertName(ChainCtxt.rgpChain[0].rgpElement[0].pCertContext, Issuer.aIssuers^)); + Flush(Trace); +{$ENDIF} pCertContext := ChainCtxt.rgpChain[0].rgpElement[0].pCertContext; SChannel.dwVersion := SCHANNEL_CRED_VERSION; @@ -573,10 +714,18 @@ function TSSLInfo.GetClientCredentials: Boolean; @Creds, nil ); + {$IFDEF LOG} + WriteLn('[SSL] AcquireCredentialsHandle returns 0x', IntToHex(Error, 8)); + {$ENDIF} + {$IFDEF TRACE} + WriteLn(Trace, T, 'AcquireCredentialsHandle returns 0x', IntToHex(Error, 8)); + Flush(Trace); + {$ENDIF} until Error = SEC_E_OK; {$IFDEF LOG}WriteLn('[SSL] GetClientCredentials.NewCredentiels');{$ENDIF} SSPI.FreeCredentialsHandle(@Credentials); Credentials := Creds; + Result := True; end; function TSSLInfo.VerifyServer: Boolean; @@ -597,6 +746,10 @@ function TSSLInfo.VerifyServer: Boolean; begin Server := nil; Error := SSPI.QueryContextAttributes(@Context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, @Server); +{$IFDEF TRACE} + WriteLn(Trace, T, 'QueryContextAttributes returns 0x', IntToHex(Error, 8)); + Flush(Trace); +{$ENDIF} if Error <> 0 then begin SSLError := 'QueryCredentialsAttributes returns ' + IntToHex(Error, 8); @@ -619,6 +772,10 @@ function TSSLInfo.VerifyServer: Boolean; nil, Chain ); +{$IFDEF TRACE} + WriteLn(Trace, T, 'CertGetCertificateChain returns ', Result); + Flush(Trace); +{$ENDIF} if Result then begin @@ -641,9 +798,18 @@ function TSSLInfo.VerifyServer: Boolean; Chain, Policy, Status); + {$IFDEF TRACE} + WriteLn(Trace, T, 'CertVerifyCertificateChainPolicy returns ', Result); + Flush(Trace); + {$ENDIF} if Result then begin + {$IFDEF TRACE} + WriteLn(Trace, T, 'CertVerifyCertificateChainPolicy, Status.dwError = 0x', IntToHex(Status.dwError, 8)); + Flush(Trace); + {$ENDIF} + CertStatus := Status.dwError; if Status.dwError = CERT_E_UNTRUSTEDROOT then Result := Validate(Chain, Status) else @@ -699,7 +865,7 @@ function TSSLInfo.Encrypt(var Data; Size: Integer): Integer; Source := @Data; while Size > 0 do begin - if Size > BuffSizes.cbMaximumMessage then + if Cardinal(Size) > BuffSizes.cbMaximumMessage then Count := BuffSizes.cbMaximumMessage else Count := Size; @@ -717,7 +883,7 @@ function TSSLInfo.Encrypt(var Data; Size: Integer): Integer; Buffers[2].cbBuffer := BuffSizes.cbTrailer; Buffers[2].BufferType := SECBUFFER_STREAM_TRAILER; - Buffers[2].pvBuffer := @SendBuffer[BuffSizes.cbHeader + Count]; + Buffers[2].pvBuffer := @SendBuffer[Integer(BuffSizes.cbHeader) + Count]; Buffers[3].BufferType := SECBUFFER_EMPTY; @@ -770,27 +936,53 @@ function SSLStart(Socket: Integer; const Host: AnsiString = ''): Integer; var Info: PSSLInfo; begin +{$IFDEF TRACE} + AssignFile(Trace, TraceFile); +{$I-} + Append(Trace); + if IoResult <> 0 then + Rewrite(Trace); + WriteLn(Trace, T, 'StartSSL on socket ' , Socket, ' for host ', Host); + Flush(Trace); + TraceDump := True; +{$ENDIF} Result := 0; + CertStatus := 0; + SSLError := ''; if SSLAvailable = False then begin + {$IFDEF DEBUG}WriteLn('SSL not available');{$ENDIF} Exit; end; New(Info); - Info.Init := []; + FillChar(Info^, SizeOf(TSSLInfo), 0); Info.Socket := Socket; - Info.Servername := Host; + Info.Servername := string(Host); if not Info.Start then begin Info.Clean; Dispose(Info); Exit; end; - +{$IFDEF TRACE} + TraceDump := False; +{$ENDIF} Result := Integer(Info); end; +procedure SSLCredentialsCallBack(SSL: Integer; CallBack: TCredentialsCallBack; UserData: Pointer); +var + Info: PSSLInfo absolute SSL; +begin + if SSL <> 0 then + begin + Info.CredentialsCallBack := CallBack; + Info.UserData := UserData; + end; +end; + function SSLConnect(SSL: Integer): Boolean; var Info: PSSLInfo absolute SSL; @@ -827,8 +1019,13 @@ function SSLClose(SSL: Integer): Integer; var Info: PSSLInfo absolute SSL; begin + Result := 0; if SSL = 0 then Exit; +{$IFDEF TRACE} + WriteLn(Trace, '-------------'); + CloseFile(Trace); +{$ENDIF} Info.Clean; Dispose(Info); end; @@ -838,4 +1035,4 @@ initialization Validators := nil; finalization FreeValidators; -end. +end. \ No newline at end of file diff --git a/Indy.SChannel/lib/Execute.WinSSPI.pas b/Indy.SChannel/lib/Execute.WinSSPI.pas index ad03984..2be663d 100644 --- a/Indy.SChannel/lib/Execute.WinSSPI.pas +++ b/Indy.SChannel/lib/Execute.WinSSPI.pas @@ -912,13 +912,16 @@ function InitSecurityInterface: PSecurityFunctionTable; stdcall; external 'secur SEC_E_BUFFER_TOO_SMALL = $80090321; SEC_E_WRONG_PRINCIPAL = $80090322; SEC_E_UNTRUSTED_ROOT = $80090325; + SEC_E_ILLEGAL_MESSAGE = $80090326; SEC_E_ENCRYPT_FAILURE = $80090329; SEC_E_DECRYPT_FAILURE = $80090330; SEC_E_CRYPTO_SYSTEM_INVALID = $80090337; CRYPT_E_NOT_FOUND = $80092004; + // https://msdn.microsoft.com/fr-fr/library/windows/desktop/aa377188(v=vs.85).aspx CERT_E_UNTRUSTEDROOT = $800B0109; + CERT_E_CN_NO_MATCH = $800B010F; // The certificate's CN name does not match the passed value. // QueryContextAttributes/QueryCredentialsAttribute extensions @@ -1176,6 +1179,8 @@ function CertName(Cert: PCCERT_CONTEXT; var Blob: CERT_NAME_BLOB): string; end; initialization +{$IFDEF WIN32} Assert(SizeOf(CERT_CHAIN_CONTEXT) = 56); +{$ENDIF} end.