Skip to content

FPHTTPClient using OpenSSL accepts wrong host certificates with certificate validation on

Summary

TFPHTTPClient can use OpenSSL's certificate validation by setting the property VerifySSlCertificate to True. This way, invalid (e.g. expired or revoked) certificates will not be accepted and an exception will be raised. This is obviously correct. However, if a server presents a certificate with a wrong host, this certificate is accepted. This is wrong and a security risk.

The reason is that OpenSSL does not automatically check the hostname in the certificate. For this check to be turned on, the function SSL_set1_host must be used (https://www.openssl.org/docs/man3.0/man3/SSL_set1_host.html). After using this function, the certificate validation is correct.

System Information

  • Operating system: Linux EndeavourOS (but should be platform independent)
  • Processor architecture: x86-64
  • Compiler version: 3.2.3-783-g2c231605 [2023/10/04] for x86_64
  • Device: Laptop

Steps to reproduce

program project1;

{$mode objfpc}{$H+}

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

const
  URLs: array[0..3] of string = (
    'https://badssl.com',
    'https://wrong.host.badssl.com',
    'https://expired.badssl.com',
    'https://revoked.badssl.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;
var
  URL: string;
begin
  with TFPHTTPClient.Create(nil) do
  try
    VerifySSlCertificate := True;
    AfterSocketHandlerCreate := @DoHaveSocketHandler;

    for URL in URLs do
      try
        Get(URL);
        WriteLn(URL, ' succeeded.');
      except
        on E: Exception do
          WriteLn(URL, ' failed: ', E.Message);
      end;
  finally
    Free;
  end;
end;

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

What is the current bug behavior?

$ ./project1
https://badssl.com succeeded.
https://wrong.host.badssl.com succeeded.
https://expired.badssl.com failed: Connect to expired.badssl.com:443 failed: SSL error code: 167772294: error:0A000086:SSL routines::certificate verify failed
https://revoked.badssl.com failed: Connect to revoked.badssl.com:443 failed: SSL error code: 167772294: error:0A000086:SSL routines::certificate verify failed
  • It is correct that badssl.com succeeds.
  • It is not correct that wrong.host.badssl.com succeeds. The reason is that OpenSSL does not verify the hostname, because the function SSL_set1_host was not used.
  • It is correct that expired.badssl.com and revoked.badssl.com fail.

What is the expected (correct) behavior?

$ ./project1
https://badssl.com succeeded.
https://wrong.host.badssl.com failed: Connect to wrong.host.badssl.com:443 failed: SSL error code: 167772294: error:0A000086:SSL routines::certificate verify failed
https://expired.badssl.com failed: Connect to expired.badssl.com:443 failed: SSL error code: 167772294: error:0A000086:SSL routines::certificate verify failed
https://revoked.badssl.com failed: Connect to revoked.badssl.com:443 failed: SSL error code: 167772294: error:0A000086:SSL routines::certificate verify failed

Possible fixes

See the attached patches to fix the issue. The patches refer to the files in the FPC Source/packages/openssl/src

Link to other bug report

This problem was also mentioned in issue 37980 (#37980 (comment 955471907)) but not solved there

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