indy ssl delphi server - delphi

I use Delphi 10.1 Berlin and Indy 10.6.2, 32bit on a Windows Server 2012.
I implemented a server based on TIdHTTPServer. Works like a charm for many years.
Now my customer wants the traffic secured. SSL is an area I have little knowledge of.
There are several helpful pointers on the web that have helped me make CA certificate and key files with OpenSSL. And from several examples I've put together the code below, using TIdServerIOHandlerSSLOpenSSL.
The cert/key files are in the exe directory and so are the OpenSSL dlls ssleay32 and libeay32.
The server responds to http://localhost:8080 but there is no response when addressing it via https://localhost. It behaves as if the TIdServerIOHandlerSSLOpenSSL is not there at all. (The IOHandler does read the cert/key files and it complains when I remove the OpenSSL DLLs). It is as if I've forgotten to throw a switch somewhere.
The analysis of Windows Network Diagnostics (in IEdge) is 'The device or resource (localhost) is not set up to accept connections on port "https".'
I tried to log a message via the OnConnect event, but that stage is never reached with HTTPS.
I have run out of ideas, and can not find relevant suggestions on the web.
Here is my code (the components are all declared in code):
procedure TServerForm.FormCreate(Sender: TObject);
var
ServerSSLIOHandler: TIdServerIOHandlerSSLOpenSSL;
rootdir : string;
begin
inherited;
rootdir:=ExtractFilePath(Application.ExeName);
ServerSSLIOHandler:=TIdServerIOHandlerSSLOpenSSL.Create(self);
ServerSSLIOhandler.SSLOptions.RootCertFile:=rootdir+'ca.cert.pem';
ServerSSLIOhandler.SSLOptions.CertFile:=rootdir+'localhost.cert.pem';
ServerSSLIOhandler.SSLOptions.KeyFile:=rootdir+'localhost.key.pem';
ServerSSLIOhandler.SSLOptions.Method:=sslvSSLv23;
ServerSSLIOhandler.SSLOptions.Mode:=sslmServer;
ServerSSLIOhandler.OnGetPassword:=NIL;
ServerSSLIOhandler.OnVerifyPeer:=OnVerifyPeer;
HTTPServer:=TIdHTTPServer.Create(self);
HTTPServer.IOhandler:=ServerSSLIOHandler;
HTTPserver.Bindings.Add.Port:=443;
HTTPserver.Bindings.Add.Port:=8080;
HTTPServer.Active:=True;
HTTPServer.AutoStartSession:=True;
HTTPServer.SessionTimeOut:=1200000;
HTTPserver.OnQuerySSLPort:=OnQuerySSLPort;
HTTPServer.OnCommandGet:=HTTPServerCommandGet;
...
end;
procedure TServerForm.OnQuerySSLPort(APort: Word; var VUseSSL: Boolean);
// This will not be called when the request is a HTTPS request
// It facilitates the use of the server for testing via HTTP://localhost:8080 (i.e. without SSL)
begin
VUseSSL := (APort<>8080);
end;
function TServerForm.OnVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
result:=AOk;
end;

Thank you. Remy's remark about OnConnect and his suggestion to use netstat did the trick. I.e. it lead me to discover that the problem was elsewhere. In the past I had to move away from port 80 because it became in use by a Windows service. From then on I specified a port number (8080) in an ini file and acted as follows.
Code:
prt:=parameters.ReadInteger('settings','port',80);
if prt<>HTTPserver.DefaultPort
then begin HTTPserver.Active:=false;
HTTPserver.Bindings.Clear;
HTTPserver.DefaultPort:=prt;
HTTPserver.Active:=true;
end;
Since this piece of code was still there, obviously only the specified port (8080) was active. netstat revealed that immediately.
Will you believe that I am very happy with your quick response!

Related

Using Windows system trust store to validate server certificates using Indy/OpenSSL

