Jump to content
idontknow

ICS TsslHttpCli: SslVerifyPeer doesn't work, if Client uses own certificate?

Recommended Posts

Hello,

 

I have a problem and I don't know what I'm missing. I send a GET-Request to a server, in this case https://www.google.de, and i would like to verify the server certificate. This works if my client doesn't load an own certificate. After loading an own, I get the error message "unable to get local issuer certificate" in TsslHttpCli.onSslVerifyPeer.

 

Does anyone have any idea what's going wrong here?

 

Here's my source:

unit Unit1;

// Simple Test-Client, based on which sends a GET-Request to a Google-Server via https-Protocol.
// Shall verify the certificate of the Google-Server.
// Only works, if I don't load an own certificate for the client.
// If I do, I will get the error message "unable to get local issuer certificate" in SslVerifyPeer.

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  System.TypInfo, System.DateUtils, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  OverbyteIcsTypes, OverbyteIcsSslBase, OverbyteIcsWndControl, OverbyteIcsHttpProt, OverbyteIcsLogger;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    MyClient: TSslHttpCli;
    procedure LogMyClient(Sender: TObject; LogOption: TLogOption; const Msg: String);
    procedure SslVerifyPeer(Sender: TObject; var Ok: Integer; Cert: TX509Base);
    procedure SslHandshakeDone(Sender: TObject; ErrCode: Word; PeerCert: TX509Base; var Disconnect: Boolean);
    procedure Info(Text: string);
    procedure Error(Text: string);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Info(Text: string);
var
  dt: string;
begin
  DateTimeToString(dt, 'd.m.yyyy hh:nn:ss.zzz', Now);
  Memo1.Lines.Add(dt + ' [INFO] ' + Text);
end;

procedure TForm1.Error(Text: string);
var
  dt: string;
begin
  DateTimeToString(dt, 'd.m.yyyy hh:nn:ss.zzz', Now);
  Memo1.Lines.Add(dt + ' [ERROR] *********' + Text + '*********');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  MyClient.RcvdStream.Free;
  MyClient.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  CertFileName: string;
  ChainResult: TChainResult;
  CertString, ErrStr: string;
  bLoadOwnCert: Boolean;
begin
  ReportMemoryLeaksOnShutdown := (DebugHook <> 0);

  MyClient := TSslHttpCli.Create(nil);
  MyClient.RcvdStream := TMemoryStream.Create;
  MyClient.SslContext := TSslContext.Create(MyClient);
  MyClient.OnSslVerifyPeer := SslVerifyPeer;
  MyClient.OnSslHandshakeDone := SslHandshakeDone;
  MyClient.IcsLogger := TIcsLogger.Create(MyClient);
  MyClient.IcsLogger.OnIcsLogEvent := LogMyClient;

  // MyClient.IcsLogger.LogOptions := [loWsockErr, loWsockInfo, loProtSpecErr, loProtSpecInfo, loProgress, loSslErr, loSslInfo, {loSslDevel, loSslDump,} loDestEvent];
  MyClient.IcsLogger.LogOptions := [loWsockErr, loProtSpecErr, loSslErr, loDestEvent];

  MyClient.SslContext.UseSharedCAStore := TRUE;   // Without that: No Server Cert Check
  MyClient.SslContext.SslVerifyPeer := TRUE;

  Info('CAStoreTotal: ' + MyClient.SslContext.GetCAStoreTotal.ToString);   // 0
  Info('RootCAStoreTotal: ' + IcsSslRootCAStore.Count.ToString);           // 311

  // Load own Certificate.
  bLoadOwnCert := TRUE;
  if bLoadOwnCert then
  begin
    CertFileName := 'maleben********_dyndns_org.pfx'; // also tried pem-bundle.
    MyClient.SslContext.SslCertX509.LoadFromFile(CertFileName, croTry, croTry, 'pass1234');
  end;

  // Validate Certificate Chain
  ChainResult := MyClient.SslContext.SslCertX509.ValidateCertChain('', IcsSslRootCAStore, CertString, ErrStr);  { really need host name, V9.1 new store  }
  case ChainResult of
    chainOK:   Info('chainOK, CertString: ' + CertString); // I get chainOK
    chainFail: Info('chainFail, Error: ' +ErrStr + ', CertString: ' + CertString);
    chainWarn: Info('chainWarn, Error: ' +ErrStr + ', CertString: ' + CertString);
    chainNone: Info('chainNone, Error: ' +ErrStr + ', CertString: ' + CertString);
  end;
end;

procedure TForm1.SslVerifyPeer(Sender: TObject; var Ok: Integer; Cert: TX509Base);
begin
  Info('SslVerifyPeer, Received certificate: Subject: ' + Cert.SubjectOneLine + ', Issuer: '  + Cert.IssuerOneLine);

  if OK <> 1 then
    Error('SslVerifyPeer, Error msg: ' + Cert.VerifyErrMsg);
