Search code examples
delphiindy

Start TLS after connection has been established in TIdCmdTcpServer


We are using a TIdCmdTCPServer with a TIdServerIOHandlerSSLOpenSSL server-side and TIdTCPClient with TIdSSLIOHandlerSocketOpenSSL client-side in Delphi 10.1.

I can successfully establish an encrypted connection when setting PassThrough to false before calling FTCPClient.Connect and having

procedure TIndyTcpCommandReceiver.TCPServerConnect(AContext: TIdContext);
begin
  if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketOpenSSL then
    TIdSSLIOHandlerSocketOpenSSL(AContext.Connection.IOHandler).PassThrough := false;
end;

We deploy the same server.exe to various servers. Some of them have certificates installed, others don't.

I only want to establish an encrypted connection, if there is a certificate.

I modified my client side code following the instructions from https://stackoverflow.com/a/46412958/865602, initially setting PassThrough := true and setting PassThrough := false once a connection has been established.

procedure TTCPConnector.Connect;
begin
  FSSLIOHandler.PassThrough := true;
  FTCPClient.Connect(FServerName, FPort); // succeeds
  FSSLIOHandler.PassThrough := false ; // fails
end;

However, I can't find an answer on how to modify my server-side code. I can only establish the initial connection if I don't use the code in the TCPServerConnect event.

What do I have to change in the server to switch to an encrypted connection after the client set PassThrough to false?


Solution

  • I only want to establish an encrypted connection, if there is a certificate.

    Then you need to change your protocol to a STARTTLS model, where the client asks the server for permission to encrypt the connection before actually doing so.

    Have the client connect normally with PassThrough=True on both sides. Then have the client send an unencrypted command to the server, and have the server respond with whether permission is granted or denied based on whether a certificate is installed. If granted, then both parties can set PassThrough=False to encrypt the connection before any further data is exchanged.

    For example:

    procedure TIndyTcpCommandReceiver.TCPServerConnect(AContext: TIdContext);
    begin
      if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase then
        TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := true;
    end;
    
    procedure TIndyTcpCommandReceiver.TCPServerSTARTTLSCommand(ASender: TIdCommand);
    begin
      if (ASender.Context.Connection.IOHandler is TIdSSLIOHandlerSocketBase) and
         TIdSSLIOHandlerSocketBase(ASender.Context.Connection.IOHandler).PassThrough and
         (Certificate Is Installed) then // <-- implement as needed...
      begin
        ASender.Reply.SetReply(220, 'Send TLS handshake');
        ASender.SendReply;
        TIdSSLIOHandlerSocketBase(ASender.Context.Connection.IOHandler).PassThrough := False;
      end
      else
        ASender.Reply.SetReply(454, 'Dont send TLS handshake');
    end;
    
    procedure TTCPConnector.Connect;
    begin
      FSSLIOHandler.PassThrough := true;
      FTCPClient.Connect(FServerName, FPort);
      FTCPClient.GetResponse; // read greeting if needed
      if FTCPClient.SendCmd('STARTTLS') = 220 then
        FSSLIOHandler.PassThrough := false;
    end;