idontknow 0 Posted October 28 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
idontknow 0 Posted October 28 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
Angus Robertson 710 Posted October 28 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
idontknow 0 Posted October 28 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
Angus Robertson 710 Posted October 28 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