end;

procedure TForm1.SslHandshakeDone(Sender: TObject; ErrCode: Word; PeerCert: TX509Base; var Disconnect: Boolean);
begin
  Info('SslHandshakeDone, PeerCert.VerifyResult= ' + PeerCert.VerifyErrMsg);
  Info('SslHandshakeDone, PeerCert.Sha256Hex=' + PeerCert.Sha256Hex);
  if ErrCode = 0 then
    Info('SslHandshakeDone, OK')
  else
    Error('SslHandshakeDone, SslHandshake failed, error #' + IntToStr(ErrCode));
end;

procedure TForm1.LogMyClient(Sender: TObject; LogOption: TLogOption; const Msg : String);
var
  lop: string;
begin
  lop := GetEnumName(TypeInfo(TLogOption), Integer(LogOption));
  Info('IcsLog: ' + lop + ' ' + Msg);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Len: Integer;
  Text: Ansistring;
begin
  Info('********** Button1Click **********');
  try
    MyClient.URL := 'https://www.google.de';
    Info('Send Get-Request: ' + MyClient.URL);
    MyClient.Get;
    Info(MyClient.RcvdHeader.Text);
    Len := MyClient.RcvdStream.Position;
    if Len > 0 then
    begin
      SetLength(Text, Len);
      MyClient.RcvdStream.Position := 0;
      MyClient.RcvdStream.ReadData(@Text[1], Len);
      Info(string(Text));
    end;
  except
    on E: Exception do
      Error(E.Message);
  end;
end;

end.

Share this post


Link to post

I tried to include the log, but the AntiSpam-Filter didn't let me (Forbidden, Contains Contacts). So, here the next try, with some asterisks in the text:

 

with bLoadOwnCert := FALSE:

28.10.2025 09:23:43.951 [INFO] CAStoreTotal: 0
28.10.2025 09:23:43.956 [INFO] RootCAStoreTotal: 311
28.10.2025 09:23:43.957 [INFO] chainFail, Error: No SSL certificate loaded, CertString:
28.10.2025 09:23:51.034 [INFO] ********** Button1Click **********
28.10.2025 09:23:51.039 [INFO] Send Get-Request: https://www.g****e.de
28.10.2025 09:23:51.323 [INFO] SslVerifyPeer, Received certificate: Subject: /C=US/O=Google Trust Services LLC/CN=GTS Root R1, Issuer: /C=US/O=Google Trust Services LLC/CN=GTS Root R1
28.10.2025 09:23:51.325 [INFO] SslVerifyPeer, Received certificate: Subject: /C=US/O=Google Trust Services/CN=WR2, Issuer: /C=US/O=Google Trust Services LLC/CN=GTS Root R1
28.10.2025 09:23:51.331 [INFO] SslVerifyPeer, Received certificate: Subject: /CN=www.google.de, Issuer: /C=US/O=Google Trust Services/CN=WR2
28.10.2025 09:23:51.342 [INFO] SslHandshakeDone, PeerCert.VerifyResult= ok
28.10.2025 09:23:51.352 [INFO] SslHandshakeDone, PeerCert.Sha256Hex=D39345550A213076D1BDC816DBA1B63DF82FFEF037E47EB29B0FDC91A0CB2C58
28.10.2025 09:23:51.359 [INFO] SslHandshakeDone, OK
28.10.2025 09:23:51.423 [INFO] SslVerifyPeer, Received certificate: Subject: /C=US/O=Google Trust Services LLC/CN=GTS Root R1, Issuer: /C=US/O=Google Trust Services LLC/CN=GTS Root R1
28.10.2025 09:23:51.428 [INFO] SslVerifyPeer, Received certificate: Subject: /C=US/O=Google Trust Services/CN=WR2, Issuer: /C=US/O=Google Trust Services LLC/CN=GTS Root R1
28.10.2025 09:23:51.433 [INFO] SslVerifyPeer, Received certificate: Subject: /CN=www.google.com, Issuer: /C=US/O=Google Trust Services/CN=WR2
28.10.2025 09:23:51.438 [INFO] SslHandshakeDone, PeerCert.VerifyResult= ok
28.10.2025 09:23:51.443 [INFO] SslHandshakeDone, PeerCert.Sha256Hex=12E2B9FD0AC210A5CD15EDF144A2487721A608E1BE652A062DF38CD395D882A5
28.10.2025 09:23:51.447 [INFO] SslHandshakeDone, OK
28.10.2025 09:23:51.515 [INFO] HTTP/1.0 200 OK
Date: Tue, 28 Oct 2025 08:23:51 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
...
//
//
//
with bLoadOwnCert := TRUE:

