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