Skip to main content
Table of Contents

Secure connections

A server that is reachable from the internet should serve wss:// rather than ws://: TLS encrypts the traffic, proves the server's identity to clients, and is the only option for browser pages served over https and for the WhatsApp webhook. TTMSFNCWebSocketServer supports TLS through an Indy server IO handler. By default it creates an OpenSSL-based handler from three certificate file properties; when you need OpenSSL 3.x or another TLS library, it lets you supply the handler yourself. This chapter covers enabling TLS, the certificate files and their format, reusing an existing Windows or IIS certificate, deploying OpenSSL, and plugging in a custom IO handler.

Enabling TLS

Set UseSSL to True and point the server at its certificate files before activating it:

Property Contents
CertificateFile The server certificate (PEM).
CertificateKeyFile The private key that belongs to the certificate (PEM).
RootCertificateFile The issuing and intermediate certificates, when the certificate authority provides a chain.

If the private key is protected by a pass phrase, supply it from OnGetSSLPassword. Clients then connect with wss:// and their own TLS setting enabled — on TTMSFNCWebsocketClient, UseSSL := True.

Start a secure (wss) serverPascal
procedure TForm1.StartSecureServer;
var
  CertDir: string;
begin
  CertDir := TPath.Combine(ExtractFilePath(ParamStr(0)), 'certs');

  // Enable TLS and set the certificate paths before the first activation.
  TMSFNCWebSocketServer1.UseSSL := True;
  TMSFNCWebSocketServer1.CertificateFile := TPath.Combine(CertDir, 'cert.pem');
  TMSFNCWebSocketServer1.CertificateKeyFile := TPath.Combine(CertDir, 'key.pem');
  TMSFNCWebSocketServer1.RootCertificateFile := TPath.Combine(CertDir, 'chain.pem');
  TMSFNCWebSocketServer1.OnGetSSLPassword := ServerGetSSLPassword;
  TMSFNCWebSocketServer1.Port := 443;
  TMSFNCWebSocketServer1.Active := True;   // clients connect to wss://<host>/
end;

procedure TForm1.ServerGetSSLPassword(Sender: TObject; var APassword: string);
begin
  // The pass phrase that protects the private key file.
  APassword := FKeyPassPhrase;
end;

Deploying OpenSSL for the default handler

The default handler is Indy's OpenSSL server IO handler, which negotiates up to TLS 1.2 and loads the OpenSSL 1.0.2 libraries at runtime. On Windows, place libeay32.dll and ssleay32.dll, in the bitness of the application, next to the executable. Sources and binaries are available from the OpenSSL 1.0.2 source archive and the Indy OpenSSL binaries repository. To keep the libraries in a subfolder, set the library path before the server is activated:

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;

Converting a PFX certificate to PEM

