Skip to main content
Table of Contents

Secure connections

Any WebSocket traffic that crosses a network you do not control should travel over TLS, which turns ws:// into wss://. Public services almost always require it, browsers refuse plain WebSockets from secure pages, and the WhatsApp integration demands it. TTMSFNCWebsocketClient supports TLS through Indy's IO handler architecture: by default it creates an OpenSSL-based handler for you, and when you need a different TLS library — most commonly to use OpenSSL 3.x — it lets you supply your own handler. This chapter covers enabling TLS, deploying the OpenSSL libraries the default handler needs, plugging in a custom IO handler, and how the picture differs for browser applications.

Enabling TLS

Set UseSSL to True before connecting whenever the server address starts with wss://, and set Port to the server's TLS port (usually 443). The TLS negotiation then runs inside Connect, before the WebSocket handshake, and OnConnect still signals that the connection is ready.

Connect to a secure (wss) serverPascal
uses
  IdSSLOpenSSL, IdSSLOpenSSLHeaders;

procedure TForm1.ConnectSecure;
begin
  // wss://echo.example.com/ws  (port 443)
  TMSFNCWebsocketClient1.HostName := 'echo.example.com';
  TMSFNCWebsocketClient1.Port := 443;
  TMSFNCWebsocketClient1.PathName := 'ws';
  TMSFNCWebsocketClient1.UseSSL := True;
  try
    TMSFNCWebsocketClient1.Connect;
  except
    on E: EIdOSSLCouldNotLoadSSLLibrary do
      ShowMessage('OpenSSL libraries not found: deploy libeay32.dll and ssleay32.dll ' +
        'next to the executable. ' + WhichFailedToLoad);
    on E: Exception do
      ShowMessage('Secure connection failed: ' + E.Message);
  end;
end;

Deploying OpenSSL for the default handler

When UseSSL is True and no custom handler is supplied, the client creates an Indy OpenSSL IO handler that negotiates TLS 1.0, 1.1, or 1.2. That handler loads the OpenSSL 1.0.2 libraries at runtime, so they must be available to the application — on Windows, libeay32.dll and ssleay32.dll next to the executable, in the bitness (32-bit or 64-bit) of the application. Sources and precompiled binaries are available from the OpenSSL 1.0.2 source archive and the Indy OpenSSL binaries repository. When the libraries cannot be loaded, Connect raises an exception that names the missing library. To keep the libraries in a subfolder, tell Indy where they are before the first secure connection:

Load the OpenSSL libraries from a subfolderPascal
uses
  IdSSLOpenSSLHeaders;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Must run before the first secure connection loads the libraries.
  // The folder contains libeay32.dll and ssleay32.dll in the application's bitness.
  IdOpenSSLSetLibPath(TPath.Combine(ExtractFilePath(ParamStr(0)), 'openssl'));
end;

Using OpenSSL 3.x or another TLS library

OpenSSL 1.0.2 is no longer maintained. To use OpenSSL 3.x, or any other TLS implementation, supply your own IO handler from OnCreateIOHandler. The event fires during Connect whenever UseSSL is True, before the default handler would be created, and passes the handler by reference in AIOHandler; whatever you assign there is used instead of the default. Pick a library that builds on Indy's TLS support — for example TaurusTLS, which adds OpenSSL 3.x support to Indy — create its IO handler class, and configure it.

The example shows the pattern with Indy's own OpenSSL handler restricted to TLS 1.2; swap in the handler class of the library you use.

Supply a custom TLS IO handlerPascal
uses
  IdIOHandler, IdSSLOpenSSL;

procedure TForm1.TMSFNCWebsocketClient1CreateIOHandler(Sender: TObject;
  var AIOHandler: TIdIOHandler);
var
  Handler: TIdSSLIOHandlerSocketOpenSSL;
begin
  // The event fires on every Connect while UseSSL is True; keep the handler
  // that was created for an earlier connection.
  if Assigned(AIOHandler) then
    Exit;

  // No owner: the client frees the handler it received.
  // For OpenSSL 3.x, create the IO handler class of an Indy TLS library
  // such as TaurusTLS here instead, and configure it the same way.
  Handler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  Handler.SSLOptions.Mode := sslmClient;
  Handler.SSLOptions.SSLVersions := [sslvTLSv1_2];
  Handler.PassThrough := False;
  AIOHandler := Handler;
end;

Warning

A custom IO handler is used exactly as you configure it. The client does not set any of its properties or hook any of its events, so configure everything the connection needs — TLS versions, certificate verification, and library paths — inside OnCreateIOHandler.

TLS in browser applications

TTMSFNCWebsocketClient is available for VCL and FMX. In TMS WEB Core browser applications, use TMS WEB Core's own TWebSocketClient component, where the browser performs TLS. A page served over http may open ws or wss connections, but a page served over https may only open wss connections; set UseSSL to True on the web client to force a secure connection from an http page. The one component of this product that runs in the browser, TTMSFNCWhatsAppReceiver, always connects securely.

Combining a custom IO handler with a secure connect

A typical OpenSSL 3.x setup combines both examples in this chapter: assign OnCreateIOHandler at design time or in FormCreate, then connect with UseSSL enabled and handle a failing TLS negotiation separately from an unreachable server. The handler from the previous example is created on the first Connect and reused by every reconnect.

Connect securely through a custom IO handlerPascal
uses
  IdSSLOpenSSLHeaders;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Supplies the TLS handler (see TMSFNCWebsocketClient1CreateIOHandler).
  TMSFNCWebsocketClient1.OnCreateIOHandler := TMSFNCWebsocketClient1CreateIOHandler;
  TMSFNCWebsocketClient1.HostName := 'push.example.com';
  TMSFNCWebsocketClient1.Port := 443;
  TMSFNCWebsocketClient1.UseSSL := True;
end;

procedure TForm1.ConnectBtnClick(Sender: TObject);
begin
  try
    // The first Connect creates the handler; later connects reuse it.
    TMSFNCWebsocketClient1.Connect;
    if TMSFNCWebsocketClient1.Active then
      StatusLabel.Text := 'Connected over TLS';
  except
    on E: EIdOpenSSLError do
      StatusLabel.Text := 'TLS negotiation failed: ' + E.Message;
    on E: Exception do
      StatusLabel.Text := 'Server unreachable: ' + E.Message;
  end;
end;

Pitfalls

  • Missing libraries fail at connect time. The application compiles and starts without OpenSSL; the error only appears on the first secure Connect. Ship the libraries with the application.
  • Match the bitness. A 64-bit application cannot load 32-bit OpenSSL libraries, and vice versa.
  • The handler is reused. OnCreateIOHandler fires on every Connect, and AIOHandler still holds the handler from the previous connection; create a new one only when it is nil.
  • Do not give the handler an owner. The client frees the handler it received when it is destroyed; a handler that is also owned by the form is freed twice.
  • Set UseSSL before connecting. It cannot change while connected.

See also