Skip to content

Suggestion to ease certificate verification using TFPHTTPClient

Summary

TFPHTTPClient allows certificate verification when accessing servers via HTTPS. IMHO, certificate verification should be encouraged as much as possible, since using TLS without verification has little added value and has security risks.

To enable certificate verification, currently the following is needed:

  • set the property TFPHTTPClient.VerifySSlCertificate to True
  • implement an AfterSocketHandlerCreate callback to provide a link to trusted (CA) certificates.

See example 1 below for an example of the current way to enable verification.

Considering that:

  • certificate verification IMHO should be encouraged as much as possible;
  • using the VerifySSlCertificate property only makes sense when also providing a link to trusted certificates (otherwise every verification will fail);
  • setting the VerifySSLCertificate property is very straightforward and easy to implement;
  • writing a callback implementation is always needed when setting this property to True;
  • writing a callback implementation is less straightforward and could be seen as a less efficient way to use 'basic' functionality;
  • both the OpenSSL and GnuTLS implementations accept a CertCA file (e.g. PEM file containing trusted CA certificates) or a TrustedCertsDir (containing trusted (CA) certificates),

I think it makes sense to add a CertCAFileName and TrustedCertsDir property to TFPHTTPClient. These properties could be used together with the VerifySSlCertificate property.

TFPHTTPClient uses the VerifySSlCertificate property only when it creates the SocketHandler (which, for HTTPS connections, will be a SSLSocketHandler and could use OpenSSL or GnuTLS handlers). When creating a SSL socket handler, it passes the value of the VerifySSlCertificate property to the socket handler. This could be a perfect moment to pass the CertCA file location or a TrustedCertsDir to the socket handler as well. By doing this, no callback implementation for the AfterSocketHandlerCreate event is needed anymore, except for exceptional cases (in which cases this will still be possible).

See example 2 below how this could be used in practice. It is now easier and more straightforward to enable certificate verification.

See the patch below for an implementation of this suggestion. I think it fits well with the way VerifySSlCertificate is currently implemented. The suggested code additions have no impact on existing code.

Example 1: using certificate verification in current situation

program project1;

{$mode objfpc}{$H+}

uses
  SysUtils, fphttpclient, ssockets, opensslsockets, fpopenssl, openssl;

const
  URL = 'https://example.com/';

type
  TTestApp = class(TObject)
  private
    procedure DoHaveSocketHandler(Sender: TObject; AHandler: TSocketHandler);
    procedure Run;
  end;

procedure TTestApp.DoHaveSocketHandler(Sender: TObject; AHandler: TSocketHandler);
var
  SSLHandler: TOpenSSLSocketHandler absolute aHandler;
begin
  if (aHandler is TOpenSSLSocketHandler) then
  begin
    SSLHandler.CertificateData.TrustedCertsDir := '/etc/ssl/certs/';
  end;
end;

procedure TTestApp.Run;
begin
  with TFPHTTPClient.Create(nil) do
  try
    VerifySSlCertificate := True;
    AfterSocketHandlerCreate := @DoHaveSocketHandler;
    WriteLn(Get(URL));
  finally
    Free;
  end;
end;

begin
  with TTestApp.Create do
  try
    Run;
  finally
    Free;
  end;
end.

Example 2: using certificate verification after proposed addition

program project2;

{$mode objfpc}{$H+}

uses
  SysUtils, fphttpclient, opensslsockets;

const
  URL = 'https://example.com/';

begin
  with TFPHTTPClient.Create(nil) do
  try
    VerifySSlCertificate := True;
    TrustedCertsDir := '/etc/ssl/certs/'; // or: CertCAFileName:='ca-bundle.crt';
    WriteLn(Get(URL));
  finally
    Free;
  end;
end.

Suggested code addition

In fphttpclient.pp, in essence, I suggest adding the following two lines:

function TFPCustomHTTPClient.GetSocketHandler(const UseSSL: Boolean): TSocketHandler;

Var
  SSLHandler : TSSLSocketHandler;

begin
  Result:=Nil;
  if Assigned(FonGetSocketHandler) then
    FOnGetSocketHandler(Self,UseSSL,Result);
  if (Result=Nil) then
    If UseSSL then
      begin
      SSLHandler:=TSSLSocketHandler.GetDefaultHandler;
      SSLHandler.VerifyPeerCert:=FVerifySSLCertificate;
      SSLHandler.OnVerifyCertificate:=@DoVerifyCertificate;
      SSLHandler.CertificateData.CertCA.FileName:=FCertCAFileName; // <----- new!!
      SSLHandler.CertificateData.TrustedCertsDir:=FTrustedCertsDir; // <----- new!!
      Result:=SSLHandler;
      end
    else
      Result:=TSocketHandler.Create;
  if Assigned(AfterSocketHandlerCreate) then
    AfterSocketHandlerCreate(Self,Result);
end;

Suggested patch

The patch below adds these lines, including declaration of these properties and an explanatory comment.
fphttpclient.pp.patch

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information