28.10.2025 09:24:45.553 [INFO] CAStoreTotal: 0
28.10.2025 09:24:45.556 [INFO] RootCAStoreTotal: 311
28.10.2025 09:24:45.577 [INFO] chainOK, CertString: Server: Issued to (CN): maleben*******testen.dyndns.org
Alt Domains (SAN): maleben*******testen.dyndns.org
Issuer (CN): R12, (O): Let's Encrypt
Expires: 2026-01-26T07:23:00, Signature: sha256WithRSAEncryption
Valid From: 2025-10-28T07:23:01, Serial Number: 05f73166e3e141b23a871648639cd5bc3c04
Fingerprint (sha256): bf4763ac8c12e22b35460d73cbc04ead8b4a573e387b01d0e5f8728cb082768f
Public Key: RSA Key Encryption 4096 bits, 4096 security bits

Intermediate: Issued to (CN): R12, (O): Let's Encrypt
Issuer (CN): ISRG Root X1, (O): Internet Security Research Group
Expires: 2027-03-12T23:59:59, Signature: sha256WithRSAEncryption
Valid From: 2024-03-13T00:00:00, Serial Number: c212324b70a9b49171dc40f7e285263c
Fingerprint (sha256): 131fce7784016899a5a00203a9efc80f18ebbd75580717edc1553580930836ec
Public Key: RSA Key Encryption 2048 bits, 2048 security bits
Trusted CA: Issued to (CN): ISRG Root X1, (O): Internet Security Research Group
Issuer: Self Signed
Expires: 2035-06-04T11:04:38, Signature: sha256WithRSAEncryption
Valid From: 2015-06-04T11:04:38, Serial Number: 8210cfb0d240e3594463e0bb63828b00
Fingerprint (sha256): 96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6
Public Key: RSA Key Encryption 4096 bits, 4096 security bits
28.10.2025 09:24:56.771 [INFO] ********** Button1Click **********
28.10.2025 09:24:56.778 [INFO] Send Get-Request: https://www.g****e.de
28.10.2025 09:24:57.053 [INFO] SslVerifyPeer, Received certificate: Subject: /C=US/O=Google Trust Services LLC/CN=GTS Root R1, Issuer: /C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
28.10.2025 09:24:57.055 [ERROR] *********SslVerifyPeer, Error msg: unable to get local issuer certificate*********
28.10.2025 09:24:57.059 [INFO] IcsLog: loSslErr 037E8390 ICB> SSL_connect: error 1 in error
28.10.2025 09:24:57.061 [INFO] IcsLog: loSslErr 037E8390 HandleSslError handle=1708  [1] error:0A000086:SSL routines::certificate verify failed
28.10.2025 09:24:57.065 [INFO] IcsLog: loSslErr 037E8390 NetworkError #10053 TCustomSslWSocket.Do_FD_READ handle=1708
28.10.2025 09:24:57.067 [INFO] SslHandshakeDone, PeerCert.VerifyResult=
28.10.2025 09:24:57.071 [INFO] SslHandshakeDone, PeerCert.Sha256Hex=
28.10.2025 09:24:57.073 [ERROR] *********SslHandshakeDone, SslHandshake failed, error #134*********
28.10.2025 09:24:57.079 [INFO]

 

Share this post


Link to post

What is your program attempting to achieve?  Are you trying to use a Let's Encrypt certificate as a personal client certificate?  I believe that is not allowed.  

 

Please try using the OverbyteIcsHttpRestTst to access the URL it has much better logging and is easier to use.  

 

Angus

Share this post


Link to post

Hello Angus,

 

The source here is only test case for a bigger problem: I want to write a client and a server, both should verify their certificates  vice versa.

I thought I could assign the client a certificate, which would be ignored from the public server where i send my GET-Request to.

I expected, that the verification of the server certificate would give the same result.

 

Also I didn't know that it is not allowed to use Let's encrypt Certificates as Client Certificates, as there is an option named "tls client" selectable in the Combobox "Cert profile or type" on the "Supplier New Order"-Tab of the "OverbyteIcsX509CertsTst"-Example. Maybe I unstood this wrong?

 

Share this post


Link to post

Let's Encrypt is phasing out support for client certificates, they used to be supported, and that 'tls client' option is a temporary solution for a few months for existing users, but will disappear early next year: https://letsencrypt.org/2025/05/14/ending-tls-client-authentication

 

Generally, client certificates are issued to people or organisations, not host names, ie code signing certificates, and can not issued automatically, so are not free.  

 

So unless Google is expecting your certificate, I'd expect the request to fail. But that debug logging you showed is not designed for end user diagnostics, the REST client has much better logging and is much easier to use.  

 

ICS servers using IcsHosts like OverbyteIcsSslMultiWebServ can be set-up to request a client certificate and report it, that is how I tested the REST client sending a client certificate to a web server.  

 

Angus

 

 

 

 

 

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×