The certificate properties expect PEM files. A certificate exported as a PFX (PKCS #12) file converts to a single PEM file that contains both the certificate and the private key with the OpenSSL command line:

openssl pkcs12 -in filename.pfx -out cert.pem -nodes

Point both CertificateFile and CertificateKeyFile at the resulting file, or split the certificate and key blocks into two files. -nodes writes the key unencrypted; protect the file accordingly, or leave out -nodes to encrypt the key and supply the pass phrase from OnGetSSLPassword.

Exporting an existing certificate from Windows Server

A certificate already installed on a Windows server, for example for IIS, can be exported to a PFX file and then converted as shown above:

  1. Press Win+R, type mmc, and select OK to open the Microsoft Management Console.
  2. Select File > Add/Remove Snap-in.
  3. Select Certificates under Available snap-ins and select Add.
  4. Choose Computer account and select Next.
  5. Select Local computer and select Finish.
  6. Select OK to add the snap-in to the console.
  7. Open the Personal > Certificates store in the left pane, right-click the certificate to export, and select All Tasks > Export.
  8. In the Certificate Export Wizard, select Next.
  9. Select Yes, export the private key and select Next.
  10. Select Personal Information Exchange - PKCS #12 (.PFX) as the format. Optionally check Include all certificates in the certification path if possible to include the intermediate certificates. Select Next.
  11. Enter a password for the PFX file and select Next.
  12. Choose the location and name of the PFX file and select Next.
  13. Select Finish. The message The export was successful. confirms that the certificate and its private key were exported.

Then convert the exported file to PEM, entering the PFX password when asked:

openssl pkcs12 -in exported.pfx -out cert.pem -nodes

Using OpenSSL 3.x or another TLS library

To use OpenSSL 3.x, supply your own server IO handler from OnCreateIOHandler. The event fires once, when the server first needs a TLS handler, and passes the handler by reference in AIOHandler; a handler you assign replaces the default. Pick a library that builds on Indy's TLS support — for example TaurusTLS — create its server IO handler class, and configure it completely.

Supply a custom server TLS IO handlerPascal
uses
  IdServerIOHandler, IdSSLOpenSSL;

procedure TForm1.TMSFNCWebSocketServer1CreateIOHandler(Sender: TObject;
  var AIOHandler: TIdServerIOHandler);
var
  Handler: TIdServerIOHandlerSSLOpenSSL;
begin
  // For OpenSSL 3.x, create the server IO handler class of an Indy TLS
  // library such as TaurusTLS here instead, and configure it the same way.
  // The form owns the handler, so it is freed with the form.
  Handler := TIdServerIOHandlerSSLOpenSSL.Create(Self);

  // Nothing is copied from the server's certificate properties: configure all of it.
  Handler.SSLOptions.CertFile := 'certs\cert.pem';
  Handler.SSLOptions.KeyFile := 'certs\key.pem';
  Handler.SSLOptions.RootCertFile := 'certs\chain.pem';
  Handler.SSLOptions.Mode := sslmServer;
  Handler.SSLOptions.SSLVersions := [sslvTLSv1_2];
  Handler.OnGetPassword := HandlerGetPassword;
  AIOHandler := Handler;
end;

procedure TForm1.HandlerGetPassword(var Password: string);
begin
  Password := FKeyPassPhrase;
end;

procedure TForm1.StartBtnClick(Sender: TObject);
begin
  // Assign the event before UseSSL; the handler is requested on first use.
  TMSFNCWebSocketServer1.OnCreateIOHandler := TMSFNCWebSocketServer1CreateIOHandler;
  TMSFNCWebSocketServer1.UseSSL := True;
  TMSFNCWebSocketServer1.Port := 443;
  TMSFNCWebSocketServer1.Active := True;
end;

Warning

A custom IO handler is used exactly as you configure it. The server does not copy CertificateFile, CertificateKeyFile, or RootCertificateFile into it and does not hook OnGetSSLPassword to it. Set the certificate paths, pass phrase handling, TLS versions, and any events the handler needs inside OnCreateIOHandler.

Combining certificate setup with a custom handler

A deployment often supports both setups — the default handler with PEM certificate properties on one machine, an OpenSSL 3.x handler on another — and picks one from configuration. The example does that and keeps the activation order right: the handler event or the certificate properties first, then UseSSL, then Active. The WhatsApp webhook host, TTMSFNCWhatsAppServer, uses the same certificate properties with TLS always enabled.

Choose the TLS handler from configurationPascal
procedure TForm1.StartFromConfig(AUseCustomHandler: Boolean; const ACertDir: string);
begin
  if AUseCustomHandler then
    // The custom handler configures certificates and pass phrase itself
    // (see TMSFNCWebSocketServer1CreateIOHandler); assign it before UseSSL.
    TMSFNCWebSocketServer1.OnCreateIOHandler := TMSFNCWebSocketServer1CreateIOHandler
  else
  begin
    // Default OpenSSL handler: the server applies these properties to it.
    TMSFNCWebSocketServer1.CertificateFile := TPath.Combine(ACertDir, 'cert.pem');
    TMSFNCWebSocketServer1.CertificateKeyFile := TPath.Combine(ACertDir, 'key.pem');
    TMSFNCWebSocketServer1.RootCertificateFile := TPath.Combine(ACertDir, 'chain.pem');
    TMSFNCWebSocketServer1.OnGetSSLPassword := ServerGetSSLPassword;
  end;

  TMSFNCWebSocketServer1.UseSSL := True;
  TMSFNCWebSocketServer1.Port := 443;
  try
    TMSFNCWebSocketServer1.Active := True;
  except
    on E: Exception do
      Log('Secure server could not start: ' + E.Message);
  end;
end;

Pitfalls

  • Configure TLS before the first activation. The server creates its internal HTTP server, with or without TLS, the first time it becomes active; set UseSSL, the certificate files, and OnCreateIOHandler before that.
  • Assign OnCreateIOHandler before enabling UseSSL in code. Setting a certificate property while UseSSL is True already creates the handler.
  • Use PEM files. Convert PFX certificates first; the default handler cannot read them.
  • Include the chain. Without the intermediate certificates in RootCertificateFile, some clients cannot verify the server.
  • Missing libraries fail at runtime. Ship the OpenSSL libraries with the application and match its bitness.
  • UseSSL cannot change while active. Stop the server first.

See also