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