I'm using Delphi 10.4 with Indy 10 updated from GitHub at the end of August.
I'm looking for the best way to let OpenSSL validate a server certificate using the Windows system certificate stores. I’m working with a custom TCP protocol which is not HTTP, so I’m using the TIdTCPClient component.
To let OpenSSL validate the server certificate against the local system's trust store, it seems that I have to add them manually into the OpenSSL certificates store. To do this I need to call X509_STORE_add_cert() for each cert stored in the Windows cert store.
First issue - from a TIdSSLIOHandlerSocketOpenSSL object, calling X509_STORE_add_cert() require to provide a pointer to a PX509_STORE variable holding the store, which is indeed wrapped by a fSSLContext.fContext.cert_store. As fContext is not public, it seems the only way to access to it is to create a class helper where to put my code. Is there a smarter way to proceed ?
Second issue - the certs have to be added to the OpenSSL Cert store, but TIdSSLIOHandlerSocketOpenSSL.SSLContext seems to be nil until the connection with the server is established. Where is the best place to do this ? It has to be done before making the SSL handshake but after the internal objects wrapping OpenSSL stuff are created and initialized. It seems that overriding TIdSSLIOHandlerSocketOpenSSL.Init and putting initialization code here would be the best place, but the method is not virtual ! Is it an error, or by design ? Is there any reason why TIdSSLIOHandlerSocketOpenSSL.Init is static while TIdServerIOHandlerSSLOpenSSL.Init – the counterpart for server IOHandlers- is virtual and could be freely overridden ?
I decided to change Indy code and make TIdSSLIOHandlerSocketOpenSSL.Init virtual. This way I can add certs just before connecting to the server :
TThSSLContext = class helper for TIdSSLContext
public
function LoadCertsFromWindowsCertStore(const AStoreName: string): Boolean;
end;
procedure TThIOHandlerSSLBase.Init;
begin
inherited;
SSLContext.LoadCertsFromWindowsCertStore('ROOT');
SSLContext.LoadCertsFromWindowsCertStore('CA');
end;
LoadCertsFromWindowsCertStore() mimics all the C++ code that can be found in the Internet to import certs from Windows cert store to OpenSSL cert store :
function TThSSLContext.LoadCertsFromWindowsCertStore(const AStoreName: string): Boolean;
var
LWinStore: HCERTSTORE;
LCert: PCCERT_CONTEXT;
LX509: PX509;
begin
Result := True;
LWinStore := CertOpenSystemStore(0, PChar(AStoreName));
if LWinStore = nil then
Exit(False);
try
LCert := CertEnumCertificatesInStore(LWinStore, nil);
while Assigned(LCert) do
begin
LX509 := d2i_X509(nil, #lCert.pbCertEncoded, LCert.cbCertEncoded);
if Assigned(LX509) then
begin
if X509_STORE_add_cert(fContext.cert_store, LX509) <= 0 then
Exit(False);
X509_free(LX509);
end;
LCert := CertEnumCertificatesInStore(LWinStore, LCert);
end;
finally
CertCloseStore(LWinStore, 0);
end;
end;
Doing this seems to work, but is there any better way to verify server certicates through OpenSSL without changing Indy source code and using class helpers to access protected variables ?

Bind delphi SOAP client to a specific local port/range

I am consuming a web service with Delphi SOAP library (using THTTPRIO). One of the customers has strict networking policy requiring specific ports to be used on both sides of connection. So I need the web service client to use a specific local port or small local port range to make the connections to the server. Is there a way to do it (either through programming or using a Windows setting)? I am using Delphi 10.4.
I am not sure if I get the question right, but I think it would be as simple as specifying a port number as part of the URL property of a THTTPRIO object as below.
// Example of AUrl: 'http://COMPNAMEORIPADD:9878'
function SoapClientClass.CreateSoapClient(AUrl: string; AInterfaceName: string): THTTPRIO;
begin
result := THTTPRIO.Create(nil);
result.URL := AUrl + AInterfaceName;
end;

Using Indy Server's multiple bindings as separate sockets?

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;

How to detect an existing installation of IIS using INNO setup?

I am looking for a way to determine if the user already has a version of IIS installed. If he doesn't, I will go ahead and run my IIS installation script.
I know of the exception handling clause where I do :
try
IIS := CreateOleObject('IISNamespace');
except
RaiseException(ExceptionType, ‘IIS not installed. Setup will now install IIS on your machine. ’#13#13'(Error ‘’’+ExceptionParam+’’’ occured)’);
end;
but for some reason, my compiler version doesn't seem to recognise RaiseException. I also tried including
uses
SysUtils;
but the compiler won't recognize SysUtils even. Is there something like a registry key that I can look at to determine whether IIS is already installed or not?
Any help would be much appreciated.
Rishi you are using the RaiseException function with 2 parameters, but the this function only support one.
procedure RaiseException(const Msg: String);
try using this function like this
var
IIS : variant;
begin
try
IIS := CreateOleObject('IISNamespace');
except
RaiseException('IIS not installed. Setup will now install IIS on your machine');
end;
end;
IIS always installs to %windir%\system32\inetsrv so you should check if specific files exist under this directory. For example, w3wp.exe should exist in this folder for IIS 6/7.
Try:
[CustomMessages]
iis_title=Internet Information Services (IIS)
[Code]
function iis(): boolean;
begin
if not RegKeyExists(HKLM, 'SYSTEM\CurrentControlSet\Services\W3SVC\Security') then
MsgBox(FmtMessage(CustomMessage('depinstall_missing'), [CustomMessage('iis_title')]), mbError, MB_OK)
else
Result := true;
end
;

Help needed coding an IRC client in Delphi 2010 using Indy Components

Im trying to code a basic irc client in Delphi 2010 using Indy components.
Im able to connect to my irc server (unrealircd) using sample A below.
After connecting I have many procedures that should perform actions when it receives a private message, ping, ctcp, channel modes etc. but they never react to any of these incoming events.
Sample A:
This connects to the IRC server when button4 is pressed.
It sucessfully joins the channel with the name specified.
procedure TForm1.Button4Click(Sender: TObject);
begin
IdIRC1.Host := '72.20.53.142';
IdIRC1.Port := 6667;
IdIRC1.Nickname := ssh.frmmain.Edit1.text;//insert your nickname here
try
idIRC1.Connect;
except
if not idIRC1.Connected then
begin
Memo2.Lines.add('Error Connecting to ' + idIRC1.Host);
Exit;
end;
end;
Memo2.Lines.add ('Connected to Auth Server');
idIRC1.Join(channel,key);
Memo2.Lines.add ('Auth Accepted');
end;
These events dont work at all and no errors are generated during a compile.
procedure TForm1.IdIRC1Connected(Sender: TObject);
begin
memo2.Lines.Clear;
memo2.Lines.add ('2Connected');
end;
procedure TForm1.IdIRC1ServerVersion(ASender: TIdContext; Version, Host, Comments: String);
begin
memo2.Lines.Add(Version +'Host '+Host+'Comments '+Comments);
end;
Ive had a few people look at this, and it just seems theres some unicode issues that destroyed my TClientSocket irc setup, and even when I moved to indy and used samples off the official site I was still unable to get anything to fire such as the onconnect event.
A friend had a copy of an application he wrote in Delphi 2010 using the same version of indy and I managed to import his project and it started working.
Not sure why

Resources