How i can set ConnectTimeout/ReadTimeout in Indy when using SSL ?
MCVE:
program mcve;
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}SysUtils, IdHTTP, IdSSLOpenSSL, DateUtils;
var
HTTP : TIdHTTP;
SSL : TIdSSLIOHandlerSocketOpenSSL;
Started : TDateTime;
begin
HTTP := TIdHTTP.Create();
try
HTTP.ReadTimeout := 1000;
HTTP.ConnectTimeout := 2000;
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP);
SSL.ConnectTimeout := HTTP.ConnectTimeout;
SSL.ReadTimeout := HTTP.ReadTimeout;
SSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
HTTP.IOHandler := SSL;
Started := Now;
try
HTTP.Get(ParamStr(1));
except
On E: Exception do WriteLn(E.Message);
end;
Writeln(FormatDateTime('hh:nn:ss', SecondsBetween(Started, Now) / SecsPerDay));
finally
HTTP.Free;
end;
end.
When using http ConnectTimeout/ReadTimeout work fine the issue only when using https see below:
:~$ ./mcve http://x.x.x.x
Read timed out.
00:00:01 <-- Correct.
:~$ ./mcve https://x.x.x.x
Socket Error # 0
00:03:38 <-- NOT Correct / More than SSL.ReadTimeout value.
Lazarus 2.0.6 Indy installed from OPM version 10.6.2.5494.
Note: On Windows same code using Delphi with shipped Indy 10.6.2.5366, The results works as expected
You don't need to manually set the ConnectTimeout and ReadTimeout on the IOHandler itself, only on the client component (in this case, TIdHTTP) . TIdTCPClient.Connect() will assign the values to the IOHandler for you.
The ConnectTimeout applies when the underlying socket is being connected to the server, before any SSL/TLS session is created, so it operates the same whether you use SSL/TLS or not.
The ReadTimeout applies when Indy attempts to read bytes from the IOHandler's internal connection. When not using SSL/TLS, that means it goes straight to the socket, and thus times out when no bytes arrive on the socket. But when using SSL/TLS, Indy uses OpenSSL's legacy SSL_...() APIs, not its newer BIO_...() APIs, which means OpenSSL is doing its own socket reading and buffering on Indy's behalf, and thus Indy times out when OpenSSL does not provide any decrypted application bytes.
One difference in how TIdSSLIOHandlerSocketOpenSSL operates on Windows vs other platforms is that on Windows Vista+ only, TIdSSLIOHandlerSocketOpenSSL does apply the ReadTimeout to the underlying socket's SO_RCVTIMEO and SO_SNDTIMEO timeouts via the IOHandler's Binding.SetSockOpt() method, as a workaround to an OpenSSL bug on Windows. For other platforms, Indy does not currently set those two socket timeouts.
A good place to set those timeouts manually would be in the IOHandler's OnBeforeConnect event, which is fired after the socket is connected to the server and before any SSL/TLS session is created.
Related
I'm writing a test Delphi 10.4 Win32 application that uses TIdHTTPServer and TIdServerIOHandlerSSLOpenSSL from Indy 10.6.2.0 installed by Delphi 10.4, using a port on localhost for the purposes of communicating between a C# (.NET 5) client application and this Win32 Delphi VCL (server) application.
I know very little about TLS, SSL, and certificates but have been learning as I attempt to build this; from the examples I have seen, I had expected this to be simple.
The "general" page for the .pfx certificate properties says "Enable all purposes for this certificate".
I started partly by reviewing examples and also Remy Lebeau's postings here:
https://stackoverflow.com/a/34422109/17371832
https://stackoverflow.com/a/63082235/17371832
Using Indy 10 IdHTTP with TLS 1.2
I have provided it with OpenSSL 1.0.2.17 DLLs:
libeay32.dll
ssleay32.dll
These DLLs load successfully.
The problem is that clients can't connect to it. .NET says "The SSL connection could not be established" (more about the error inside Indy later).
WireShark shows "Client Hello" (TLS 1.2 with about 20 cipher types) but no "Server Hello", alerts, or errors are sent. In fact, only one packet marked as TLS 1.2 is sent (when the client said "Client Hello").
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:(and many others)
I had tried providing the full list (as formatted above) to the TIdHTTPServer's CipherList property, but got an error (I think it was "SetCipherList failed").
If the cipher list is left empty, it seems to internally use "AES:ALL:!aNULL:!eNULL:+RC4:#STRENGTH". I left it that way.
Here are the components involved:
object srv: TIdHTTPServer
Bindings = <
item
IP = '127.0.0.1'
Port = 5500
end>
DefaultPort = 5500
IOHandler = iohSSL
MaxConnections = 25
AutoStartSession = True
KeepAlive = True
SessionState = True
OnCreatePostStream = srvCreatePostStream
OnDoneWithPostStream = srvDoneWithPostStream
OnCommandGet = srvCommandGet
Left = 183
Top = 118
end
object iohSSL: TIdServerIOHandlerSSLOpenSSL
SSLOptions.CertFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.KeyFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.Method = sslvTLSv1_2
SSLOptions.SSLVersions = [sslvTLSv1_2]
SSLOptions.Mode = sslmServer
OnGetPasswordEx = iohSSLGetPasswordEx
Left = 256
Top = 120
end
The code isn't interesting:
procedure TdmCloud.DataModuleCreate(Sender: TObject);
begin
iohSSL.SSLOptions.CertFile:='.\cert.pfx';
//iohSSL.SSLOptions.RootCertFile:='.\root2.pem';
//iohSSL.SSLOptions.KeyFile:='.\key.pem';
srv.Active:=true;
end;
procedure TdmCloud.iohSSLGetPasswordEx(ASender: TObject; var VPassword: string;
const AIsWrite: Boolean);
begin
VPassword := 'password';
end;
procedure TdmCloud.srvCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := HTTP_OK;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := '<html><head><title>Title</title></head><body>HELLO</body></html>';
end;
procedure TdmCloud.srvCreatePostStream(AContext: TIdContext;
AHeaders: TIdHeaderList; var VPostStream: TStream);
begin
VPostStream := TMemoryStream.Create;
end;
procedure TdmCloud.srvDoneWithPostStream(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean);
begin
VCanFree := true;
end;
I have seen in the debugger that the certificate file is loaded successfully with the password provided by the event handler. The listener thread runs ok until it receives a request.
TIdHTTPServer's OnCommandGet event doesn't fire.
I've seen in the debugger that InternalReadLn() returns a string that looks encrypted; it's definitely binary. Anyway, below, I get "error in parsing command"; judging from the data it's looking at, I'm not surprised.
var
i: integer;
s, LInputLine, LRawHTTPCommand, LCmd, LContentType, LAuthType: String;
LURI: TIdURI;
LContinueProcessing, LCloseConnection: Boolean;
LConn: TIdTCPConnection;
LEncoding: IIdTextEncoding;
begin
LContinueProcessing := True;
Result := False;
LCloseConnection := not KeepAlive;
try
try
LConn := AContext.Connection;
repeat
// InternalReadLn( ) returns a string that looks encrypted; it's definitely binary
LInputLine := InternalReadLn(LConn.IOHandler);
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin // true
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
As it happens, InternalReadLn() calls TIdServerIOHandlerSSLOpenSSL's Readln() method, which has provided this binary data.
Remy Lebeau said this about the error in an online posting:
The error is actually coming from several lines further down:
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
Which means your client is sending a malformed HTTP request. The first
line in EVERY request must end with an HTTP version number, ie: GET /login.html HTTP/1.0 Hence the call to RPos(). The error means there
is no space character anywhere in the line.
Although it could certainly be a malformed request, I'm told that the application's requests have been tested and found to be valid/working.
I've tried a browser directed to https://localhost:5500/ with similar results.
That brings me back to the conclusion that TLS negotiation didn't start at "Client hello". I have no idea why. I don't know where that process is handled in Indy.
Here's a typical, working TLS negotiation: https://tls.ulfheim.net/
Key areas of suspicion for me; I've made some effort to explore each possibility:
might the .pfx certificate missing something important (even though I'm told it's a good certificate)? It has the "Server Authentication" option checked in the properties.
which of CertFile, RootCertFile, KeyFile, and CipherList do I need to provide for TLS 1.2 to work? (and how do I choose what to use?)
is Delphi 10.4 update 2's Indy SSL broken or incompatible with OpenSSL 1.0.2q?
Unhelpfully, all of documentation links at https://www.indyproject.org/documentation/ are broken! My Delphi install didn't include any Indy help either.
Help being absent, I'm uncertain which to provide of RootCertFile, CertFile, and KeyFile. I've tried the .pfx which was accepted for CertFile and KeyFile but later saw https://stackoverflow.com/a/15416234/17371832 so I followed the instructions and was able to make a .pem file (root file was not accepted by Indy). This provided no improvement.
Is Indy dying? Is there something else that's better? Haven't looked into that at all.
Why won't it connect?
The standard TCP port for HTTPS is 443, but you are running your TIdHTTPServer on port 5500 instead. As such, you must use the TIdHTTPServer.OnQuerySSLPort event to explicitly tell TIdHTTPServer that you want to use SSL/TLS on that port, eg:
procedure TdmCloud.srvQuerySSLPort(APort: TIdPort; var VUseSSL: Boolean);
begin
VUseSSL := APort = 5500;
end;
This event allows HTTP and HTTPS to co-exist on a single server (via multiple TIdHTTPServer.Bindings items) even when using non-standard ports. Without this event handler assigned, this is why your server is not handling the client's SSL/TLS handshake correctly.
Code stopped working on Windows 8.
It works fine on Windows7, Windows XP...
I found a workaround for this issue: start application in Windows compatibility mode: Windows XP (Service Pack 3) - code working.
Code not working if Windows compatibility mode is Windows 7.
I run application as Administrator. Have already tried to switch off antivirus and firewall. I can send email with the same parameters using another smtp client, e.g. .Net SmtpClient. The problem is reproduced on different Windows 8 computers(home, office).
I created simple test application. Code is written on Delphi XE, Indy 10.5.7, OpenSSL 1.0.1.3 dlls are placed in test.exe folder.
Any ideas?
Code:
SSLHandler.MaxLineAction := maException;
SSLHandler.SSLOptions.Method := sslvTLSv1;
SSLHandler.SSLOptions.Mode := sslmUnassigned;
SSLHandler.SSLOptions.VerifyMode := [];
SSLHandler.SSLOptions.VerifyDepth := 0;
SSLHandler.OnStatusInfo := IdSSLIOHandlerSocketOpenSSL1StatusInfo;
SMTP.IOHandler := SSLHandler;
SMTP.Host := 'smtp.gmail.com';
SMTP.Port := 587;
SMTP.UseTLS := utUseExplicitTLS;
SMTP.Username := FromAddress;
SMTP.Password := AuthPassword;
Email.From.Address := FromAddress;
Email.Recipients.EmailAddresses := ToAddress;
Email.Subject := Subject;
Email.Body.Text := Body;
SMTP.Connect;
SMTP.Send(Email);
SMTP.Disconnect;
Output:
SSL status: "before/connect initialization"
SSL status: "before/connect initialization"
SSL status: "SSLv3 write client hello A"
SSL status: "SSLv3 read server hello A"
EIdSocketError with message 'Socket Error # 10060 Connection timed out.'
It is IdHTTP's ReadTimeout. By default it is -1 and doesn't work with Windows 8. Set it to anything positive like 15000ms or 30000 will fix the problem.
Note that you need to set the numerical value programmatically on the IdHTTP object. Do not try to set the binded IoHandler via the object inspector because it won't work in that way.
Example:
IdHTTP1.ReadTimeout := 30000;
One tells Indy to use an infinite timeout on its socket operations. However, to work around a deadlock in OpenSSL on Vista+ when a connection is dropped, TIdSSLIOHandlerSocketOpenSSL forces a 30 second timeout at the lower socket layer if the ReadTimeout is <= 0.}
A solution Bob Daneshfar posted on this forum.
Finally I found that it is Indy related issue.
Upgrade to Indy 10.5.8 or 10.5.9 fixes the problem.
Thanks all for advices.
Since you can successfully send email using the .NET SmtpClient class, I suggest you use a packet sniffer, such as Wireshark, to view SmtpClient's network traffic and compare it to Indy/OpenSSL's network traffic and see exactly what the differences are. Something is happening during OpenSSL's TLSv1 handshake that is not happening to SmtpClient, so go see what their handshakes actually look like.
Are you sure you've distributed the OpenSSL libraries with you're application (like 'libeay32.dll' and 'ssleay32.dll')?
When you forget those, you get timeouts too, because the error of the OpenSSL library is not correctly transferred to the outer IO object (like TIdSMTP or TIdHTTP).
To check whether OpenSLL for Indy can load I use the following code (which raises an erre when SSL is not available) - uses unit 'IdSSLOpenSSL'
TIdSSLContext.Create.Free; // Raises an error, when unable to load SSL lib and such
You can also plug into the OpenSSL headers unit 'IdSSLOpenSSLHeader' and try to load the library manually and check whether it can:
if not IdSSLOpenSSLHeaders.Load then ; // Raises no error, but return 'true' when it can load
I'm still getting used to Indy, being a multi-threaded socket system with vast capabilities. One of the big things I've seen is how a server socket can have a number of different bindings. For example, you could have 3 bindings for 3 ports on the same IP address. I'm using Indy 10 on Delphi XE2.
I'm re-building an old system of mine which uses the old fashioned TServerSocket and TClientSocket components from ScktComps and re-doing it with Indy TIdTCPServer and TIdTCPClient. The old system actually consists of 3 completely different server/client sockets on each end, each socket serving a distinct purpose, and working together - similar to how FTP uses one socket for binary data and the other socket for commands.
Is it possible to mimic three separate server/client sockets within the same component using these bindings? It would be great if I can declare just one server socket with 3 ports bound, and same on the client, connected to 3 different ports on the server. All I would like to do is eliminate the need to create 3 separate server/client socket components and combine them into one.
Yes, you can use a single TIdTCPServer to manage multiple ports at a time. On the client side, you still need 3 distinct client components to connect to the different ports, though.
Create 3 entries in the TIdTCPServer.Bindings collection, one for each local IP/Port that you want to listen on, where the TIdSocketHandle.Port property would be the equivilent of the TServerSocket.Port property. TServerSocket does not natively support binding to a specific IP (though it can be done with some manual work), but the TIdSocketHandle.IP property is used for that purpose, where a blank string is equivilent to INADDR_ANY.
In the TIdCPServer.OnConnect, TIdCPServer.OnDisconnect, and TIdCPServer.OnExecute events, you can use the TIdContext.Binding.IP and TIdContext.Binding.Port properties to differentiate which binding the calling socket is connected to.
A common use of this is to support SSL and non-SSL clients on different ports, such as for protocols like POP3 and SMTP which support implicit and explicit SSL/TLS on different ports. TIdHTTPServer does this for supporting HTTP and HTTPS urls on a single server (you can use the TIdHTTPServer.OnQuerySSLPort to customize which ports use SSL/TLS versus not).
For example:
procedure TForm1.StartButtonCick(Sender: TObject);
begin
IdTCPServer1.Active := False;
IdTCPServer1.Bindings.Clear;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2000;
end;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2001;
end;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2002;
end;
IdTCPServer1.Active := True;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
case AContext.Binding.Port of
2000: begin
// do something...
end;
2001: begin
// do something else...
end;
2002: begin
// do yet something else ...
end;
end;
end;
I have to provide an SSL front for a plain TCP server, so I'm making a "pump" application that will provide an SSL connection to the outside while the original server can stay plain.
I'm trying to use the Indy components to support my SSL needs, but I can't seem to get any data from the SSL port. I assigned the TIdTCPServer.OnExecute to the following event handler:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
c:TIdTCPClient;
cs,ss:TIdIOHandler;
b:TBytes;
begin
c:=TIdTCPClient.Create(Self);
try
c.Host:='127.0.0.1';
c.Port:=60675;
c.ConnectTimeout:=500;
c.Connect;
ss:=c.IOHandler;
cs:=AContext.Connection.IOHandler;
while (cs.Connected) and (ss.Connected) do
begin
if cs.CheckForDataOnSource(1) then
begin
try
cs.ReadBytes(b,1,False);
except on e:Exception do
Memo1.Lines.Add(e.Message); //BAD out of Thread context
end;
if Length(b)>0 then
ss.Write(b);
end;
if ss.CheckForDataOnSource(1) then
begin
ss.ReadBytes(b,1,False);
if Length(b)>0 then
cs.Write(b);
end;
end;
finally
c.Free;
end;
end;
The TCP server has an SSL handler attached. I did the same on a plain HTTP server and it worked fine, so I'm assuming my SSL setup is not the issue.
cs=Client Side (the server socket) and ss=Server side (the client for the TCP server I'm trying to add SSL to).
Now, I know it needs cleanup and doing 1ms waits isn't pretty, but before I can attack that issue, I'd like to receive some data.
Neither of my ReadBytes get called. When I used cs.Readable(), I get true just once, but I still couldn't read.
What can I do to make a pump? Why am I not getting data?
Try using the TIdMappedPortTCP component instead of TIdTCPServer directly. TIdMappedPortTCP handles all the work of passing data back and forth between a client and another server. By default, the outbound connection to the second server is not encrypted, even if the inbound connection to TIdMappedPortTCP is encrypted.
I need to consume a Web Service via SSL. In order to accomplish that I have built a web client in Delphi 6 that uses Indy to read the client certificates and write the soap request via https. The compilated version of the code is a DLL that runs in IIS 5.0. After tested the code in my local machine it works fine (I'm behind a proxy). But after the code is deployed to prod servers (not proxy) the SSL connection fails saying "Error connecting with SSL".
Here is my code:
var
Response: TStringStream;
IdHttp: TIdHTTP;
IdCnxSLL: TIdConnectionInterceptOpenSSL;
XmlSoapDoc: IXMLDocument;
begin
Response := TStringStream.Create('');
IdHttp := TIdHTTP.Create(nil);
IdCnxSLL := TIdConnectionInterceptOpenSSL.Create(nil);
XmlSoapDoc := TXMLDocument.Create(nil);
with IdCnxSLL do
begin
IdCnxSLL.SSLOptions.Method := sslvSSLv23;
IdCnxSLL.SSLOptions.RootCertFile := IniHttpConnectionData.Values['RootCertFile'];
IdCnxSLL.SSLOptions.CertFile := IniHttpConnectionData.Values['CertFile'];
IdCnxSLL.SSLOptions.KeyFile := IniHttpConnectionData.Values['KeyFile'];
IdCnxSLL.OnGetPassword := IdConInterceptOpenSSLGetPassword;
end;
with IdHttp do
begin
if bUseProxy then
begin
Request.ProxyServer := IniHttpConnectionData.Values['ProxyServer'];
Request.ProxyPort := StrToIntDef(IniHttpConnectionData.Values['ProxyPort'], 0);
end
else
begin
Host := IniHttpConnectionData.Values['HTTPHost'];
Port := StrToIntDef(IniHttpConnectionData.Values['HTTPPort'], 443);
end;
Request.ContentType := 'text/xml';
Intercept := IdCnxSLL;
InterceptEnabled := True;
end;
try
IdHttp.Post(ServiceURL, SoapEnv, Response);
except
on E:EIdOSSLConnectError do
LogError('SSL Connect Error: ' + E.Message);
on E:Exception do
LogError('Error' + E.ClassName + ' - ' + E.Message);
end;
I also try this code compiling into an exe program and it works. Is there something else I need to configure/add?
Thanks.
The fact that you are using TIdConnectionInterceptOpenSSL tells me that you are using a VERY old version of Indy. I am guessing Indy 8, which shipped with D6. Indy 8 and earlier are no longer officially supported by the Indy development team (which I am a member of). You really should upgrade to Indy 9, if not to Indy 10. In Indy 9, TIdConnectionInterceptOpenSSL was replaced with a new TIdSSLIOHandlerSocket component. Also, Indy 9 and earlier required custom-made OpenSSL DLLs, which may be contributing to your error as well, if you are using the wrong DLLs for your version of Indy. Indy 10, on the other hand, uses the standard DLLs from OpenSSL's website now.
Finnally It worked. Although I strongly encourage you to use a newer version of Indy as Remy suggests. I will post the steps that did the trick for me since there should be other people with the same problem.
The original code I posted is functional, it works when we need to post information via secured http (https) but the remote server requires prior authentification using a client certificate.
In order to make it work, it is necessary to verify the following:
TIdHttp and TIdConnectionInterceptOpenSSL configuration
Certificates
For the first 2 steps follow the steps mentioned here link text or (in case link is expired) Google "IndySSL - using certificate authentication". It worked for me.
Indy SSL DLLs. (For D6/Indy 8 download indy_openssl096g.zip from Indy SSL or Intelicom) This DLLs where the only ones that worked for this version of Indy.
Hope this will help.